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::from_name(name.to_string()).expect("valid pitch")
68    }
69
70    #[test]
71    fn diatonic_scale_degree_lookup() {
72        let scale = DiatonicScale::new(pitch("A4"), 0, "minor");
73        assert_eq!(scale.pitch_from_degree(1).unwrap().name_with_octave(), "A4");
74        assert_eq!(scale.pitch_from_degree(3).unwrap().name_with_octave(), "C5");
75        assert_eq!(scale.pitch_from_degree(7).unwrap().name_with_octave(), "G5");
76    }
77
78    #[test]
79    fn diatonic_scale_degree_chords() {
80        let scale = DiatonicScale::new(pitch("C4"), 0, "major");
81        assert_eq!(
82            scale.triad_from_degree(1).unwrap().pitched_common_name(),
83            "C-major triad"
84        );
85        assert_eq!(
86            scale
87                .seventh_chord_from_degree(5)
88                .unwrap()
89                .pitched_common_name(),
90            "G-dominant seventh chord"
91        );
92    }
93}