1use crate::{
2 midi::MidiNote,
3 set::IntervalSet,
4 Interval, Natural, Note, Pitch,
5};
6use core::{
7 fmt::{self, Write},
8 iter,
9 str::FromStr,
10};
11
12mod builder;
13pub use builder::Builder;
14
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct Chord {
17 root: Pitch,
18 builder: Builder,
19}
20
21impl Chord {
22 pub fn major() -> Builder {
23 Self::builder()
24 .root()
25 .interval(Interval::MAJOR_THIRD)
26 .interval(Interval::PERFECT_FIFTH)
27 }
28
29 pub fn minor() -> Builder {
30 Self::builder()
31 .root()
32 .interval(Interval::MINOR_THIRD)
33 .interval(Interval::PERFECT_FIFTH)
34 }
35
36 pub fn seventh() -> Builder {
46 Self::major().interval(Interval::MINOR_SEVENTH)
47 }
48
49 pub fn major_seventh() -> Builder {
50 Self::major().interval(Interval::MAJOR_SEVENTH)
51 }
52
53 pub fn minor_seventh() -> Builder {
54 Self::minor().interval(Interval::MINOR_SEVENTH)
55 }
56
57 pub fn minor_major_seventh() -> Builder {
58 Self::minor().interval(Interval::MAJOR_SEVENTH)
59 }
60
61 pub fn half_diminished() -> Builder {
62 Self::builder()
63 .root()
64 .interval(Interval::MINOR_THIRD)
65 .interval(Interval::TRITONE)
66 .interval(Interval::MINOR_SEVENTH)
67 }
68
69 pub fn builder() -> Builder {
70 Builder {
71 bass: None,
72 is_inversion: false,
73 intervals: IntervalSet::default(),
74 }
75 }
76
77 pub fn from_midi<I>(root: MidiNote, iter: I) -> Self
91 where
92 I: IntoIterator<Item = MidiNote>,
93 {
94 let mut iter = iter.into_iter();
95 let mut intervals = IntervalSet::default();
96
97 let bass_note = iter.next().unwrap();
98 let root_pitch = root.pitch();
99 let bass = if bass_note != root {
100 let bass_pitch = bass_note.pitch();
101 intervals.push(bass_pitch - root_pitch);
102 Some(bass_note.pitch())
103 } else {
104 intervals.push(Interval::UNISON);
105 None
106 };
107
108 let is_inversion = if let Some(note) = iter.next() {
109 let ret = if note == root { false } else { true };
110
111 intervals.push(note.pitch() - root_pitch);
112 intervals.extend(iter.map(|midi| midi - root));
113 ret
114 } else {
115 false
116 };
117
118 Self {
119 root: root.pitch(),
120 builder: Builder {
121 bass,
122 is_inversion,
123 intervals,
124 },
125 }
126 }
127
128 pub fn root(self) -> Pitch {
129 self.root
130 }
131
132 pub fn intervals(self) -> Intervals {
133 let (high, low) = if let Some(bass) = self.builder.bass {
135 let bass_interval =
136 Interval::new((self.root.into_byte() as i8 - bass.into_byte() as i8).abs() as u8);
137 if self.builder.is_inversion {
138 self.builder.intervals.split(bass_interval)
139 } else {
140 (
141 self.builder.intervals,
142 [bass_interval].into_iter().collect(),
143 )
144 }
145 } else {
146 (IntervalSet::default(), self.builder.intervals)
147 };
148
149 Intervals { low, high }
150 }
151}
152
153impl FromIterator<MidiNote> for Chord {
154 fn from_iter<T: IntoIterator<Item = MidiNote>>(iter: T) -> Self {
155 let mut notes = iter.into_iter();
156 let root = notes.next().unwrap_or(MidiNote::from_byte(0));
157
158 Self::from_midi(root, iter::once(root).chain(notes))
159 }
160}
161
162impl IntoIterator for Chord {
163 type Item = Pitch;
164
165 type IntoIter = Iter;
166
167 fn into_iter(self) -> Self::IntoIter {
168 Iter {
169 root: self.root,
170 intervals: self.intervals(),
171 }
172 }
173}
174
175pub struct Intervals {
176 low: IntervalSet,
177 high: IntervalSet,
178}
179
180impl Iterator for Intervals {
181 type Item = Interval;
182
183 fn next(&mut self) -> Option<Self::Item> {
184 self.low.next().or_else(|| self.high.next())
185 }
186}
187
188pub struct Iter {
189 root: Pitch,
190 intervals: Intervals,
191}
192
193impl Iterator for Iter {
194 type Item = Pitch;
195
196 fn next(&mut self) -> Option<Self::Item> {
197 self.intervals.next().map(|interval| self.root + interval)
198 }
199}
200
201impl fmt::Display for Chord {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 self.root.fmt(f)?;
204
205 if self.builder.intervals.contains(Interval::MINOR_THIRD) {
206 f.write_char('m')?
207 } else if self.builder.intervals.contains(Interval::MAJOR_SECOND) {
208 f.write_str("sus2")?
209 } else if self.builder.intervals.contains(Interval::PERFECT_FOURTH) {
210 f.write_str("sus4")?
211 }
212
213 let mut has_fifth = true;
214 if self.builder.intervals.contains(Interval::TRITONE) {
215 f.write_str("b5")?
216 } else if !self.builder.intervals.contains(Interval::PERFECT_FIFTH) {
217 has_fifth = false;
218 }
219
220 if self.builder.intervals.contains(Interval::MINOR_SEVENTH) {
221 f.write_char('7')?
222 } else if self.builder.intervals.contains(Interval::MAJOR_SEVENTH) {
223 f.write_str("maj7")?
224 }
225
226 if let Some(bass) = self.builder.bass {
227 write!(f, "/{}", bass)?;
228 }
229
230 if !self.builder.intervals.contains(Interval::UNISON) {
231 f.write_str("(no root)")?
232 }
233
234 if !has_fifth {
235 f.write_str("(no5)")?
236 }
237
238 Ok(())
239 }
240}
241
242impl FromStr for Chord {
243 type Err = ();
244
245 fn from_str(s: &str) -> Result<Self, Self::Err> {
246 let mut chars = s.chars();
247 let natural = match chars.next().unwrap() {
248 'A' => Natural::A,
249 'B' => Natural::B,
250 'C' => Natural::C,
251 'D' => Natural::D,
252 'E' => Natural::E,
253 'F' => Natural::F,
254 'G' => Natural::G,
255 _ => todo!(),
256 };
257 let mut next = chars.next();
258 let root: Pitch = match next {
259 Some('b') => {
260 next = chars.next();
261 if next == Some('b') {
262 next = chars.next();
263 Note::double_flat(natural).into()
264 } else {
265 Note::flat(natural).into()
266 }
267 }
268 Some('#') => {
269 next = chars.next();
270 if next == Some('#') {
271 next = chars.next();
272 Note::double_sharp(natural).into()
273 } else {
274 Note::sharp(natural).into()
275 }
276 }
277 _ => natural.into(),
278 };
279
280 let mut builder = match next {
281 Some('m') => {
282 next = chars.next();
283 Chord::minor()
284 },
285 _ => Chord::major()
286 };
287
288 loop {
289 if let Some(c) = next {
290 match c {
291 '7' => builder.intervals.push(Interval::MINOR_SEVENTH),
292 _ => todo!(),
293 }
294 next = chars.next();
295 } else {
296 break;
297 }
298 }
299
300 Ok(builder.build(root))
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use crate::{Chord, Pitch};
307
308 #[test]
309 fn it_parses_d_double_sharp_major() {
310 let chord: Chord = "D##".parse().unwrap();
311 assert_eq!(chord, Chord::major().build(Pitch::E));
312 }
313
314 #[test]
315 fn it_parses_c_minor_seven() {
316 let chord: Chord = "Cm7".parse().unwrap();
317 assert_eq!(chord, Chord::minor_seventh().build(Pitch::C));
318 }
319}