From e7102f67158360244cdccb7f3e401ab4538697ff Mon Sep 17 00:00:00 2001 From: Reinder Nijhoff Date: Tue, 19 Mar 2024 17:02:45 +0100 Subject: [PATCH] started with example visualiser --- example/index.html | 1 + example/src/dittytoyJukebox.js | 4 +- example/src/dittytoyVisualiser.js | 158 ++++++++++++++++++++++++++++++ example/style.css | 19 +++- 4 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 example/src/dittytoyVisualiser.js diff --git a/example/index.html b/example/index.html index a4345dd..2f672b3 100644 --- a/example/index.html +++ b/example/index.html @@ -7,6 +7,7 @@ +
diff --git a/example/src/dittytoyJukebox.js b/example/src/dittytoyJukebox.js index 7f9d47f..19726fc 100644 --- a/example/src/dittytoyJukebox.js +++ b/example/src/dittytoyJukebox.js @@ -1,4 +1,5 @@ import {Dittytoy, MSG_INIT, MSG_NOTE_PLAYED, MSG_UPDATE, RUN_AS_WORKER} from 'dittytoy'; +import DittytoyVisualiser from "./dittytoyVisualiser.js"; function $(id) { return document.getElementById(id); @@ -7,11 +8,12 @@ function $(id) { export default class DittytoyJukebox { constructor() { this.dittytoy = new Dittytoy(); + this.visualizer = new DittytoyVisualiser(this.dittytoy); this.ditty = null; this.paused = 2; this.fetchDitties(); - this.fetchDitty('13d7ee15e5'); + this.fetchDitty('24373308b4'); $('play-button').addEventListener('click', async () => { if (this.paused === 0) { diff --git a/example/src/dittytoyVisualiser.js b/example/src/dittytoyVisualiser.js new file mode 100644 index 0000000..f537dba --- /dev/null +++ b/example/src/dittytoyVisualiser.js @@ -0,0 +1,158 @@ +import {MSG_INIT, MSG_UPDATE, MSG_WORKLET_READY} from "dittytoy"; + +const innerCircleRadius = 0.15; + +function smootherstep01(x) { + x = Math.max(0, Math.min(1, x)); // Clamp x to the range [0, 1] + return x * x * x * (x * (x * 6 - 15) + 10); +} + +function smoothstep(a, b, t) { + // Clamp t to range [0, 1] + t = Math.max(0, Math.min(1, (t - a) / (b - a))); + + return t * t * (3 - 2 * t); +} + +function step(x) { + const sw = 0.5; + + x += sw; + + const xi = Math.floor(x); + const f = x - xi; + + return xi + smootherstep01(f / sw); +} + +function lerp(a, b, t) { + return a * (1 - t) + b * t; +} + +export default class DittytoyVisualiser { + constructor(dittytoy) { + this.dittytoy = dittytoy; + this.initialized = false; + + this.loops = {}; + this.bmp = 0; + this.tick = 0; + this.sampleRate = 0; + this.volume = 0; + + this.canvas = document.getElementById('visualiser'); + this.ctx = this.canvas.getContext('2d'); + + this.dittytoy.addListener(MSG_INIT, ({structure}) => { + // ditty is compiled and ready to play + + this.loops = {}; + this.bmp = structure.bpm; + this.sampleRate = structure.sampleRate; + this.tick = 0; + + structure.loops.forEach(loop => { + this.loops[loop.name] = { + color: 'white', + lines: [], + volume: 0, + }; + }); + }); + + this.dittytoy.addListener(MSG_WORKLET_READY, ({context, source}) => { + const analyserFreq = context.createAnalyser(); + source.connect(analyserFreq); + analyserFreq.fftSize = 512; + this.freqAnalyser = analyserFreq; + this.freqData = new Uint8Array(256); + + if (!this.initialized) this.update(); + this.initialized = true; + }); + + this.dittytoy.addListener(MSG_UPDATE, (data) => { + if (data.amp) { + this.volume = lerp(this.volume, Math.sqrt(data.amp.master.volume), .1); + } + if (data.state) { + this.tick = data.state.tick; + } + }); + } + + update() { + window.requestAnimationFrame(() => this.update()); + const canvas = this.canvas; + const ctx = this.ctx; + const {width, height} = canvas.getBoundingClientRect(); + + if (canvas.width !== width || canvas.height !== height) { + canvas.width = width; + canvas.height = height; + } + + ctx.lineWidth = height / 300; + + // clear canvas + ctx.fillStyle = 'black'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + const aspect = width / height; + const t = (x, y) => [(x * .5 + .5 * aspect) * height, (y * .5 + .5) * height]; + + this.drawInnerCircle(t, height); + this.drawAnalyser(t); + } + + drawInnerCircle(t, height) { + const ctx = this.ctx; + ctx.fillStyle = 'white'; + + const x = this.tick - Math.floor(this.tick); + const x4 = this.tick/4 - Math.floor(this.tick/4); + + // create set of ripples, fading out over distance + + ctx.lineWidth *= .5; + const count = 10; + for (let i=0; i