music21_rs/figuredbass/
scale.rs1use std::fmt;
10use std::sync::LazyLock;
11
12use crate::defaults::IntegerType;
13use crate::error::{Error, Result};
14use crate::figuredbass::Notation;
15use crate::interval::Interval;
16use crate::key::keysignature::{KeySignature, pitch_to_sharps};
17use crate::pitch::Pitch;
18use crate::scale::{Scale, ScaleType};
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub enum FiguredBassMode {
24 Major,
26 Minor,
28 Dorian,
30 Phrygian,
32 Hypophrygian,
34}
35
36impl FiguredBassMode {
37 pub const ALL: [Self; 5] = [
39 Self::Major,
40 Self::Minor,
41 Self::Dorian,
42 Self::Phrygian,
43 Self::Hypophrygian,
44 ];
45
46 pub fn name(self) -> &'static str {
48 match self {
49 Self::Major => "major",
50 Self::Minor => "minor",
51 Self::Dorian => "dorian",
52 Self::Phrygian => "phrygian",
53 Self::Hypophrygian => "hypophrygian",
54 }
55 }
56
57 pub fn from_name(name: &str) -> Result<Self> {
63 Self::ALL
64 .into_iter()
65 .find(|mode| mode.name() == name)
66 .ok_or_else(|| Error::FiguredBass(format!("Unsupported scale type-> {name}")))
67 }
68
69 fn scale_type(self) -> ScaleType {
70 match self {
71 Self::Major => ScaleType::Major,
72 Self::Minor => ScaleType::Minor,
73 Self::Dorian => ScaleType::Dorian,
74 Self::Phrygian => ScaleType::Phrygian,
75 Self::Hypophrygian => ScaleType::Hypophrygian,
76 }
77 }
78}
79
80impl fmt::Display for FiguredBassMode {
81 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82 formatter.write_str(self.name())
83 }
84}
85
86static DEFAULT_MAX_PITCH: LazyLock<Pitch> =
89 LazyLock::new(|| Pitch::from_name("B5").expect("B5 is a pitch name"));
90
91static DIMINISHED_OCTAVE: LazyLock<Interval> =
93 LazyLock::new(|| Interval::from_name("d8").expect("d8 is an interval name"));
94
95#[derive(Clone, Debug)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110#[must_use]
111pub struct FiguredBassScale {
112 mode: FiguredBassMode,
113 scale: Scale,
114 key_signature: KeySignature,
115}
116
117impl PartialEq for FiguredBassScale {
118 fn eq(&self, other: &Self) -> bool {
121 self.mode == other.mode && self.scale == other.scale
122 }
123}
124
125impl Default for FiguredBassScale {
126 fn default() -> Self {
128 Self::new(
129 Pitch::from_name("C").expect("C is a pitch name"),
130 FiguredBassMode::Major,
131 )
132 .expect("C major has a key signature")
133 }
134}
135
136impl FiguredBassScale {
137 pub fn new(tonic: Pitch, mode: FiguredBassMode) -> Result<Self> {
143 let sharps = pitch_to_sharps(&tonic, Some(mode.name()))?;
144 Ok(Self {
145 mode,
146 scale: Scale::new(mode.scale_type(), tonic),
147 key_signature: KeySignature::new(sharps),
148 })
149 }
150
151 pub fn mode(&self) -> FiguredBassMode {
153 self.mode
154 }
155
156 pub fn scale(&self) -> &Scale {
158 &self.scale
159 }
160
161 pub fn key_signature(&self) -> &KeySignature {
164 &self.key_signature
165 }
166
167 pub fn pitch_names(&self, bass: &Pitch, notation: &Notation) -> Result<Vec<String>> {
180 let degree = match self.scale.degree_of(bass)? {
181 Some(degree) => degree,
182 None => {
183 let mut spelled = bass.clone();
184 let from_key = self
185 .key_signature
186 .accidental_by_step(spelled.step().as_char())?;
187 if from_key.as_ref() != spelled.written_accidental() {
188 spelled.set_written_accidental(from_key);
189 }
190 self.scale.degree_of(&spelled)?.ok_or_else(|| {
191 Error::FiguredBass(format!(
192 "{} is on no degree of {} {}",
193 bass.name_with_octave(),
194 self.scale.tonic().name(),
195 self.mode
196 ))
197 })?
198 }
199 };
200 let modifiers = notation.modifiers();
201 let mut names = Vec::with_capacity(notation.numbers().len() + 1);
202 for (number, modifier) in notation.numbers().iter().zip(modifiers) {
203 let Some(number) = number else {
204 continue;
205 };
206 let on = (degree as IntegerType + number - 1) % 7;
207 let sample = self.scale.pitch_at_degree(on)?;
208 names.push(modifier.modify_pitch_name(&sample.name())?);
209 }
210 names.push(bass.name());
211 names.reverse();
212 Ok(names)
213 }
214
215 pub fn sample_pitches(&self, bass: &Pitch, notation: &Notation) -> Result<Vec<Pitch>> {
223 let top = bass.transpose(&DIMINISHED_OCTAVE)?;
224 self.pitches(bass, notation, Some(&top))
225 }
226
227 pub fn pitches(
235 &self,
236 bass: &Pitch,
237 notation: &Notation,
238 max_pitch: Option<&Pitch>,
239 ) -> Result<Vec<Pitch>> {
240 let max_pitch = max_pitch.unwrap_or(&DEFAULT_MAX_PITCH);
241 let names = self.pitch_names(bass, notation)?;
242 let mut pitches = Vec::new();
243 for name in &names {
244 for octave in 0..=max_pitch.implicit_octave() {
245 let pitch = Pitch::from_name(format!("{name}{octave}"))?;
246 if pitch.ps() >= bass.ps() && pitch.ps() <= max_pitch.ps() {
247 pitches.push(pitch);
248 }
249 }
250 }
251 pitches.sort_by(|a, b| a.ps().total_cmp(&b.ps()));
252 Ok(pitches)
253 }
254}