diff options
Diffstat (limited to 'app/client/audio/draw.js')
| -rw-r--r-- | app/client/audio/draw.js | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/app/client/audio/draw.js b/app/client/audio/draw.js new file mode 100644 index 0000000..8caf8d8 --- /dev/null +++ b/app/client/audio/draw.js @@ -0,0 +1,270 @@ +import { + browser, requestAudioContext, + randint, randrange, clamp, mod, +} from './lib/util' + +import './lib/vendor/hidpi-canvas' + +import mouse from './lib/mouse' +import color from './lib/color' + +let w, h +let rx, ry + +const pixels_per_second = 512 // 1024 + +const canvas = document.createElement('canvas') +// document.body.appendChild(canvas) +// document.body.addEventListener('resize', resize) +resize() +recenter() +requestAnimationFrame(animate) + +// must request context after resizing +const ctx = canvas.getContext('2d') + +const scratch = document.createElement('canvas') +const scratchCtx = scratch.getContext('2d-lodpi') + +function resize(ww, hh){ + w = canvas.width = ww || window.innerWidth + h = canvas.height = hh || window.innerHeight + canvas.style.width = w + 'px' + canvas.style.height = h + 'px' +} +function recenter(){ + rx = randint(w), ry = randint(h) +} +let frame = null +function onFrame(fn){ + frame = fn +} +function animate(t){ + requestAnimationFrame(animate) + if (frame) { + frame(t) + frame = null + } + // ctx.save() + // ctx.globalAlpha = 0.0001 + // ctx.translate(w/2, h/2) + // ctx.rotate(0.1) + // ctx.translate(-rx, -ry) + // ctx.drawImage(canvas, 0, 0) + // ctx.restore() +} +function clear(n, x, y, ww, hh){ + ctx.fillStyle = 'rgba(255,255,255,' + (n || 0.9) + ')' + ctx.fillRect(x || 0, y || 0, ww || w, hh || h) + recenter() +} +function triangle(px,py,r){ + setTimeout( () => tri(px,py,r), Math.random()*10) + // setTimeout( () => tri(px,py,r), Math.random()*200) + // setTimeout( () => tri(px,py,r), Math.random()*300) +} +function tri(px, py, r) { + ctx.save() + ctx.globalCompositeOperation = 'multiply' + ctx.fillStyle = color.color((px+py)/(w+h), 0, 1, 0.2) + function p(){ + let theta = randrange(0, Math.PI*2) + let x = px + Math.cos(theta) * r + let y = py + Math.sin(theta) * r + return { x, y } + } + ctx.beginPath() + const p0 = p(), p1 = p(), p2 = p() + ctx.moveTo(p0.x, p0.y) + ctx.lineTo(p1.x, p1.y) + ctx.lineTo(p2.x, p2.y) + ctx.lineTo(p0.x, p0.y) + ctx.fill() + ctx.restore() +} +function line(y){ + ctx.beginPath() + ctx.moveTo(0, y) + ctx.lineTo(w, y) + ctx.strokeStyle = "#888" + ctx.strokeWidth = 1 + ctx.stroke() +} +function dot(x, y, r){ + ctx.fillStyle = "#f00" + ctx.beginPath() + ctx.moveTo(x, y) + ctx.arc(x, y, r, 0, 2*Math.PI) + ctx.fill() +} +function waveform(pcm, sr, pos, zoom){ + sr = sr || 44100 + pos = pos || 0 + + var width = w + var height = Math.floor(h/4) + var half_height = Math.floor(height/2) + var x0 = 0 + var y0 = 20 + var ymid = y0 + half_height + var max_width_in_seconds = width / pixels_per_second + var max_width_in_samples = max_width_in_seconds * sr + var pcm_length = pcm.length + var len = Math.min(pcm_length, max_width_in_samples) + var pcm_step = sr / pixels_per_second + var i + ctx.save() + + clear(1, x0, y0, width, height) + + line(ymid) + ctx.beginPath() + for (i = 0; i < width; i += 0.5) { + var si = Math.floor(pcm_step * i + pos) + if (si > pcm_length) break + var val = pcm[si] // -1, 1 + // ctx.moveTo(x0 + i, ymid) + ctx.lineTo(x0 + i, ymid + val * half_height) + } + ctx.strokeStyle = "rgba(250,20,0,0.9)" + ctx.strokeWidth = 1 + ctx.stroke() + ctx.restore() +} + +function spectrum(spec, x0, y0, ww, hh){ + const data = spec.data + const fft_size = spec.fft_size + const half_fft_size = spec.fft_size / 2 + const spec_len = data.length + + scratch.width = data.length + scratch.height = half_fft_size + + var imageData = ctx.createImageData(scratch.width, scratch.height) + var pixels = imageData.data + + let i, j, u, v, _r, _i, col, hsl + + for (i = 0; i < spec_len; i++) { + col = data[i] + + for (j = 0; j < half_fft_size; j++) { + u = ((half_fft_size - j) * spec_len + i) * 4 + v = j * 2 + _r = col[v] + _i = mod(col[v+1], Math.PI*2) / (Math.PI*2) + hsl = color.hsl2rgb((_i + 1) / 2, 1.0, 1 - Math.abs(_r / 10)) + // red - real part + // pixels[u] = _r * 127 + 127 + // // green - imag part + // pixels[u+1] = _i * 127 + 127 + // // blue - magnitude + // pixels[u+2] = Math.sqrt(Math.pow(_r, 2) + Math.pow(_i, 2)) * 128 + 127 + // pixels[u+3] = 255 + pixels[u] = hsl[0] + pixels[u+1] = hsl[1] + pixels[u+2] = hsl[2] + pixels[u+3] = 255 + } + } + + scratchCtx.putImageData(imageData, 0, 0) + + var pcm_length = spec.fft_overlap * spec_len + + x0 = x0 * devicePixelRatio || 0 + y0 = y0 * devicePixelRatio || Math.floor(h/4) + ww = ww * devicePixelRatio || w + hh = hh * devicePixelRatio || h/4 + + const width = Math.round(pcm_length / spec.sr * pixels_per_second) + const height = Math.floor(hh) + + ctx.save() + clear(1, x0, y0, w, height) + ctx.drawImage(scratch, x0, y0, width, height) + ctx.restore() +} +function raw_spectrum(spec, x0, y0, ww, hh, def_min_r, def_min_i){ + const data = spec.data + const fft_size = spec.fft_size + const half_fft_size = spec.fft_size / 2 + const spec_len = data.length + + const _scratch = document.createElement('canvas') + const _scratchCtx = _scratch.getContext('2d-lodpi') + _scratch.width = data.length + _scratch.height = half_fft_size + // console.log("spectrum w/h:", _scratch.width, _scratch.height) + + var imageData = _scratchCtx.createImageData(_scratch.width, _scratch.height) + var pixels = imageData.data + + let i, j, u, v, _r, _i, col, hsl + // let min_r = Infinity, max_r = -Infinity + // let min_i = Infinity, max_i = -Infinity + + // determined empirically.. + // let min_r = -60.4894057005308 + // let max_r = 107.23800966675353 + // let min_i = -59.4894057005308 + // let max_i = 108.23800966675353 + let min_r = -def_min_r + let max_r = def_min_r + let min_i = -def_min_i + let max_i = def_min_i + let delta_r = max_r - min_r + let delta_i = max_i - min_i + let mean_r = 0 + let mean_i = 0 + let sum_mean_r = 0, sum_mean_i = 0 + let real, imag + + for (i = 0; i < spec_len; i++) { + col = data[i] + mean_r = 0 + mean_i = 0 + + for (j = 0; j < half_fft_size; j++) { + u = (j * spec_len + i) * 4 + v = j * 2 + real = col[v] + imag = col[v+1] + mean_r += real + mean_i += imag + _r = clamp((real - min_r) / delta_r * 255, 0, 255) + _i = clamp((imag - min_i) / delta_i * 255, 0, 255) + + // hsl = color.hsl2rgb((_i + 1) / 2, 1.0, 1 - Math.abs(_r / 10)) + pixels[u+0] = _r + pixels[u+1] = _i + pixels[u+2] = 127 // hsl[2] + pixels[u+3] = 255 + + // min_r = Math.min(min_r, col[v]) + // max_r = Math.max(max_r, col[v]) + // min_i = Math.min(min_i, col[v]+1) + // max_i = Math.max(max_i, col[v]+1) + } + mean_r /= half_fft_size + mean_i /= half_fft_size + sum_mean_r += mean_r + sum_mean_i += mean_i + } + + sum_mean_r /= spec_len + sum_mean_i /= spec_len + // console.log(sum_mean_r, sum_mean_i) + // console.log("r:", min_r, max_r) + // console.log("i:", min_i, max_i) + _scratchCtx.putImageData(imageData, 0, 0) + + return { canvas: _scratch, imageData } +} + +export default { + canvas, ctx, onFrame, resize, + triangle, clear, line, dot, + waveform, spectrum, raw_spectrum, +}
\ No newline at end of file |
