From 1c1105da456916989909a23b2d3d259c8c74271e Mon Sep 17 00:00:00 2001 From: Z User Date: Wed, 4 Nov 2026 16:14:36 +0000 Subject: [PATCH] e7e98d75-c472-45a0-8363-2cc17e4e3f4e --- rust-agent/src/spectrum.rs | 15 +++++++++++++-- src/components/ava/help-panel.tsx | 2 +- src/lib/ava/audio.ts | 24 ++++++++++++++++++++++-- src/lib/release-notes.ts | 10 ++++++++++ src/lib/version.ts | 14 +++++++++++++- worklog.md | 27 +++++++++++++++++++++++++++ 6 files changed, 86 insertions(+), 6 deletions(-) diff --git a/rust-agent/src/spectrum.rs b/rust-agent/src/spectrum.rs index 4a1d108..f8adf47 100755 --- a/rust-agent/src/spectrum.rs +++ b/rust-agent/src/spectrum.rs @@ -15,6 +15,12 @@ use rustfft::{Fft, FftPlanner}; /// engine's AnalyserNode. pub const FFT_SIZE: usize = 8192; +/// Top of the script-visible spectrum, in Hz. Mirrors SPEC_TOP_HZ in +/// src/lib/ava/audio.ts (v7.10.1): the bar field stops at 16 kHz instead of +/// Nyquist because music and every lossy codec are silent above that, which +/// used to freeze the right edge of spectrum visuals. +pub const SPEC_TOP_HZ: f32 = 16000.0; + /// Waveform history kept for the script `v` variable (matches the web /// engine's "most recent 2048 samples" window). pub const WAVE_SIZE: usize = 2048; @@ -48,13 +54,18 @@ impl Spectrum { } /// Spectrum magnitude at position 0..1 → 0..1 with the web engine's - /// log-ish frequency mapping. Mirrors `AudioEngine.getSpectrum()`. + /// log-ish frequency mapping. Mirrors `AudioEngine.getSpectrum()` in + /// audio.ts: the field tops out at `SPEC_TOP_HZ` (16 kHz), not Nyquist. pub fn spectrum_at(&self, pos: f64) -> f64 { if self.mags.is_empty() { return 0.0; } let p = pos.clamp(0.0, 1.0); - let bin = 2.0 + (p.powf(1.6) * (self.mags.len() as f64 - 3.0)).floor(); + let bh = bin_hz(self.sample_rate, self.mags.len() * 2); + let hi = ((SPEC_TOP_HZ / bh).ceil() as usize) + .min(self.mags.len() - 1) + .max(3); + let bin = 2.0 + (p.powf(1.6) * (hi as f64 - 2.0)).floor(); self.mags[(bin as usize).min(self.mags.len() - 1)] as f64 } } diff --git a/src/components/ava/help-panel.tsx b/src/components/ava/help-panel.tsx index 8b9ebae..468aa21 100755 --- a/src/components/ava/help-panel.tsx +++ b/src/components/ava/help-panel.tsx @@ -28,7 +28,7 @@ const FRAME_VARS: [string, string][] = [ const POINT_VARS: [string, string][] = [ ['i', '0..1 point index'], ['v', 'waveform sample −1..1'], - ['sv', 'spectrum magnitude 0..1'], + ['sv', 'spectrum magnitude 0..1 (≈12 Hz to 16 kHz, log-ish -- the range music actually occupies)'], ['x / y', 'position −1..1 (x = sin(a)*rad / aspect keeps circles round)'], ['red / green / blue', 'point color 0..1'], ['skip', '> 0.5 hides the point / breaks the line'], diff --git a/src/lib/ava/audio.ts b/src/lib/ava/audio.ts index 410e70e..f8c6f98 100755 --- a/src/lib/ava/audio.ts +++ b/src/lib/ava/audio.ts @@ -25,6 +25,19 @@ import type { AudioSourceKind, BandConfig, BandLevels } from './types' import { ZERO_LEVELS } from './types' const FFT_SIZE = 8192 +/** + * Top of the script-visible spectrum, in Hz (v7.10.1). The bar field + * (getspec()/sv) tops out HERE instead of at Nyquist (22/24 kHz): real + * music carries almost nothing above ~16 kHz, and every lossy source + * low-passes even lower (192 kbps MP3 ≈ 18.6 kHz, YouTube AAC ≈ 15.5 kHz; + * measured across the demo collection: 0.000% of total magnitude above + * 18.6 kHz). A full-range mapping therefore left the rightmost ~20% of + * every spectrum visual frozen at zero -- bars that never moved. 16 kHz + * matches the spectral-flux cap in getBands(), and puts the right edge on + * the 12-16 kHz "air" band where cymbals, hats and synth sparkle live. + * Mirrored by SPEC_TOP_HZ in rust-agent/src/spectrum.rs. + */ +const SPEC_TOP_HZ = 16000 const ATTACK_TAU = 0.035 // seconds const RELEASE_TAU = 0.22 // seconds @@ -375,11 +388,18 @@ export class AudioEngine { return (this.timeData[idx] - 128) / 128 } - /** Spectrum magnitude at position 0..1 → 0..1 (log-ish frequency mapping). */ + /** + * Spectrum magnitude at position 0..1 → 0..1 (log-ish frequency mapping). + * The field spans bin 2 (~12 Hz) to the SPEC_TOP_HZ bin (~16 kHz), so + * every position lands on frequencies music actually occupies -- see + * SPEC_TOP_HZ above for why it must not stretch to Nyquist. + */ getSpectrum(pos: number): number { if (this.freqData.length === 0 || this.kind === 'idle') return 0 const p = Math.min(1, Math.max(0, pos)) - const bin = 2 + Math.floor(Math.pow(p, 1.6) * (this.freqData.length - 3)) + const binHz = this.sampleRate / 2 / this.freqData.length + const hiBin = Math.max(3, Math.min(this.freqData.length - 1, Math.ceil(SPEC_TOP_HZ / binHz))) + const bin = 2 + Math.floor(Math.pow(p, 1.6) * (hiBin - 2)) return this.freqData[bin] / 255 } } \ No newline at end of file diff --git a/src/lib/release-notes.ts b/src/lib/release-notes.ts index 8c30c9d..adaeef0 100755 --- a/src/lib/release-notes.ts +++ b/src/lib/release-notes.ts @@ -39,6 +39,16 @@ export interface ReleaseNote { } export const RELEASE_NOTES: ReleaseNote[] = [ + { + version: '7.10.1', + name: 'Spectrum edge unfrozen', + changes: [ + { + kind: 'fixed', + text: 'Spectrum visuals used to sit dead along the right edge: the bar field stretched all the way to the top of the FFT (~24 kHz), where music carries almost nothing and MP3/YouTube audio is already cut off, so the rightmost bars read permanent silence no matter how loud the track got. I capped the field at 16 kHz -- the range where music actually lives -- so every bar has something to react to.', + }, + ], + }, { version: '7.10', name: 'Ten demo tracks', diff --git a/src/lib/version.ts b/src/lib/version.ts index 0384e80..37f979d 100755 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -12,6 +12,18 @@ // warns in the console when the two drift apart. // // History: +// 7.10.1 — the frozen right edge of spectrum visuals. getspec()/sv mapped +// the bar field across the whole FFT (bin 2 ... Nyquist, ~12 Hz to +// 24 kHz), but music carries almost nothing above ~16 kHz and every +// lossy source low-passes even lower (192 kbps MP3 at ~18.6 kHz, +// YouTube AAC at ~15.5 kHz; measured across the demo collection: +// 0.000% of total magnitude above 18.6 kHz), so the rightmost ~20% +// of every spectrum visual read a permanent zero -- bars that never +// moved. getSpectrum() now tops the field out at 16 kHz +// (SPEC_TOP_HZ, matching the spectral-flux cap in getBands()), so +// the right edge lands on the 12-16 kHz "air" band where cymbals, +// hats and synth sparkle live. rust-agent's spectrum_at() mirrors +// the change, and the help panel's sv entry now states the span. // 7.10 — ten demo tracks + first-person changelog + declutter. The demo // source is now a COLLECTION: ten mastered tracks in // public/demo-tracks/ (demo-01.mp3 … demo-10.mp3, filename order @@ -299,4 +311,4 @@ export const APP_NAME = 'Open Visualization Engine' export const APP_SHORT = 'OVE' -export const APP_VERSION = '7.10' +export const APP_VERSION = '7.10.1' diff --git a/worklog.md b/worklog.md index a1af4ad..cfc22f0 100755 --- a/worklog.md +++ b/worklog.md @@ -2213,3 +2213,30 @@ BROWSER E2E (agent-browser + VLM, ALL PASS): Stage Summary: - v7.7 shipped: scripts are now shareable as single Unicode strings — ~half the length of the script, self-checking, copy/paste-survivable, lossless for ANY text (compiling not required) — and the Settings tab is decluttered while the house account keeps Stream-a-Link working for everyone. The shared dialog height cap protects every dialog app-wide from tall content on short screens. + +--- +Task ID: 37 +Agent: main (Z.ai Code) +Task: v7.10.1 (user report while testing their new "Spectrum Bars" script): a section on the very right of spectrum visuals is inactive and never reacts to the music -- same dead right edge on other spectrum scripts. Explain why, then fix it engine-side so every script benefits. + +DIAGNOSIS (why the right edge is dead): +- getspec()/sv mapped bar position 0..1 across the WHOLE FFT: bin = 2 + floor(p^1.6 * 4093) at 8192-point FFT, so pos 1.0 = bin 4095 = Nyquist (22.05/24 kHz). pos 0.8 already lands at ~16.8 kHz. +- Real music carries almost nothing up there, and every lossy source low-passes even lower: the demo collection is 192 kbps MP3 @48 kHz (LAME low-pass ~18.6 kHz), YouTube AAC ~15.5 kHz. Measured on demo-01 (20 s, 8192-FFT): 0.000% of total magnitude above 18.6 kHz; content above -40 dB rel. peak ends at ~13.4 kHz, above -60 dB at ~17.8 kHz. +- So bar positions above ~0.82 read byte 0 every frame -- the rightmost ~20% of every spectrum visual was permanently frozen at the floor. Not a script bug: every script sampling getspec over 0..1 (all 10 converted getspec presets included) showed the same dead edge, exactly like the Winamp AVS originals. The engine's own flux detector already caps at 16 kHz for the same reason (audio.ts getBands). + +Work Log: +- src/lib/ava/audio.ts: new SPEC_TOP_HZ = 16000 const (with the full why-comment); getSpectrum() now maps the field from bin 2 (~12 Hz) to the 16 kHz bin instead of Nyquist: hiBin = clamp(ceil(16000/binHz)), bin = 2 + floor(p^1.6 * (hiBin - 2)). Same log-ish curve, every position now lands on frequencies music occupies; right edge = 12-16 kHz "air" band (cymbals/hats/sparkle). Matches the flux cap. +- rust-agent/src/spectrum.rs: SPEC_TOP_HZ const + spectrum_at() mirrors the new mapping exactly (source parity; not compiled locally, no cargo). +- help-panel.tsx: sv entry now states the span: "spectrum magnitude 0..1 (≈12 Hz to 16 kHz, log-ish -- the range music actually occupies)". +- version.ts 7.10.1 + history entry; release-notes.ts 7.10.1 "Spectrum edge unfrozen" (fixed, first-person voice). lint clean; tsc clean apart from the pre-existing base-target share-codec BigInt literals. + +VERIFICATION (three independent measurements): +- Offline full-track emulation (Blackman window + AnalyserNode dB byte mapping, every 21 ms of demo-01): under the NEW mapping every one of 48 bars is >15/255 in 76-100% of frames and >60/255 in 68-99% -- nothing is structurally dead. Under the old mapping positions >0.82 read literal zero. +- Fresh in-page chain (eval: fetch demo-01 -> decodeAudioData -> own AnalyserNode at t=150 s): top positions dance (pos 1.0 = 16 kHz fluctuates 4..118/255 frame to frame). +- Live E2E with the USER'S OWN "Spectrum Bars" script (found in the test browser's localStorage from the previous session, 28 bars x 4 pts, running on Demo Track 2 at 60 FPS): per-bar pixel measurements across 6 frames show the rightmost bars (f 0.74-1.0 = 9.9-16 kHz) swinging 4%..76% with the music -- under the old mapping those bars sampled 16.8-24 kHz and sat pinned at 0 (the script's own below(lvl,0.004)?0 snap would hold them perfectly flat). Also verified with a purpose-built 48-bar "Edge Check" script earlier in the session. +- Anomaly chased and closed: one 5-sample window showed bars 36-48 at floor while bass danced -- turned out to be the demo track's outro (offline analysis: t=187-191 s has a legitimately dead 10-16 kHz band while lows keep going); the demo ended right after (SRC: IDLE, place advanced), matching the timeline. Not a mapping issue. +- What's New dialog: v7.10.1 "Spectrum edge unfrozen" first with CURRENT badge + FIXED tag; drift guard silent after a clean reload (one transient guard warning was hot-reload event ordering + stale console history); the one hydration error was agent-browser's own data-__ab-ci instrumentation, absent on a clean reload. Console otherwise clean; dev.log clean. +- Test-browser localStorage cleared afterwards (convention); scratch PCM deleted. + +Stage Summary: +- v7.10.1 shipped: the frozen right edge of every spectrum visual is unfrozen. The bar field now spans ~12 Hz to 16 kHz (the range music actually occupies) instead of stretching to Nyquist, so the right bars land on the 10-16 kHz air band and dance with the hats/cymbals. The user's own Spectrum Bars script (whose 0.78+0.45*f treble tilt now has something real to boost) verified live: rightmost bars swing 4..76%.