1pub(crate) mod accidental;
2pub(crate) mod microtone;
3pub(crate) mod pitchclass;
4pub(crate) mod pitchclassstring;
5
6use crate::defaults::FloatType;
7use crate::defaults::IntegerType;
8use crate::defaults::Octave;
9use crate::defaults::PITCH_OCTAVE;
10use crate::defaults::PITCH_SPACE_SIGNIFICANT_DIGITS;
11use crate::defaults::PITCH_STEP;
12use crate::defaults::UnsignedIntegerType;
13use crate::error::Error;
14use crate::error::Result;
15use crate::interval::Interval;
16use crate::interval::IntervalArgument;
17use crate::interval::PitchOrNote;
18use crate::key::keysignature::KeySignature;
19use crate::stepname::StepName;
20use crate::tuningsystem::OCTAVE_SIZE;
21use crate::tuningsystem::TuningSystem;
22
23use accidental::IntoAccidental;
24pub use accidental::{Accidental, AccidentalSpecifier};
25use microtone::IntoCentShift;
26pub use microtone::{Microtone, MicrotoneSpecifier};
27use pitchclass::convert_ps_to_oct;
28pub use pitchclass::{PitchClass, PitchClassSpecifier};
29
30use itertools::Itertools;
31use num::Num;
32use num_traits::ToPrimitive;
33use ordered_float::OrderedFloat;
34use std::cmp::Ordering;
35use std::fmt::{Display, Formatter};
36use std::str::FromStr;
37use std::sync::Arc;
38use std::sync::LazyLock;
39
40pub const CHROMATIC_PITCH_CLASS_NAMES: [&str; 12] = [
42 "C", "D-", "D", "E-", "E", "F", "F#", "G", "A-", "A", "B-", "B",
43];
44
45pub fn pitch_class_name(pitch_class: u8) -> &'static str {
47 CHROMATIC_PITCH_CLASS_NAMES[pitch_class as usize % 12]
48}
49
50static DIMINISHED_SECOND_UP: LazyLock<Interval> = LazyLock::new(|| {
53 Interval::new(IntervalArgument::Str("d2".to_string())).expect("d2 is a valid interval")
54});
55static DIMINISHED_SECOND_DOWN: LazyLock<Interval> = LazyLock::new(|| {
56 Interval::new(IntervalArgument::Str("-d2".to_string())).expect("-d2 is a valid interval")
57});
58
59#[derive(Clone, Debug, PartialEq)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61pub enum PitchName {
63 Name(String),
65 Number(FloatType),
67}
68
69impl From<&str> for PitchName {
70 fn from(value: &str) -> Self {
71 Self::Name(value.to_string())
72 }
73}
74
75impl From<String> for PitchName {
76 fn from(value: String) -> Self {
77 Self::Name(value)
78 }
79}
80
81impl From<IntegerType> for PitchName {
82 fn from(value: IntegerType) -> Self {
83 Self::Number(value as FloatType)
84 }
85}
86
87impl From<FloatType> for PitchName {
88 fn from(value: FloatType) -> Self {
89 Self::Number(value)
90 }
91}
92
93#[derive(Clone, Debug, Default, PartialEq)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95pub struct PitchOptions {
97 pub name: Option<PitchName>,
99 pub step: Option<char>,
101 pub octave: Octave,
103 pub accidental: Option<AccidentalSpecifier>,
105 pub microtone: Option<MicrotoneSpecifier>,
107 pub pitch_class: Option<PitchClassSpecifier>,
109 pub midi: Option<IntegerType>,
111 pub ps: Option<FloatType>,
113 pub fundamental: Option<Pitch>,
115}
116
117impl PitchOptions {
118 pub fn new() -> Self {
120 Self::default()
121 }
122
123 pub fn name(mut self, name: impl Into<PitchName>) -> Self {
125 self.name = Some(name.into());
126 self
127 }
128
129 pub fn step(mut self, step: char) -> Self {
131 self.step = Some(step);
132 self
133 }
134
135 pub fn octave(mut self, octave: IntegerType) -> Self {
137 self.octave = Some(octave);
138 self
139 }
140
141 pub fn accidental(mut self, accidental: impl Into<AccidentalSpecifier>) -> Self {
143 self.accidental = Some(accidental.into());
144 self
145 }
146
147 pub fn microtone(mut self, microtone: impl Into<MicrotoneSpecifier>) -> Self {
149 self.microtone = Some(microtone.into());
150 self
151 }
152
153 pub fn pitch_class(mut self, pitch_class: impl Into<PitchClassSpecifier>) -> Self {
155 self.pitch_class = Some(pitch_class.into());
156 self
157 }
158
159 pub fn midi(mut self, midi: IntegerType) -> Self {
161 self.midi = Some(midi);
162 self
163 }
164
165 pub fn ps(mut self, ps: FloatType) -> Self {
167 self.ps = Some(ps);
168 self
169 }
170
171 pub fn pitch_space(mut self, pitch_space: FloatType) -> Self {
173 self.ps = Some(pitch_space);
174 self
175 }
176
177 pub fn fundamental(mut self, fundamental: Pitch) -> Self {
179 self.fundamental = Some(fundamental);
180 self
181 }
182
183 pub fn build(self) -> Result<Pitch> {
185 Pitch::from_options(self)
186 }
187}
188
189#[derive(Clone, Debug)]
190#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
191pub struct Pitch {
193 _step: StepName,
194 _octave: Octave,
195 _accidental: Accidental,
196 _microtone: Option<Microtone>,
197 spelling_is_infered: bool,
198 #[cfg_attr(feature = "serde", serde(skip))]
199 fundamental: Option<Arc<Pitch>>,
200}
201
202impl PartialEq for Pitch {
203 fn eq(&self, other: &Self) -> bool {
204 self._step == other._step
205 && self._octave == other._octave
206 && self._accidental == other._accidental
207 && self._microtone == other._microtone
208 }
209}
210
211impl FromStr for Pitch {
212 type Err = Error;
213
214 fn from_str(value: &str) -> Result<Self> {
215 Self::from_name(value)
216 }
217}
218
219impl TryFrom<&str> for Pitch {
220 type Error = Error;
221
222 fn try_from(value: &str) -> Result<Self> {
223 Self::from_name(value)
224 }
225}
226
227impl TryFrom<String> for Pitch {
228 type Error = Error;
229
230 fn try_from(value: String) -> Result<Self> {
231 Self::from_name(value)
232 }
233}
234
235impl TryFrom<&Pitch> for Pitch {
236 type Error = Error;
237
238 fn try_from(value: &Pitch) -> Result<Self> {
239 Ok(value.clone())
240 }
241}
242
243impl TryFrom<IntegerType> for Pitch {
244 type Error = Error;
245
246 fn try_from(value: IntegerType) -> Result<Self> {
247 Self::from_midi(value)
248 }
249}
250
251impl TryFrom<FloatType> for Pitch {
252 type Error = Error;
253
254 fn try_from(value: FloatType) -> Result<Self> {
255 Self::from_pitch_space(value)
256 }
257}
258
259impl Display for Pitch {
260 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
261 write!(f, "{}", self.name_with_octave())
262 }
263}
264
265impl Pitch {
266 pub fn from_options(options: PitchOptions) -> Result<Self> {
268 let step = options.step.map(StepName::try_from).transpose()?;
269
270 Self::new(
271 options.name,
272 step,
273 options.octave,
274 options.accidental,
275 options.microtone,
276 options.pitch_class,
277 options.midi,
278 options.ps,
279 options.fundamental,
280 )
281 }
282
283 pub fn builder() -> PitchOptions {
285 PitchOptions::new()
286 }
287
288 pub fn from_name(name: impl Into<String>) -> Result<Self> {
290 Self::new(
291 Some(name.into()),
292 None,
293 None,
294 Option::<IntegerType>::None,
295 Option::<IntegerType>::None,
296 None,
297 None,
298 None,
299 None,
300 )
301 }
302
303 pub fn from_number(number: FloatType) -> Result<Self> {
305 Self::new(
306 Some(PitchName::Number(number)),
307 None,
308 None,
309 Option::<IntegerType>::None,
310 Option::<IntegerType>::None,
311 None,
312 None,
313 None,
314 None,
315 )
316 }
317
318 pub fn from_step(step: char) -> Result<Self> {
320 Self::new(
321 Option::<String>::None,
322 Some(StepName::try_from(step)?),
323 None,
324 Option::<IntegerType>::None,
325 Option::<IntegerType>::None,
326 None,
327 None,
328 None,
329 None,
330 )
331 }
332
333 pub fn from_name_and_octave(name: impl Into<String>, octave: IntegerType) -> Result<Self> {
335 PitchOptions::new().name(name.into()).octave(octave).build()
336 }
337
338 pub fn from_pitch_class(pitch_class: impl Into<PitchClassSpecifier>) -> Result<Self> {
340 PitchOptions::new().pitch_class(pitch_class).build()
341 }
342
343 pub fn from_midi(midi: IntegerType) -> Result<Self> {
345 Self::new(
346 Option::<String>::None,
347 None,
348 None,
349 Option::<IntegerType>::None,
350 Option::<IntegerType>::None,
351 None,
352 Some(midi),
353 None,
354 None,
355 )
356 }
357
358 pub fn from_pitch_space(ps: FloatType) -> Result<Self> {
360 Self::new(
361 Option::<String>::None,
362 None,
363 None,
364 Option::<IntegerType>::None,
365 Option::<IntegerType>::None,
366 None,
367 None,
368 Some(ps),
369 None,
370 )
371 }
372
373 #[allow(clippy::too_many_arguments)]
374 fn new<T, U, V>(
378 name: Option<T>,
379 step: Option<StepName>,
380 octave: Octave,
381 accidental: Option<U>,
382 microtone: Option<V>,
383 pitch_class: Option<PitchClassSpecifier>,
384 midi: Option<IntegerType>,
385 ps: Option<FloatType>,
386 fundamental: Option<Pitch>,
387 ) -> Result<Self>
388 where
389 T: IntoPitchName,
390 U: IntoAccidental,
391 V: IntoCentShift,
392 {
393 let has_explicit_step = step.is_some();
394 let has_explicit_octave = octave.is_some();
395 let has_explicit_accidental = accidental.is_some();
396 let has_explicit_microtone = microtone.is_some();
397
398 let mut self_name = None;
400 let mut self_step = PITCH_STEP;
401 let mut self_accidental: Option<Accidental> = None;
402 let mut self_microtone: Option<Microtone> = None;
403 let mut self_spelling_is_inferred = false;
404 let mut self_octave = None;
405 let self_pitch_class = pitch_class;
406 let self_fundamental = fundamental;
407 let self_midi = midi;
408 let self_ps = ps;
409
410 if let Some(name) = name {
411 let x = name.into_name();
412 self_name = x.name;
413 if let Some(step) = x.step {
414 self_step = step;
415 }
416 if let Some(accidental) = x.accidental {
417 self_accidental = Some(accidental);
418 }
419 if let Some(inferred) = x.spelling_is_inferred {
420 self_spelling_is_inferred = inferred;
421 }
422 self_octave = x.octave;
423 } else if let Some(s) = step {
424 self_step = s;
425 }
426
427 if let Some(oct) = octave {
428 self_octave = Some(oct);
429 }
430
431 let self_accidental: Accidental = match accidental {
432 Some(acc) if acc.is_accidental() => acc.accidental(),
433 Some(acc) => acc.into_accidental()?,
434 None => match self_accidental {
435 Some(acc) => acc,
436 None => Accidental::new("natural")?,
437 },
438 };
439
440 if let Some(mt) = microtone {
441 self_microtone = Some(if mt.is_microtone() {
442 mt.microtone()
443 } else {
444 mt.into_microtone()?
445 });
446 }
447
448 let mut pitch = Pitch {
450 _step: self_step,
451 _accidental: self_accidental,
452 _microtone: self_microtone,
453 _octave: self_octave,
454 spelling_is_infered: self_spelling_is_inferred,
455 fundamental: None,
456 };
457
458 if let Some(ref n) = self_name {
460 pitch.name_setter(n)?;
461 }
462
463 if has_explicit_step || self_name.is_none() {
464 pitch.step_setter(self_step);
465 }
466
467 if has_explicit_octave || self_name.is_none() {
468 pitch.octave_setter(self_octave);
469 }
470
471 if has_explicit_accidental || self_name.is_none() {
472 pitch.accidental_setter(pitch._accidental.clone());
473 }
474 if has_explicit_microtone {
475 let Some(mt) = pitch._microtone.clone() else {
476 return Err(Error::Pitch(
477 "microtone was expected but missing".to_string(),
478 ));
479 };
480 pitch.microtone_setter(mt.clone());
481 }
482 if let Some(pc) = self_pitch_class {
483 pitch.pitch_class_setter(pc)?;
484 }
485 if let Some(f) = self_fundamental {
486 pitch.fundamental_setter(f);
487 }
488 if let Some(m) = self_midi {
489 pitch.midi_setter(m);
490 }
491 if let Some(p) = self_ps {
492 pitch.ps_setter(p);
493 }
494
495 Ok(pitch)
496 }
497
498 pub fn name_with_octave(&self) -> String {
500 match self._octave {
501 Some(octave) => format!("{}{}", self.name(), octave),
502 None => self.name(),
503 }
504 }
505
506 pub fn name(&self) -> String {
508 format!("{}{}", self._step.as_char(), self._accidental.modifier())
509 }
510
511 fn name_setter(&mut self, usr_str: &str) -> Result<()> {
512 let usr_str = usr_str.trim();
513
514 let digit_index = usr_str
515 .char_indices()
516 .find(|&(_, c)| c.is_ascii_digit())
517 .map(|(i, _)| i);
518
519 let (pitch_part, octave_part) = if let Some(i) = digit_index {
520 if i == 0 {
521 return Err(Error::Pitch(format!(
522 "Cannot have octave given before pitch name in {usr_str:?}"
523 )));
524 }
525 (&usr_str[..i], &usr_str[i..])
526 } else {
527 (usr_str, "")
528 };
529
530 let mut pitch_chars = pitch_part.chars();
532 let step = pitch_chars.next().ok_or(Error::Pitch(format!(
533 "Cannot make a name out of {pitch_part:?}"
534 )))?;
535 self.step_setter(StepName::try_from(step)?);
536
537 let accidental_str: String = pitch_chars.collect();
538 if accidental_str.is_empty() {
539 self.accidental_setter(Accidental::natural());
540 } else {
541 self.accidental_setter(Accidental::new(accidental_str)?);
542 }
543
544 if !octave_part.is_empty() {
545 let octave = octave_part
546 .parse::<IntegerType>()
547 .map_err(|_| Error::Pitch(format!("Cannot parse {octave_part:?} to octave")))?;
548 self.octave_setter(Some(octave));
549 }
550
551 Ok(())
552 }
553
554 pub fn alter(&self) -> FloatType {
556 let mut post = 0.0;
557
558 post += self._accidental._alter;
559
560 if let Some(microtone) = &self._microtone {
561 post += microtone.alter();
562 }
563
564 post
565 }
566
567 pub fn accidental(&self) -> &Accidental {
572 &self._accidental
573 }
574
575 pub fn microtone(&self) -> Option<&Microtone> {
577 self._microtone.as_ref()
578 }
579
580 pub fn pitch_class(&self) -> PitchClass {
582 PitchClass::from_number(self.ps()).unwrap_or_else(|err| {
583 panic!("pitch-space value should always map to pitch class: {err}")
584 })
585 }
586
587 pub(crate) fn octave_setter(&mut self, octave: Octave) {
588 self._octave = octave;
589 }
590
591 fn get_all_common_enharmonics(&mut self, alter_limit: FloatType) -> Result<Vec<Pitch>> {
592 let mut post = Vec::new();
593
594 let simplified = self.clone().simplify_enharmonic(false)?;
595 if simplified.name() != self.name() {
596 post.push(simplified);
597 }
598
599 let mut higher = self.clone();
600 while let Ok(next) = higher.get_higher_enharmonic() {
601 if next._accidental._alter.abs() > alter_limit {
602 break;
603 }
604 if post.contains(&next) {
605 break;
606 }
607 post.push(next.clone());
608 higher = next;
609 }
610
611 let mut lower = self.clone();
612 while let Ok(next) = lower.get_lower_enharmonic() {
613 if next._accidental._alter.abs() > alter_limit {
614 break;
615 }
616 if post.contains(&next) {
617 break;
618 }
619 post.push(next.clone());
620 lower = next;
621 }
622
623 Ok(post)
624 }
625
626 pub(crate) fn transpose(&self, interval: &Interval) -> Pitch {
627 let mut p = interval
628 .transpose_pitch_with_options(self, false, Some(4))
629 .unwrap_or_else(|_| self.clone());
630
631 if !interval.implicit_diatonic {
632 p.spelling_is_infered = self.spelling_is_infered;
633 }
634 if p.spelling_is_infered {
635 let _ = p.simplify_enharmonic_in_place(true);
636 }
637
638 p
639 }
640
641 pub fn ps(&self) -> FloatType {
643 self.pitch_space()
644 }
645
646 pub fn pitch_space(&self) -> FloatType {
648 let octave = self._octave.unwrap_or(PITCH_OCTAVE as IntegerType);
649 ((octave + 1) * 12) as FloatType + self._step.step_ref() as FloatType + self.alter()
650 }
651
652 pub fn midi(&self) -> IntegerType {
654 self.pitch_space().round() as IntegerType
655 }
656
657 pub fn frequency_hz(&self) -> FloatType {
659 self.frequency_hz_in(TuningSystem::EqualTemperament {
660 octave_size: OCTAVE_SIZE,
661 })
662 }
663
664 pub fn frequency_hz_in(&self, tuning_system: TuningSystem) -> FloatType {
669 tuning_system.frequency_at(self.pitch_space())
670 }
671
672 fn step_setter(&mut self, step_name: StepName) {
673 self._step = step_name;
674 self.spelling_is_infered = true;
675 }
676
677 fn accidental_setter(&mut self, value: Accidental) {
678 self._accidental = value;
679 }
680
681 fn microtone_setter(&mut self, mt: Microtone) {
682 self._microtone = Some(mt);
683 }
684
685 fn pitch_class_setter(&mut self, pc: PitchClassSpecifier) -> Result<()> {
686 self.pitch_class_value_setter(PitchClass::new(pc)?.number());
687 Ok(())
688 }
689
690 fn pitch_class_value_setter(&mut self, pc: FloatType) {
691 let (step, accidental, _microtone, _harmonic_shift) = convert_ps_to_step(pc);
692 self._step = step;
693 self._accidental = accidental;
694 self.spelling_is_infered = true;
695 }
696
697 fn fundamental_setter(&mut self, f: Pitch) {
698 self.fundamental = Some(Arc::new(f));
699 }
700
701 pub fn fundamental(&self) -> Option<&Pitch> {
703 self.fundamental.as_deref()
704 }
705
706 fn midi_setter(&mut self, m: IntegerType) {
707 self.ps_setter(normalize_midi(m) as FloatType);
708 }
709
710 fn ps_setter(&mut self, p: FloatType) {
711 let (step, accidental, microtone, octave_shift) = convert_ps_to_step(p);
712 self._step = step;
713 self._accidental = accidental;
714 if microtone.alter() == 0.0 {
715 self._microtone = None;
716 } else {
717 self._microtone = Some(microtone);
718 }
719
720 let octave = convert_ps_to_oct(p) + octave_shift;
721 self._octave = Some(octave);
722 self.spelling_is_infered = true;
723 }
724
725 pub fn simplify_enharmonic(&self, most_common: bool) -> Result<Pitch> {
731 let mut pitch = self.clone();
732 pitch.simplify_enharmonic_in_place(most_common)?;
733 Ok(pitch)
734 }
735
736 pub fn simplify_enharmonic_in_place(&mut self, most_common: bool) -> Result<()> {
738 const EXCLUDED_NAMES: [&str; 4] = ["E#", "B#", "C-", "F-"];
739 if self._accidental._alter.abs().partial_cmp(&2.0) != Some(Ordering::Less)
740 || EXCLUDED_NAMES.contains(&self.name().as_str())
741 {
742 let save_octave = self._octave;
744 self.ps_setter(self.ps());
745 if save_octave.is_none() {
746 self.octave_setter(None);
747 }
748 }
749
750 if most_common {
751 match self.name().as_str() {
752 "D#" => {
753 self.step_setter(StepName::E);
754 self.accidental_setter(Accidental::new("flat")?);
755 }
756 "A#" => {
757 self.step_setter(StepName::B);
758 self.accidental_setter(Accidental::new("flat")?);
759 }
760 "G-" => {
761 self.step_setter(StepName::F);
762 self.accidental_setter(Accidental::new("sharp")?);
763 }
764 "D-" => {
765 self.step_setter(StepName::C);
766 self.accidental_setter(Accidental::new("sharp")?);
767 }
768 _ => {}
769 }
770 }
771
772 Ok(())
773 }
774
775 pub fn get_higher_enharmonic(&self) -> Result<Pitch> {
777 self._get_enharmonic_helper(true)
778 }
779
780 pub fn get_higher_enharmonic_in_place(&mut self) -> Result<()> {
782 self._get_enharmonic_helper_in_place(true)
783 }
784
785 pub fn get_lower_enharmonic(&self) -> Result<Pitch> {
787 self._get_enharmonic_helper(false)
788 }
789
790 pub fn get_lower_enharmonic_in_place(&mut self) -> Result<()> {
792 self._get_enharmonic_helper_in_place(false)
793 }
794
795 fn _get_enharmonic_helper(&self, up: bool) -> Result<Pitch> {
796 let interval: &Interval = if up {
797 &DIMINISHED_SECOND_UP
798 } else {
799 &DIMINISHED_SECOND_DOWN
800 };
801
802 let octave_stored = self._octave;
803
804 let mut p = interval.transpose_pitch_with_options(self, false, None)?;
805 if octave_stored.is_none() {
806 p.octave_setter(None);
807 }
808 Ok(p)
809 }
810
811 fn _get_enharmonic_helper_in_place(&mut self, up: bool) -> Result<()> {
812 *self = self._get_enharmonic_helper(up)?;
813 Ok(())
814 }
815
816 pub fn octave(&self) -> Octave {
822 self._octave
823 }
824
825 pub(crate) fn step(&self) -> StepName {
826 self._step
827 }
828
829 pub(crate) fn set_ps(&mut self, p: FloatType) {
830 self.ps_setter(p);
831 }
832}
833
834impl Default for Pitch {
835 fn default() -> Self {
836 Self::from_options(PitchOptions::default())
837 .expect("default Pitch construction should never fail")
838 }
839}
840
841pub(crate) struct PitchParameteres {
842 pub(crate) name: Option<String>,
843 pub(crate) step: Option<StepName>,
844 pub(crate) accidental: Option<Accidental>,
845 pub(crate) spelling_is_inferred: Option<bool>,
846 pub(crate) octave: Octave,
847}
848
849pub(crate) trait IntoPitchName {
850 fn into_name(self) -> PitchParameteres;
851}
852
853impl IntoPitchName for Pitch {
854 fn into_name(self) -> PitchParameteres {
855 self.name_with_octave().into_name()
856 }
857}
858
859impl IntoPitchName for PitchName {
860 fn into_name(self) -> PitchParameteres {
861 match self {
862 PitchName::Name(name) => name.into_name(),
863 PitchName::Number(number) => number.into_name(),
864 }
865 }
866}
867
868impl IntoPitchName for IntegerType {
869 fn into_name(self) -> PitchParameteres {
870 let (step_name, accidental, _, _) = convert_ps_to_step(self);
871
872 let octave = if self >= 12 {
873 Some(self / 12 - 1)
874 } else {
875 None
876 };
877
878 PitchParameteres {
879 name: None,
880 step: Some(step_name),
881 accidental: Some(accidental),
882 spelling_is_inferred: Some(true),
883 octave,
884 }
885 }
886}
887
888impl IntoPitchName for FloatType {
889 fn into_name(self) -> PitchParameteres {
890 let (step_name, accidental, _, _) = convert_ps_to_step(self);
891
892 let octave = if self >= 12.0 {
893 Some((self / 12.0) as IntegerType - 1)
894 } else {
895 None
896 };
897
898 PitchParameteres {
899 name: None,
900 step: Some(step_name),
901 accidental: Some(accidental),
902 spelling_is_inferred: Some(true),
903 octave,
904 }
905 }
906}
907
908impl IntoPitchName for String {
909 fn into_name(self) -> PitchParameteres {
910 PitchParameteres {
911 name: Some(self),
912 step: None,
913 accidental: None,
914 spelling_is_inferred: None,
915 octave: None,
916 }
917 }
918}
919
920impl IntoPitchName for &str {
921 fn into_name(self) -> PitchParameteres {
922 PitchParameteres {
923 name: Some(self.to_string()),
924 step: None,
925 accidental: None,
926 spelling_is_inferred: None,
927 octave: None,
928 }
929 }
930}
931
932fn convert_ps_to_step<T: Num + ToPrimitive>(
933 ps: T,
934) -> (StepName, Accidental, Microtone, IntegerType) {
935 const NATURAL_PCS: [IntegerType; 7] = [0, 2, 4, 5, 7, 9, 11];
936
937 let ps = ps.to_f64().unwrap_or(0.0);
938 let (pc, alter, micro) = if ps.fract() == 0.0 {
939 ((ps as IntegerType).rem_euclid(12), 0.0, 0.0)
940 } else {
941 let ps = round_to_digits(ps, PITCH_SPACE_SIGNIFICANT_DIGITS);
942 let pc_real = ps.rem_euclid(12.0);
943 let pc = pc_real.floor() as IntegerType;
944 let mut micro = pc_real - pc as FloatType;
945
946 let alter = if round_to_digits(micro, 1) == 0.5 || (0.25..0.75).contains(µ) {
947 micro -= 0.5;
948 0.5
949 } else if (0.75..1.0).contains(µ) {
950 micro -= 1.0;
951 1.0
952 } else if micro > 0.0 {
953 0.0
954 } else {
955 micro = 0.0;
956 0.0
957 };
958
959 (pc, alter, micro)
960 };
961
962 let mut octave_shift = 0;
963 let (pc_name, accidental_alter) = if alter == 1.0 && matches!(pc, 4 | 11) {
964 if pc == 11 {
965 octave_shift = 1;
966 }
967 ((pc + 1).rem_euclid(12), 0.0)
968 } else if NATURAL_PCS.contains(&pc) {
969 (pc, alter)
970 } else if [0, 5, 7].contains(&(pc - 1)) && alter >= 1.0 {
971 (pc + 1, alter - 1.0)
972 } else if [0, 5, 7].contains(&(pc - 1)) || ([11, 4].contains(&(pc + 1)) && alter <= -1.0) {
973 (pc - 1, 1.0 + alter)
974 } else if [11, 4].contains(&(pc + 1)) {
975 (pc + 1, -1.0 + alter)
976 } else {
977 panic!("cannot match condition for pitch class: {pc}");
978 };
979
980 let step = StepName::ref_to_step(pc_name.rem_euclid(12))
981 .unwrap_or_else(|err| panic!("pitch class should map to a step: {err}"));
982 let accidental = Accidental::new(accidental_alter)
983 .unwrap_or_else(|err| panic!("accidental conversion should not fail: {err}"));
984 let microtone = Microtone::from_cent_shift(Some(micro * 100.0), None)
985 .unwrap_or_else(|err| panic!("microtone conversion should not fail: {err}"));
986
987 (step, accidental, microtone, octave_shift)
988}
989
990fn round_to_digits(value: FloatType, digits: UnsignedIntegerType) -> FloatType {
991 let factor = (10 as FloatType).powi(digits as IntegerType);
992 (value * factor).round() / factor
993}
994
995fn normalize_midi(midi: IntegerType) -> IntegerType {
996 if midi > 127 {
997 let mut value = (12 * 9) + midi.rem_euclid(12);
998 if value < (127 - 12) {
999 value += 12;
1000 }
1001 value
1002 } else if midi < 0 {
1003 midi.rem_euclid(12)
1004 } else {
1005 midi
1006 }
1007}
1008
1009type CriterionFunction = fn(&[Pitch]) -> Result<FloatType>;
1010
1011pub(crate) fn simplify_multiple_enharmonics(
1012 pitches: &[Pitch],
1013 criterion: Option<CriterionFunction>,
1014 key_context: Option<KeySignature>,
1015) -> Result<Vec<Pitch>> {
1016 let mut old_pitches: Vec<Pitch> = pitches.to_vec();
1017 if old_pitches.is_empty() {
1018 return Ok(Vec::new());
1019 }
1020
1021 let criterion: CriterionFunction = criterion.unwrap_or(default_dissonance_score);
1022
1023 let remove_first: bool = match key_context {
1024 Some(key) => {
1025 old_pitches.insert(0, key.as_key("major").tonic());
1026 true
1027 }
1028 None => false,
1029 };
1030
1031 let mut simplified_pitches = match old_pitches.len() < 5 {
1032 true => brute_force_enharmonics_search(&mut old_pitches, criterion)?,
1033 false => greedy_enharmonics_search(&mut old_pitches, criterion)?,
1034 };
1035
1036 for (new_p, old_p) in simplified_pitches.iter_mut().zip(old_pitches) {
1037 new_p.spelling_is_infered = old_p.spelling_is_infered;
1038 }
1039
1040 if remove_first {
1041 simplified_pitches.remove(0);
1042 }
1043
1044 Ok(simplified_pitches)
1045}
1046
1047fn brute_force_enharmonics_search(
1048 old_pitches: &mut [Pitch],
1049 score_func: CriterionFunction,
1050) -> Result<Vec<Pitch>> {
1051 let all_possible_pitches: Result<Vec<Vec<Pitch>>> = old_pitches[1..]
1052 .iter_mut()
1053 .map(|p| -> Result<Vec<Pitch>> {
1054 let mut enharmonics = p.get_all_common_enharmonics(2 as FloatType)?;
1055 enharmonics.insert(0, p.clone());
1056 Ok(enharmonics)
1057 })
1058 .collect();
1059
1060 let all_pitch_combinations = all_possible_pitches?.into_iter().multi_cartesian_product();
1061
1062 let mut min_score = FloatType::MAX;
1063 let mut best_combination: Vec<Pitch> = Vec::new();
1064
1065 for combination in all_pitch_combinations {
1066 let mut pitches: Vec<Pitch> = old_pitches[..1].to_vec();
1067 pitches.extend(combination);
1068 let score = score_func(&pitches)?;
1069 if score < min_score {
1070 min_score = score;
1071 best_combination = pitches;
1072 }
1073 }
1074
1075 Ok(best_combination)
1076}
1077
1078fn greedy_enharmonics_search(
1079 old_pitches: &mut [Pitch],
1080 score_func: CriterionFunction,
1081) -> Result<Vec<Pitch>> {
1082 let mut new_pitches = vec![];
1083
1084 if let Some(first) = old_pitches.first() {
1085 new_pitches.push(first.clone());
1086 } else {
1087 return Err(Error::Pitch(
1088 "can't perform greedy enharmonics search on empty pitches".into(),
1089 ));
1090 }
1091
1092 for old_pitch in old_pitches.iter_mut().skip(1) {
1093 let mut candidates = vec![old_pitch.clone()];
1094 candidates.extend(old_pitch.get_all_common_enharmonics(2 as FloatType)?);
1095
1096 let mut best_candidate = None;
1097 let mut best_score: Option<OrderedFloat<FloatType>> = None;
1098 for candidate in candidates.iter() {
1099 let mut candidate_list = new_pitches.clone();
1100 candidate_list.push(candidate.clone());
1101 let score = score_func(&candidate_list)?;
1102 let score = OrderedFloat(score);
1103 if best_score.is_none() || score < best_score.unwrap() {
1104 best_score = Some(score);
1105 best_candidate = Some(candidate);
1106 }
1107 }
1108 let best_candidate = best_candidate
1109 .ok_or_else(|| Error::Pitch("candidates list is unexpectedly empty".to_string()))?;
1110 new_pitches.push(best_candidate.clone());
1111 }
1112 Ok(new_pitches)
1113}
1114
1115fn default_dissonance_score(pitches: &[Pitch]) -> Result<FloatType> {
1116 dissonance_score(pitches, true, true, true)
1117}
1118
1119fn dissonance_score(
1120 pitches: &[Pitch],
1121 small_pythagorean_ratio: bool,
1122 accidental_penalty: bool,
1123 triad_award: bool,
1124) -> Result<FloatType> {
1125 let mut score_accidentals: FloatType = 0.0;
1126 let mut score_ratio: FloatType = 0.0;
1127 let mut score_triad: FloatType = 0.0;
1128
1129 if pitches.is_empty() {
1130 return Ok(0.0);
1131 }
1132
1133 if accidental_penalty {
1134 let accidentals = pitches
1135 .iter()
1136 .map(|p| p.alter().abs())
1137 .collect::<Vec<FloatType>>();
1138 score_accidentals = accidentals
1139 .iter()
1140 .map(|a| if *a > 1.0 { *a } else { 0.0 })
1141 .sum::<FloatType>()
1142 / pitches.len() as FloatType;
1143 }
1144
1145 let mut intervals: Vec<Interval> = vec![];
1146
1147 if small_pythagorean_ratio | triad_award {
1148 for (index, p1) in pitches.iter().enumerate() {
1149 for p2 in pitches.iter().skip(index + 1) {
1150 let mut p2 = (*p2).clone();
1151 p2.octave_setter(None);
1152 let Ok(interval) = Interval::between(
1153 PitchOrNote::Pitch(p1.clone()),
1154 PitchOrNote::Pitch(p2.clone()),
1155 ) else {
1156 return Ok(FloatType::INFINITY);
1157 };
1158 intervals.push(interval);
1159 }
1160 }
1161
1162 if small_pythagorean_ratio {
1163 for interval in intervals.iter() {
1164 score_ratio += pythagorean_denominator_log(interval)? * 0.075_853_268_88
1165 }
1166 score_ratio /= pitches.len() as FloatType;
1167 }
1168
1169 if triad_award {
1170 intervals.into_iter().for_each(|interval| {
1171 let simple_directed = interval.generic().simple_directed();
1172 let interval_semitones = interval.chromatic.semitones % 12;
1173 if (simple_directed == 3 && (interval_semitones == 3 || interval_semitones == 4))
1174 || (simple_directed == 6
1175 && (interval_semitones == 8 || interval_semitones == 9))
1176 {
1177 score_triad -= 1.0;
1178 }
1179 });
1180 score_triad /= pitches.len() as FloatType;
1181 }
1182 }
1183
1184 Ok((score_accidentals + score_ratio + score_triad)
1185 / (small_pythagorean_ratio as IntegerType
1186 + accidental_penalty as IntegerType
1187 + triad_award as IntegerType) as FloatType)
1188}
1189
1190fn pythagorean_denominator_log(interval: &Interval) -> Result<FloatType> {
1191 let start_pitch = Pitch::from_name("C1".to_string())?;
1192 let end_pitch = interval.transpose_pitch_with_options(&start_pitch, false, Some(4))?;
1193
1194 let natural_fifths = match end_pitch.step() {
1195 StepName::C => 0,
1196 StepName::D => 2,
1197 StepName::E => 4,
1198 StepName::F => -1,
1199 StepName::G => 1,
1200 StepName::A => 3,
1201 StepName::B => 5,
1202 };
1203 let fifth_count = natural_fifths + (end_pitch.alter().round() as IntegerType * 7);
1204 let found_pitch_space = start_pitch.ps() + (7 * fifth_count) as FloatType;
1205 let octave_adjust = ((end_pitch.ps() - found_pitch_space) / 12.0).round() as IntegerType;
1206
1207 let mut denominator_twos = if fifth_count > 0 { fifth_count } else { 0 };
1208 let denominator_threes = if fifth_count < 0 { -fifth_count } else { 0 };
1209 denominator_twos = (denominator_twos - octave_adjust).max(0);
1210
1211 Ok(denominator_twos as FloatType * (2.0 as FloatType).ln()
1212 + denominator_threes as FloatType * (3.0 as FloatType).ln())
1213}
1214
1215fn convert_harmonic_to_cents(_harmonic_shift: IntegerType) -> IntegerType {
1216 let mut value = _harmonic_shift as FloatType;
1217 if value < 0.0 {
1218 value = 1.0 / value.abs();
1219 }
1220 (1200.0 * value.log2()).round() as IntegerType
1221}
1222
1223#[cfg(test)]
1224mod tests {
1225 use crate::defaults::IntegerType;
1226 use crate::interval::{Interval, IntervalArgument};
1227 use crate::tuningsystem::TuningSystem;
1228
1229 use super::{
1230 Accidental, Microtone, Pitch, convert_harmonic_to_cents, simplify_multiple_enharmonics,
1231 };
1232
1233 #[test]
1234 fn fundamental_round_trips_through_the_builder() {
1235 let pitch = Pitch::builder()
1236 .name("E4")
1237 .fundamental(Pitch::from_name("C2").unwrap())
1238 .build()
1239 .unwrap();
1240 assert_eq!(
1241 pitch.fundamental().map(Pitch::name_with_octave),
1242 Some("C2".to_string())
1243 );
1244 assert_eq!(Pitch::from_name("E4").unwrap().fundamental(), None);
1245 }
1246
1247 #[test]
1248 fn simplify_multiple_enharmonics_test() {
1249 let more_than_five = vec![
1250 Pitch::from_number(0.0).unwrap(),
1251 Pitch::from_number(1.0).unwrap(),
1252 Pitch::from_number(2.0).unwrap(),
1253 Pitch::from_number(3.0).unwrap(),
1254 Pitch::from_number(4.0).unwrap(),
1255 Pitch::from_number(5.0).unwrap(),
1256 Pitch::from_number(12.0).unwrap(),
1257 Pitch::from_number(13.0).unwrap(),
1258 ];
1259
1260 let _x = simplify_multiple_enharmonics(&more_than_five, None, None);
1261 let _less_than_five = [
1262 Pitch::from_number(0.0),
1263 Pitch::from_number(1.0),
1264 Pitch::from_number(2.0),
1265 Pitch::from_number(12.0),
1266 Pitch::from_number(13.0),
1267 ];
1268 }
1269
1270 #[test]
1271 fn test_convert_harmonic_to_cents_values() {
1272 assert_eq!(convert_harmonic_to_cents(8), 3600);
1273 assert_eq!(convert_harmonic_to_cents(5), 2786);
1274 assert_eq!(convert_harmonic_to_cents(-2), -1200);
1275 }
1276
1277 #[test]
1278 fn test_pitch_transpose_interval() {
1279 let c4 = Pitch::from_name("C4".to_string()).unwrap();
1280 let m3 = Interval::new(IntervalArgument::Str("m3".to_string())).unwrap();
1281 let out = c4.transpose(&m3);
1282 assert_eq!(out.name_with_octave(), "E-4");
1283 }
1284
1285 #[test]
1286 fn test_pitch_frequency_helpers() {
1287 let a4 = Pitch::from_name("A4").unwrap();
1288 assert!((a4.frequency_hz() - 440.0).abs() < 0.0001);
1289
1290 let e4 = Pitch::from_name("E4").unwrap();
1291 assert!((e4.frequency_hz_in(TuningSystem::FiveLimit) - 327.032).abs() < 0.001);
1292 assert!(e4.frequency_hz_in(TuningSystem::FiveLimit) < e4.frequency_hz());
1293 }
1294
1295 #[test]
1296 fn pitch_exposes_accidental_object() {
1297 let custom_accidental = Accidental::new("half-flat").unwrap();
1298 let pitch = Pitch::builder()
1299 .step('D')
1300 .accidental(custom_accidental.clone())
1301 .octave(4)
1302 .build()
1303 .unwrap();
1304
1305 assert_eq!(pitch.name_with_octave(), "D`4");
1306 assert_eq!(pitch.accidental(), &custom_accidental);
1307 assert_eq!(pitch.accidental().name(), "half-flat");
1308 assert_eq!(pitch.accidental().alter(), -0.5);
1309 }
1310
1311 #[test]
1312 fn pitch_exposes_microtone_object() {
1313 let microtone = Microtone::new(-25.0).unwrap();
1314 let pitch = Pitch::builder()
1315 .name("G#4")
1316 .microtone(microtone.clone())
1317 .build()
1318 .unwrap();
1319
1320 assert_eq!(pitch.microtone(), Some(µtone));
1321 assert_eq!(pitch.microtone().unwrap().to_string(), "(-25c)");
1322 assert_eq!(pitch.alter(), 0.75);
1323 }
1324
1325 #[test]
1326 fn pitch_supports_rust_conversion_traits() {
1327 let parsed: Pitch = "C#4".parse().unwrap();
1328 assert_eq!(parsed.to_string(), "C#4");
1329
1330 let midi = Pitch::try_from(60 as IntegerType).unwrap();
1331 assert_eq!(midi.name_with_octave(), "C4");
1332 assert_eq!(midi.midi(), 60);
1333
1334 let pitch_space = Pitch::try_from(61.5).unwrap();
1335 assert_eq!(pitch_space.pitch_space(), 61.5);
1336 assert_eq!(pitch_space.midi(), 62);
1337
1338 let built = Pitch::builder().pitch_space(60.0).build().unwrap();
1339 assert_eq!(built.name_with_octave(), "C4");
1340 }
1341
1342 #[test]
1343 fn pitch_exposes_enharmonic_helpers() {
1344 let c_sharp = Pitch::from_name("C#3".to_string()).unwrap();
1345 let out = c_sharp.get_higher_enharmonic().unwrap();
1346 assert_eq!(out.name_with_octave(), "D-3");
1347
1348 let mut d_flat = out;
1349 d_flat.get_lower_enharmonic_in_place().unwrap();
1350 assert_eq!(d_flat.name_with_octave(), "C#3");
1351
1352 let d_sharp = Pitch::from_name("D#4").unwrap();
1353 assert_eq!(
1354 d_sharp
1355 .simplify_enharmonic(true)
1356 .unwrap()
1357 .name_with_octave(),
1358 "E-4"
1359 );
1360 }
1361}