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