summaryrefslogtreecommitdiff
path: root/client/draw.js
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2018-03-06 13:28:12 +0100
committerJules Laplace <julescarbon@gmail.com>2018-03-06 13:28:12 +0100
commit25fec83f6f7db468721a38adb2c84a0c0a2121f1 (patch)
tree5ea8b40951e98639b88b0378618a9ca2f835de7a /client/draw.js
displaying waveform and spectrogram
Diffstat (limited to 'client/draw.js')
-rw-r--r--client/draw.js197
1 files changed, 197 insertions, 0 deletions
diff --git a/client/draw.js b/client/draw.js
new file mode 100644
index 0000000..a9c182d
--- /dev/null
+++ b/client/draw.js
@@ -0,0 +1,197 @@
+import {
+ browser, requestAudioContext,
+ randint, randrange, clamp,
+} from './lib/util'
+
+import mouse from './lib/mouse'
+import color from './lib/color'
+
+let w, h
+let rx, ry
+
+const canvas = document.createElement('canvas')
+const ctx = canvas.getContext('2d')
+
+document.body.appendChild(canvas)
+document.body.addEventListener('resize', resize)
+resize()
+recenter()
+requestAnimationFrame(animate)
+
+function resize(){
+ w = canvas.width = window.innerWidth
+ h = canvas.height = window.innerHeight
+ clear()
+}
+function recenter(){
+ rx = randint(w), ry = randint(h)
+}
+function animate(t){
+ requestAnimationFrame(animate)
+ 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.5) + ')'
+ 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((px+py)/(w+h), 0, 1, 1)
+ 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 pixels_per_second = 1024
+ 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()
+}
+
+const signalWindows = require('signal-windows').windows
+const FFTJS = require('fft.js')
+const fft_size = 1024
+const fft = new FFTJS(fft_size)
+
+function toSpectrum(pcm){
+ const ham = signalWindows.construct('ham', fft_size)
+ const pcm_in = new Array(fft_size)
+ const pcm_length = pcm.length
+ const pcm_q_length = Math.ceil(pcm_length / fft_size) * fft_size
+
+ let i, j, fft_out, spec = [];
+ for (i = 0; i < pcm_q_length; i += fft_size/4) {
+ for (j = 0; j < fft_size; j++) {
+ pcm_in[j] = pcm[i+j] * ham[j] || 0
+ }
+ fft_out = fft.createComplexArray()
+ fft.realTransform(fft_out, pcm_in)
+ spec.push(fft_out)
+ }
+
+ return spec
+}
+function spectrum(pcm, sr){
+ sr = sr || 44100
+ const spec = toSpectrum(pcm)
+
+ ctx.save()
+
+ const scratch = document.createElement('canvas')
+ scratch.width = spec.length
+ scratch.height = fft_size
+ const scratchCtx = scratch.getContext('2d')
+
+ var imageData = ctx.createImageData(scratch.width, scratch.height)
+ var data = imageData.data
+
+ let i, j, u, v, _r, _i, col, spec_len = spec.length
+ for (i = 0; i < spec_len; i++) {
+ col = spec[i]
+ for (j = 0; j < fft_size; j++) {
+ u = (j * spec_len + i) * 4
+ v = j * 2
+ _r = col[v]
+ _i = col[v+1]
+ // red - real part
+ data[u] = _r * 127 + 127
+ // green - imag part
+ data[u+1] = _i * 127 + 127
+ // blue - magnitude
+ data[u+2] = Math.sqrt(Math.pow(_r, 2) + Math.pow(_i, 2)) * 128 + 127
+ // data[u] = 128
+ // data[u+1] = 128
+ data[u+2] = 128
+ data[u+3] = 255
+ }
+ }
+
+ scratchCtx.putImageData(imageData, 0, 0)
+
+ var pcm_length = pcm.length
+ var pixels_per_second = 1024
+
+ const width = Math.round(pcm_length / sr * pixels_per_second) // ok not really this
+ const height = Math.floor(h*3/4)
+
+ const x0 = 0
+ const y0 = Math.floor(h/4) + 20
+ clear(1, x0, y0, w, height)
+ ctx.drawImage(scratch, x0, y0, width, height)
+
+ ctx.restore()
+}
+
+export default {
+ canvas, ctx,
+ triangle, clear, line, dot,
+ waveform, spectrum
+} \ No newline at end of file