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