harmonia
Instrument-agnostic music theory primitives in Rust.
harmonia gives you typed, parseable, analyzable musical values: pitch
classes, intervals, scales, chords, keys, Roman numerals, and a small
catalogue of analysis routines. There's no fretboard, no keyboard, no
audio — just the theory you'd write on a chalkboard, expressed as Rust
values you can compose into whatever instrument or interface layer you
need on top.
Why harmonia?
-
It's the in-between layer most music libraries skip. Audio crates handle synthesis. Notation crates handle rendering.
harmoniahandles the theory between them: which chords are diatonic, what the V7 ofiiis, whether a progression sits in C major or A minor. -
Strong types over strings. A
Chordis not aString; aRomanNumeralis not aString. You can still parse them from user input — every public type implementsFromStrand round-trips throughDisplay— but once parsed they're values you can match on, hash, and pass through type-checked APIs. -
Real analysis built in. Detect a key from a chord progression, label chords with Roman numerals, find scales that fit over a chord transition, and generate context-aware next-chord suggestions across six categories (diatonic, resolution, borrowed, secondary, relative, chromatic).
-
Pluggable. Optional
serdefor persistence; zero required dependencies otherwise.
The theory model is extracted from fretboard-explorer, where the same primitives drove a guitar fretboard, a piano keyboard, and a trumpet fingering chart. The split was useful in JavaScript; it's useful in Rust too. Plausible uses: chord-chart generators, practice trainers, theory-teaching aids, exercise generators, and the brain of any instrument-specific layer.
Examples
Parse a progression and detect the key
use ;
let progression: =
.iter.map.collect;
let candidates = detect_key;
let best = &candidates;
println!;
// → C major: 4/4 chords fit
Roman-numeral analysis in a key
use ;
let c_major = new;
for symbol in
// Am → vi
// F → IV
// G7 → V7
// C7 → I7 (the I-as-dom7 fuzzy fallback)
// F# → — (out of key)
Spell a scale with diatonic letter names
use Scale;
let g_major: Scale = "G Ionian".parse.unwrap;
let labels: = g_major.spelled.unwrap
.iter.map.collect;
assert_eq!;
The spelling algorithm picks the right accidentals so each natural
letter A–G appears exactly once — no A♯ next to an A.
Suggest the next chord
use ;
let history: =
.iter.map.collect;
let result = suggest_next_chords;
let key = result.key.unwrap;
println!;
for s in result.suggestions.iter.take
// Key: C major
//
// C (I) — diatonic in C major
// Dm (ii) — diatonic in C major
// Em (iii) — diatonic in C major
// F (IV) — diatonic in C major
// G (V) — diatonic in C major
// Am (vi) — diatonic in C major
Suggestions also span resolution moves (V→I, ii→V, IV→V), borrowed chords (♭III, iv, ♭VI, ♭VII), secondary dominants (V7/ii, V7/iii, …), relative major/minor, and chromatic motion — each tagged with its category so a UI can group them.
Find scales that fit over a chord transition
use ;
let g: Chord = "G".parse.unwrap;
let am: Chord = "Am".parse.unwrap;
let suggestions = suggest_scales_for_bracket;
for s in suggestions.iter.take
// G Ionian (major) G and Am are both diatonic to G Ionian (major)
// G Mixolydian G and Am are both diatonic to G Mixolydian
// A Dorian G and Am are both diatonic to A Dorian
(G-rooted modes win the prev-chord bias; A Dorian sneaks ahead of C Ionian on the next-chord bias.)
Persist results (with the serde feature)
let json = to_string?;
let restored: ChordSuggestionResult = from_str?;
What's in
Foundation — PitchClass, Interval, Note (Letter +
Accidental), and spell_heptatonic for diatonic letter spelling.
Catalogue — ScaleKind (16 scales: modes, pentatonics,
harmonic/melodic minor, symmetric) and ChordQuality (12 qualities:
6 triads + 6 sevenths), each with Scale / Chord companions.
Analysis — Key with diatonic chord templates, RomanNumeral
(typed with alteration, degree, quality, and secondary-of),
detect_key, suggest_scales_for_bracket, suggest_next_chords.
Every public data type implements FromStr, Display, PartialEq,
and Hash.
Crate features
-
serde(off by default) — derivesSerialize/Deserializeon every public data type.PitchClassandIntervalserialize as integers;SuggestionCategoryas a lowercase string. Enable with:= { = "0.1", = ["serde"] }
License
MIT