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

Monophonic-buffer chord recognition with confidence gating. More...

#include <ChordDetector.h>

Classes

struct  Result
 One detection result. More...
 

Public Types

enum class  ChordType : std::uint8_t {
  None = 0 , Major , Minor , Diminished ,
  Augmented , Sus2 , Sus4 , Dominant7 ,
  Major7 , Minor7 , HalfDim7
}
 Recognized chord families. More...
 

Public Member Functions

bool prepare (const AudioSpec &spec, int windowSize=0)
 Prepares the analysis pipeline.
 
void reset () noexcept
 Clears the analysis ring and forgets the held chord.
 
void setConfidenceThreshold (float threshold) noexcept
 Confidence below which the previous chord is held (default 0.55).
 
float getConfidenceThreshold () const noexcept
 
int getWindowSize () const noexcept
 
int getHopSize () const noexcept
 
std::uint64_t getFrameCount () const noexcept
 Number of analysis frames produced since prepare()/reset().
 
const std::array< T, 12 > & getChroma () const noexcept
 The chroma vector of the most recent analysis frame.
 
void processBlock (AudioBufferView< const T > buffer) noexcept
 Feeds a block (channels averaged to mono).
 
void pushSamples (std::span< const T > samples) noexcept
 Feeds mono samples directly.
 
Result getChord () const noexcept
 

Static Public Member Functions

static int getChordName (const Result &result, char *dest, int size) noexcept
 Writes a human-readable chord name ("C", "F#m7", "Bbsus4"...).
 

Detailed Description

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

Monophonic-buffer chord recognition with confidence gating.

Threading: getChord() is the atomic foreign-thread publication. getChroma() is a stream-owner reference readout and is valid only on the thread that owns processBlock() / pushSamples(), between that thread's own calls. Calling getChroma() from another thread while processing runs would race the non-atomic chroma storage.

Template Parameters
TSample type (float or double).

Definition at line 80 of file ChordDetector.h.

Member Enumeration Documentation

◆ ChordType

template<FloatType T>
enum class dspark::ChordDetector::ChordType : std::uint8_t
strong

Recognized chord families.

Enumerator
None 
Major 
Minor 
Diminished 
Augmented 
Sus2 
Sus4 
Dominant7 
Major7 
Minor7 
HalfDim7 

Definition at line 84 of file ChordDetector.h.

Member Function Documentation

◆ getChord()

template<FloatType T>
Result dspark::ChordDetector< T >::getChord ( ) const
inlinenoexcept
Returns
The current (possibly held) chord.

Definition at line 330 of file ChordDetector.h.

◆ getChordName()

template<FloatType T>
static int dspark::ChordDetector< T >::getChordName ( const Result &  result,
char *  dest,
int  size 
)
inlinestaticnoexcept

Writes a human-readable chord name ("C", "F#m7", "Bbsus4"...).

Parameters
resultChord to name.
destDestination buffer.
sizeCapacity of dest (8+ recommended).
Returns
Number of characters written (excluding the terminator).

Definition at line 342 of file ChordDetector.h.

◆ getChroma()

template<FloatType T>
const std::array< T, 12 > & dspark::ChordDetector< T >::getChroma ( ) const
inlinenoexcept

The chroma vector of the most recent analysis frame.

Twelve bins of summed note ENERGY (Goertzel magnitude squared) over MIDI 36..83, folded by pitch class with index 0 = C. Raw, unnormalized and in the units the analysis produces, because a consumer that accumulates frames needs to choose its own weighting – normalizing here would destroy the frame-to-frame level information and pre-empt that choice. All zeros before the first frame.

The register the numbers are trustworthy in is the register documented on prepare(): energy below the leakage floor or above the MIDI 83 bin ceiling is not present in these bins, and adjacent-semitone leakage is.

stream-owner reference readout: the reference is to state owned by the thread that calls processBlock()/pushSamples(), and it is valid on that thread only, between that thread's own calls. A caller on any other thread would be reading these words while the owning thread writes them. getChord() is the readout for any other thread.

Definition at line 297 of file ChordDetector.h.

◆ getConfidenceThreshold()

template<FloatType T>
float dspark::ChordDetector< T >::getConfidenceThreshold ( ) const
inlinenoexcept
Returns
The current confidence-gating threshold.

Definition at line 239 of file ChordDetector.h.

◆ getFrameCount()

template<FloatType T>
std::uint64_t dspark::ChordDetector< T >::getFrameCount ( ) const
inlinenoexcept

Number of analysis frames produced since prepare()/reset().

Increments once per hop, so a caller can tell a fresh chroma frame from the one it already consumed.

Reads plain state owned by the thread that pushes samples: call it from that thread only. It is NOT a cross-thread readout – getChord() is the one that is.

Definition at line 275 of file ChordDetector.h.

◆ getHopSize()

template<FloatType T>
int dspark::ChordDetector< T >::getHopSize ( ) const
inlinenoexcept
Returns
Samples between consecutive analysis frames (windowSize/2).

A second consumer of this front end feeds it in chunks of at most this many samples and polls getFrameCount(): frames are exactly this far apart, so a chunk that long spans at most one of them and none can be missed.

Definition at line 261 of file ChordDetector.h.

◆ getWindowSize()

template<FloatType T>
int dspark::ChordDetector< T >::getWindowSize ( ) const
inlinenoexcept
Returns
The analysis window (samples) in effect: the automatic choice if prepare() was called with windowSize <= 0, the clamped explicit request otherwise (member default 4096 before the first successful prepare()). Latency is one full window; readings update every windowSize/2 samples.

Definition at line 251 of file ChordDetector.h.

◆ prepare()

template<FloatType T>
bool dspark::ChordDetector< T >::prepare ( const AudioSpec &  spec,
int  windowSize = 0 
)
inline

Prepares the analysis pipeline.

Parameters
specAudio environment specification.
windowSizeAnalysis window in samples. Values <= 0 (the default) select the AUTOMATIC window: the smallest power of two in [1024, 16384] spanning at least 4096/48000 s (~85 ms) at spec.sampleRate – 4096 at 44.1/48 kHz, 8192 at 88.2/96 kHz, 16384 at 176.4/192 kHz. Explicit values are clamped to [1024, 16384]. Rates that cannot place the highest analysed note (MIDI 83, B5) below Nyquist are rejected and leave a previous valid configuration untouched.

REGISTER BOUNDS – every number below is measured (a sweep of root-position pure-tone major triads, roots MIDI 36..84); "reliable" means correct root AND chord type through the 0.55 confidence gate:

  • Upper bound (every configuration): the analysis bins stop at MIDI 83 (B5), so chord tones above B5 are invisible and root-position triads with roots above E5 (MIDI 76) are NEVER detected. Above that ceiling, exactly as below the F#3 floor, the window-fill transient can still pass the confidence gate with a WRONG chord (measured: a C6 root-position major triad latches B Maj7 at ~0.58 during the first window, then never again), and the hold-last-confident rule keeps that reading on display indefinitely. Call reset() on programme change, and treat a Result whose confidence is below the threshold as STALE rather than as a current reading.
  • Lower bound: adjacent-semitone leakage from the Hann main lobe, ~4*fs/windowSize Hz wide. The floor is a property of that RATIO, not of the rate alone; measured floors: 4*fs/N = 46.9 Hz (48k/4096, 96k/8192, 192k/16384) -> F#3..E5 4*fs/N = 43.1 Hz (44.1k/4096) -> E3..E5 4*fs/N = 93.8 Hz (96k/4096, 192k/8192) -> F#4..E5 4*fs/N >= 187.5 Hz (48k/1024, 192k/4096) -> EMPTY 4*fs/N = 23.4 Hz (48k/8192, 96k/16384) -> C2..E5, gaps 4*fs/N <= 11.7 Hz (44.1k or 48k with 16384) -> C2..E5
  • Below the floor the detector does not merely lose confidence: leakage adds phantom adjacent semitones that match a richer template and can pass the 0.55 gate, producing a CONFIDENTLY WRONG reading (measured: C3 major at 48 kHz/4096 reads C Maj7 at ~0.58; C4 major at 96 kHz with a forced 4096 window reads C Maj7 at ~0.57). Treat the bounds as hard usage limits, not soft advice.
  • The AUTOMATIC window keeps 4*fs/windowSize <= 46.9 Hz for every rate up to 192 kHz, so the F#3..E5 register (use ~G3 up as a comfortable margin) holds at 44.1, 48, 88.2, 96, 176.4 and 192 kHz alike. Above 192 kHz the 16384 ceiling widens the lobe again (93.8 Hz at 384 kHz: reliable only F#4..E5, and C4 again misreads as Maj7).
  • Small explicit windows at professional rates have NO reliable register: 1024 at 48 kHz detects 0 of 49 swept roots (several confidently wrong) because its 187.5 Hz lobe exceeds the semitone spacing of every note below the bin ceiling. Explicit 1024/2048 windows are only meaningful at low rates (1024 spans 128 ms at 8 kHz and resolves G3/C4 majors exactly); requests below 1024 clamp INTO 1024 and inherit all of this. Prefer the automatic window.
Returns
true when the specification was accepted and state was reset; false when the complete previous state was preserved.

Definition at line 161 of file ChordDetector.h.

◆ processBlock()

template<FloatType T>
void dspark::ChordDetector< T >::processBlock ( AudioBufferView< const T >  buffer)
inlinenoexcept

Feeds a block (channels averaged to mono).

Definition at line 302 of file ChordDetector.h.

◆ pushSamples()

template<FloatType T>
void dspark::ChordDetector< T >::pushSamples ( std::span< const T >  samples)
inlinenoexcept

Feeds mono samples directly.

Definition at line 320 of file ChordDetector.h.

◆ reset()

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

Clears the analysis ring and forgets the held chord.

Allocation-free, but it rewrites the stream state: call it from the thread that owns the stream (or while processing is stopped), not concurrently with processBlock()/pushSamples().

Definition at line 215 of file ChordDetector.h.

◆ setConfidenceThreshold()

template<FloatType T>
void dspark::ChordDetector< T >::setConfidenceThreshold ( float  threshold)
inlinenoexcept

Confidence below which the previous chord is held (default 0.55).

Callable from any thread. Non-finite values are ignored (a NaN would make every comparison false and freeze the detector on the held chord forever).

Definition at line 232 of file ChordDetector.h.


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