Skip to main content

music21_rs/scale/
diatonicscale.rs

1use crate::{chord::Chord, defaults::IntegerType, error::Result, pitch::Pitch};
2
3use super::concretescale::ConcreteScale;
4
5#[derive(Clone, Debug)]
6/// A diatonic scale realized from a tonic, key signature, and mode.
7pub struct DiatonicScale {
8    concrete: ConcreteScale,
9    mode: String,
10}
11
12impl DiatonicScale {
13    pub(crate) fn new(tonic: Pitch, sharps: IntegerType, mode: &str) -> Self {
14        Self {
15            concrete: ConcreteScale::new(tonic, sharps),
16            mode: mode.to_string(),
17        }
18    }
19
20    /// Returns the scale mode.
21    pub fn mode(&self) -> &str {
22        &self.mode
23    }
24
25    /// Returns the tonic pitch.
26    pub fn tonic(&self) -> &Pitch {
27        self.concrete.tonic()
28    }
29
30    /// Returns the pitch at a one-based scale degree.
31    pub fn pitch_from_degree(&self, degree: usize) -> Result<Pitch> {
32        self.concrete.pitch_from_degree(degree)
33    }
34
35    /// Returns pitches from degree 1 through the octave.
36    pub fn pitches(&self) -> Result<Vec<Pitch>> {
37        self.concrete.pitches()
38    }
39
40    /// Builds a diatonic triad from a one-based degree.
41    pub fn triad_from_degree(&self, degree: usize) -> Result<Chord> {
42        let notes = vec![
43            self.pitch_from_degree(degree)?,
44            self.pitch_from_degree(degree + 2)?,
45            self.pitch_from_degree(degree + 4)?,
46        ];
47        Chord::new(notes.as_slice())
48    }
49
50    /// Builds a diatonic seventh chord from a one-based degree.
51    pub fn seventh_chord_from_degree(&self, degree: usize) -> Result<Chord> {
52        let notes = vec![
53            self.pitch_from_degree(degree)?,
54            self.pitch_from_degree(degree + 2)?,
55            self.pitch_from_degree(degree + 4)?,
56            self.pitch_from_degree(degree + 6)?,
57        ];
58        Chord::new(notes.as_slice())
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    fn pitch(name: &str) -> Pitch {
67        Pitch::new(
68            Some(name.to_string()),
69            None,
70            None,
71            Option::<IntegerType>::None,
72            Option::<IntegerType>::None,
73            None,
74            None,
75            None,
76            None,
77        )
78        .expect("valid pitch")
79    }
80
81    #[test]
82    fn diatonic_scale_degree_lookup() {
83        let scale = DiatonicScale::new(pitch("A4"), 0, "minor");
84        assert_eq!(scale.pitch_from_degree(1).unwrap().name_with_octave(), "A4");
85        assert_eq!(scale.pitch_from_degree(3).unwrap().name_with_octave(), "C5");
86        assert_eq!(scale.pitch_from_degree(7).unwrap().name_with_octave(), "G5");
87    }
88
89    #[test]
90    fn diatonic_scale_degree_chords() {
91        let scale = DiatonicScale::new(pitch("C4"), 0, "major");
92        assert_eq!(
93            scale.triad_from_degree(1).unwrap().pitched_common_name(),
94            "C-major triad"
95        );
96        assert_eq!(
97            scale
98                .seventh_chord_from_degree(5)
99                .unwrap()
100                .pitched_common_name(),
101            "G-dominant seventh chord"
102        );
103    }
104}