DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
dspark::OnsetDetector< T > Class Template Referencefinal

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).
 

Detailed Description

template<FloatType T>
class dspark::OnsetDetector< T >

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.

Template Parameters
TSample type (float or double).

Definition at line 127 of file OnsetDetector.h.

Member Enumeration Documentation

◆ Method

template<FloatType T>
enum class dspark::OnsetDetector::Method
strong

Onset-detection function family. Default SuperFlux.

Enumerator
SpectralFlux 
ComplexDomain 
SuperFlux 

Definition at line 131 of file OnsetDetector.h.

Member Function Documentation

◆ beginOffline()

template<FloatType T>
void dspark::OnsetDetector< T >::beginOffline ( int64_t  expectedSamples = 0)
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.

Parameters
expectedSamplesOptional length hint, used only to reserve the envelope up front.

Definition at line 594 of file OnsetDetector.h.

◆ detectOffline()

template<FloatType T>
std::vector< int64_t > dspark::OnsetDetector< T >::detectOffline ( AudioBufferView< const T >  whole)
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.

◆ detectOfflineOnsets()

template<FloatType T>
std::vector< Onset > dspark::OnsetDetector< T >::detectOfflineOnsets ( AudioBufferView< const T >  whole)
inline

detectOffline() with each onset's strength alongside its position.

Definition at line 569 of file OnsetDetector.h.

◆ finishOffline()

template<FloatType T>
std::vector< int64_t > dspark::OnsetDetector< T >::finishOffline ( )
inline

Closes the session and returns onset positions, as detectOffline(). Empty when no session is open.

Definition at line 636 of file OnsetDetector.h.

◆ finishOfflineOnsets()

template<FloatType T>
std::vector< Onset > dspark::OnsetDetector< T >::finishOfflineOnsets ( )
inline

Closes the session and returns onsets with their strengths.

Definition at line 642 of file OnsetDetector.h.

◆ getAdaptiveWhitening()

template<FloatType T>
bool dspark::OnsetDetector< T >::getAdaptiveWhitening ( ) const
inlinenoexcept

True when adaptive whitening is on.

Definition at line 332 of file OnsetDetector.h.

◆ getEnvelopeLatencySamples()

template<FloatType T>
int dspark::OnsetDetector< T >::getEnvelopeLatencySamples ( ) const
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.

◆ getFftSize()

template<FloatType T>
int dspark::OnsetDetector< T >::getFftSize ( ) const
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.

◆ getHopSize()

template<FloatType T>
int dspark::OnsetDetector< T >::getHopSize ( ) const
inlinenoexcept

Hop in samples in effect (round(fs/200) unless overridden).

Definition at line 436 of file OnsetDetector.h.

◆ getLastOdfFrame()

template<FloatType T>
OdfFrame dspark::OnsetDetector< T >::getLastOdfFrame ( ) const
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.

◆ getLastOnsetSample()

template<FloatType T>
int64_t dspark::OnsetDetector< T >::getLastOnsetSample ( ) const
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.

◆ getLatencySamples()

template<FloatType T>
int dspark::OnsetDetector< T >::getLatencySamples ( ) const
inlinenoexcept

The single causal reporting latency, L = fftSize + hop (samples).

Definition at line 424 of file OnsetDetector.h.

◆ getMethod()

template<FloatType T>
Method dspark::OnsetDetector< T >::getMethod ( ) const
inlinenoexcept

ODF family in force.

Definition at line 320 of file OnsetDetector.h.

◆ getNumBands()

template<FloatType T>
int dspark::OnsetDetector< T >::getNumBands ( ) const
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.

◆ getOnsetStrength()

template<FloatType T>
T dspark::OnsetDetector< T >::getOnsetStrength ( ) const
inlinenoexcept

Onset strength (ODF value, frame-invariant scale; see prepare()) of the most recent reported onset.

Definition at line 409 of file OnsetDetector.h.

◆ getThreshold()

template<FloatType T>
T dspark::OnsetDetector< T >::getThreshold ( ) const
inlinenoexcept

Peak-pick delta in force, after setThreshold()'s clamping.

Definition at line 326 of file OnsetDetector.h.

◆ getWarmupFrames()

template<FloatType T>
int dspark::OnsetDetector< T >::getWarmupFrames ( ) const
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.

◆ onsetDetected()

template<FloatType T>
bool dspark::OnsetDetector< T >::onsetDetected ( ) const
inlinenoexcept

True if an onset was reported during the most recent call.

Definition at line 402 of file OnsetDetector.h.

◆ prepare()

template<FloatType T>
void dspark::OnsetDetector< T >::prepare ( const AudioSpec &  spec,
int  fftSize = 0,
int  hop = 0 
)
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.

Parameters
specAudio environment (only sampleRate is used; the detector is mono – feed channel 0, or mix down before pushing).
fftSizeAnalysis 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().
hopHop 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:

  • STFT bin width = fs/fftSize (23.44 Hz at 48 kHz/2048)
  • quarter-tone floor f_qt = (fs/fftSize) / (2^(1/24) - 1), the frequency above which the filterbank's declared quarter-tone spacing is really delivered (measured band counts: 135 bands and f_qt 800 Hz at 48 kHz/2048; 111 bands and 1600 Hz at 96 kHz/2048; 88 bands and 3199 Hz at 192 kHz/2048 – the same 2048 samples, four times the floor)
  • reporting latency L/fs = (fftSize + hop)/fs seconds

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.

◆ processBlock()

template<FloatType T>
void dspark::OnsetDetector< T >::processBlock ( AudioBufferView< const T >  in)
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.

◆ pushOffline()

template<FloatType T>
void dspark::OnsetDetector< T >::pushOffline ( std::span< const T >  samples)
inline

Feeds the next piece of an offline session. No-op outside one.

Definition at line 615 of file OnsetDetector.h.

◆ pushSamples()

template<FloatType T>
void dspark::OnsetDetector< T >::pushSamples ( std::span< const T >  samples)
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.

◆ reset()

template<FloatType T>
void dspark::OnsetDetector< T >::reset ( )
inlinenoexcept

Clears all streaming state and abandons an open offline session. Not concurrent with pushSamples().

Definition at line 671 of file OnsetDetector.h.

◆ setAdaptiveWhitening()

template<FloatType T>
void dspark::OnsetDetector< T >::setAdaptiveWhitening ( bool  on)
inlinenoexcept

Enables Stowell-Plumbley adaptive whitening (default off).

Definition at line 314 of file OnsetDetector.h.

◆ setMethod()

template<FloatType T>
void dspark::OnsetDetector< T >::setMethod ( Method  m)
inlinenoexcept

Selects the ODF family. Lock-free.

Definition at line 298 of file OnsetDetector.h.

◆ setThreshold()

template<FloatType T>
void dspark::OnsetDetector< T >::setThreshold ( T  deltaAboveMean)
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.

Member Data Documentation

◆ kNumRegisters

template<FloatType T>
constexpr int dspark::OnsetDetector< T >::kNumRegisters = detail::OnsetNovelty<T>::kNumRegisters
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.


The documentation for this class was generated from the following file: