|
DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
|
Causal SuperFlux onset detector with lock-free readout. More...
#include <OnsetDetector.h>
Classes | |
| struct | OdfFrame |
| struct | Onset |
| One offline onset: where it is and how strong it was. More... | |
Public Types | |
| enum class | Method { SpectralFlux , ComplexDomain , SuperFlux } |
| Onset-detection function family. Default SuperFlux. More... | |
Public Member Functions | |
| void | prepare (const AudioSpec &spec, int fftSize=0, int hop=0) |
| Allocates all state and configures the STFT front-end. | |
| void | setMethod (Method m) noexcept |
| Selects the ODF family. Lock-free. | |
| void | setThreshold (T deltaAboveMean) noexcept |
| Sets the adaptive peak-pick delta (margin above the moving mean). Non-finite values are ignored; negative values are clamped to 0. The delta is read against the frame-invariant ODF scale (see prepare()), so one value selects the same sensitivity at every rate and frame length. | |
| void | setAdaptiveWhitening (bool on) noexcept |
| Enables Stowell-Plumbley adaptive whitening (default off). | |
| Method | getMethod () const noexcept |
| ODF family in force. | |
| T | getThreshold () const noexcept |
| Peak-pick delta in force, after setThreshold()'s clamping. | |
| bool | getAdaptiveWhitening () const noexcept |
| True when adaptive whitening is on. | |
| void | processBlock (AudioBufferView< const T > in) noexcept |
| Feeds a mono block; reads channel 0 only. Const, never mutated. | |
| void | pushSamples (std::span< const T > samples) noexcept |
| Feeds a mono stream of samples. Lock-free, allocation-free. | |
| bool | onsetDetected () const noexcept |
| True if an onset was reported during the most recent call. | |
| T | getOnsetStrength () const noexcept |
| Onset strength (ODF value, frame-invariant scale; see prepare()) of the most recent reported onset. | |
| int64_t | getLastOnsetSample () const noexcept |
| Reference sample index (frame centre) of the most recent onset. The latch fires exactly getLatencySamples() samples after this index. | |
| int | getLatencySamples () const noexcept |
| The single causal reporting latency, L = fftSize + hop (samples). | |
| int | getFftSize () const noexcept |
| Analysis frame in samples actually in effect: the automatic choice when prepare() got fftSize <= 0, the rounded explicit request otherwise. Divide by the sample rate for the span in seconds; fs/getFftSize() is the STFT bin width. | |
| int | getHopSize () const noexcept |
| Hop in samples in effect (round(fs/200) unless overridden). | |
| int | getNumBands () const noexcept |
| Number of log-frequency filterbank bands built for the resolved frame; a direct readout of the analysis resolution in force. | |
| OdfFrame | getLastOdfFrame () const noexcept |
| The most recent analysis frame's onset-strength value. | |
| int | getWarmupFrames () const noexcept |
| Frames at the start of a stream whose ODF value is warm-up, not signal. | |
| int | getEnvelopeLatencySamples () const noexcept |
| How far behind the newest input sample a frame's reference sample sits, in samples. | |
| std::vector< int64_t > | detectOffline (AudioBufferView< const T > whole) |
| Offline detection over a whole mono buffer (channel 0). | |
| std::vector< Onset > | detectOfflineOnsets (AudioBufferView< const T > whole) |
| detectOffline() with each onset's strength alongside its position. | |
| void | beginOffline (int64_t expectedSamples=0) |
| Opens an incremental offline analysis. | |
| void | pushOffline (std::span< const T > samples) |
| Feeds the next piece of an offline session. No-op outside one. | |
| std::vector< int64_t > | finishOffline () |
| Closes the session and returns onset positions, as detectOffline(). Empty when no session is open. | |
| std::vector< Onset > | finishOfflineOnsets () |
| Closes the session and returns onsets with their strengths. | |
| void | reset () noexcept |
| Clears all streaming state and abandons an open offline session. Not concurrent with pushSamples(). | |
Static Public Attributes | |
| static constexpr int | kNumRegisters = detail::OnsetNovelty<T>::kNumRegisters |
| One frame of the onset-strength envelope (the ODF before the peak picker). | |
Causal SuperFlux onset detector with lock-free readout.
Role: analysis readout. It consumes const audio (AudioBufferView<const T> or a raw span) and never mutates it. All heap use happens in prepare(); the audio path (processBlock/pushSamples) allocates nothing, takes no lock and throws nothing.
| T | Sample type (float or double). |
Definition at line 127 of file OnsetDetector.h.
|
strong |
Onset-detection function family. Default SuperFlux.
| Enumerator | |
|---|---|
| SpectralFlux | |
| ComplexDomain | |
| SuperFlux | |
Definition at line 131 of file OnsetDetector.h.
|
inline |
Opens an incremental offline analysis.
For material that arrives in pieces – a file decoded block by block, a recording too long to hold – without concatenating it first. Feed it with pushOffline() in blocks of any size and close it with finishOffline(); the result is bit-identical to detectOffline() over the concatenation, whatever the blocking. Only the onset-strength envelope is kept between calls (one value and one position per hop), not the audio.
Resets the streaming state and latches the method and whitening in force for the whole session; the threshold is read when the session finishes.
| expectedSamples | Optional length hint, used only to reserve the envelope up front. |
Definition at line 594 of file OnsetDetector.h.
|
inline |
Offline detection over a whole mono buffer (channel 0).
Runs the same ODF with the symmetric (post_max/post_avg > 0) picker for slightly higher F, and returns onset sample positions (frame-centre references, ascending). Allocates – not an audio-thread call. Resets the streaming state on entry. Identical to beginOffline(), one pushOffline() of the whole buffer, finishOffline().
Definition at line 563 of file OnsetDetector.h.
|
inline |
detectOffline() with each onset's strength alongside its position.
Definition at line 569 of file OnsetDetector.h.
|
inline |
Closes the session and returns onset positions, as detectOffline(). Empty when no session is open.
Definition at line 636 of file OnsetDetector.h.
|
inline |
Closes the session and returns onsets with their strengths.
Definition at line 642 of file OnsetDetector.h.
|
inlinenoexcept |
True when adaptive whitening is on.
Definition at line 332 of file OnsetDetector.h.
|
inlinenoexcept |
How far behind the newest input sample a frame's reference sample sits, in samples.
The envelope has its own delay and it is NOT getLatencySamples(). That one is the ONSET latch delay: detected events are deliberately held and released at a fixed offset so a caller sees one block-size-independent latency. An envelope reader takes each frame as it is computed and does not wait for that release, so what it pays is only the distance from the frame's reference sample to the input sample that completed the frame – half the analysis frame, less the group-delay compensation already folded into the reference. Always smaller than getLatencySamples(); a consumer that reports positions in the caller's timeline uses the reference sample directly and needs this only to state its own delay.
Definition at line 540 of file OnsetDetector.h.
|
inlinenoexcept |
Analysis frame in samples actually in effect: the automatic choice when prepare() got fftSize <= 0, the rounded explicit request otherwise. Divide by the sample rate for the span in seconds; fs/getFftSize() is the STFT bin width.
Definition at line 433 of file OnsetDetector.h.
|
inlinenoexcept |
Hop in samples in effect (round(fs/200) unless overridden).
Definition at line 436 of file OnsetDetector.h.
|
inlinenoexcept |
The most recent analysis frame's onset-strength value.
STREAM OWNER ONLY – this is not a cross-thread readout. It hands back three plain words that describe one frame, and it is meant for the component that is itself feeding pushSamples(): that caller knows exactly when a frame boundary passed (every getHopSize() samples from the last reset), so it can read the frame it just caused without any publication at all. Reading it from another thread would race the writer word by word and could pair a fresh value with a stale reference sample, which is the one thing a beat grid cannot survive. Other threads use onsetDetected() / getOnsetStrength() / getLastOnsetSample(), which are published atomically for exactly that purpose.
frameIndex counts from 1 for the first frame after a reset and is 0 before any frame has been computed. Frames below getWarmupFrames() are computed over a partly-empty analysis ring and their values are not meaningful; see that method.
Reflects the streaming path (processBlock/pushSamples). detectOffline() runs its own envelope internally and leaves this cleared.
Definition at line 495 of file OnsetDetector.h.
|
inlinenoexcept |
Reference sample index (frame centre) of the most recent onset. The latch fires exactly getLatencySamples() samples after this index.
Definition at line 418 of file OnsetDetector.h.
|
inlinenoexcept |
The single causal reporting latency, L = fftSize + hop (samples).
Definition at line 424 of file OnsetDetector.h.
|
inlinenoexcept |
ODF family in force.
Definition at line 320 of file OnsetDetector.h.
|
inlinenoexcept |
Number of log-frequency filterbank bands built for the resolved frame; a direct readout of the analysis resolution in force.
Definition at line 440 of file OnsetDetector.h.
|
inlinenoexcept |
Onset strength (ODF value, frame-invariant scale; see prepare()) of the most recent reported onset.
Definition at line 409 of file OnsetDetector.h.
|
inlinenoexcept |
Peak-pick delta in force, after setThreshold()'s clamping.
Definition at line 326 of file OnsetDetector.h.
|
inlinenoexcept |
Frames at the start of a stream whose ODF value is warm-up, not signal.
The analysis ring starts empty, so the first frames measure the step from silence into the first input as well as the input itself, and part of the flux they report is an artefact of that step. The detector suppresses its own onsets over this span, which is what the number is for.
Whether an envelope reader should discard the same span is its own decision and is not obviously yes. Measured on a beat grid built from this envelope, discarding it costs a real beat whenever the material starts at sample 0 – F 0.9919 against 1.0000, one beat missing at the head of every such signal – while the hazard it guards against did not appear even on a full-level tone starting at sample 0 with no beat there, because a consumer that removes a local baseline has already removed the step. That consumer therefore keeps the frames.
Equal to fftSize/hop + 2: the frames needed to fill the ring, plus two so the flux to the previous frame is itself computed from two full frames.
Definition at line 523 of file OnsetDetector.h.
|
inlinenoexcept |
True if an onset was reported during the most recent call.
Definition at line 402 of file OnsetDetector.h.
|
inline |
Allocates all state and configures the STFT front-end.
Not real-time safe (allocates). Any previous stream state is cleared. An invalid spec (non-finite or non-positive sample rate) is ignored, preserving the previous configuration.
| spec | Audio environment (only sampleRate is used; the detector is mono – feed channel 0, or mix down before pushing). |
| fftSize | Analysis frame size. Values <= 0 (the default) select the AUTOMATIC frame: the smallest power of two in [512, 16384] spanning at least 2048/48000 s (~42.7 ms) at spec.sampleRate – 2048 at 44.1/48 kHz, 4096 at 88.2/96 kHz, 8192 at 176.4/192 kHz, 16384 at 384 kHz, 512 at 8 kHz. Above 384 kHz the 16384 ceiling binds and the bin width widens again (documented, not fixed: pass an explicit frame there). Explicit positive values are rounded up to a power of two in [64, 1<<16] and honoured as given, with the reduced validity described below. Read the resolved value back with getFftSize(). |
| hop | Hop in samples; hop <= 0 selects round(fs/200) (~5 ms). Clamped to [1, fftSize]. |
FRAME LENGTH IS A TIME REQUIREMENT. Everything the frame decides is a function of the RATIO fs/fftSize, never of the count alone:
With a CONSTANT sample count the low register therefore degrades as the rate rises: measured on soft bass onsets (E1..B2, 10 ms attacks) the fixed 2048 frame recalls 7/8 at 44.1/48 kHz but 5/8 at 96 kHz, while the automatic frame holds the 7/8 reference recall there (the missed F#1 sits below the default delta at every rate; see the file header). Percussive clicks, mid-register notes and the vibrato/tremolo false-positive guard were measured unaffected at every rate from 44.1 to 192 kHz, so this is a low-register loss, not a general one. Explicit frames stay available for callers who want the shorter one: at 192 kHz an explicit 2048 buys ~10.7 ms of frame span (L ~= 15.7 ms) at the cost of the register above.
ODF SCALE. The SuperFlux band magnitudes are scaled by 2048/fftSize before the log10(x + 1) compression, so the onset-strength scale – and with it the meaning of setThreshold()'s delta – is the same at every frame length, and therefore at every rate under the automatic frame. Without this, |X| grows linearly with the frame and quiet-onset sensitivity roughly doubles per rate-family doubling against a fixed delta. The factor is exactly 1 at the 2048-sample reference where the default delta was tuned, so 44.1/48 kHz default behaviour (and any explicit-2048 caller at any rate) is bit-identical to previous releases. Explicit frames OTHER than 2048 now read the ODF on the reference scale too – an intentional behaviour change: one delta means one sensitivity, at every frame length. Under adaptive whitening the per-bin peak division removes the growth wherever the running peak exceeds the whitening floor (1e-4), so the whitened path is not scaled again. Caveat: the floor is an absolute magnitude, so bins whose peak is held AT the floor keep the raw frame-scaled magnitude – very quiet whitened material therefore retains a residual rate dependence (borderline events can appear at high rates that a 48 kHz session does not report).
CPU AND MEMORY. The hop is TIME-fixed (round(fs/200), ~200 frames per second at every rate) while the automatic frame follows the rate, so CPU per second of audio is NOT rate-invariant under the automatic frame: measured ~2x at 88.2/96 kHz and ~3.8x at 176.4/192 kHz versus the old fixed-2048 default at the same rate (50.4 / 95.6 / 189.6 ms of CPU per 10 s of audio at 48/96/192 kHz automatic, vs 50.4 ms at 192 kHz with an explicit 2048; g++ -O2, one core – absolute numbers vary by machine, the growth tracks the frame size). prepare()-time heap grows the same way: ~125 KB at 44.1/48 kHz, ~223 KB at 88.2/96 kHz, ~420 KB at 176.4/192 kHz (float instantiation). The audio path stays allocation-free at every size. Budget from these figures; an explicit 2048 restores the old cost at the cost of the register above.
Definition at line 215 of file OnsetDetector.h.
|
inlinenoexcept |
Feeds a mono block; reads channel 0 only. Const, never mutated.
Lock-free and allocation-free. Safe no-op before prepare().
Definition at line 344 of file OnsetDetector.h.
|
inline |
Feeds the next piece of an offline session. No-op outside one.
Definition at line 615 of file OnsetDetector.h.
|
inlinenoexcept |
Feeds a mono stream of samples. Lock-free, allocation-free.
Onsets fire at the fixed reporting latency L = fftSize + hop after their reference sample; onsetDetected() latches per processing call (see the file header). Safe no-op before prepare().
Definition at line 358 of file OnsetDetector.h.
|
inlinenoexcept |
Clears all streaming state and abandons an open offline session. Not concurrent with pushSamples().
Definition at line 671 of file OnsetDetector.h.
|
inlinenoexcept |
Enables Stowell-Plumbley adaptive whitening (default off).
Definition at line 314 of file OnsetDetector.h.
|
inlinenoexcept |
Selects the ODF family. Lock-free.
Definition at line 298 of file OnsetDetector.h.
|
inlinenoexcept |
Sets the adaptive peak-pick delta (margin above the moving mean). Non-finite values are ignored; negative values are clamped to 0. The delta is read against the frame-invariant ODF scale (see prepare()), so one value selects the same sensitivity at every rate and frame length.
Definition at line 307 of file OnsetDetector.h.
|
staticconstexpr |
One frame of the onset-strength envelope (the ODF before the peak picker).
The peak picker answers "was there an onset"; a periodicity analysis needs the continuous strength curve the picker thresholds, because the pulse it looks for is carried by the shape between onsets as much as by the events that clear the threshold. This is that curve, one frame at a time. Register groups the SuperFlux bands are split into for the per-register readout: below 200 Hz (kick, bass), 200-800 Hz, 800 Hz to 3.2 kHz, and above (hats, consonants). Two octaves each above the first.
Definition at line 457 of file OnsetDetector.h.