A Rust crate for real-time audio analysis: compute 12-bin chromagrams and detect musical chords with minimal latency.
- Streaming chromagram extraction via a builder API
- Real-time chord detection from 12-bin chromagrams
- Customizable bleed suppression for chord matching
- Zero-allocation in the hot path after initialization
This library uses and modifies work from:
- Chord Detection Algorithm: Based on "Real-Time Chord Recognition For Live Performance" by A. M. Stark and M. D. Plumbley, ICMC 2009, Montreal. Expanded in Adam Stark's PhD thesis: "Musicians and Machines: Bridging the Semantic Gap in Live Performance", Queen Mary University of London, 2011.
- Original implementation: https://github.com/adamstark/Chord-Detector-and-Chromagram
Add to your Cargo.toml
:
[dependencies]
chord_detector = "0.1.0"
use chord_detector::{Chromagram, ChordDetector};
fn run() -> Result<(), Box<dyn std::error::Error>> {
// 1) Build a chromagram pipeline
let mut chroma = Chromagram::builder()
.frame_size(1024)
.sampling_rate(48_000)
.build()?;
// 2) Build a chord detector
let mut detector = ChordDetector::builder()
.bleed(0.15)
.build();
// 3) In your audio loop:
let audio_frame: Vec<f32> = vec![0.0; 1024]; // fill with actual samples
if let Some(chroma_bins) = chroma.next(&audio_frame)? {
let chord = detector.detect_chord(&chroma_bins)?;
println!(
"Detected {} {} chord with confidence {:.3}",
chord.root,
chord.kind,
chord.confidence
);
}
Ok(())
}
Compute 12-bin chromagrams from a stream of audio frames.
-
ChromagramBuilder::new() -> Self
-
.frame_size(usize) -> Self
- Set the frame size for audio processing
-
.sampling_rate(usize) -> Self
- Set the sampling rate of the audio
-
.downsample_factor(usize) -> Self
- Set the downsample factor for processing
-
.num_harmonics(usize) -> Self
- Set the number of harmonics to consider for chord detection
-
.num_octaves(usize) -> Self
- Set the number of octaves to consider for chord detection
-
.search_width(usize) -> Self
- Set the search width for finding spectral peaks
-
.build() -> Result<Chromagram, ChromagramError>
- Finalize and create the Chromagram
-
Chromagram::builder() -> ChromagramBuilder
- Start customizing with a builder
-
chromagram.next(frame: &[f32]) -> Result<Option<[f32; 12]>, ChromagramError>
- Returns
Ok(None)
until enough data accumulates (half FFT buffer) - Returns
Ok(Some(chroma))
when a new chromagram is ready
- Returns
Match 12-bin chromagrams to chord profiles.
-
ChordDetectorBuilder::new() -> Self
- Create a new builder with default bleed = 0.157
-
.bleed(f32) -> Self
- Set the bleed suppression factor (0.0..1.0)
-
.build() -> ChordDetector
- Build the
ChordDetector
- Build the
-
ChordDetector::builder() -> ChordDetectorBuilder
- Return a builder to customize bleed suppression factor
-
ChordDetector::new() -> ChordDetector
- Create a detector with default bleed = 0.157
-
detect_chord(chroma: &[f32]) -> Result<Chord, ChordError>
- Detect the single best chord from a chromagram slice.
- Returns
Err(ChordError::InvalidLength)
ifchroma.len() != SEMITONES
.
-
top_k(chroma: &[f32], k: usize) -> Result<Vec<Chord>, ChordError>
- Detect the top
k
chords from a chromagram slice. - Returns:
Err(InvalidLength)
ifchroma.len() != SEMITONES
.Err(InvalidArgument)
ifk == 0
.
- Detect the top
pub enum NoteName {
C, Cs, D, Ds, E, F, Fs, G, Gs, A, As, B, Unknown
}
pub enum ChordKind {
Major,
Minor,
Power,
DominantSeventh,
MajorSeventh,
MinorSeventh,
Diminished,
Augmented,
SuspendedSecond,
SuspendedFourth,
}
pub struct Chord {
pub root: NoteName,
pub kind: ChordKind,
pub confidence: f32, // lower is a better match
}
pub enum ChromagramError { /* frame size & config errors */ }
pub enum ChordError { /* invalid length & argument errors */ }
- rustfft: Fast Fourier Transform implementation
- thiserror: Error handling utilities
This crate is licensed under GNU General Public License v3.0. See the LICENSE file for full text.
-
Initial release
-
Streaming chromagram builder and real-time chord detector
-
Customizable builder APIs for both modules
The integration tests use the lewton crate to load .ogg
files from the tests/chord-samples
directory. Ensure the test files have been created with tests/generate_chords.py
and run tests with cargo test
.