Expand description
Running normalization: turns raw magnitudes into “loud relative to this track’s recent past” (ADR-0049).
Raw band means on real music sit at 0.006-0.040 while the authoring stimuli
reached 0.187-0.8, so every threshold in the shipped library was a magic
number against a table that moved three times in one week. Dividing each
signal by its own slowly-decaying running peak makes > 0.5 mean the same
thing on every track, at every gain setting, on every stimulus.
Three properties, each pinned by a test rather than by a comment:
- Instant attack. A new peak is adopted on the hop it arrives, so a hit reads high immediately instead of fading in.
- Slow release. The peak decays with a seconds-scale time constant, so a quiet passage lifts gradually rather than pumping bar to bar.
- Silence floor. Below a floor the output is zero, not amplified noise — the difference between a quiet room and a loud one must not be a full-scale visual.
The ceiling is reached routinely, and that is what these properties buy.
The reading is raw / peak against the signal’s own peak, so it is exactly
1.0 on any hop that is the loudest since the peak last released — on periodic
material, every kick, at any input level. It is scale-invariant for the same
reason: halve the input and both terms halve, so no input gain moves it. A
consumer that wants a magnitude rather than an excitation must read the raw
value beside it; one that reads a levelled scalar as a dimmer sees a term
pinned at its ceiling and no gain control that can unpin it.
Pure and allocation-free after construction: state is a fixed set of floats and every step is arithmetic on the input, so the same sequence always yields the same output (NFR section 6).
What is levelled here, each against its own running peak: the 64-band
spectrum array (BandNormalizer, one shared peak so the array’s internal
ratios survive), the bass/mid/treb scalars and the onset envelope
(PeakNormalizer), and the waveform trace (TraceNormalizer,
ADR-0139). The trace is the odd one of the set on two counts: it is a signal
rather than a magnitude, so it is divided rather than rectified and clamps to
-1..=1; and its divisor is published as AnalysisFrame::waveform_gain
rather than discarded, making it the one levelled output whose raw amplitude
a consumer can reconstruct. Levelling it is what makes the two frontends
agree, since the plugin taps its stream before the output volume and the
standalone taps loopback after it.
Where this sits matters. Normalization is applied at the published frame boundary only. The onset detector, the tempo tracker and the novelty detector all keep reading raw values, because each is tuned against raw magnitudes and would be actively harmed by an AGC: autocorrelating a peak-normalized envelope distorts the very periodicity the tempo tracker looks for, and per-band normalization flattens exactly the spectral-shape difference novelty exists to measure.
Structs§
- Band
Normalizer - The 64-band array’s normalizer: one running peak, tracking the loudest band, applied as a uniform gain across the whole array.
- Peak
Normalizer - One signal’s running peak, and the normalized reading it produces.
- Trace
Normalizer - The waveform’s normalizer: one running peak of the trace’s magnitude, applied as a uniform gain to the whole trace.
Constants§
- BAND_
FLOOR - Silence floor for the band scalars and the 64-band array.
- ONSET_
FLOOR - Silence floor for
onset. An order of magnitude lower because spectral flux is an order of magnitude smaller: the same groove peaks at 0.0167 with a 0.0016 mean, so this keeps the same ~1000x margin. - WAVE_
FLOOR - Silence floor for the waveform trace, in signal amplitude — a different
quantity from
BAND_FLOOR, which is a band mean, and deliberately an order of magnitude above it.