|
1 | | -## Rust Music Theory |
| 1 | +# Music Theory for Rust |
2 | 2 |
|
| 3 | +[](https://github.com/music-comp/mt-rs/actions/workflows/ci.yml) |
3 | 4 | [](https://github.com/music-comp/mt-rs) |
4 | | -[](https://crates.io/crates/rust-music-theory) |
5 | | -[](https://docs.rs/rust-music-theory) |
| 5 | +[](https://crates.io/crates/music-comp-mt) |
| 6 | +[](https://docs.rs/music-comp-mt) |
| 7 | +[](LICENSE) |
6 | 8 |
|
7 | | -A library and executable that provides programmatic implementation of the basis of the music theory. |
| 9 | +[![][logo]][logo-large] |
8 | 10 |
|
9 | | -## Table of Contents |
| 11 | +*A comprehensive music theory library and CLI for Rust* |
10 | 12 |
|
11 | | -- [Overview](#overview) |
12 | | -- [Usage as a Library](#usage-as-a-library) |
13 | | -- [Usage as an Executable](#usage-as-an-executable) |
14 | | -- [Building From Source](#building-from-source) |
15 | | -- [Roadmap](#roadmap) |
| 13 | +This library covers music-theoretic fundamentals through graduate-level theory: notes, intervals, chords, scales, harmony analysis, voice leading, neo-Riemannian transformations, pitch-class set theory, counterpoint, and figured bass. |
16 | 14 |
|
17 | | -## Overview |
| 15 | +Every music theory fact in the library has been verified against 4,315 concept cards from 29 authoritative textbooks. |
18 | 16 |
|
19 | | -`Rust Music Theory` is used to procedurally utilize music theory notions like Note, Chord, Scale, |
20 | | -Interval and more. The main purpose of this library is to let music theory be used in other programs and produce music/audio in a programmatic way. |
| 17 | +Note that this project started as a fork of Ozan Kaşıkçı's [excellent library](https://github.com/ozankasikci/rust-music-theory). |
21 | 18 |
|
22 | | -## Usage as a Library |
| 19 | +## Quick Start |
| 20 | + |
| 21 | +### As a Library |
23 | 22 |
|
24 | | -Add `rust-music-theory` as a dependency in your Cargo.toml. |
25 | 23 | ```toml |
26 | 24 | [dependencies] |
27 | | -rust-music-theory = "0.3" |
| 25 | +music-comp-mt = "0.4" |
28 | 26 | ``` |
29 | 27 |
|
30 | | -After installing the dependencies, you can use the library as follows. |
31 | 28 | ```rust |
32 | | -extern crate rust_music_theory as rustmt; |
33 | | -use rustmt::note::{Note, Notes, Pitch, PitchSymbol::*}; |
34 | | -use rustmt::scale::{Scale, ScaleType, Mode, Direction}; |
35 | | -use rustmt::chord::{Chord, Number as ChordNumber, Quality as ChordQuality}; |
36 | | - |
37 | | -// to create a Note, specify a pitch and an octave; |
38 | | -let note = Note::new(Pitch::from(As), 4); |
39 | | - |
40 | | -// Scale Example |
41 | | -let scale = Scale::new( |
42 | | - ScaleType::Diatonic, |
43 | | - Pitch::from(C), |
44 | | - 4, |
45 | | - Some(Mode::Ionian), |
46 | | - Direction::Ascending, |
| 29 | +use music_comp_mt::note::{Notes, Pitch, PitchSymbol::*}; |
| 30 | +use music_comp_mt::chord::{Chord, Quality, Number}; |
| 31 | +use music_comp_mt::scale::{Scale, ScaleType, Mode, Direction}; |
| 32 | +use music_comp_mt::interval::Interval; |
| 33 | + |
| 34 | +// Notes and chords |
| 35 | +let chord = Chord::new(Pitch::from(C), Quality::Major, Number::Triad); |
| 36 | +assert_eq!(chord.format_notes(), "Notes:\n 1: C\n 2: E\n 3: G\n"); |
| 37 | + |
| 38 | +// Correct enharmonic spelling everywhere |
| 39 | +let gm = Chord::new(Pitch::from(G), Quality::Minor, Number::Triad); |
| 40 | +let notes = gm.notes(); // G, Bb, D — not G, A#, D |
| 41 | + |
| 42 | +// Identify chords from notes |
| 43 | +let matches = Chord::identify(&[Pitch::from(E), Pitch::from(G), Pitch::from(C)]); |
| 44 | +// Finds: C major, first inversion |
| 45 | + |
| 46 | +// Calculate intervals between pitches (letter-aware) |
| 47 | +let interval = Interval::between( |
| 48 | + &Pitch::from(F), |
| 49 | + &Pitch::from(B), |
47 | 50 | ).unwrap(); |
48 | | - |
49 | | -let scale_notes = scale.notes(); |
50 | | - |
51 | | -// Chord Example |
52 | | -let chord = Chord::new(Pitch::from(C), ChordQuality::Major, ChordNumber::Triad); |
53 | | - |
54 | | -let chord_notes = chord.notes(); |
| 51 | +// Augmented 4th (not diminished 5th — letters matter) |
| 52 | + |
| 53 | +// Find scales containing a set of notes |
| 54 | +let scales = Scale::identify(&[ |
| 55 | + Pitch::from(C), Pitch::from(D), Pitch::from(E), |
| 56 | + Pitch::from(Fs), Pitch::from(G), Pitch::from(A), Pitch::from(B), |
| 57 | +]); |
| 58 | +// Matches: C Lydian, G Ionian, ... |
55 | 59 | ``` |
56 | 60 |
|
57 | | -For detailed examples, please see the tests folder. |
| 61 | +### As a CLI |
58 | 62 |
|
59 | | -## Usage as an Executable |
| 63 | +```sh |
| 64 | +cargo install music-comp-mt-cli |
| 65 | +``` |
60 | 66 |
|
61 | | -`cargo install --git https://github.com/ozankasikci/rust-music-theory` |
| 67 | +After building with `make build`, the binary is at `./bin/mt`: |
62 | 68 |
|
63 | | -This lets cargo install the library as an executable called `rustmt`. Some usage examples; |
| 69 | +```sh |
| 70 | +$ ./bin/mt scale C Ionian |
| 71 | +Notes: |
| 72 | + 1: C |
| 73 | + 2: D |
| 74 | + 3: E |
| 75 | + 4: F |
| 76 | + 5: G |
| 77 | + 6: A |
| 78 | + 7: B |
| 79 | + 8: C |
| 80 | + |
| 81 | +$ ./bin/mt chord G "dominant seventh" |
| 82 | +Notes: |
| 83 | + 1: G |
| 84 | + 2: B |
| 85 | + 3: D |
| 86 | + 4: F |
64 | 87 |
|
65 | | -`rustmt scale D Locrian` |
66 | | -```yaml |
| 88 | +$ ./bin/mt scale D Locrian |
67 | 89 | Notes: |
68 | 90 | 1: D |
69 | | - 2: D# |
| 91 | + 2: Eb |
70 | 92 | 3: F |
71 | 93 | 4: G |
72 | | - 5: G# |
73 | | - 6: A# |
| 94 | + 5: Ab |
| 95 | + 6: Bb |
74 | 96 | 7: C |
75 | 97 | 8: D |
| 98 | + |
| 99 | +$ ./bin/mt scale list |
| 100 | +$ ./bin/mt chord list |
76 | 101 | ``` |
77 | | -`rustmt chord C# Dominant Eleventh` |
78 | | -```yaml |
79 | | -Notes: |
80 | | - 1: C# |
81 | | - 2: F |
82 | | - 3: G# |
83 | | - 4: B |
84 | | - 5: D# |
85 | | - 6: G |
| 102 | + |
| 103 | +## Modules |
| 104 | + |
| 105 | +### Fundamentals |
| 106 | + |
| 107 | +| Module | Description | |
| 108 | +|--------|-------------| |
| 109 | +| `note` | `Pitch`, `Note`, `NoteLetter`, `PitchSymbol`, `KeySignature`, enharmonic equivalence, transposition | |
| 110 | +| `interval` | Simple and compound intervals (0-24 semitones), quality/number classification, letter-aware `between()`, inversion | |
| 111 | +| `chord` | 22+ chord types, letter-based spelling, identification with inversion detection, regex parsing | |
| 112 | +| `scale` | 8 scale types, 14 modes, identification from notes, ascending/descending support | |
| 113 | + |
| 114 | +### Harmony & Analysis |
| 115 | + |
| 116 | +| Module | Description | |
| 117 | +|--------|-------------| |
| 118 | +| `harmony` | Diatonic triads/sevenths, common tones, chord-scale compatibility, pivot chords | |
| 119 | +| `analysis` | Roman numeral labeling (I, vi, V7, vii°, etc.), secondary dominant detection (V/x) | |
| 120 | +| `voice_leading` | Optimal voice assignment minimizing total semitone movement | |
| 121 | + |
| 122 | +### Advanced Theory |
| 123 | + |
| 124 | +| Module | Description | |
| 125 | +|--------|-------------| |
| 126 | +| `neo_riemannian` | P, R, L operations on triads with chaining | |
| 127 | +| `set_class` | `PitchClassSet` with normal/prime form, T_n, I_n, interval vector, Forte numbers | |
| 128 | +| `counterpoint` | First-species rule checking (parallel 5ths/8ves, consonance, voice crossing) | |
| 129 | +| `figured_bass` | Realize figured bass symbols into chord voicings | |
| 130 | + |
| 131 | +## Feature Flags |
| 132 | + |
| 133 | +| Flag | Description | |
| 134 | +|------|-------------| |
| 135 | +| `midi` | Enables `Note::midi_pitch()` for MIDI pitch number conversion | |
| 136 | +| `serde` | Derives `Serialize`/`Deserialize` on all public types | |
| 137 | + |
| 138 | +```toml |
| 139 | +music-comp-mt = { version = "0.4", features = ["serde", "midi"] } |
| 140 | +``` |
| 141 | + |
| 142 | +## Examples |
| 143 | + |
| 144 | +### Harmony Analysis |
| 145 | + |
| 146 | +```rust |
| 147 | +use music_comp_mt::harmony; |
| 148 | +use music_comp_mt::analysis; |
| 149 | +use music_comp_mt::note::{Pitch, PitchSymbol::*}; |
| 150 | +use music_comp_mt::chord::{Chord, Quality, Number}; |
| 151 | +use music_comp_mt::scale::Mode; |
| 152 | + |
| 153 | +// Diatonic chords in C major |
| 154 | +let chords = harmony::diatonic_triads(Pitch::from(C), Mode::Ionian); |
| 155 | +// I=C maj, ii=D min, iii=E min, IV=F maj, V=G maj, vi=A min, vii°=B dim |
| 156 | + |
| 157 | +// Roman numeral analysis |
| 158 | +let g7 = Chord::new(Pitch::from(G), Quality::Dominant, Number::Seventh); |
| 159 | +let rn = analysis::roman_numeral(Pitch::from(C), Mode::Ionian, &g7).unwrap(); |
| 160 | +assert_eq!(rn.label, "V7"); |
| 161 | + |
| 162 | +// Secondary dominant detection |
| 163 | +let d_major = Chord::new(Pitch::from(D), Quality::Major, Number::Triad); |
| 164 | +let sd = analysis::secondary_dominant(Pitch::from(C), Mode::Ionian, &d_major).unwrap(); |
| 165 | +assert_eq!(sd.label, "V/V"); |
| 166 | + |
| 167 | +// Pivot chords between C major and G major |
| 168 | +let pivots = harmony::pivot_chords( |
| 169 | + Pitch::from(C), Mode::Ionian, |
| 170 | + Pitch::from(G), Mode::Ionian, |
| 171 | +); |
| 172 | +// G major is V in C, I in G; C major is I in C, IV in G; etc. |
86 | 173 | ``` |
87 | 174 |
|
88 | | -`rustmt scale list` |
89 | | -```yaml |
90 | | -Available Scales: |
91 | | - - Major|Ionian |
92 | | - - Minor|Aeolian |
93 | | - - Dorian |
94 | | - - Phrygian |
95 | | - - Lydian |
96 | | - - Mixolydian |
97 | | - - Locrian |
98 | | - - Harmonic Minor |
99 | | - - Melodic Minor |
| 175 | +### Neo-Riemannian Transformations |
| 176 | + |
| 177 | +```rust |
| 178 | +use music_comp_mt::neo_riemannian::{transform, transform_chain, NROperation}; |
| 179 | +use music_comp_mt::chord::{Chord, Quality, Number}; |
| 180 | +use music_comp_mt::note::{Pitch, PitchSymbol::*}; |
| 181 | + |
| 182 | +let c_major = Chord::new(Pitch::from(C), Quality::Major, Number::Triad); |
| 183 | + |
| 184 | +// P (Parallel): C major → C minor |
| 185 | +let c_minor = transform(&c_major, NROperation::P).unwrap(); |
| 186 | + |
| 187 | +// R (Relative): C major → A minor |
| 188 | +let a_minor = transform(&c_major, NROperation::R).unwrap(); |
| 189 | + |
| 190 | +// Chain operations: C major → P → R → L |
| 191 | +let path = transform_chain(&c_major, &[NROperation::P, NROperation::R, NROperation::L]).unwrap(); |
100 | 192 | ``` |
101 | 193 |
|
| 194 | +### Pitch-Class Set Theory |
| 195 | + |
| 196 | +```rust |
| 197 | +use music_comp_mt::set_class::PitchClassSet; |
| 198 | + |
| 199 | +let major_triad = PitchClassSet::new(&[0, 4, 7]); |
| 200 | +assert_eq!(major_triad.prime_form(), vec![0, 3, 7]); |
| 201 | +assert_eq!(major_triad.forte_number(), Some("3-11".to_string())); |
| 202 | +assert_eq!(major_triad.interval_vector(), [0, 0, 1, 1, 1, 0]); |
102 | 203 |
|
103 | | -`rustmt chord list` |
104 | | -```yaml |
105 | | -Available chords: |
106 | | - - Major Triad |
107 | | - - Minor Triad |
108 | | - - Suspended2 Triad |
109 | | - - Suspended4 Triad |
110 | | - - Augmented Triad |
111 | | - - Diminished Triad |
112 | | - - Major Seventh |
113 | | - - Minor Seventh |
114 | | - - Augmented Seventh |
115 | | - - Augmented Major Seventh |
116 | | - - Diminished Seventh |
117 | | - - Half Diminished Seventh |
118 | | - - Minor Major Seventh |
119 | | - - Dominant Seventh |
120 | | - - Dominant Ninth |
121 | | - - Major Ninth |
122 | | - - Dominant Eleventh |
123 | | - - Major Eleventh |
124 | | - - Minor Eleventh |
125 | | - - Dominant Thirteenth |
126 | | - - Major Thirteenth |
127 | | - - Minor Thirteenth |
| 204 | +// Transpose and invert |
| 205 | +let transposed = major_triad.transpose(5); // T_5 |
| 206 | +let inverted = major_triad.invert(0); // I_0 |
128 | 207 | ``` |
129 | 208 |
|
130 | 209 | ## Building From Source |
131 | 210 |
|
132 | | -To quickly build and run the executable locally; |
133 | | - |
134 | | -`git clone http://github.com/ozankasikci/rust-music-theory && cd rust-music-theory` |
| 211 | +```sh |
| 212 | +git clone https://github.com/music-comp/mt-rs && cd mt-rs |
| 213 | +make build # Build library + CLI |
| 214 | +make test # Run all 349 tests |
| 215 | +make lint # Clippy + fmt (same checks as CI) |
| 216 | +make coverage # Generate coverage report (96%+) |
| 217 | +make docs # Build rustdoc (warnings as errors) |
| 218 | +make check-all # Build + lint + coverage + docs |
| 219 | +``` |
135 | 220 |
|
136 | | -Then you can directly compile using cargo. An example; |
| 221 | +## Project Structure |
137 | 222 |
|
138 | | -`cargo run -- scale D Locrian` |
139 | | -```yaml |
140 | | -Notes: |
141 | | - 1: D |
142 | | - 2: D# |
143 | | - 3: F |
144 | | - 4: G |
145 | | - 5: G# |
146 | | - 6: A# |
147 | | - 7: C |
148 | | - 8: D |
149 | 223 | ``` |
| 224 | +Cargo.toml workspace root |
| 225 | +mt/ library crate (music-comp-mt) |
| 226 | + src/ |
| 227 | + lib.rs |
| 228 | + note/, interval/, chord/, scale/ |
| 229 | + harmony/, analysis/, voice_leading/ |
| 230 | + neo_riemannian/, set_class/, counterpoint/, figured_bass/ |
| 231 | + tests/ |
| 232 | +mt-cli/ binary crate (music-comp-mt-cli) |
| 233 | + src/ |
| 234 | + main.rs, cli.rs |
| 235 | + tests/ |
| 236 | +``` |
| 237 | + |
| 238 | +## License |
| 239 | + |
| 240 | +MIT |
| 241 | + |
| 242 | +[//]: ---Named-Links--- |
150 | 243 |
|
151 | | -## Roadmap |
152 | | -- [x] Properly display enharmonic spelling |
153 | | -- [x] Add inversion support for chords |
154 | | -- [ ] Add missing modes for Melodic & Harmonic minor scales |
155 | | -- [ ] Add support for arbitrary accidentals |
156 | | -- [ ] Add a mechanism to find the chord from the given notes |
| 244 | +[logo]: https://avatars.githubusercontent.com/u/255628285?s=250 |
| 245 | +[logo-large]: https://avatars.githubusercontent.com/u/255628285 |
0 commit comments