music21_rs/pitch/
enharmonic.rs1use super::*;
5
6impl Pitch {
7 pub(super) fn get_all_common_enharmonics(
8 &mut self,
9 alter_limit: FloatType,
10 ) -> Result<Vec<Pitch>> {
11 let mut post = Vec::new();
12
13 let simplified = self.clone().simplify_enharmonic(false)?;
14 if simplified.name() != self.name() {
15 post.push(simplified);
16 }
17
18 let mut higher = self.clone();
19 while let Ok(next) = higher.get_higher_enharmonic() {
20 if next.accidental.alter.abs() > alter_limit {
21 break;
22 }
23 if post.contains(&next) {
24 break;
25 }
26 post.push(next.clone());
27 higher = next;
28 }
29
30 let mut lower = self.clone();
31 while let Ok(next) = lower.get_lower_enharmonic() {
32 if next.accidental.alter.abs() > alter_limit {
33 break;
34 }
35 if post.contains(&next) {
36 break;
37 }
38 post.push(next.clone());
39 lower = next;
40 }
41
42 Ok(post)
43 }
44
45 pub fn simplify_enharmonic(&self, most_common: bool) -> Result<Pitch> {
51 let mut pitch = self.clone();
52 pitch.simplify_enharmonic_in_place(most_common)?;
53 Ok(pitch)
54 }
55
56 pub fn simplify_enharmonic_in_place(&mut self, most_common: bool) -> Result<()> {
58 const EXCLUDED_NAMES: [&str; 4] = ["E#", "B#", "C-", "F-"];
59 if self.accidental.alter.abs().partial_cmp(&2.0) != Some(Ordering::Less)
60 || EXCLUDED_NAMES.contains(&self.name().as_str())
61 {
62 let save_octave = self.octave;
64 self.ps_setter(self.ps());
65 if save_octave.is_none() {
66 self.octave_setter(None);
67 }
68 }
69
70 if most_common {
71 match self.name().as_str() {
72 "D#" => {
73 self.step_setter(StepName::E);
74 self.accidental_setter(Accidental::new("flat")?);
75 }
76 "A#" => {
77 self.step_setter(StepName::B);
78 self.accidental_setter(Accidental::new("flat")?);
79 }
80 "G-" => {
81 self.step_setter(StepName::F);
82 self.accidental_setter(Accidental::new("sharp")?);
83 }
84 "D-" => {
85 self.step_setter(StepName::C);
86 self.accidental_setter(Accidental::new("sharp")?);
87 }
88 _ => {}
89 }
90 }
91
92 Ok(())
93 }
94
95 pub fn get_higher_enharmonic(&self) -> Result<Pitch> {
97 self.enharmonic_neighbour(true)
98 }
99
100 pub fn get_higher_enharmonic_in_place(&mut self) -> Result<()> {
102 self.enharmonic_neighbour_in_place(true)
103 }
104
105 pub fn get_lower_enharmonic(&self) -> Result<Pitch> {
107 self.enharmonic_neighbour(false)
108 }
109
110 pub fn get_lower_enharmonic_in_place(&mut self) -> Result<()> {
112 self.enharmonic_neighbour_in_place(false)
113 }
114
115 pub(super) fn enharmonic_neighbour(&self, up: bool) -> Result<Pitch> {
116 let interval: &Interval = if up {
117 &DIMINISHED_SECOND_UP
118 } else {
119 &DIMINISHED_SECOND_DOWN
120 };
121
122 let octave_stored = self.octave;
123
124 let mut p = interval.transpose_pitch_with_options(self, false, None)?;
125 if octave_stored.is_none() {
126 p.octave_setter(None);
127 }
128 Ok(p)
129 }
130
131 pub(super) fn enharmonic_neighbour_in_place(&mut self, up: bool) -> Result<()> {
132 *self = self.enharmonic_neighbour(up)?;
133 Ok(())
134 }
135
136 pub fn respelled_for(&self, signature: &crate::key::KeySignature) -> Result<Pitch> {
148 if !self.has_accidental() {
149 return Ok(self.clone());
150 }
151 let alter = self.accidental().alter();
152 for altered in signature.altered_pitches()? {
153 if altered.pitch_class() == self.pitch_class() && altered.accidental().alter() != alter
154 {
155 return self.get_enharmonic();
156 }
157 }
158 Ok(self.clone())
159 }
160
161 pub fn get_enharmonic(&self) -> Result<Pitch> {
167 let alter = self.accidental.alter();
168 let downward = if alter > 0.0 {
169 false
170 } else if alter < 0.0 {
171 true
172 } else {
173 matches!(self.step.as_char(), 'C' | 'D' | 'G')
174 };
175 if downward {
176 self.get_lower_enharmonic()
177 } else {
178 self.get_higher_enharmonic()
179 }
180 }
181
182 pub fn is_enharmonic(&self, other: &Pitch) -> bool {
185 if self.octave.is_none() || other.octave.is_none() {
186 (other.ps() - self.ps()).rem_euclid(12.0) == 0.0
187 } else {
188 other.ps() == self.ps()
189 }
190 }
191
192 pub fn all_common_enharmonics(&self, alter_limit: IntegerType) -> Vec<Pitch> {
196 let mut found = Vec::new();
197 if let Ok(simplified) = self.simplify_enharmonic(false)
198 && simplified.name() != self.name()
199 {
200 found.push(simplified);
201 }
202 for upward in [true, false] {
203 let mut current = self.clone();
204 while let Ok(next) = current.enharmonic_neighbour(upward) {
205 if next.accidental().alter().abs() > alter_limit as FloatType
206 || found.contains(&next)
207 {
208 break;
209 }
210 found.push(next.clone());
211 current = next;
212 }
213 }
214 found
215 }
216
217 pub fn transpose_below_target(&self, target: &Pitch, minimize: bool) -> Result<Pitch> {
220 let mut pitch = self.octave_bearing_copy("transposeBelowTarget")?;
221 while pitch.ps() > target.ps() {
222 pitch.shift_octave(-1);
223 }
224 if minimize {
225 while target.ps() - pitch.ps() >= 12.0 {
226 pitch.shift_octave(1);
227 }
228 }
229 Ok(pitch)
230 }
231
232 pub fn transpose_above_target(&self, target: &Pitch, minimize: bool) -> Result<Pitch> {
235 let mut pitch = self.octave_bearing_copy("transposeAboveTarget")?;
236 while pitch.ps() < target.ps() {
237 pitch.shift_octave(1);
238 }
239 if minimize {
240 while pitch.ps() - target.ps() >= 12.0 {
241 pitch.shift_octave(-1);
242 }
243 }
244 Ok(pitch)
245 }
246}
247
248pub(super) use crate::interval::constants::{DIMINISHED_SECOND_DOWN, DIMINISHED_SECOND_UP};
249
250pub type CriterionFunction = fn(&[Pitch]) -> Result<FloatType>;
253
254const SCORE_TOLERANCE: FloatType = 1e-9;
257
258pub fn simplify_multiple_enharmonics(
267 pitches: &[Pitch],
268 criterion: Option<CriterionFunction>,
269 key_context: Option<KeySignature>,
270) -> Result<Vec<Pitch>> {
271 let mut old_pitches: Vec<Pitch> = pitches.to_vec();
272 if old_pitches.is_empty() {
273 return Ok(Vec::new());
274 }
275
276 let criterion: CriterionFunction = criterion.unwrap_or(dissonance_score);
277
278 let remove_first: bool = match key_context {
279 Some(key) => {
280 old_pitches.insert(0, key.as_key("major").tonic());
281 true
282 }
283 None => false,
284 };
285
286 let mut simplified_pitches = match old_pitches.len() < 5 {
287 true => brute_force_enharmonics_search(&mut old_pitches, criterion)?,
288 false => greedy_enharmonics_search(&mut old_pitches, criterion)?,
289 };
290
291 for (new_p, old_p) in simplified_pitches.iter_mut().zip(old_pitches) {
292 new_p.spelling_is_inferred = old_p.spelling_is_inferred;
293 }
294
295 if remove_first {
296 let _ = simplified_pitches.remove(0);
297 }
298
299 Ok(simplified_pitches)
300}
301
302pub(super) fn brute_force_enharmonics_search(
303 old_pitches: &mut [Pitch],
304 score_func: CriterionFunction,
305) -> Result<Vec<Pitch>> {
306 let all_possible_pitches: Result<Vec<Vec<Pitch>>> = old_pitches[1..]
307 .iter_mut()
308 .map(|p| -> Result<Vec<Pitch>> {
309 let mut enharmonics = p.get_all_common_enharmonics(2 as FloatType)?;
310 enharmonics.insert(0, p.clone());
311 Ok(enharmonics)
312 })
313 .collect();
314
315 let all_pitch_combinations = all_possible_pitches?.into_iter().multi_cartesian_product();
316
317 let mut min_score = FloatType::MAX;
318 let mut best_combination: Vec<Pitch> = Vec::new();
319
320 for combination in all_pitch_combinations {
321 let mut pitches: Vec<Pitch> = old_pitches[..1].to_vec();
322 pitches.extend(combination);
323 let score = score_func(&pitches)?;
324 if score < min_score - SCORE_TOLERANCE {
328 min_score = score;
329 best_combination = pitches;
330 }
331 }
332
333 Ok(best_combination)
334}
335
336pub(super) fn greedy_enharmonics_search(
337 old_pitches: &mut [Pitch],
338 score_func: CriterionFunction,
339) -> Result<Vec<Pitch>> {
340 let mut new_pitches = vec![];
341
342 if let Some(first) = old_pitches.first() {
343 new_pitches.push(first.clone());
344 } else {
345 return Err(Error::Pitch(
346 "can't perform greedy enharmonics search on empty pitches".into(),
347 ));
348 }
349
350 for old_pitch in old_pitches.iter_mut().skip(1) {
351 let mut candidates = vec![old_pitch.clone()];
352 candidates.extend(old_pitch.get_all_common_enharmonics(2 as FloatType)?);
353
354 let mut best_candidate = None;
355 let mut best_score: Option<OrderedFloat<FloatType>> = None;
356 for candidate in candidates.iter() {
357 let mut candidate_list = new_pitches.clone();
358 candidate_list.push(candidate.clone());
359 let score = score_func(&candidate_list)?;
360 let score = OrderedFloat(score);
361 if best_score.is_none_or(|best| score < best - SCORE_TOLERANCE) {
362 best_score = Some(score);
363 best_candidate = Some(candidate);
364 }
365 }
366 let best_candidate = best_candidate
367 .ok_or_else(|| Error::Pitch("candidates list is unexpectedly empty".to_string()))?;
368 new_pitches.push(best_candidate.clone());
369 }
370 Ok(new_pitches)
371}
372
373pub fn dissonance_score(pitches: &[Pitch]) -> Result<FloatType> {
379 weighted_dissonance_score(pitches, true, true, true)
380}
381
382pub(super) fn weighted_dissonance_score(
383 pitches: &[Pitch],
384 small_pythagorean_ratio: bool,
385 accidental_penalty: bool,
386 triad_award: bool,
387) -> Result<FloatType> {
388 let mut score_accidentals: FloatType = 0.0;
389 let mut score_ratio: FloatType = 0.0;
390 let mut score_triad: FloatType = 0.0;
391
392 if pitches.is_empty() {
393 return Ok(0.0);
394 }
395
396 if accidental_penalty {
397 let accidentals = pitches
398 .iter()
399 .map(|p| p.alter().abs())
400 .collect::<Vec<FloatType>>();
401 score_accidentals = accidentals
402 .iter()
403 .map(|a| if *a > 1.0 { *a } else { 0.0 })
404 .sum::<FloatType>()
405 / pitches.len() as FloatType;
406 }
407
408 let mut intervals: Vec<Interval> = vec![];
409
410 if small_pythagorean_ratio | triad_award {
411 for (index, p1) in pitches.iter().enumerate() {
412 for p2 in pitches.iter().skip(index + 1) {
413 let mut p2 = (*p2).clone();
414 p2.octave_setter(None);
415 let Ok(interval) = Interval::between(
416 PitchOrNote::Pitch(p1.clone()),
417 PitchOrNote::Pitch(p2.clone()),
418 ) else {
419 return Ok(FloatType::INFINITY);
420 };
421 intervals.push(interval);
422 }
423 }
424
425 if small_pythagorean_ratio {
426 for interval in intervals.iter() {
427 score_ratio += pythagorean_denominator_log(interval)? * 0.075_853_268_88
428 }
429 score_ratio /= pitches.len() as FloatType;
430 }
431
432 if triad_award {
433 intervals.into_iter().for_each(|interval| {
434 let simple_directed = interval.generic().simple_directed();
435 let interval_semitones = interval.chromatic.whole_semitones() % 12;
436 if (simple_directed == 3 && (interval_semitones == 3 || interval_semitones == 4))
437 || (simple_directed == 6
438 && (interval_semitones == 8 || interval_semitones == 9))
439 {
440 score_triad -= 1.0;
441 }
442 });
443 score_triad /= pitches.len() as FloatType;
444 }
445 }
446
447 Ok((score_accidentals + score_ratio + score_triad)
448 / (small_pythagorean_ratio as IntegerType
449 + accidental_penalty as IntegerType
450 + triad_award as IntegerType) as FloatType)
451}
452
453pub(super) fn pythagorean_denominator_log(interval: &Interval) -> Result<FloatType> {
454 let start_pitch = Pitch::from_name("C1")?;
455 let end_pitch = interval.transpose_pitch_with_options(&start_pitch, false, Some(4))?;
456
457 let natural_fifths = match end_pitch.step() {
458 StepName::C => 0,
459 StepName::D => 2,
460 StepName::E => 4,
461 StepName::F => -1,
462 StepName::G => 1,
463 StepName::A => 3,
464 StepName::B => 5,
465 };
466 let fifth_count = natural_fifths + (end_pitch.alter().round() as IntegerType * 7);
467 let found_pitch_space = start_pitch.ps() + (7 * fifth_count) as FloatType;
468 let octave_adjust = ((end_pitch.ps() - found_pitch_space) / 12.0).round() as IntegerType;
469
470 let mut denominator_twos = if fifth_count > 0 { fifth_count } else { 0 };
471 let denominator_threes = if fifth_count < 0 { -fifth_count } else { 0 };
472 denominator_twos = (denominator_twos - octave_adjust).max(0);
473
474 Ok(denominator_twos as FloatType * (2.0 as FloatType).ln()
475 + denominator_threes as FloatType * (3.0 as FloatType).ln())
476}