Envelope¶
Reporting item 2 — attack time distribution and item 4 — tempo / modulation rate.
from debussy.envelope import attack_times_ms, tempo_bpm, modulation_peak_hz
attack_times_ms(y, fs, librosa) -> dict¶
Per-onset attack time: onsets from librosa.onset.onset_detect with
backtracking, then for each onset the 10 %–90 % rise time of the Hilbert
envelope (smoothed over 5 ms) up to the next onset.
Returns n_onsets, mean_ms, median_ms, sd_ms, frac_below_50ms and
n_below_50ms. With fewer than two onsets the statistics are None — an
honest "undefined", not zero.
The companion review's Tier-1 onset-dynamics check uses the median
(> 50 ms). The share and count of sub-50 ms onsets are reported alongside but
only ever downgrade a pass to CAUTION, never fail outright: envelope rise time
ignores absolute level, so it is a descriptor of onset shape rather than a
validated startle metric, and a couple of incidental transients should not
condemn a long quiet piece.
tempo_bpm(y, fs, librosa) -> float | None¶
librosa.feature.tempo. Returns None rather than raising when beat tracking
fails — a pure tone or an unmodulated drone has no meaningful tempo.
modulation_peak_hz(y, fs) -> float | None¶
Dominant amplitude-modulation rate in the 0.5–20 Hz band: the Hilbert
envelope is resampled to ~200 Hz, mean-removed, and the largest peak of its
magnitude spectrum within the band is returned. None if the signal is shorter
than one second of envelope.
This is the band where modulation interacts with respiratory and cardiac rhythms, which is why the guideline pairs it with tempo — a breath-paced stimulus may have no beat at all but a clear 0.1–0.3 Hz envelope cycle.