diff --git a/src/audio.js b/src/audio.js index 118f27f..254cb81 100644 --- a/src/audio.js +++ b/src/audio.js @@ -48,6 +48,11 @@ class AudioEngine { // Log-spaced spectrum for VU/bar visualisers. this.NB = 32; this.spectrum = new Float32Array(this.NB); + + // Time-domain waveform (oscilloscope), -1..1. + this.NW = 128; + this.wave = new Float32Array(this.NW); + this.timeData = new Uint8Array(this.analyser.fftSize); } resume() { if (this.ctx.state === 'suspended') this.ctx.resume(); } @@ -213,6 +218,13 @@ class AudioEngine { this.spectrum[i] = this.spectrum[i] * 0.55 + val * 0.45; // smooth } + // Time-domain waveform. + this.analyser.getByteTimeDomainData(this.timeData); + const wstep = this.timeData.length / this.NW; + for (let i = 0; i < this.NW; i++) { + this.wave[i] = (this.timeData[Math.floor(i * wstep)] - 128) / 128; + } + return this; } @@ -222,7 +234,7 @@ class AudioEngine { } get values() { - return { bass: this.bass, mid: this.mid, treble: this.treble, level: this.level, beat: this.beat, spectrum: this.spectrum }; + return { bass: this.bass, mid: this.mid, treble: this.treble, level: this.level, beat: this.beat, spectrum: this.spectrum, wave: this.wave }; } } diff --git a/src/effects.js b/src/effects.js index e06f22b..e9019d5 100644 --- a/src/effects.js +++ b/src/effects.js @@ -27,7 +27,13 @@ const FAMILIES = [ { name: 'SVG/Immagine', scale: 1.0, bgDark: true }, { name: 'VU Barre', scale: 1.0, bgDark: true }, { name: 'VU Analogico', scale: 1.0, bgDark: true }, - { name: 'VU Stereo', scale: 1.0, bgDark: true } + { name: 'VU Stereo', scale: 1.0, bgDark: true }, + { name: 'Waveform', scale: 1.0, bgDark: true }, + { name: 'Waveform Radiale', scale: 1.0, bgDark: true }, + { name: 'Tri-Banda', scale: 1.0, bgDark: true }, + { name: 'Reattivo Bassi', scale: 1.0, bgDark: true }, + { name: 'Reattivo Medi', scale: 1.0, bgDark: true }, + { name: 'Reattivo Alti', scale: 1.0, bgDark: true } ]; // Palettes: low colour (a) -> high colour (b), plus optional hue-cycle/sat. diff --git a/src/shaders.js b/src/shaders.js index 81daec5..8257622 100644 --- a/src/shaders.js +++ b/src/shaders.js @@ -22,6 +22,7 @@ uniform float uHueBase, uHueCycle, uSat, uContrast, uInvert, uWarp, uAudioMix, u uniform vec3 uColorA, uColorB; uniform sampler2D uTex; // custom SVG / image source (uploaded flipped-Y) uniform float uSpectrum[32]; // live 32-band spectrum for VU meters +uniform float uWave[128]; // live time-domain waveform (-1..1) uniform float uBgDark; // 1 = force the empty field (v→0) to black float uT = 0.0; // time * effect speed (set in main) @@ -280,6 +281,56 @@ float famVUStereo(vec2 uv) { return v; } +// ---- Waveform + band-reactive families ---- +float waveAt(int i) { return uWave[i]; } + +// Horizontal oscilloscope line. +float famWave(vec2 uv) { + float xn = uv.x * 0.5 + 0.5; + if (xn < 0.0 || xn > 1.0) return 0.0; + int idx = int(clamp(xn * 128.0, 0.0, 127.0)); + float y = waveAt(idx) * 0.42; + float d = abs(uv.y - y); + return smoothstep(0.018, 0.0, d) + 0.3 * smoothstep(0.09, 0.0, d); +} + +// Radial oscilloscope: the waveform wrapped around a circle. +float famWaveCircle(vec2 uv) { + float a = atan(uv.y, uv.x); + float r = length(uv); + float t = a / 6.2831853 + 0.5; + int idx = int(clamp(t * 128.0, 0.0, 127.0)); + float radius = 0.42 + waveAt(idx) * 0.2 * (0.6 + uLevel * aMix); + float d = abs(r - radius); + return smoothstep(0.018, 0.0, d) + 0.28 * smoothstep(0.08, 0.0, d); +} + +// Concentric zones, each reacting to a different band (inner=bass … outer=treble). +float famTriBand(vec2 uv) { + float r = length(uv); + float band = r < 0.33 ? uBass : (r < 0.66 ? uMid : uTreble); + float rings = 0.5 + 0.5 * sin(r * 30.0 - uT * 3.0); + return rings * (0.18 + 1.6 * band * aMix) * smoothstep(1.1, 0.05, r); +} + +float famBass(vec2 uv) { + float r = length(uv), b = uBass * aMix; + float blob = smoothstep(0.7 + b * 0.6, 0.0, r); + float rings = (0.5 + 0.5 * sin(r * 12.0 - uT * 2.0)) * smoothstep(1.2, 0.2, r); + return (blob + rings * 0.6) * (0.3 + 1.4 * b); +} +float famMid(vec2 uv) { + float r = length(uv), a = atan(uv.y, uv.x), m = uMid * aMix; + float petals = 0.5 + 0.5 * sin(a * 6.0 + uT * 2.0 + r * 8.0); + return petals * smoothstep(0.95, 0.0, r) * (0.25 + 1.6 * m); +} +float famTreble(vec2 uv) { + float t = uTreble * aMix, r = length(uv); + float g = hash(floor(uv * 42.0) + floor(vec2(uT * 8.0))); + float sparkle = step(0.72, g) * g; + return sparkle * (0.2 + 2.2 * t) * smoothstep(1.1, 0.1, r); +} + float field(int f, vec2 uv) { if (f == 0) return famJulia(uv); if (f == 1) return famMandel(uv); @@ -303,6 +354,12 @@ float field(int f, vec2 uv) { if (f == 20) return famVUBars(uv); if (f == 21) return famVUNeedle(uv); if (f == 22) return famVUStereo(uv); + if (f == 23) return famWave(uv); + if (f == 24) return famWaveCircle(uv); + if (f == 25) return famTriBand(uv); + if (f == 26) return famBass(uv); + if (f == 27) return famMid(uv); + if (f == 28) return famTreble(uv); return famCrystals(uv); // f == 15 } diff --git a/src/visualizer.js b/src/visualizer.js index b99f68b..baefbb4 100644 --- a/src/visualizer.js +++ b/src/visualizer.js @@ -26,7 +26,7 @@ class Visualizer { uFamily: U('uFamily'), uScale: U('uScale'), uRot: U('uRot'), uRotSpeed: U('uRotSpeed'), uSym: U('uSym'), uHueBase: U('uHueBase'), uHueCycle: U('uHueCycle'), uSat: U('uSat'), uContrast: U('uContrast'), uInvert: U('uInvert'), uWarp: U('uWarp'), uAudioMix: U('uAudioMix'), uSpeed: U('uSpeed'), - uColorA: U('uColorA'), uColorB: U('uColorB'), uTex: U('uTex'), uSpectrum: U('uSpectrum'), uBgDark: U('uBgDark') + uColorA: U('uColorA'), uColorB: U('uColorB'), uTex: U('uTex'), uSpectrum: U('uSpectrum'), uBgDark: U('uBgDark'), uWave: U('uWave') }; // Custom-source texture (for the SVG/Image effect family). Starts empty. @@ -125,6 +125,7 @@ class Visualizer { gl.uniform1f(u.uBgDark, e.bgDark || 0); if (u.uSpectrum && audio.spectrum) gl.uniform1fv(u.uSpectrum, audio.spectrum); + if (u.uWave && audio.wave) gl.uniform1fv(u.uWave, audio.wave); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.tex);