redact_composer_musical/
key.rs

1use crate::{
2    Chord, ChordShape, Degree, Interval, IntervalCollection, IntervalStepSequence, Mode, Note,
3    NoteIter, NoteIterator, NoteName, PitchClass, PitchClassCollection, Scale,
4};
5use std::hash::{Hash, Hasher};
6use std::ops::RangeBounds;
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11#[cfg(feature = "redact-composer")]
12use redact_composer_core::derive::Element;
13
14/// Musical key signature represented as a root [`PitchClass`], [`Scale`]
15/// (e.g. Major/Minor), and [`Mode`].
16/// ```
17/// use redact_composer_musical::{Key, Mode, NoteName::*, Scale::Major, Note, PitchClassCollection};
18/// let c_major = Key::from((C, Major));
19/// assert_eq!(c_major.pitch_classes(), vec![C, D, E, F, G, A, B]);
20/// ```
21#[derive(Debug, Clone, Copy, Eq)]
22#[cfg_attr(feature = "redact-composer", derive(Element))]
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24pub struct Key {
25    /// First pitch of the scale.
26    pub(crate) root: PitchClass,
27    /// The interval sequence (relative to the `root`) defining the notes this [Key].
28    pub(crate) scale: Scale,
29    /// Offset amount for the scale.
30    pub(crate) mode: Mode,
31    /// The preferred [`NoteName`] when naming notes in this key.
32    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
33    pub(crate) name_pref: Option<NoteName>,
34}
35
36impl PartialEq for Key {
37    fn eq(&self, other: &Self) -> bool {
38        self.root == other.root && self.scale == other.scale && self.mode == other.mode
39    }
40}
41
42impl Hash for Key {
43    fn hash<H: Hasher>(&self, state: &mut H) {
44        self.root.hash(state);
45        self.scale.hash(state);
46        self.mode.hash(state);
47    }
48}
49
50impl From<(PitchClass, Scale, Mode)> for Key {
51    fn from(value: (PitchClass, Scale, Mode)) -> Self {
52        let (root, scale, mode, name_pref) = (value.0, value.1, value.2, None);
53
54        Key {
55            root,
56            scale,
57            mode,
58            name_pref,
59        }
60    }
61}
62
63impl From<(PitchClass, Scale)> for Key {
64    fn from(value: (PitchClass, Scale)) -> Self {
65        Self::from((value.0, value.1, Mode::default()))
66    }
67}
68
69impl From<(NoteName, Scale, Mode)> for Key {
70    fn from(value: (NoteName, Scale, Mode)) -> Self {
71        let (tonic, scale, mode, name_pref) = (
72            PitchClass::from(value.0),
73            value.1,
74            value.2,
75            Some(Self::simplify_root(value.0)),
76        );
77
78        Key {
79            root: tonic,
80            scale,
81            mode,
82            name_pref,
83        }
84    }
85}
86
87impl From<(NoteName, Scale)> for Key {
88    fn from(value: (NoteName, Scale)) -> Self {
89        Self::from((value.0, value.1, Mode::default()))
90    }
91}
92
93impl IntervalStepSequence for Key {
94    fn interval_steps(&self) -> Vec<Interval> {
95        let steps = self.scale.interval_steps();
96        let num_steps = steps.len();
97        steps
98            .into_iter()
99            .cycle()
100            .skip(self.mode as usize)
101            .take(num_steps)
102            .collect()
103    }
104}
105
106impl PitchClassCollection for Key {
107    fn pitch_classes(&self) -> Vec<PitchClass> {
108        self.intervals()
109            .into_iter()
110            .map(|i| self.root + i)
111            .collect()
112    }
113}
114
115impl NoteIterator for Key {
116    fn iter_notes_in_range<R: RangeBounds<Note>>(&self, note_range: R) -> NoteIter<R> {
117        NoteIter::from((self.root, self.intervals(), note_range))
118    }
119}
120
121impl Key {
122    /// Creates a [`Key`] from a [`PitchClass`], [`Scale`], and [`Mode`].
123    /// Alternatively, several [`From`] implementations are supported:
124    /// ```
125    /// use redact_composer_musical::{Key, Mode::Ionian, NoteName::C, PitchClass, Scale::Major};
126    ///
127    /// // All the ways to define C Major (Ionian)
128    /// let first = Key::new(PitchClass(0), Major, Ionian);
129    /// let second = Key::from((PitchClass(0), Major));
130    /// let third = Key::from((PitchClass(0), Major, Ionian));
131    /// let fourth = Key::from((C, Major));
132    /// let fifth = Key::from((C, Major, Ionian));
133    ///
134    /// assert!([second, third, fourth, fifth].into_iter().all(|k| k == first));
135    /// ```
136    pub fn new(root: PitchClass, scale: Scale, mode: Mode) -> Key {
137        Key {
138            root,
139            scale,
140            mode,
141            name_pref: None,
142        }
143    }
144
145    /// Returns the key's root [`PitchClass`].
146    pub fn root(&self) -> PitchClass {
147        self.root
148    }
149
150    /// Returns the key's [`Scale`].
151    pub fn scale(&self) -> Scale {
152        self.scale
153    }
154
155    /// Returns the key's [`Mode`].
156    pub fn mode(&self) -> Mode {
157        self.mode
158    }
159
160    /// Returns the chords that use notes exclusively from this key.
161    pub fn chords(&self) -> Vec<Chord> {
162        self.chords_with_shape(ChordShape::all())
163    }
164
165    /// Returns chords of the given shapes which use notes exclusively from this key.
166    /// ```
167    /// use redact_composer_musical::{Key, NoteName::*, Chord, ChordShape, ChordShape::{dim, maj, min}, Scale, Mode};
168    /// use redact_composer_musical::Scale::Major;
169    ///
170    /// let key = Key::from((C, Major));
171    /// assert_eq!(
172    ///     key.chords_with_shape(ChordShape::triad()),
173    ///     vec![
174    ///         Chord::from((C, maj)),
175    ///         Chord::from((D, min)),
176    ///         Chord::from((E, min)),
177    ///         Chord::from((F, maj)),
178    ///         Chord::from((G, maj)),
179    ///         Chord::from((A, min)),
180    ///         Chord::from((B, dim)),
181    ///     ]
182    /// )
183    /// ```
184    pub fn chords_with_shape(&self, shape: Vec<ChordShape>) -> Vec<Chord> {
185        Degree::values()
186            .into_iter()
187            .map(|d| self.relative_pitch(d))
188            .flat_map(|root| shape.iter().map(move |chord_shape| (root, *chord_shape)))
189            .map(Chord::from)
190            .filter(|chord| self.contains(chord))
191            .collect()
192    }
193
194    /// Checks if all [`PitchClass`]s from a collection (for example, [`Chord`]) belong to this key.
195    /// ```
196    /// use redact_composer_musical::{Chord, ChordShape::{maj, min}, Key, Mode::Ionian, NoteName::*, Scale::Major};
197    ///
198    /// assert!(Key::from((C, Major)).contains(&Chord::from((C, maj))));
199    /// assert!(!Key::from((C, Major)).contains(&Chord::from((C, min))));
200    /// ```
201    pub fn contains<P: PitchClassCollection>(&self, pitches: &P) -> bool {
202        let scale_pitches = self.pitch_classes();
203        pitches
204            .pitch_classes()
205            .iter()
206            .all(|pitch| scale_pitches.contains(pitch))
207    }
208
209    /// Returns the pitch class for a given degree of this scale.
210    /// ```
211    /// use redact_composer_musical::{Degree, Key, Scale::Major, Mode::Locrian, NoteName::{B, D}};
212    ///
213    /// let key = Key::from((B, Major, Locrian));
214    /// assert_eq!(key.relative_pitch(Degree::III), D);
215    /// ```
216    pub fn relative_pitch<D: Into<Degree>>(&self, degree: D) -> PitchClass {
217        self.root + self.intervals()[degree.into() as usize]
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use crate::NoteName::C;
224    use crate::{Key, Note, NoteIterator, NoteName::*, Scale::*};
225
226    #[test]
227    fn middle_c_major_scale() {
228        assert_eq!(
229            Key::from((C, Major)).notes_in_range(Note(60)..=Note(72)),
230            [
231                (C, 4),
232                (D, 4),
233                (E, 4),
234                (F, 4),
235                (G, 4),
236                (A, 4),
237                (B, 4),
238                (C, 5)
239            ]
240        )
241    }
242
243    #[test]
244    fn middle_c_natural_minor_scale() {
245        assert_eq!(
246            Key::from((C, NaturalMinor)).notes_in_range(Note(60)..=Note(72)),
247            [
248                (C, 4),
249                (D, 4),
250                (Eb, 4),
251                (F, 4),
252                (G, 4),
253                (Ab, 4),
254                (Bb, 4),
255                (C, 5)
256            ]
257        )
258    }
259
260    #[test]
261    fn middle_c_melodic_minor_scale() {
262        assert_eq!(
263            Key::from((C, MelodicMinor)).notes_in_range(C.in_octave(4)..=C.in_octave(5)),
264            [
265                (C, 4),
266                (D, 4),
267                (Eb, 4),
268                (F, 4),
269                (G, 4),
270                (A, 4),
271                (B, 4),
272                (C, 5)
273            ]
274        )
275    }
276
277    #[test]
278    fn middle_c_harmonic_minor_scale() {
279        assert_eq!(
280            Key::from((C, HarmonicMinor)).notes_in_range(Note(60)..=Note(72)),
281            [
282                (C, 4),
283                (D, 4),
284                (Eb, 4),
285                (F, 4),
286                (G, 4),
287                (Ab, 4),
288                (B, 4),
289                (C, 5)
290            ]
291        )
292    }
293}