music21/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 with_scale(mut self, scale: Scale) -> Self {
155 self.scale = scale;
156 self
157 }
158
159 pub fn with_key_signature(mut self, key_signature: KeySignature) -> Self {
162 self.key_signature = key_signature;
163 self
164 }
165
166 pub fn mode(&self) -> FiguredBassMode {
168 self.mode
169 }
170
171 pub fn scale(&self) -> &Scale {
173 &self.scale
174 }
175
176 pub fn key_signature(&self) -> &KeySignature {
179 &self.key_signature
180 }
181
182 pub fn pitch_names(&self, bass: &Pitch, notation: &Notation) -> Result<Vec<String>> {
195 let degree = match self.scale.degree_of(bass)? {
196 Some(degree) => degree,
197 None => {
198 let mut spelled = bass.clone();
199 let from_key = self
200 .key_signature
201 .accidental_by_step(spelled.step().as_char())?;
202 if from_key.as_ref() != spelled.written_accidental() {
203 spelled.set_written_accidental(from_key);
204 }
205 self.scale.degree_of(&spelled)?.ok_or_else(|| {
206 Error::FiguredBass(format!(
207 "{} is on no degree of {} {}",
208 bass.name_with_octave(),
209 self.scale.tonic().name(),
210 self.mode
211 ))
212 })?
213 }
214 };
215 let modifiers = notation.modifiers();
216 let mut names = Vec::with_capacity(notation.numbers().len() + 1);
217 for (number, modifier) in notation.numbers().iter().zip(modifiers) {
218 let Some(number) = number else {
219 continue;
220 };
221 let on = (degree as IntegerType + number - 1) % 7;
222 let sample = self.scale.pitch_at_degree(on)?;
223 names.push(modifier.modify_pitch_name(&sample.name())?);
224 }
225 names.push(bass.name());
226 names.reverse();
227 Ok(names)
228 }
229
230 pub fn sample_pitches(&self, bass: &Pitch, notation: &Notation) -> Result<Vec<Pitch>> {
238 let top = bass.transpose(&DIMINISHED_OCTAVE)?;
239 self.pitches(bass, notation, Some(&top))
240 }
241
242 pub fn pitches(
250 &self,
251 bass: &Pitch,
252 notation: &Notation,
253 max_pitch: Option<&Pitch>,
254 ) -> Result<Vec<Pitch>> {
255 let max_pitch = max_pitch.unwrap_or(&DEFAULT_MAX_PITCH);
256 let names = self.pitch_names(bass, notation)?;
257 let mut pitches = Vec::new();
258 for name in &names {
259 for octave in 0..=max_pitch.implicit_octave() {
260 let pitch = Pitch::from_name(format!("{name}{octave}"))?;
261 if pitch.ps() >= bass.ps() && pitch.ps() <= max_pitch.ps() {
262 pitches.push(pitch);
263 }
264 }
265 }
266 pitches.sort_by(|a, b| a.ps().total_cmp(&b.ps()));
267 Ok(pitches)
268 }
269}