Skip to content

fix(viz): log/dB scaling for bar visualisers#57

Merged
vxcozy merged 1 commit into
mainfrom
fix/viz-bar-scaling
Apr 18, 2026
Merged

fix(viz): log/dB scaling for bar visualisers#57
vxcozy merged 1 commit into
mainfrom
fix/viz-bar-scaling

Conversation

@vxcozy
Copy link
Copy Markdown
Owner

@vxcozy vxcozy commented Apr 18, 2026

Summary

  • New visualiser::scaling::SpectrumScaler — dB conversion + envelope-follower AGC shared by bars_dot, bars_outline, classic_peak.
  • Bar-family visualisers now map loudness to bar height instead of raw linear magnitude, so typical-listening audio actually uses the pane.
  • Per-viz test: after the AGC converges on a peak-bin magnitude of ~6.4 (models peak_sample ≈ 0.05), the tallest bar reaches ≥60% of pane height.
  • Existing tint/peak-cap/gutter behaviour from fix(viz): render braille visualisers edge-to-edge #55 is preserved — the tinted area is simply smaller now that the bars are bigger.

Why

Raw FFT magnitudes span a huge dynamic range and are perceived logarithmically. Mapping them linearly to bar height leaves bars pinned to the bottom 5-10% of the pane at typical listening volumes (peak_sample ≈ 0.05-0.15), which reads as "visualiser doesn't fill the window".

Two layers of fix:

  1. dB compression. 20 * log10(mag) with a -60 dB floor and a 40 dB display range. Loudness → row-height becomes linear.
  2. Envelope-follower AGC. Tracks the loudest band across frames with ~50 ms attack (transients pop immediately) and ~2.5 s release (ceiling doesn't dive during quiet passages). Peak is clamped at a -30 dB minimum so silence doesn't auto-gain a whisper into full-height bars.

The shared module keeps the math in one place — the three bar viz all call SpectrumScaler::update + normalise. Non-bar viz (pulse, wave, scope, terrain, heartbeat, etc.) are untouched.

Test plan

  • cargo fmt --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace --all-features
  • new unit tests: scaling::typical_listening_peak_fills_majority_of_pane, scaling::quiet_passage_stays_visible_but_not_pinned, scaling::loud_transient_clamps_at_one, scaling::silence_does_not_auto_gain, scaling::attack_faster_than_release, scaling::no_nan_on_zero_input, plus typical_listening_fills_pane per bar viz
  • manual: play live radio in clitunes, cycle bars_dot / bars_outline / classic_peak, confirm bars span the pane at listening volume
  • manual: drop volume to ~5%, verify bars stay visible without pinning; push to 100%, verify no overshoot

Raw linear FFT magnitudes map bars to ~5-10% of pane height at typical
listening volumes, leaving the top of the viewport black. Replace the
`(1 + mag/1000).ln()` soft-compress with a two-layer mapping shared by
bars_dot, bars_outline, and classic_peak:

1. dB conversion (`20 * log10`) with a -60 dB floor and 40 dB display
   range so perceived loudness maps linearly to bar height.
2. Envelope-follower AGC on the loudest band: ~50 ms attack, ~2.5 s
   release, with a -30 dB minimum peak so silence doesn't auto-gain.

The shared helper lives in `visualiser::scaling::SpectrumScaler`.
Per-viz tests assert ≥60% bar fill once the AGC converges on a
typical-listening input. Existing tint/peak-cap/gutter behaviour is
preserved.

Closes clitunes-kfj / CLI-89
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@vxcozy vxcozy merged commit 611ce9b into main Apr 18, 2026
12 checks passed
@vxcozy vxcozy deleted the fix/viz-bar-scaling branch April 18, 2026 17:48
vxcozy added a commit that referenced this pull request Apr 18, 2026
Users report the entire visualiser suite feels sluggish — not just the
bar family. The shared sluggishness is the one-pole EnergyTracker used
by ~20 viz with release=0.88 (tau ~258ms at 30fps), plus the PR #57 AGC
whose 2.5s release was tuned for safety over feel. Tightening time
constants where the viz is meant to track the beat (not slowly evolve
textures) restores the "it's moving with the music" quality.

scaling.rs (SpectrumScaler AGC, bars family):
  attack:  0.48  -> 0.735 (tau 50ms -> 25ms; kick snaps same frame)
  release: 0.013 -> 0.027 (tau 2.5s -> 1.2s; ceiling tracks sections)

Per-viz EnergyTracker release retained 0.88 -> 0.75 (tau ~258ms -> 115ms):
  heartbeat, wave, scope, matrix, terrain, retro, rain, binary,
  butterfly, scatter, fire, sakura, ripples, bars_dot, bars_outline,
  classic_peak.

bars_outline per-bar release: 0.85 -> 0.8 (tau 203ms -> 148ms, matches
bars_dot).

Deliberately untouched:
  - pulse, firework (0.3, 0.85) — delta-based transient detection needs
    a smoother baseline; tightening release defeats the spike trigger.
  - tunnel, vortex, plasma, metaballs, moire (>=0.9 retention) — these
    drive smoothly-evolving textures where jitter would read as noise,
    not snappiness.

Existing scaling.rs unit tests (attack_faster_than_release,
typical_listening_peak_fills_majority_of_pane, etc.) all pass under
the new coefficients — the attack:release ratio is still ~27:1.

Closes clitunes-lc4 / CLI-91
vxcozy added a commit that referenced this pull request Apr 18, 2026
Users report the entire visualiser suite feels sluggish — not just the
bar family. The shared sluggishness is the one-pole EnergyTracker used
by ~20 viz with release=0.88 (tau ~258ms at 30fps), plus the PR #57 AGC
whose 2.5s release was tuned for safety over feel. Tightening time
constants where the viz is meant to track the beat (not slowly evolve
textures) restores the "it's moving with the music" quality.

scaling.rs (SpectrumScaler AGC, bars family):
  attack:  0.48  -> 0.735 (tau 50ms -> 25ms; kick snaps same frame)
  release: 0.013 -> 0.027 (tau 2.5s -> 1.2s; ceiling tracks sections)

Per-viz EnergyTracker release retained 0.88 -> 0.75 (tau ~258ms -> 115ms):
  heartbeat, wave, scope, matrix, terrain, retro, rain, binary,
  butterfly, scatter, fire, sakura, ripples, bars_dot, bars_outline,
  classic_peak.

bars_outline per-bar release: 0.85 -> 0.8 (tau 203ms -> 148ms, matches
bars_dot).

Deliberately untouched:
  - pulse, firework (0.3, 0.85) — delta-based transient detection needs
    a smoother baseline; tightening release defeats the spike trigger.
  - tunnel, vortex, plasma, metaballs, moire (>=0.9 retention) — these
    drive smoothly-evolving textures where jitter would read as noise,
    not snappiness.

Existing scaling.rs unit tests (attack_faster_than_release,
typical_listening_peak_fills_majority_of_pane, etc.) all pass under
the new coefficients — the attack:release ratio is still ~27:1.

Closes clitunes-lc4 / CLI-91
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant