import Tone from 'tone' import output from './lib/output' import { browser, requestAudioContext, randint, randrange, clamp, mod, } 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.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 = 2048 const fft_overlap = fft_size / 4 const half_fft_size = fft_size / 2 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_overlap) { 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) fft.completeSpectrum(fft_out) spec.push(fft_out) } return spec } function fromSpectrum(spec, sr){ const spec_len = spec.length const ham = signalWindows.construct('ham', fft_size) const out = fft.createComplexArray() const pcm_length = fft_overlap * spec_len const audioBuffer = Tone.context.createBuffer(1, pcm_length, sr) const pcm = audioBuffer.getChannelData(0); let i, j, u, v, _r, _i, col for (i = 0; i < spec_len; i++) { col = spec[i] fft.inverseTransform(out, col) u = i * (fft_overlap) for (j = fft_size * 1/4; j < fft_size * 3/4; j++) { pcm[u+j] = out[j*2] / ham[j] || 0 } } const player = new Tone.Player(audioBuffer) player.connect(output) player.start(Tone.now() + pcm_length / sr) // console.log(Tone.now() + pcm_length / sr) return audioBuffer } function spectrum(pcm, sr){ sr = sr || 44100 const spec = toSpectrum(pcm) ctx.save() const scratch = document.createElement('canvas') scratch.width = spec.length scratch.height = half_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, hsl, spec_len = spec.length for (i = 0; i < spec_len; i++) { col = spec[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 // 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+3] = 255 data[u] = hsl[0] data[u+1] = hsl[1] data[u+2] = hsl[2] 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*2/3) 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() const new_pcm = fromSpectrum(spec, sr) console.log(new_pcm) } export default { canvas, ctx, triangle, clear, line, dot, waveform, spectrum }