|
console.clear(); |
|
|
|
var user_id = "harun-pehl-van"; |
|
var list_id = "206730089"; |
|
var client_id = "e20d2e2dfe7b0084b6851cb0f7610a48"; |
|
|
|
var set = new SoundCloudSet({ |
|
client_id: client_id, |
|
list_id: list_id |
|
}); |
|
|
|
var input = document.getElementById("playlist"); |
|
var load = document.getElementById("load"); |
|
load.addEventListener("click", function() { |
|
var url = input.value; |
|
if(url) { |
|
var get_list = listFromUrl(client_id, url); |
|
get_list.then(function(r) { |
|
console.log(r); |
|
if(r.kind === "playlist") { |
|
set.update(r.id); |
|
} else { |
|
alert("Must be a SoundCloud Playlist URL"); |
|
} |
|
}, function(e) { |
|
alert(e); |
|
}); |
|
} else { |
|
alert("Must be a SoundCloud Playlist URL"); |
|
} |
|
}); |
|
|
|
function listFromUrl(client_id, value) { |
|
var url = "https://api.soundcloud.com/resolve.json?url="+ encodeURI(value) +"&client_id=" + client_id; |
|
var xmlhttp = new XMLHttpRequest(); |
|
return new Promise(function(res, rej) { |
|
xmlhttp.onreadystatechange = function() { |
|
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { |
|
var r = JSON.parse(xmlhttp.responseText); |
|
res(r); |
|
} else if (xmlhttp.readyState == 4 && xmlhttp.status != 200) { |
|
rej("Could not load playlist \"" + value + "\""); |
|
} |
|
}; |
|
xmlhttp.open("GET", url, true); |
|
xmlhttp.send(); |
|
}); |
|
} |
|
|
|
function SoundCloudSet(params) { |
|
var SCS = {}; |
|
init(); |
|
return SCS; |
|
|
|
function init() { |
|
SCS.SC = SC; |
|
SCS.client_id = params.client_id; |
|
SCS.SC.initialize({ |
|
client_id: SCS.client_id |
|
}); |
|
SCS.update = update; |
|
update(params.list_id); |
|
} |
|
|
|
function update(list_id) { |
|
SCS.list_id = list_id; |
|
|
|
if(!SCS.svg) { |
|
setupVisualizer(); |
|
} |
|
|
|
var load_list = loadList(); |
|
load_list.then(function(list) { |
|
SCS.list = list; |
|
generateInfo(); |
|
generateArt(); |
|
generateList(); |
|
}, function(err) { |
|
alert(err); |
|
}); |
|
} |
|
|
|
function loadList() { |
|
return new Promise(function(res, rej) { |
|
SCS.SC.get("/playlists/" + SCS.list_id).then(function(list) { |
|
if(list.embeddable_by === "all") { |
|
res(list); |
|
} else { |
|
rej("You aren't allowed to embed this playlist"); |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
function generateInfo() { |
|
if(!SCS.info) { |
|
SCS.info = document.createElement("section"); |
|
document.body.appendChild(SCS.info); |
|
SCS.info.addEventListener("click", function(e) { |
|
var el = e.target; |
|
if(e.target.getAttribute("data-playing") === "true") { |
|
pause(); |
|
} |
|
}); |
|
} else { |
|
SCS.info.innerHTML = ""; |
|
} |
|
var title = document.createElement("h1"); |
|
var link = document.createElement("a"); |
|
link.href = SCS.list.permalink_url; console.log(SCS.list); |
|
link.innerHTML = SCS.list.title + "<br><small>" + SCS.list.user.username + "</small>"; |
|
title.appendChild(link); |
|
SCS.info.appendChild(title); |
|
} |
|
|
|
function generateArt() { |
|
var image_source = SCS.list.artwork_url ? SCS.list.artwork_url : SCS.list.tracks[0].artwork_url; |
|
var image_url = image_source.replace(/-large/, "-t500x500"); |
|
var canvas = document.createElement("canvas"); |
|
var context = canvas.getContext("2d"); |
|
canvas.width = 500; canvas.height = 500; |
|
var image = new Image(); |
|
image.crossOrigin = "anonymous"; |
|
image.src = image_url; |
|
image.onload = function() { |
|
SCS.info.style.backgroundImage = "url(" + image_url + ")"; |
|
context.drawImage(image, 0, 0); |
|
SCS.image_data = context.getImageData(0, 0, 500, 500); |
|
processImageData(); |
|
} |
|
SCS.bg = document.getElementById("bg"); |
|
SCS.bg.style.backgroundImage = "url(" + image_url + ")"; |
|
} |
|
|
|
function generateList() { |
|
var list = document.createElement("ul"); |
|
for(var t = 0; t < SCS.list.tracks.length; t++) { |
|
var track = SCS.list.tracks[t]; |
|
var $track = document.createElement("li"); |
|
var $artist = document.createElement("a"); |
|
$artist.href = track.user.permalink_url; |
|
$artist.title = track.user.username; |
|
$artist.style.backgroundImage = "url(" + track.user.avatar_url + ")"; |
|
var $song = document.createElement("span"); |
|
$song.innerHTML = track.title; |
|
$track.appendChild($artist); |
|
$track.appendChild($song); |
|
list.appendChild($track); |
|
$song.setAttribute("data-id", track.id); |
|
$song.addEventListener("click", function(e) { |
|
var curr = document.querySelector(".active"); |
|
if(curr) curr.className = ""; |
|
e.target.className = "active"; |
|
togglePlay(e.target.getAttribute("data-id")); |
|
}); |
|
} |
|
SCS.info.appendChild(list); |
|
} |
|
|
|
function processImageData() { |
|
SCS.hsls = []; |
|
var data = SCS.image_data.data; |
|
for(var i = 0; i < data.length; i += 4) { |
|
var hsl = RGBtoHSL(data[i], data[i + 1], data[i + 2]); |
|
SCS.hsls.push(hsl); |
|
} |
|
} |
|
|
|
function togglePlay(track_id) { |
|
if(SCS.playing) { |
|
if(SCS.current_track === track_id) { |
|
pause(); |
|
} else { |
|
play(track_id); |
|
} |
|
} else { |
|
if(SCS.current_track === track_id) { |
|
play(); |
|
} else { |
|
play(track_id); |
|
} |
|
} |
|
} |
|
|
|
function pause() { |
|
SCS.player.pause(); |
|
SCS.playing = false; |
|
document.body.className = ""; |
|
SCS.info.setAttribute("data-playing", "false"); |
|
} |
|
|
|
function play(track_id) { |
|
document.body.className = "playing"; |
|
SCS.info.setAttribute("data-playing", "true"); |
|
SCS.playing = true; |
|
updateVisualizer(); |
|
if(track_id) { |
|
SCS.current_track = track_id; |
|
var url = "https://api.soundcloud.com/tracks/" + track_id + "/stream?client_id=" + SCS.client_id; |
|
loadAnalyser(url); |
|
} else { |
|
SCS.player.play(); |
|
} |
|
} |
|
|
|
function setupVisualizer() { |
|
SCS.cvs = document.createElement("canvas"); |
|
SCS.ctx = SCS.cvs.getContext("2d"); |
|
SCS.cvs.width = 1024; |
|
SCS.cvs.height = 200; |
|
document.body.appendChild(SCS.cvs); |
|
SCS.svg = document.getElementById("svg"); |
|
SCS.svgNS = SCS.svg.namespaceURI; |
|
// SCS.polyline = document.createElementNS(SCS.svgNS, "polyline"); |
|
SCS.polygon_1 = document.createElementNS(SCS.svgNS, "polygon"); |
|
|
|
var di = Math.min(window.innerWidth, window.innerHeight); |
|
SCS.di = di; |
|
SCS.rad = di / 2; |
|
SCS.max_height = di / 2; |
|
SCS.tilt = -40; |
|
SCS.choke = 130; |
|
|
|
SCS.svg.setAttribute("width", SCS.di+"px"); |
|
SCS.svg.setAttribute("height", SCS.di+"px"); |
|
SCS.svg.setAttribute("viewBox", "0 0 " + SCS.di + " " + SCS.di); |
|
// SCS.svg.appendChild(SCS.polyline); |
|
SCS.svg.appendChild(SCS.polygon_1); |
|
} |
|
|
|
function loadAnalyser(url) { |
|
SCS.player = SCS.player || new Audio(); |
|
SCS.player.src = url; |
|
SCS.player.crossOrigin = "anonymous"; |
|
SCS.player.addEventListener("canplaythrough", function() { |
|
if(!SCS.player_ctx) { |
|
var AudioContext = window.AudioContext || window.webkitAudioContext; |
|
SCS.player_ctx = new AudioContext(); |
|
var fftSize = 512; |
|
SCS.framerate = 0; |
|
SCS.analyser = (SCS.analyser || SCS.player_ctx.createAnalyser()); |
|
SCS.analyser.minDecibels = -90; |
|
SCS.analyser.maxDecibels = -10; |
|
SCS.analyser.smoothingTimeConstant = 0.3;//0.75; |
|
SCS.analyser.fftSize = fftSize; |
|
|
|
SCS.sourceNode = SCS.player_ctx.createMediaElementSource(SCS.player); |
|
SCS.sourceNode.connect(SCS.analyser); |
|
SCS.sourceNode.connect(SCS.player_ctx.destination); |
|
} |
|
SCS.player.play(); |
|
|
|
updateVisualizer(); |
|
}); |
|
} |
|
|
|
function getPoints(freq_value, freq_sequence, freq_count) { |
|
var freq_ratio = freq_sequence/freq_count, |
|
x = (SCS.di - (SCS.tilt * 2)) * freq_ratio + SCS.tilt, |
|
y = SCS.di / 2; |
|
|
|
var // using power to increase highs and decrease lows |
|
freq_ratio = freq_value / 255, |
|
throttled_ratio = (freq_value - SCS.choke) / (255 - SCS.choke), |
|
stroke_width = SCS.di / freq_count * 0.6 * throttled_ratio, |
|
throttled_y = Math.max(throttled_ratio, 0) * SCS.max_height; |
|
|
|
var loc_x = x - stroke_width / 2, |
|
loc_y1 = y - throttled_y / 2, |
|
loc_y2 = y + throttled_y / 2, |
|
x_offset = SCS.tilt * throttled_ratio; |
|
|
|
if (throttled_ratio > 0) { |
|
var point_1 = (loc_x - x_offset) + "," + loc_y1, |
|
point_2 = (loc_x + x_offset) + "," + loc_y2; |
|
if(freq_sequence % 2 == 0) { |
|
return point_1; |
|
} else { |
|
return point_2; |
|
} |
|
} else { |
|
return loc_x + "," + y |
|
} |
|
} |
|
|
|
function getPolygonPoints(freq_value, freq_sequence, freq_count, colorSequence) { |
|
var freq_ratio = freq_sequence/freq_count, |
|
x1 = (SCS.rad + Math.cos(freq_sequence * Math.PI / freq_count) * freq_value / 0.85), |
|
y1 = (SCS.rad + Math.sin(freq_sequence * Math.PI / freq_count) * freq_value / 0.85), |
|
x2 = (SCS.rad + Math.cos(freq_sequence * Math.PI / -freq_count) * freq_value / 0.85), |
|
y2 = (SCS.rad + Math.sin(freq_sequence * Math.PI / -freq_count) * freq_value / 0.85); |
|
|
|
// x = y, y = x to rotate 90deg. |
|
// css rotation killed the frame rate. |
|
return [y1 + "," + x1, y2 + "," + x2]; |
|
} |
|
|
|
function updateVisualizer() { |
|
if(SCS.framerate % 1 === 0) { |
|
var buffer = new Uint8Array(SCS.analyser.frequencyBinCount); |
|
SCS.analyser.getByteTimeDomainData(buffer); |
|
|
|
// clear points array for polyline |
|
// var points = []; |
|
// clear points array for polygon |
|
var points1 = [], |
|
points2 = []; |
|
|
|
var average = 0; |
|
for (var i = 0; i < buffer.length; i++) { |
|
var v = buffer[i]; |
|
// points.push(getPoints(v, i+1, buffer.length)); |
|
var ps = getPolygonPoints(buffer[i], i + 1, buffer.length, SCS.framerate); |
|
points1.push( ps[0] ); |
|
points2.push( ps[1] ); |
|
average += buffer[i]; |
|
} |
|
average /= buffer.length; |
|
var avg_ratio = (average - 100) / (255 - 100); |
|
// SCS.rms = Math.sqrt(average); // avg ratio should cover this. |
|
|
|
var p = [points1, points2.reverse()].join(" "); |
|
SCS.polygon_1.setAttribute("points", p); |
|
|
|
// SCS.polyline.setAttribute("points", points.join(" ")); |
|
|
|
var hsl = SCS.hsls[Math.floor(Math.random() * SCS.hsls.length)]; |
|
var lit = Math.min(hsl[2] + avg_ratio * 60, 100); |
|
var dark = Math.max(0, lit - 50); |
|
var alpha = avg_ratio * 0.5 + 0.5; |
|
var light_color = "hsla(" + hsl[0] + "," + hsl[1] + "%," + lit + "%, " + alpha + ")"; |
|
var dark_color = "hsl(" + hsl[0] + "," + hsl[1] + "%," + dark + "%)"; |
|
|
|
if(SCS.framerate % 16 === 0) { |
|
// SCS.polyline.setAttribute("stroke", light_color); |
|
SCS.polygon_1.setAttribute("stroke", light_color); |
|
SCS.polygon_1.setAttribute("fill", light_color); |
|
// SCS.bg.style.backgroundColor = dark_color; |
|
} |
|
|
|
// clear the current state |
|
SCS.ctx.clearRect(0, 0, 1024, 200); |
|
|
|
// set the fill style |
|
SCS.ctx.fillStyle = "rgba(255,255,255,0.2)"; |
|
drawSpectrum(buffer); |
|
|
|
// blur the bg |
|
var blur = "blur(" + (avg_ratio * 8) + "px)"; |
|
SCS.bg.style.webkitFilter = blur; |
|
SCS.bg.style.filter = blur; |
|
} |
|
|
|
if(SCS.playing) { |
|
SCS.framerate += 1; |
|
requestAnimationFrame(updateVisualizer); |
|
} |
|
} |
|
|
|
function drawSpectrum(buffer) { |
|
for(var i = 0; i < buffer.length; i += 2 ) { |
|
var value = buffer[i]; |
|
var rat_x = i / (buffer.length - 2); |
|
var rat_y = (value + 40) / 255; |
|
var w = 4; |
|
var x = (1024 - w) * rat_x; |
|
SCS.ctx.fillRect(x, 100 - 50 * rat_y, w, 50 * rat_y * 2); |
|
} |
|
} |
|
|
|
// Source: http://stackoverflow.com/questions/24218783/javascript-canvas-pixel-manipulation |
|
function RGBtoHSL(r, g, b) { |
|
var hsl = []; |
|
var K = 0.0, |
|
swap = 0; |
|
if (g < b) { |
|
swap = g; |
|
g = b; |
|
b = swap; |
|
K = -1.0; |
|
} |
|
if (r < g) { |
|
swap = r; |
|
r = g; |
|
g = swap; |
|
K = -2.0 / 6.0 - K; |
|
} |
|
var chroma = r - (g < b ? g : b); |
|
hsl[0] = Math.abs(K + (g - b) / (6.0 * chroma + 1e-20)) * 100; |
|
hsl[1] = chroma / (r + 1e-20) * 100; |
|
hsl[2] = r; |
|
return hsl; |
|
} |
|
} |
|
|
|
|
|
|
|
|