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