Add waveform (oscilloscope) and band-reactive effect families
- Time-domain waveform uniform (uWave) fed to the shader. - New families: Waveform, Waveform Radiale, Tri-Banda (concentric per-band zones), and Reattivo Bassi/Medi/Alti (single-band reactive). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
19085e9c16
commit
5f9e33b7dd
4 files changed
+79
-3
No files matched your search
+13
-1
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+7
-1
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user