music_note/chord/
mod.rs

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    /// ```
37    /// use music_note::{Chord, Pitch};
38    ///
39    /// // D7
40    /// let chord = Chord::seventh().build(Pitch::D);
41    ///
42    /// let notes = [Pitch::D, Pitch::FSharp, Pitch::A, Pitch::C];
43    /// assert!(chord.into_iter().eq(notes));
44    /// ```
45    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    /// ```
78    /// use music_note::{midi, Chord, Pitch};
79    ///
80    /// let chord = Chord::from_midi(
81    ///     midi!(C, 4),
82    ///     [midi!(E, 3), midi!(G, 3), midi!(C, 4)]
83    /// );
84    ///
85    /// assert_eq!(chord.to_string(), "C/E");
86    ///
87    /// let pitches = [Pitch::E, Pitch::G, Pitch::C];
88    /// assert!(chord.into_iter().eq(pitches));
89    /// ```
90    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        // TODO maybe use rotate_right?
134        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}