@toyrobot/chord-theory
v1.0.0
Published
Music theory engine with borrowed chords, reverse diatonic lookup, and intelligent chord symbol generation — the algorithms that tonal and teoria don't have
Maintainers
Readme
@toyrobot/chord-theory
Music theory engine with the algorithms that tonal and teoria don't have.
Zero dependencies. Pure TypeScript. Works everywhere.
What this does that nothing else does
| Feature | chord-theory | tonal | teoria | |---------|:---:|:---:|:---:| | Borrowed chords (modal interchange) | Yes | No | No | | Reverse diatonic lookup | Yes | No | No | | Intelligent partial voicing naming | Yes | No | No | | Chord parser with 30+ notation aliases | Yes | Yes | Yes | | 4 heptatonic scale systems | Yes | Partial | Partial | | MIDI voicing generation | Yes | No | Yes |
Borrowed Chords
"What chords can I borrow in C major?" — answered with one function call. Searches natural minor, harmonic minor, and melodic minor for chords that don't exist in your current system. Deduplicates by pitch+type, sorts by chromatic distance. No other library does this.
Reverse Diatonic Lookup
"I like the sound of Ebmaj7 — where does it live?" — searches all 4 scale systems across all 12 keys to find every diatonic context where a chord naturally occurs. Returns Roman numerals with key and system context. Work backwards from sound to theory.
Intelligent Chord Symbol Generation
Takes a chord and a set of active intervals, generates the correct symbol for partial voicings. Missing the 3rd? It knows that's a sus chord. Have a 9th but no 7th? That's an add9. Non-consecutive extensions get proper (add) notation. 20+ edge cases handled.
Install
npm install @toyrobot/chord-theoryQuick Start
import {
getBorrowedChords,
findDiatonicMatches,
findMatchesForSymbol,
parseChord,
chordSymbol,
buildChordInfo,
} from '@toyrobot/chord-theory';Borrowed Chords
// What chords can I borrow in C major?
const borrowed = getBorrowedChords(0, 'major');
borrowed.forEach(b => {
console.log(`${b.chord.name} — from ${b.sourceLabel}`);
});
// Cm7 — from Natural Minor
// C°7 — from Harmonic Minor
// Dbmaj7 — from Harmonic Minor
// Dm7 — from Melodic Minor (wait, that's diatonic... no, this is the m(maj7) variant)
// ...every borrowable chord, sorted by distanceReverse Lookup
// Where does Dm7b5 naturally occur?
const matches = findDiatonicMatches(2, 'hdim7');
matches.forEach(m => {
console.log(`${m.numeral} in ${m.key} ${m.systemLabel}`);
});
// vii° in Eb Major
// ii° in C Natural Minor
// ii° in C Harmonic Minor
// vi° in F Melodic Minor
// vii° in Eb Melodic Minor
// Or use the convenience wrapper with a chord symbol string:
findMatchesForSymbol('Ebmaj7');
// [{ numeral: 'I', key: 'Eb', system: 'major', ... }, ...]Parse Chord Symbols
// Handles sharps, flats, Unicode, slash chords, beat durations
parseChord('C#m7b5'); // { root: 1, qualityKey: 'hdim7', ... }
parseChord('Bb7/F'); // { root: 10, qualityKey: 'dom7', bass: 5, ... }
parseChord('Am(8)'); // { root: 9, qualityKey: 'min', beats: 8, ... }
parseChord('Δ7'); // { root: 0, qualityKey: 'maj7', ... }
// Parse a full progression
parseChanges('Dm7 G7 | Cmaj7');
// [{ root: 2, qualityKey: 'min7' }, { root: 7, qualityKey: 'dom7' }, ...]Dynamic Chord Naming
const chord = buildChordInfo(0, 0); // I in C major = Cmaj7
// Full voicing
chordSymbol(chord, new Set([1, 3, 5, 7]));
// { symbol: 'Cmaj7', quality: 'Maj7' }
// Drop the 3rd → sus chord
chordSymbol(chord, new Set([1, 5, 7, 11]));
// { symbol: 'Csus4', quality: 'Sus4' }
// Add 9th without 7th → add chord
chordSymbol(chord, new Set([1, 3, 5, 9]));
// { symbol: 'Cadd9', quality: 'Add9' }
// Root and 5th only → power chord
chordSymbol(chord, new Set([1, 5]));
// { symbol: 'C5', quality: 'Power' }Build Chord Info
// From a diatonic context (key + degree)
const ii = buildChordInfo(0, 1); // ii in C major
// { root: 'D', numeral: 'ii', type: 'm7', triad: ['D', 'F', 'A'], seventh: 'C', ... }
// From any root + quality (free mode)
const chord = buildFreeChordInfo(6, 'dom7'); // F#7
// { root: 'F#', name: 'F#7', triad: ['F#', 'A#', 'C#'], seventh: 'E', ... }MIDI Voicing
import { getVoicingMidi, midiToNames } from '@toyrobot/chord-theory';
const chord = buildChordInfo(0, 0); // Cmaj7
const midi = getVoicingMidi(chord, new Set([1, 3, 5, 7]));
// [48, 52, 55, 59]
midiToNames(midi);
// ['C3', 'E3', 'G3', 'B3']
// Use with Web Audio, Tone.js, or any MIDI library
synth.triggerAttack(midiToNames(midi));Chord Suggestions (Autocomplete)
getChordSuggestions('C');
// ['C', 'Cm', 'C7', 'Cm7', 'Cmaj7', 'Cdim', 'Caug', 'Csus4']
getChordSuggestions('Ebm');
// ['Ebm', 'Ebm7', 'Ebmaj7', 'Ebm7b5', ...]API Reference
Core
| Function | Description |
|----------|-------------|
| parseChord(symbol) | Parse a chord symbol string → ParsedChord |
| parseChanges(input) | Parse space/pipe-separated chord changes → ParsedChord[] |
| getChordSuggestions(input, limit?) | Autocomplete chord symbols |
| buildChordInfo(keyRoot, degIdx, useFlats?, system?) | Build chord info from diatonic context |
| buildFreeChordInfo(rootPitch, qualityKey, useFlats?) | Build chord info from root + quality |
| chordSymbol(chordInfo, activeIntervals) | Generate symbol for partial voicing |
| noteName(pitchClass, useFlats?) | Pitch class → note name |
| useFlatsForKey(keyPitch, system?) | Should this key use flat notation? |
| buildScale(rootIdx, intervals, useFlats?, letterOffsets?) | Build a spelled scale |
Borrowed Chords
| Function | Description |
|----------|-------------|
| getBorrowedChords(keyRoot, currentSystem?, useFlats?) | All borrowable chords from parallel systems |
| getParallelChord(keyRoot, degIdx, currentSystem?, useFlats?) | Chord from the next parallel system |
Reverse Lookup
| Function | Description |
|----------|-------------|
| findDiatonicMatches(rootPitch, qualityKey) | Find all diatonic contexts for a chord |
| findMatchesForSymbol(symbol) | Same, but takes a chord symbol string |
Voicing
| Function | Description |
|----------|-------------|
| getVoicingMidi(chord, activeIntervals, baseOctave?) | Chord → MIDI note numbers |
| midiToNames(midi) | MIDI numbers → note name strings |
Data
Raw data exports for advanced use: CHROMATIC, SCALE_SYSTEMS, CHORD_QUALITIES, QUALITY_ALIASES, ROOT_NAMES, HEPTATONIC_SYSTEMS, PARALLEL_SYSTEM.
Pitch Classes
All pitch parameters use integer pitch classes: 0 = C, 1 = C#/Db, 2 = D, ... 11 = B.
Scale Systems
Four heptatonic systems, each with 7 diatonic chords:
- Major — I ii iii IV V vi vii°
- Natural Minor — i ii° III iv v VI VII
- Harmonic Minor — i ii° III+ iv V VI vii°
- Melodic Minor — i ii III+ IV V vi° vii°
Plus augmented triad and diminished 7th symmetric systems.
Chord Qualities
31 chord types across triads, 7ths, and extended chords. 30+ notation aliases (e.g. Δ7 = maj7 = M7 = Maj7). See CHORD_QUALITIES and QUALITY_ALIASES for the full list.
Origin
Extracted from TOYROBOT, an interactive chord tone fretboard tool for guitarists and bassists.
License
MIT
