Created
March 5, 2020 02:23
-
-
Save velipso/3eb9f963bdf4c80733cf3b81d22c68f6 to your computer and use it in GitHub Desktop.
Random noise color
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head><title>Random Noise Color</title></head> | |
<body style="background-color: #eef;"> | |
<div id="ranges"></div> | |
<canvas width="2048" height="1200" style="width: 1024px; height: 600px;" id="cnv"></canvas> | |
<script> | |
var cnv = document.getElementById('cnv'); | |
var ctx = cnv.getContext('2d'); | |
var mix = {}; | |
var setVol = function(){}; | |
ctx.fillStyle = '#fff'; | |
ctx.fillRect(0, 0, cnv.width, cnv.height); | |
ctx.fillStyle = '#000'; | |
ctx.font = '80px sans-serif'; | |
ctx.textAlign = 'center'; | |
ctx.fillText('Click to play', cnv.width / 2, cnv.height / 2); | |
cnv.addEventListener('click', run); | |
(function(){ | |
var ranges = document.getElementById('ranges'); | |
([ | |
'volume', | |
'red', | |
'pink', | |
'white', | |
'blue', | |
'purple' | |
]).forEach(function(key){ | |
var p = document.createElement('p'); | |
var span = document.createElement('span'); | |
span.style.display = 'inline-block'; | |
span.style.width = '100px'; | |
var input = document.createElement('input'); | |
input.type = 'range'; | |
input.min = 0; | |
input.max = 120; | |
input.style.width = '900px'; | |
input.value = key === 'white' ? 100 : 0; | |
function update(){ | |
mix[key] = parseFloat(input.value) / 100; | |
span.innerHTML = key + ' ' + input.value + '%'; | |
if (key === 'volume') | |
setVol(); | |
} | |
input.addEventListener('change', update); | |
input.addEventListener('input', update); | |
update(); | |
p.appendChild(span); | |
p.appendChild(input); | |
if (key === 'pink'){ | |
var select = document.createElement('select'); | |
for (var i = 1; i <= 16; i++){ | |
var option = document.createElement('option'); | |
option.value = '' + i; | |
option.appendChild(document.createTextNode(option.value)); | |
select.appendChild(option); | |
} | |
select.selectedIndex = 5; | |
select.addEventListener('change', function(){ | |
pinkNoise = makePinkNoise(parseFloat(select.options[select.selectedIndex].value)); | |
}); | |
p.appendChild(select); | |
} | |
ranges.appendChild(p); | |
}); | |
})(); | |
var tot = []; | |
for (var i = 0; i < 2048; i++) | |
tot.push(0); | |
var totmax = 50; | |
var totwin = []; | |
function randfloat(){ | |
return Math.random() * 2 - 1; | |
} | |
function clip(v){ | |
if (v < -1) | |
return -1; | |
if (v > 1) | |
return 1; | |
return v; | |
} | |
var whiteNoise = function(){ | |
return clip(randfloat()); | |
}; | |
function makePinkNoise(depth){ | |
var o = 0; | |
var rnd = []; | |
var S = 0; | |
for (var i = 0; i < (depth - 1); i++){ | |
var r = randfloat(); | |
S += r; | |
rnd.push(r); | |
} | |
return function(){ | |
var idx = 0; | |
var bitmask = 2; | |
for (var idx = 0; idx < (depth - 1); idx++, bitmask *= 2){ | |
if ((o & bitmask) != 0){ | |
S -= rnd[idx]; | |
rnd[idx] = randfloat(); | |
S += rnd[idx]; | |
} | |
} | |
o = (o + 1) % (1 << depth); | |
return clip((randfloat() + S) / depth); | |
}; | |
} | |
var pinkNoise = makePinkNoise(6); | |
var redNoise = (function(){ | |
var S = 0; | |
return function(){ | |
var r, o; | |
while (true){ | |
r = randfloat(); | |
o = S + r; | |
if (o >= -8 && o <= 8) | |
break; | |
} | |
S = o; | |
return clip(o / 8); | |
}; | |
})(); | |
function makeBlueNoise(depth){ | |
var o = 0; | |
var rnd = []; | |
var S = 0; | |
for (var i = 0; i < (depth - 1); i++){ | |
var r = randfloat(); | |
S += r; | |
rnd.push(r); | |
} | |
return function(){ | |
var idx = 0; | |
var bitmask = 2; | |
for (var idx = 0; idx < (depth - 1); idx++, bitmask *= 2){ | |
if ((o & bitmask) != 0){ | |
S -= rnd[idx]; | |
rnd[idx] = randfloat(); | |
S += rnd[idx]; | |
} | |
} | |
o = (o + 1) % (1 << depth); | |
return clip((randfloat() + S) / depth); | |
}; | |
} | |
var blueNoise = makeBlueNoise(6); | |
var purpleNoise = (function(){ | |
var S = 0; | |
return function(){ | |
var r, o; | |
while (true){ | |
r = randfloat(); | |
o = S - r; | |
if (o >= -8 && o <= 8) | |
break; | |
} | |
S = r; | |
return clip(o / 8); | |
}; | |
})(); | |
function nextSample(){ | |
return redNoise() * mix.red + | |
pinkNoise() * mix.pink + | |
whiteNoise() * mix.white + | |
blueNoise() * mix.blue + | |
purpleNoise() * mix.purple; | |
} | |
function run(){ | |
cnv.removeEventListener('click', run); | |
var rate = 48000; | |
var actx = new AudioContext({ sampleRate: rate }); | |
var dest = actx.destination; | |
var sn = actx.createScriptProcessor(1024, 0, 1); | |
sn.onaudioprocess = function(e){ | |
var outputBuffer = e.outputBuffer; | |
var outputData = outputBuffer.getChannelData(0); | |
for (var i = 0; i < outputData.length; i++) | |
outputData[i] = clip(nextSample()); | |
}; | |
var gain = actx.createGain(); | |
setVol = function(){ | |
gain.gain.setValueAtTime(mix.volume, actx.currentTime); | |
}; | |
setVol(); | |
gain.connect(dest); | |
sn.connect(gain); | |
var az = actx.createAnalyser(); | |
az.fftSize = 4096; | |
az.smoothingTimeConstant = 0; | |
sn.connect(az); | |
function draw(){ | |
var ar = new Uint8Array(az.fftSize); | |
az.getByteFrequencyData(ar); | |
totwin.push(ar); | |
if (totwin.length > totmax){ | |
var oldar = totwin.shift(); | |
for (var i = 0; i < 2048; i++) | |
tot[i] -= oldar[i]; | |
} | |
for (var i = 0; i < 2048; i++) | |
tot[i] += ar[i]; | |
ctx.fillStyle = '#fff'; | |
ctx.fillRect(0, 0, cnv.width, cnv.height); | |
for (var sx = 0; sx < 2048; sx++){ | |
var y = tot[sx] / totwin.length; | |
ctx.beginPath(); | |
ctx.moveTo(sx, cnv.height); | |
ctx.lineTo(sx, cnv.height - y * cnv.height / 255); | |
ctx.stroke(); | |
} | |
} | |
setInterval(draw, 0); | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment