1mod display;
2mod enharmonic;
3mod harmonics;
4mod names;
5
6pub use display::AccidentalDisplayOptions;
7pub use enharmonic::{CriterionFunction, dissonance_score, simplify_multiple_enharmonics};
8pub use names::{CHROMATIC_PITCH_CLASS_NAMES, pitch_class_name};
9
10use harmonics::*;
11
12pub(crate) mod accidental;
13pub(crate) mod microtone;
14pub(crate) mod pitchclass;
15
16use crate::defaults::FloatType;
17use crate::defaults::IntegerType;
18use crate::defaults::Octave;
19use crate::defaults::PITCH_OCTAVE;
20use crate::defaults::PITCH_SPACE_SIGNIFICANT_DIGITS;
21use crate::defaults::PITCH_STEP;
22use crate::defaults::UnsignedIntegerType;
23use crate::error::Error;
24use crate::error::Result;
25use crate::interval::Interval;
26use crate::key::keysignature::KeySignature;
27use crate::stepname::StepName;
28use crate::tuningsystem::TuningSystem;
29
30pub use accidental::{Accidental, AccidentalAttribute, AccidentalSpecifier};
31pub use microtone::{Microtone, MicrotoneSpecifier};
32use pitchclass::convert_ps_to_oct;
33pub use pitchclass::{PitchClass, PitchClassSpecifier, convert_pitch_class_to_str};
34
35use itertools::Itertools;
36use num::Num;
37use num_traits::ToPrimitive;
38use ordered_float::OrderedFloat;
39use std::cmp::Ordering;
40use std::fmt::{Display, Formatter};
41use std::hash::{Hash, Hasher};
42use std::str::FromStr;
43use std::sync::{Arc, LazyLock};
44
45#[derive(Clone, Debug, PartialEq)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47pub enum PitchName {
49 Name(String),
51 Number(FloatType),
53}
54
55impl From<&str> for PitchName {
56 fn from(value: &str) -> Self {
57 Self::Name(value.to_string())
58 }
59}
60
61impl From<String> for PitchName {
62 fn from(value: String) -> Self {
63 Self::Name(value)
64 }
65}
66
67impl From<IntegerType> for PitchName {
68 fn from(value: IntegerType) -> Self {
69 Self::Number(value as FloatType)
70 }
71}
72
73impl From<FloatType> for PitchName {
74 fn from(value: FloatType) -> Self {
75 Self::Number(value)
76 }
77}
78
79#[derive(Clone, Debug, Default, PartialEq)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81pub struct PitchOptions {
83 pub name: Option<PitchName>,
85 pub step: Option<char>,
87 pub octave: Octave,
89 pub accidental: Option<AccidentalSpecifier>,
91 pub microtone: Option<MicrotoneSpecifier>,
93 pub pitch_class: Option<PitchClassSpecifier>,
95 pub midi: Option<IntegerType>,
97 pub ps: Option<FloatType>,
99 pub fundamental: Option<Pitch>,
101}
102
103impl PitchOptions {
104 pub fn new() -> Self {
106 Self::default()
107 }
108
109 pub fn name(mut self, name: impl Into<PitchName>) -> Self {
111 self.name = Some(name.into());
112 self
113 }
114
115 pub fn step(mut self, step: char) -> Self {
117 self.step = Some(step);
118 self
119 }
120
121 pub fn octave(mut self, octave: IntegerType) -> Self {
123 self.octave = Some(octave);
124 self
125 }
126
127 pub fn accidental(mut self, accidental: impl Into<AccidentalSpecifier>) -> Self {
129 self.accidental = Some(accidental.into());
130 self
131 }
132
133 pub fn microtone(mut self, microtone: impl Into<MicrotoneSpecifier>) -> Self {
135 self.microtone = Some(microtone.into());
136 self
137 }
138
139 pub fn pitch_class(mut self, pitch_class: impl Into<PitchClassSpecifier>) -> Self {
141 self.pitch_class = Some(pitch_class.into());
142 self
143 }
144
145 pub fn midi(mut self, midi: IntegerType) -> Self {
147 self.midi = Some(midi);
148 self
149 }
150
151 pub fn ps(mut self, ps: FloatType) -> Self {
153 self.ps = Some(ps);
154 self
155 }
156
157 pub fn pitch_space(mut self, pitch_space: FloatType) -> Self {
159 self.ps = Some(pitch_space);
160 self
161 }
162
163 pub fn fundamental(mut self, fundamental: Pitch) -> Self {
165 self.fundamental = Some(fundamental);
166 self
167 }
168
169 pub fn build(self) -> Result<Pitch> {
171 Pitch::from_options(self)
172 }
173}
174
175#[derive(Clone, Debug)]
176#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
177#[must_use]
179pub struct Pitch {
180 step: StepName,
181 octave: Octave,
182 accidental: Option<Accidental>,
185 microtone: Option<Microtone>,
186 spelling_is_inferred: bool,
187 #[cfg_attr(feature = "serde", serde(skip))]
188 fundamental: Option<Arc<Pitch>>,
189}
190
191impl PartialEq for Pitch {
192 fn eq(&self, other: &Self) -> bool {
193 self.step == other.step
194 && self.octave == other.octave
195 && self.accidental == other.accidental
196 && self.microtone == other.microtone
197 }
198}
199
200impl Eq for Pitch {}
201
202impl Hash for Pitch {
205 fn hash<H: Hasher>(&self, state: &mut H) {
206 self.step.hash(state);
207 self.octave.hash(state);
208 self.accidental.hash(state);
209 self.microtone.hash(state);
210 }
211}
212
213impl FromStr for Pitch {
214 type Err = Error;
215
216 fn from_str(value: &str) -> Result<Self> {
217 Self::from_name(value)
218 }
219}
220
221impl TryFrom<&str> for Pitch {
222 type Error = Error;
223
224 fn try_from(value: &str) -> Result<Self> {
225 Self::from_name(value)
226 }
227}
228
229impl TryFrom<String> for Pitch {
230 type Error = Error;
231
232 fn try_from(value: String) -> Result<Self> {
233 Self::from_name(value)
234 }
235}
236
237impl TryFrom<&Pitch> for Pitch {
238 type Error = Error;
239
240 fn try_from(value: &Pitch) -> Result<Self> {
241 Ok(value.clone())
242 }
243}
244
245impl TryFrom<IntegerType> for Pitch {
246 type Error = Error;
247
248 fn try_from(value: IntegerType) -> Result<Self> {
249 Self::from_midi(value)
250 }
251}
252
253impl TryFrom<FloatType> for Pitch {
254 type Error = Error;
255
256 fn try_from(value: FloatType) -> Result<Self> {
257 Self::from_pitch_space(value)
258 }
259}
260
261impl Display for Pitch {
262 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
263 write!(f, "{}", self.name_with_octave())
264 }
265}
266
267impl Pitch {
268 pub fn from_options(options: PitchOptions) -> Result<Self> {
275 let PitchOptions {
276 name,
277 step,
278 octave,
279 accidental,
280 microtone,
281 pitch_class,
282 midi,
283 ps,
284 fundamental,
285 } = options;
286 let explicit_step = step.map(StepName::try_from).transpose()?;
287 let has_explicit_octave = octave.is_some();
288 let has_explicit_accidental = accidental.is_some();
289
290 if let Some(PitchName::Number(number)) = &name
291 && !number.is_finite()
292 {
293 return Err(Error::Pitch(format!(
294 "a pitch-space number must be finite, got {number}"
295 )));
296 }
297 let parsed = name.map(PitchParameters::from).unwrap_or_default();
298 let name = parsed.name;
299 let step = if name.is_some() || parsed.step.is_some() {
300 parsed.step
301 } else {
302 explicit_step
303 }
304 .unwrap_or(PITCH_STEP);
305 let octave = octave.or(parsed.octave);
306 let accidental = match accidental {
307 Some(accidental) => Some(Accidental::new(accidental)?),
308 None => parsed.accidental,
309 };
310 let microtone = match microtone {
311 Some(microtone) => Some(Microtone::new(microtone)?),
312 None => parsed.microtone,
313 };
314
315 let explicit_accidental = accidental.clone().filter(|_| has_explicit_accidental);
316 let mut pitch = Pitch {
317 step,
318 accidental,
319 microtone,
320 octave,
321 spelling_is_inferred: parsed.spelling_is_inferred,
322 fundamental: None,
323 };
324
325 if let Some(name) = &name {
326 pitch.name_setter(name)?;
327 pitch.spelling_is_inferred = parsed.spelling_is_inferred;
328 }
329 if explicit_step.is_some() || name.is_none() {
330 pitch.step_setter(step);
331 pitch.spelling_is_inferred = parsed.spelling_is_inferred;
332 }
333 if has_explicit_octave || name.is_none() {
334 pitch.octave_setter(octave);
335 }
336 if let Some(accidental) = explicit_accidental {
337 pitch.accidental_setter(accidental);
338 }
339 if let Some(microtone) = pitch.microtone.clone() {
340 pitch.microtone_setter(microtone);
341 }
342 if let Some(pitch_class) = pitch_class {
343 pitch.pitch_class_setter(pitch_class)?;
344 }
345 if let Some(fundamental) = fundamental {
346 pitch.fundamental_setter(fundamental);
347 }
348 if let Some(midi) = midi {
349 pitch.midi_setter(midi);
350 }
351 if let Some(ps) = ps {
352 if !ps.is_finite() {
353 return Err(Error::Pitch(format!(
354 "a pitch-space number must be finite, got {ps}"
355 )));
356 }
357 pitch.ps_setter(ps);
358 }
359
360 Ok(pitch)
361 }
362
363 pub fn builder() -> PitchOptions {
365 PitchOptions::new()
366 }
367
368 pub fn from_name(name: impl AsRef<str>) -> Result<Self> {
370 let mut pitch = Self {
374 step: PITCH_STEP,
375 accidental: None,
376 microtone: None,
377 octave: None,
378 spelling_is_inferred: false,
379 fundamental: None,
380 };
381 pitch.name_setter(name.as_ref())?;
382 pitch.spelling_is_inferred = false;
385 Ok(pitch)
386 }
387
388 pub fn from_number(number: FloatType) -> Result<Self> {
390 PitchOptions::new().name(PitchName::Number(number)).build()
391 }
392
393 pub fn from_step(step: char) -> Result<Self> {
395 PitchOptions::new().step(step).build()
396 }
397
398 pub fn from_name_and_octave(name: impl Into<String>, octave: IntegerType) -> Result<Self> {
400 PitchOptions::new().name(name.into()).octave(octave).build()
401 }
402
403 pub fn from_pitch_class(pitch_class: impl Into<PitchClassSpecifier>) -> Result<Self> {
405 PitchOptions::new().pitch_class(pitch_class).build()
406 }
407
408 pub fn from_midi(midi: IntegerType) -> Result<Self> {
410 PitchOptions::new().midi(midi).build()
411 }
412
413 pub fn from_pitch_space(ps: FloatType) -> Result<Self> {
415 PitchOptions::new().ps(ps).build()
416 }
417
418 pub fn name_with_octave(&self) -> String {
420 let mut name = self.name();
421 if let Some(octave) = self.octave {
422 use std::fmt::Write;
423 let _ = write!(name, "{octave}");
424 }
425 name
426 }
427
428 pub fn name(&self) -> String {
430 let modifier = self.accidental().modifier();
434 let mut name = String::with_capacity(1 + modifier.len());
435 name.push(self.step.as_char());
436 name.push_str(modifier);
437 name
438 }
439
440 pub(crate) fn is_named(&self, name: &str) -> bool {
443 let mut letters = name.chars();
444 letters.next() == Some(self.step.as_char())
445 && Accidental::from_standard(letters.as_str())
446 .is_some_and(|accidental| accidental == *self.accidental())
447 }
448
449 fn name_setter(&mut self, usr_str: &str) -> Result<()> {
450 let usr_str = usr_str.trim();
451
452 let digits_at = usr_str.find(|character: char| character.is_ascii_digit());
457 let written_plainly = digits_at
458 .is_none_or(|at| at > 0 && usr_str[at..].bytes().all(|byte| byte.is_ascii_digit()));
459 if written_plainly {
460 let (name_part, octave_part) = usr_str.split_at(digits_at.unwrap_or(usr_str.len()));
461 let mut characters = name_part.chars();
462 let step = characters.next().ok_or(Error::Pitch(format!(
463 "Cannot make a name out of {name_part:?}"
464 )))?;
465 self.step_setter(StepName::try_from(step)?);
466 let accidental_str = characters.as_str();
467 if accidental_str.is_empty() {
468 self.accidental = None;
469 } else if let Some(accidental) = Accidental::from_standard(accidental_str) {
470 self.accidental_setter(accidental);
471 } else {
472 self.accidental_setter(Accidental::new(accidental_str.to_string())?);
475 }
476 if !octave_part.is_empty() {
477 let octave = octave_part
478 .parse::<IntegerType>()
479 .map_err(|_| Error::Pitch(format!("Cannot parse {octave_part:?} to octave")))?;
480 self.octave_setter(Some(octave));
481 }
482 return Ok(());
483 }
484
485 let mut pitch_part = String::with_capacity(usr_str.len());
486 let mut octave_part = String::new();
487 for character in usr_str.chars() {
488 if character.is_ascii_digit() {
489 if pitch_part.is_empty() {
490 return Err(Error::Value(format!(
491 "Cannot have octave given before pitch name in '{usr_str}'."
492 )));
493 }
494 octave_part.push(character);
495 } else {
496 pitch_part.push(character);
497 }
498 }
499
500 let mut pitch_chars = pitch_part.chars();
501 let step = pitch_chars.next().ok_or(Error::Pitch(format!(
502 "Cannot make a name out of {pitch_part:?}"
503 )))?;
504 self.step_setter(StepName::try_from(step)?);
505
506 let accidental_str: String = pitch_chars.collect();
507 if accidental_str.is_empty() {
508 self.accidental = None;
509 } else {
510 self.accidental_setter(Accidental::new(accidental_str)?);
511 }
512
513 if !octave_part.is_empty() {
514 let octave = octave_part
515 .parse::<IntegerType>()
516 .map_err(|_| Error::Pitch(format!("Cannot parse {octave_part:?} to octave")))?;
517 self.octave_setter(Some(octave));
518 }
519
520 Ok(())
521 }
522
523 pub fn alter(&self) -> FloatType {
525 let mut post = 0.0;
526
527 post += self
528 .accidental
529 .as_ref()
530 .map_or(0.0, |accidental| accidental.alter);
531
532 if let Some(microtone) = &self.microtone {
533 post += microtone.alter();
534 }
535
536 post
537 }
538
539 pub fn accidental(&self) -> &Accidental {
543 static NATURAL: LazyLock<Accidental> = LazyLock::new(Accidental::natural);
544 self.accidental.as_ref().unwrap_or(&NATURAL)
545 }
546
547 pub fn written_accidental(&self) -> Option<&Accidental> {
551 self.accidental.as_ref()
552 }
553
554 pub fn written_accidental_mut(&mut self) -> Option<&mut Accidental> {
556 self.accidental.as_mut()
557 }
558
559 pub fn microtone(&self) -> Option<&Microtone> {
561 self.microtone.as_ref()
562 }
563
564 pub fn pitch_class(&self) -> PitchClass {
566 PitchClass::from_number(self.ps()).unwrap_or_else(|err| {
567 panic!("pitch-space value should always map to pitch class: {err}")
568 })
569 }
570
571 pub fn set_octave(&mut self, octave: Octave) {
574 self.octave = octave;
575 }
576
577 pub(crate) fn octave_setter(&mut self, octave: Octave) {
578 self.set_octave(octave);
579 }
580
581 pub(crate) fn take_name_of(&mut self, other: &Pitch) -> Result<()> {
585 self.step_setter(other.step);
586 self.accidental = match other.written_accidental() {
587 Some(accidental) if !accidental.modifier().is_empty() => {
588 Some(Accidental::new(accidental.modifier())?)
589 }
590 _ => None,
591 };
592 Ok(())
593 }
594
595 pub fn transpose(&self, interval: &Interval) -> Result<Pitch> {
600 let mut p = interval.transpose_pitch_with_options(self, false, Some(4))?;
601
602 if !interval.implicit_diatonic {
603 p.spelling_is_inferred = self.spelling_is_inferred;
604 }
605 if p.spelling_is_inferred {
606 p.simplify_enharmonic_in_place(true)?;
607 }
608 if let Some(fundamental) = &self.fundamental {
609 p.fundamental_setter(fundamental.transpose(interval)?);
610 }
611
612 Ok(p)
613 }
614
615 pub fn ps(&self) -> FloatType {
617 self.pitch_space()
618 }
619
620 pub fn pitch_space(&self) -> FloatType {
622 let octave = self.octave.unwrap_or(PITCH_OCTAVE as IntegerType);
623 ((octave + 1) * 12) as FloatType + self.step.step_ref() as FloatType + self.alter()
624 }
625
626 pub fn midi(&self) -> IntegerType {
631 normalize_midi((self.pitch_space() + 0.5).floor() as IntegerType)
632 }
633
634 pub fn frequency_hz(&self) -> FloatType {
636 let twelfth_root_of_two = (2.0 as FloatType).powf(1.0 / 12.0);
639 crate::tuningsystem::A4 * twelfth_root_of_two.powf(self.pitch_space() - 69.0)
640 }
641
642 pub fn frequency_hz_tuned(
666 &self,
667 tuning: &impl crate::tuningsystem::Tuning,
668 reference: &crate::tuningsystem::Reference,
669 ) -> Result<FloatType> {
670 let (steps, semitones, cents) = self.spelled_place();
671 let (reference_steps, reference_semitones, reference_cents) =
672 reference.pitch().spelled_place();
673 let interval = crate::tuningsystem::Monzo::pythagorean(
674 steps - reference_steps,
675 semitones - reference_semitones,
676 );
677 let cents = tuning.cents_of(&interval)? + cents - reference_cents;
678 Ok(reference.hertz() * (2.0 as FloatType).powf(cents / 1200.0))
679 }
680
681 fn spelled_place(&self) -> (IntegerType, IntegerType, FloatType) {
684 let accidental = self.accidental.as_ref().map_or(0.0, Accidental::alter);
685 let whole = accidental.trunc();
686 let natural = self.pitch_space() - self.alter();
687 let microtone = self.microtone.as_ref().map_or(0.0, Microtone::cents);
688 (
689 self.diatonic_note_number(),
690 (natural + whole).round() as IntegerType,
691 (accidental - whole) * 100.0 + microtone,
692 )
693 }
694
695 pub fn frequency_hz_in(&self, tuning_system: TuningSystem) -> FloatType {
702 if tuning_system.is_keyboard() {
703 return tuning_system.frequency_at(self.pitch_space());
704 }
705 let degree = tuning_system
706 .nearest_degree(self.frequency_hz())
707 .expect("an equal-tempered frequency is finite and above nought");
708 tuning_system.frequency_at(degree.into())
709 }
710
711 pub fn from_frequency_in(hertz: FloatType, tuning_system: TuningSystem) -> Result<Self> {
726 tuning_system.pitch_at(tuning_system.nearest_degree(hertz)?)
727 }
728
729 fn step_setter(&mut self, step_name: StepName) {
730 self.step = step_name;
731 self.spelling_is_inferred = false;
732 }
733
734 pub(crate) fn set_diatonic_note_number(&mut self, dnn: IntegerType) -> Result<()> {
737 let (step, octave) = crate::interval::convert_diatonic_number_to_step(dnn);
738 self.step_setter(StepName::try_from(step)?);
739 self.octave_setter(Some(octave));
740 Ok(())
741 }
742
743 pub fn set_accidental(&mut self, accidental: Accidental) {
745 self.accidental_setter(accidental);
746 }
747
748 pub fn set_written_accidental(&mut self, accidental: Option<Accidental>) {
752 self.accidental = accidental;
753 }
754
755 pub fn set_accidental_alter(&mut self, alter: FloatType) -> Result<()> {
760 let (alter_shift, cents) = cents_to_alter_and_cents(alter * 100.0);
761 self.accidental_setter(Accidental::new(alter_shift)?);
762 if cents.abs() > 0.01 {
763 self.microtone_setter(Microtone::new(cents)?);
764 }
765 Ok(())
766 }
767
768 pub fn spelling_is_inferred(&self) -> bool {
773 self.spelling_is_inferred
774 }
775
776 pub fn set_spelling_is_inferred(&mut self, inferred: bool) {
778 self.spelling_is_inferred = inferred;
779 }
780
781 fn accidental_setter(&mut self, value: Accidental) {
782 self.accidental = Some(value);
783 }
784
785 pub fn set_microtone_cents(&mut self, cents: FloatType) -> Result<()> {
788 if cents == 0.0 {
789 self.microtone = None;
790 } else {
791 self.microtone = Some(Microtone::new(cents)?);
792 }
793 Ok(())
794 }
795
796 fn microtone_setter(&mut self, mt: Microtone) {
797 self.microtone = Some(mt);
798 }
799
800 fn pitch_class_setter(&mut self, pc: PitchClassSpecifier) -> Result<()> {
801 self.pitch_class_value_setter(PitchClass::new(pc)?.number());
802 Ok(())
803 }
804
805 fn pitch_class_value_setter(&mut self, pc: FloatType) {
806 let (step, accidental, _, _) = convert_ps_to_step(pc);
807 self.step = step;
808 self.accidental = (accidental.alter() != 0.0).then_some(accidental);
809 self.spelling_is_inferred = true;
810 }
811
812 pub(crate) fn fundamental_setter(&mut self, f: Pitch) {
813 self.fundamental = Some(Arc::new(f));
814 }
815
816 pub fn fundamental(&self) -> Option<&Pitch> {
818 self.fundamental.as_deref()
819 }
820
821 fn midi_setter(&mut self, m: IntegerType) {
822 self.ps_setter(normalize_midi(m) as FloatType);
823 }
824
825 fn ps_setter(&mut self, p: FloatType) {
826 let (step, accidental, microtone, octave_shift) = convert_ps_to_step(p);
827 self.step = step;
828 self.accidental = (accidental.alter() != 0.0).then_some(accidental);
829 if microtone.alter() == 0.0 {
830 self.microtone = None;
831 } else {
832 self.microtone = Some(microtone);
833 }
834
835 let octave = convert_ps_to_oct(p) + octave_shift;
836 self.octave = Some(octave);
837 self.spelling_is_inferred = true;
838 }
839
840 pub fn implicit_octave(&self) -> IntegerType {
847 self.octave.unwrap_or(PITCH_OCTAVE as IntegerType)
848 }
849
850 #[must_use]
856 pub fn octave_is_implicit(&self) -> bool {
857 self.octave.is_none()
858 }
859
860 pub fn set_octave_is_implicit(&mut self, implicit: bool) {
867 if implicit == self.octave.is_none() {
868 return;
869 }
870 self.octave = if implicit {
871 None
872 } else {
873 Some(PITCH_OCTAVE as IntegerType)
874 };
875 }
876
877 pub fn pitch_class_string(&self) -> String {
883 crate::pitch::pitchclass::convert_pitch_class_to_str(
884 self.ps().round_ties_even() as IntegerType
885 )
886 }
887
888 pub fn cent_shift_from_midi(&self) -> IntegerType {
892 let mut distance = self.ps() - FloatType::from(self.midi());
893 while distance < -11.0 {
894 distance += 12.0;
895 }
896 while distance > 11.0 {
897 distance -= 12.0;
898 }
899 (distance * 100.0).round() as IntegerType
900 }
901
902 pub fn from_frequency(hertz: FloatType) -> Result<Self> {
905 if !hertz.is_finite() || hertz <= 0.0 {
906 return Err(Error::Pitch(format!(
907 "frequency must be a finite number greater than zero, got {hertz}"
908 )));
909 }
910 let mut pitch = Pitch::default();
911 pitch.ps_setter(12.0 * (hertz / crate::tuningsystem::A4).log2() + 69.0);
912 Ok(pitch)
913 }
914
915 pub fn diatonic_note_number(&self) -> IntegerType {
918 let octave = self.octave.unwrap_or(PITCH_OCTAVE as IntegerType);
919 self.step.step_to_dnn_offset() + 7 * octave
920 }
921
922 pub(crate) fn step_index(&self) -> IntegerType {
924 self.step as IntegerType - 1
925 }
926
927 pub fn is_twelve_tone(&self) -> bool {
930 self.accidental().is_twelve_tone()
931 && self
932 .microtone
933 .as_ref()
934 .is_none_or(|microtone| microtone.cents() == 0.0)
935 }
936
937 fn octave_bearing_copy(&self, operation: &str) -> Result<Pitch> {
938 if self.octave.is_none() {
939 return Err(Error::Pitch(format!(
940 "Cannot call {operation} with an octaveless Pitch."
941 )));
942 }
943 Ok(self.clone())
944 }
945
946 fn shift_octave(&mut self, octaves: IntegerType) {
947 let octave = self.octave.unwrap_or(PITCH_OCTAVE as IntegerType);
948 self.octave_setter(Some(octave + octaves));
949 }
950
951 pub fn octave(&self) -> Octave {
957 self.octave
958 }
959
960 pub(crate) fn step(&self) -> StepName {
961 self.step
962 }
963
964 pub(crate) fn set_ps(&mut self, p: FloatType) {
965 self.ps_setter(p);
966 }
967}
968
969impl Default for Pitch {
970 fn default() -> Self {
971 Self::from_options(PitchOptions::default())
972 .expect("default Pitch construction should never fail")
973 }
974}
975
976#[derive(Default)]
977struct PitchParameters {
978 name: Option<String>,
979 step: Option<StepName>,
980 accidental: Option<Accidental>,
981 microtone: Option<Microtone>,
982 spelling_is_inferred: bool,
983 octave: Octave,
984}
985
986impl From<PitchName> for PitchParameters {
987 fn from(value: PitchName) -> Self {
988 match value {
989 PitchName::Name(name) => Self {
990 name: Some(name),
991 ..Self::default()
992 },
993 PitchName::Number(number) => {
994 let (step, accidental, microtone, octave_shift) = convert_ps_to_step(number);
995 let octave = (number >= 12.0).then(|| convert_ps_to_oct(number) + octave_shift);
996 Self {
997 name: None,
998 step: Some(step),
999 accidental: Some(accidental),
1000 microtone: (microtone.cents() != 0.0).then_some(microtone),
1001 spelling_is_inferred: true,
1002 octave,
1003 }
1004 }
1005 }
1006 }
1007}
1008
1009fn convert_ps_to_step<T: Num + ToPrimitive>(
1010 ps: T,
1011) -> (StepName, Accidental, Microtone, IntegerType) {
1012 let ps = ps.to_f64().unwrap_or(0.0);
1013 let (pc, alter, micro) = if ps.fract() == 0.0 {
1014 ((ps as IntegerType).rem_euclid(12), 0.0, 0.0)
1015 } else {
1016 let ps = round_to_digits(ps, PITCH_SPACE_SIGNIFICANT_DIGITS);
1017 let pc_real = ps.rem_euclid(12.0);
1018 let pc = pc_real.floor() as IntegerType;
1019 let mut micro = pc_real - pc as FloatType;
1020
1021 let alter = if round_to_digits(micro, 1) == 0.5 || (0.25 < micro && micro < 0.75) {
1025 micro -= 0.5;
1026 0.5
1027 } else if (0.75..1.0).contains(µ) {
1028 micro -= 1.0;
1029 1.0
1030 } else if micro > 0.0 {
1031 0.0
1032 } else {
1033 micro = 0.0;
1034 0.0
1035 };
1036
1037 (pc, alter, micro)
1038 };
1039
1040 let octave_shift = IntegerType::from(pc == 11 && alter == 1.0);
1041 let (pc_name, accidental_alter) = match pc {
1042 4 | 11 if alter == 1.0 => ((pc + 1).rem_euclid(12), 0.0),
1043 1 | 6 | 8 if alter >= 1.0 => (pc + 1, alter - 1.0),
1044 1 | 6 | 8 => (pc - 1, 1.0 + alter),
1045 3 | 10 if alter <= -1.0 => (pc - 1, 1.0 + alter),
1046 3 | 10 => (pc + 1, -1.0 + alter),
1047 _ => (pc, alter),
1048 };
1049
1050 let step = StepName::ref_to_step(pc_name.rem_euclid(12))
1051 .unwrap_or_else(|err| panic!("pitch class should map to a step: {err}"));
1052 let accidental = Accidental::new(accidental_alter)
1053 .unwrap_or_else(|err| panic!("accidental conversion should not fail: {err}"));
1054 let microtone = Microtone::from_cents(micro * 100.0, 1);
1055
1056 (step, accidental, microtone, octave_shift)
1057}
1058
1059fn round_to_digits(value: FloatType, digits: UnsignedIntegerType) -> FloatType {
1060 let factor = (10 as FloatType).powi(digits as IntegerType);
1061 (value * factor).round() / factor
1062}
1063
1064fn normalize_midi(midi: IntegerType) -> IntegerType {
1065 if midi > 127 {
1066 let mut value = (12 * 9) + midi.rem_euclid(12);
1067 if value < (127 - 12) {
1068 value += 12;
1069 }
1070 value
1071 } else if midi < 0 {
1072 midi.rem_euclid(12)
1073 } else {
1074 midi
1075 }
1076}
1077
1078#[cfg(test)]
1079mod tests {
1080 #[test]
1084 fn a_pitch_space_number_that_is_not_finite_is_refused() {
1085 use crate::pitch::Pitch;
1086
1087 for number in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
1088 assert!(Pitch::from_pitch_space(number).is_err());
1089 assert!(Pitch::from_number(number).is_err());
1090 assert!(Pitch::builder().ps(number).build().is_err());
1091 }
1092 assert_eq!(Pitch::from_pitch_space(61.0).unwrap().name(), "C#");
1093 }
1094
1095 #[test]
1097 fn equal_pitches_are_one_set_entry() {
1098 use crate::pitch::Pitch;
1099 use std::collections::HashSet;
1100
1101 let pitches: HashSet<Pitch> = ["B-4", "B-4", "A#4", "Bb3"]
1102 .into_iter()
1103 .map(|name| Pitch::from_name(name).unwrap())
1104 .collect();
1105 assert_eq!(pitches.len(), 3);
1106 }
1107
1108 #[test]
1110 fn a_pitch_is_named_either_flat_spelling() {
1111 use crate::pitch::Pitch;
1112
1113 let a_flat = Pitch::from_name("Ab2").unwrap();
1114 assert!(a_flat.is_named("Ab"));
1115 assert!(a_flat.is_named("A-"));
1116 assert!(!a_flat.is_named("A"));
1117 }
1118
1119 #[test]
1121 fn every_pitch_has_an_accidental() {
1122 use crate::pitch::Pitch;
1123
1124 let bare = Pitch::from_name("D").unwrap();
1125 let natural = Pitch::from_name("Dn").unwrap();
1126 assert_eq!(bare.accidental().name(), "natural");
1127 assert_eq!(natural.accidental().name(), "natural");
1128 assert!(bare.written_accidental().is_none());
1129 assert!(natural.written_accidental().is_some());
1130 }
1131
1132 #[test]
1133 fn a_frequency_that_is_not_finite_is_refused() {
1134 use crate::pitch::Pitch;
1135
1136 assert!(Pitch::from_frequency(f64::INFINITY).is_err());
1137 assert!(Pitch::from_frequency(f64::NAN).is_err());
1138 assert!(Pitch::from_frequency(0.0).is_err());
1139 assert_eq!(
1140 Pitch::from_frequency(440.0).unwrap().name_with_octave(),
1141 "A4"
1142 );
1143 }
1144
1145 #[test]
1146 fn a_pitch_is_built_from_a_step_a_number_or_an_owned_name() {
1147 use crate::pitch::{Pitch, PitchName, pitch_class_name};
1148
1149 assert_eq!(Pitch::from_step('d').unwrap().name(), "D");
1150 assert!(Pitch::from_step('h').is_err());
1151 assert_eq!(
1152 Pitch::try_from("Eb4".to_string())
1153 .unwrap()
1154 .name_with_octave(),
1155 "Eb4"
1156 );
1157 assert!(matches!(PitchName::from(60), PitchName::Number(n) if n == 60.0));
1158 assert!(matches!(PitchName::from(61.5), PitchName::Number(n) if n == 61.5));
1159 assert_eq!(pitch_class_name(1), "Db");
1160 assert_eq!(pitch_class_name(13), "Db");
1161 assert_eq!(pitch_class_name(10), "Bb");
1162 }
1163
1164 #[test]
1165 fn spelling_is_inferred_for_a_pitch_built_from_a_number_and_can_be_unsaid() {
1166 use crate::pitch::Pitch;
1167
1168 let mut pitch = Pitch::from_midi(61).unwrap();
1169 assert!(pitch.spelling_is_inferred());
1170 pitch.set_spelling_is_inferred(false);
1171 assert!(!pitch.spelling_is_inferred());
1172 assert!(!Pitch::from_name("C#").unwrap().spelling_is_inferred());
1173
1174 let mut raised = Pitch::from_name("C#4").unwrap();
1175 raised.get_higher_enharmonic_in_place().unwrap();
1176 assert_eq!(raised.name_with_octave(), "Db4");
1177 }
1178
1179 #[test]
1180 fn a_pitch_knows_whether_a_key_signature_already_writes_it() {
1181 use crate::pitch::Pitch;
1182
1183 let altered = [
1184 Pitch::from_name("F#").unwrap(),
1185 Pitch::from_name("C#").unwrap(),
1186 ];
1187 assert!(
1188 Pitch::from_name("F#4")
1189 .unwrap()
1190 .name_in_key_signature(&altered)
1191 );
1192 assert!(
1193 !Pitch::from_name("F4")
1194 .unwrap()
1195 .name_in_key_signature(&altered)
1196 );
1197 assert!(
1198 !Pitch::from_name("Fb4")
1199 .unwrap()
1200 .name_in_key_signature(&altered)
1201 );
1202 assert!(
1203 Pitch::from_name("F4")
1204 .unwrap()
1205 .step_in_key_signature(&altered)
1206 );
1207 assert!(
1208 !Pitch::from_name("G4")
1209 .unwrap()
1210 .step_in_key_signature(&altered)
1211 );
1212 }
1213
1214 #[test]
1219 fn accidental_display_follows_the_pitches_before_it() {
1220 use crate::pitch::{AccidentalDisplayOptions, Pitch};
1221
1222 let first = Pitch::from_name("F#4").unwrap();
1223 let mut repeat = Pitch::from_name("F#4").unwrap();
1224 let past = [first.clone()];
1225 repeat.update_accidental_display(&AccidentalDisplayOptions {
1226 pitch_past: &past,
1227 ..AccidentalDisplayOptions::default()
1228 });
1229 assert_eq!(repeat.accidental().display_status(), Some(false));
1230
1231 let mut natural = Pitch::from_name("F4").unwrap();
1232 natural.update_accidental_display(&AccidentalDisplayOptions {
1233 pitch_past: &past,
1234 ..AccidentalDisplayOptions::default()
1235 });
1236 assert!(natural.written_accidental().is_some());
1237 assert_eq!(natural.accidental().display_status(), Some(true));
1238
1239 let mut in_key = Pitch::from_name("F#4").unwrap();
1240 in_key.update_accidental_display(&AccidentalDisplayOptions {
1241 altered_pitches: &[Pitch::from_name("F#").unwrap()],
1242 ..AccidentalDisplayOptions::default()
1243 });
1244 assert_eq!(in_key.accidental().display_status(), Some(false));
1245
1246 let mut fresh = Pitch::from_name("B-4").unwrap();
1247 fresh.update_accidental_display(&AccidentalDisplayOptions::default());
1248 assert_eq!(fresh.accidental().display_status(), Some(true));
1249 }
1250
1251 #[test]
1254 fn an_octave_is_implicit_until_one_is_given() {
1255 use crate::pitch::Pitch;
1256
1257 let mut anywhere = Pitch::from_name("G#").unwrap();
1258 assert!(anywhere.octave_is_implicit());
1259 assert_eq!(anywhere.octave(), None);
1260 assert_eq!(anywhere.implicit_octave(), 4);
1261
1262 let somewhere = Pitch::from_name("E-6").unwrap();
1263 assert!(!somewhere.octave_is_implicit());
1264 assert_eq!(somewhere.octave(), Some(6));
1265 assert_eq!(somewhere.implicit_octave(), 6);
1266
1267 anywhere.set_octave_is_implicit(false);
1270 assert!(!anywhere.octave_is_implicit());
1271 assert_eq!(anywhere.octave(), Some(4));
1272 anywhere.set_octave_is_implicit(true);
1273 assert_eq!(anywhere.octave(), None);
1274
1275 let mut high = Pitch::from_name("C7").unwrap();
1277 high.set_octave_is_implicit(false);
1278 assert_eq!(high.octave(), Some(7));
1279 }
1280
1281 #[test]
1282 fn a_pitch_is_respelled_to_agree_with_the_key_signature() {
1283 use crate::key::KeySignature;
1284 let f_sharp = Pitch::from_name("F#4").unwrap();
1287 assert_eq!(
1288 f_sharp.respelled_for(&KeySignature::new(2)).unwrap().name(),
1289 "F#"
1290 );
1291 assert_eq!(
1292 f_sharp
1293 .respelled_for(&KeySignature::new(-5))
1294 .unwrap()
1295 .name(),
1296 "Gb"
1297 );
1298 assert_eq!(
1301 Pitch::from_name("C4")
1302 .unwrap()
1303 .respelled_for(&KeySignature::new(-5))
1304 .unwrap()
1305 .name(),
1306 "C"
1307 );
1308 }
1309
1310 #[test]
1311 fn an_octave_digit_may_sit_anywhere_after_the_step() {
1312 for (name, expected) in [
1313 ("e4-", "Eb4"),
1314 ("e-4", "Eb4"),
1315 ("b3-", "Bb3"),
1316 ("g4--", "Gbb4"),
1317 ("c#4", "C#4"),
1318 ("f##2", "F##2"),
1319 ("d1", "D1"),
1320 ] {
1321 assert_eq!(
1322 Pitch::from_name(name).unwrap().name_with_octave(),
1323 expected,
1324 "parsing {name:?}"
1325 );
1326 }
1327 assert_eq!(
1328 Pitch::from_name("c4#2").unwrap().name_with_octave(),
1329 "C#42",
1330 "the digits concatenate wherever they fall, as music21's do"
1331 );
1332 assert!(Pitch::from_name("4c").is_err());
1333 }
1334
1335 #[test]
1336 fn options_keep_an_explicit_accidental_and_a_numeric_name_stays_inferred() {
1337 let sharp = crate::PitchOptions::new()
1338 .name("C")
1339 .accidental("#")
1340 .octave(7)
1341 .microtone(-30)
1342 .build()
1343 .unwrap();
1344 assert_eq!(sharp.full_name(), "C-sharp in octave 7 (-30c)");
1345 let double_flat = crate::PitchOptions::new()
1346 .name("D")
1347 .accidental(Accidental::new("double-flat").unwrap())
1348 .build()
1349 .unwrap();
1350 assert_eq!(double_flat.name(), "Dbb");
1351 let inferred = Pitch::from_number(6.0).unwrap();
1352 assert_eq!(
1353 inferred
1354 .transpose(&Interval::from_name("-m2").unwrap())
1355 .unwrap()
1356 .name(),
1357 "F"
1358 );
1359 }
1360
1361 #[test]
1362 fn midi_folds_into_range_like_music21() {
1363 let high = Pitch::from_name("C#10").unwrap();
1364 assert_eq!(high.midi(), 121);
1365 assert_eq!(high.cent_shift_from_midi(), 0);
1366 let half_flat = crate::PitchOptions::new().name("F`10").build().unwrap();
1367 assert_eq!(half_flat.midi(), 125);
1368 assert_eq!(
1369 Pitch::from_name("C#-2")
1370 .unwrap_or_else(|_| Pitch::from_pitch_space(-11.0).unwrap())
1371 .midi(),
1372 1
1373 );
1374 assert_eq!(Pitch::from_name("C~4").unwrap().midi(), 61);
1375 assert_eq!(Pitch::from_name("A4").unwrap().frequency_hz(), 440.0);
1376 }
1377
1378 #[test]
1379 fn harmonics_chain_and_transpose_with_their_fundamental() {
1380 let a4 = Pitch::from_name("A4").unwrap();
1381 let seventh = a4.harmonic(7).unwrap();
1382 assert_eq!(seventh.name_with_octave(), "F#~7");
1383 assert_eq!(seventh.microtone().unwrap().cents().round(), 19.0);
1384 let doubled = seventh.harmonic(2).unwrap();
1385 assert_eq!(doubled.name_with_octave(), "F#~8");
1386 assert_eq!(doubled.microtone().unwrap().cents().round(), 19.0);
1387 assert_eq!(doubled.fundamental().unwrap().name_with_octave(), "F#~7");
1388
1389 let second = a4.harmonic(2).unwrap();
1390 assert_eq!(second.fundamental().unwrap().name_with_octave(), "A4");
1391 let up_a_fifth = second
1392 .transpose(&Interval::from_name("p5").unwrap())
1393 .unwrap();
1394 assert_eq!(up_a_fifth.name_with_octave(), "E6");
1395 assert_eq!(up_a_fifth.fundamental().unwrap().name_with_octave(), "E5");
1396 }
1397
1398 #[test]
1399 fn numbers_and_chromatic_transposition_keep_microtones() {
1400 let sharp_twenty = Pitch::from_number(60.2).unwrap();
1401 assert_eq!(sharp_twenty.name_with_octave(), "C4");
1402 assert_eq!(sharp_twenty.microtone().unwrap().cents().round(), 20.0);
1403 let harmonic = Pitch::from_name("A4")
1404 .unwrap()
1405 .harmonic(7)
1406 .unwrap()
1407 .harmonic(2)
1408 .unwrap();
1409 let down_two_octaves = harmonic
1410 .transpose(&Interval::from_semitones(-24).unwrap())
1411 .unwrap();
1412 assert_eq!(down_two_octaves.name_with_octave(), "F#~6");
1413 assert_eq!(down_two_octaves.microtone().unwrap().cents().round(), 19.0);
1414 }
1415
1416 #[test]
1417 fn transposition_reaches_octave_minus_one() {
1418 let low = Pitch::from_name("D2")
1419 .unwrap()
1420 .transpose(&Interval::from_name("m-23").unwrap())
1421 .unwrap();
1422 assert_eq!(low.name_with_octave(), "C#-1");
1423 assert_eq!(low.ps(), 1.0);
1424 }
1425
1426 #[test]
1427 fn simplify_multiple_enharmonics_matches_music21() {
1428 let names = |names: &[&str]| -> Vec<Pitch> {
1429 names
1430 .iter()
1431 .map(|n| Pitch::from_name(*n).unwrap())
1432 .collect()
1433 };
1434 let spelled = |pitches: Vec<Pitch>| -> Vec<String> {
1435 pitches.iter().map(Pitch::name_with_octave).collect()
1436 };
1437 let cases: [(&[&str], &[&str]); 11] = [
1438 (&["C#", "Db", "E"], &["C#", "C#", "E"]),
1439 (&["A#4", "C#5", "E#5"], &["A#4", "C#5", "E#5"]),
1440 (&["F#", "A#", "C#"], &["F#", "A#", "C#"]),
1441 (&["Gb", "Bb", "Db"], &["Gb", "Bb", "Db"]),
1442 (&["C", "Eb", "Gb", "B--"], &["C", "Eb", "F#", "A"]),
1443 (&["B#", "D##", "F##"], &["B#", "E", "G"]),
1444 (&["C", "E", "G#", "B"], &["C", "E", "G#", "B"]),
1445 (&["E#", "G##", "B#"], &["E#", "G##", "B#"]),
1446 (
1447 &["C#", "E#", "G#", "B", "D#"],
1448 &["C#", "E#", "G#", "B", "D#"],
1449 ),
1450 (
1451 &["Db", "F", "Ab", "Cb", "Eb", "Gb"],
1452 &["Db", "F", "Ab", "Cb", "Eb", "Gb"],
1453 ),
1454 (&["C4", "Eb4", "F#4"], &["C4", "Eb4", "Gb4"]),
1455 ];
1456 for (input, expected) in cases {
1457 let simplified = simplify_multiple_enharmonics(&names(input), None, None).unwrap();
1458 assert_eq!(spelled(simplified), expected, "{input:?}");
1459 }
1460 let three_flats = crate::KeySignature::new(-3);
1461 assert_eq!(
1462 spelled(
1463 simplify_multiple_enharmonics(&names(&["C#", "Db", "E"]), None, Some(three_flats))
1464 .unwrap()
1465 ),
1466 ["Db", "Db", "Fb"]
1467 );
1468 let six_sharps = crate::KeySignature::new(6);
1469 assert_eq!(
1470 spelled(
1471 simplify_multiple_enharmonics(&names(&["Gb", "B-", "Db"]), None, Some(six_sharps))
1472 .unwrap()
1473 ),
1474 ["F#", "A#", "C#"]
1475 );
1476 assert!(
1477 simplify_multiple_enharmonics(&[], None, None)
1478 .unwrap()
1479 .is_empty()
1480 );
1481 assert_eq!(super::dissonance_score(&[]).unwrap(), 0.0);
1482 assert!(
1483 super::dissonance_score(&names(&["C", "E", "G"])).unwrap()
1484 < super::dissonance_score(&names(&["C", "F-", "G"])).unwrap()
1485 );
1486 }
1487
1488 #[test]
1489 fn frequency_rounds_as_music21_rounds() {
1490 let cases = [
1491 ("F#6", 1_479.977_690_846_539),
1492 ("D3", 146.832_383_958_703_64),
1493 ("F1", 43.653_528_929_125_41),
1494 ("C4", 261.625_565_300_598_5),
1495 ("A~4", 452.892_984_123_136_5),
1496 ("B-7", 3_729.310_092_144_725),
1497 ];
1498 for (name, hertz) in cases {
1499 let pitch: Pitch = name.parse().unwrap();
1500 assert_eq!(pitch.frequency_hz(), hertz, "{name}");
1501 }
1502 }
1503
1504 #[test]
1505 fn dissonance_rounds_as_music21_rounds() {
1506 let score = |spelled: &[&str]| {
1509 let pitches: Vec<Pitch> = spelled.iter().map(|name| name.parse().unwrap()).collect();
1510 super::dissonance_score(&pitches).unwrap()
1511 };
1512 let flat = score(&["Eb", "Gb", "A"]);
1513 let sharp = score(&["Eb", "F#", "A"]);
1514 assert_eq!(flat, 0.051_031_336_396_830_44);
1515 assert_eq!(sharp, 0.051_031_336_396_830_475);
1516
1517 let chord = crate::Chord::new([3, 6, 9].as_slice()).unwrap();
1519 assert_eq!(chord.pitch_names(), ["Eb", "Gb", "A"]);
1520
1521 assert_eq!(score(&["D#6", "B#5", "E#5", "D5"]), 0.235_696_022_824_957_3);
1523 assert_eq!(score(&["D#6", "C6", "F5", "D5"]), 0.235_696_022_824_957_36);
1524 let respelled = simplify_multiple_enharmonics(
1525 &["D#6", "C6", "F5", "C##5"].map(|name| name.parse().unwrap()),
1526 None,
1527 None,
1528 )
1529 .unwrap();
1530 let respelled: Vec<String> = respelled.iter().map(Pitch::name_with_octave).collect();
1531 assert_eq!(respelled, ["D#6", "B#5", "E#5", "D5"]);
1532 }
1533
1534 #[test]
1535 fn get_enharmonic_picks_the_direction_music21_does() {
1536 let cases = [
1537 ("C#4", "Db4"),
1538 ("Db4", "C#4"),
1539 ("C4", "B#3"),
1540 ("D4", "C##4"),
1541 ("G4", "F##4"),
1542 ("E4", "Fb4"),
1543 ("F4", "Gbb4"),
1544 ("B4", "Cb5"),
1545 ("A-4", "G#4"),
1546 ("F##4", "G4"),
1547 ("C--4", "Bb3"),
1548 ("B#4", "C5"),
1549 ("G##4", "A4"),
1550 ];
1551 for (name, expected) in cases {
1552 let pitch = Pitch::from_name(name).unwrap();
1553 assert_eq!(
1554 pitch.get_enharmonic().unwrap().name_with_octave(),
1555 expected,
1556 "{name}"
1557 );
1558 }
1559 }
1560
1561 #[test]
1562 fn cent_shift_from_midi_matches_music21() {
1563 let cases: [(&str, FloatType, IntegerType, i32, &str); 8] = [
1564 ("C4", 0.0, 60, 0, "0"),
1565 ("C4", 20.0, 60, 20, "0"),
1566 ("C4", -20.0, 60, -20, "0"),
1567 ("C4", 60.0, 61, -40, "1"),
1568 ("C~4", 0.0, 61, -50, "0"),
1569 ("C`4", 0.0, 60, -50, "0"),
1570 ("C4", -60.0, 59, 40, "B"),
1571 ("C4", 130.0, 61, 30, "1"),
1572 ];
1573 for (name, cents, midi, shift, pitch_class) in cases {
1574 let pitch = crate::PitchOptions::new()
1575 .name(name)
1576 .microtone(cents)
1577 .build()
1578 .unwrap();
1579 assert_eq!(pitch.midi(), midi, "{name} {cents}");
1580 assert_eq!(pitch.cent_shift_from_midi(), shift, "{name} {cents}");
1581 assert_eq!(pitch.pitch_class_string(), pitch_class, "{name} {cents}");
1582 assert_eq!(pitch.implicit_octave(), 4);
1583 }
1584 assert_eq!(Pitch::from_name("G").unwrap().implicit_octave(), 4);
1585 assert_eq!(Pitch::from_name("G2").unwrap().implicit_octave(), 2);
1586 }
1587
1588 #[test]
1589 fn quarter_tones_and_microtones_convert_both_ways() {
1590 let build = |name: &str, cents: FloatType| {
1591 crate::PitchOptions::new()
1592 .name(name)
1593 .microtone(cents)
1594 .build()
1595 .unwrap()
1596 };
1597 let describe = |pitch: &Pitch| {
1598 (
1599 pitch.name_with_octave(),
1600 pitch.microtone().map_or(0.0, Microtone::cents),
1601 pitch.accidental().name().to_string(),
1602 )
1603 };
1604
1605 let to_microtones: [(&str, FloatType, &str, FloatType, &str); 6] = [
1606 ("C~4", 0.0, "C4", 50.0, "natural"),
1607 ("C`4", 0.0, "C4", -50.0, "natural"),
1608 ("D#~4", 0.0, "D#4", 50.0, "sharp"),
1609 ("E-`4", 0.0, "Eb4", -50.0, "flat"),
1610 ("C~4", 10.0, "C4", 60.0, "natural"),
1611 ("C#4", 0.0, "C#4", 0.0, "sharp"),
1612 ];
1613 for (name, cents, expected_name, expected_cents, accidental) in to_microtones {
1614 let converted = build(name, cents)
1615 .convert_quarter_tones_to_microtones()
1616 .unwrap();
1617 assert_eq!(
1618 describe(&converted),
1619 (
1620 expected_name.to_string(),
1621 expected_cents,
1622 accidental.to_string()
1623 ),
1624 "{name} {cents}"
1625 );
1626 }
1627
1628 let to_quarter_tones: [(&str, FloatType, &str, FloatType, &str); 11] = [
1629 ("C4", 50.0, "C~4", 0.0, "half-sharp"),
1630 ("C4", -50.0, "C`4", 0.0, "half-flat"),
1631 ("C4", 30.0, "C~4", -20.0, "half-sharp"),
1632 ("C4", -30.0, "C`4", 20.0, "half-flat"),
1633 ("C4", 70.0, "C~4", 20.0, "half-sharp"),
1634 ("C#4", 50.0, "C#~4", 0.0, "one-and-a-half-sharp"),
1635 ("C4", 150.0, "C#4", 50.0, "sharp"),
1636 ("C4", -150.0, "Cb4", -50.0, "flat"),
1637 ("C4", 120.0, "C#4", 20.0, "sharp"),
1638 ("Cb4", -50.0, "Cb`4", 0.0, "one-and-a-half-flat"),
1639 ("C4", 0.0, "C4", 0.0, "natural"),
1640 ];
1641 for (name, cents, expected_name, expected_cents, accidental) in to_quarter_tones {
1642 let converted = build(name, cents)
1643 .convert_microtones_to_quarter_tones()
1644 .unwrap();
1645 assert_eq!(
1646 describe(&converted),
1647 (
1648 expected_name.to_string(),
1649 expected_cents,
1650 accidental.to_string()
1651 ),
1652 "{name} {cents}"
1653 );
1654 }
1655 }
1656
1657 #[test]
1658 fn harmonic_and_fundamental_match_music21() {
1659 let cases = [
1660 ("E5", "C2", 10, 14.0, "10thH/C2(+14c)"),
1661 ("G4", "C2", 6, -2.0, "6thH/C2(-2c)"),
1662 ("B-4", "C2", 7, 31.0, "7thH/C2(+31c)"),
1663 ("F#5", "D3", 5, 14.0, "5thH/D3(+14c)"),
1664 ("C5", "C4", 2, 0.0, "2ndH/C4"),
1665 ];
1666 for (name, fundamental, number, cents, text) in cases {
1667 let pitch = Pitch::from_name(name).unwrap();
1668 let fundamental = Pitch::from_name(fundamental).unwrap();
1669 let (harmonic, retuned) = pitch
1670 .harmonic_and_fundamental_from_pitch(&fundamental)
1671 .unwrap();
1672 assert_eq!(harmonic, number, "{name}");
1673 let retuned_cents = retuned.microtone().map_or(0.0, Microtone::cents);
1674 assert!(
1675 (retuned_cents - cents).abs() < 1e-6,
1676 "{name}: {retuned_cents}"
1677 );
1678 assert_eq!(
1679 pitch
1680 .harmonic_and_fundamental_string_from_pitch(&fundamental)
1681 .unwrap(),
1682 text,
1683 "{name}"
1684 );
1685 }
1686 let c4 = Pitch::from_name("C4").unwrap();
1687 assert!(c4.harmonic_and_fundamental_from_pitch(&c4).is_err());
1688 }
1689
1690 #[test]
1691 fn full_name_matches_music21() {
1692 let cases = [
1693 ("C4", "C in octave 4"),
1694 ("E-4", "E-flat in octave 4"),
1695 ("F#", "F-sharp"),
1696 ("B--3", "B-double-flat in octave 3"),
1697 ("G##5", "G-double-sharp in octave 5"),
1698 ("A~4", "A-half-sharp in octave 4"),
1699 ("C`4", "C-half-flat in octave 4"),
1700 ("D#~4", "D-one-and-a-half-sharp in octave 4"),
1701 ];
1702 for (name, expected) in cases {
1703 assert_eq!(
1704 Pitch::from_name(name).unwrap().full_name(),
1705 expected,
1706 "{name}"
1707 );
1708 }
1709 let sharp = crate::PitchOptions::new()
1710 .name("C4")
1711 .microtone(20)
1712 .build()
1713 .unwrap();
1714 assert_eq!(sharp.full_name(), "C in octave 4 (+20c)");
1715 let flat = crate::PitchOptions::new()
1716 .name("E-4")
1717 .microtone(-33)
1718 .build()
1719 .unwrap();
1720 assert_eq!(flat.full_name(), "E-flat in octave 4 (-33c)");
1721 let hair = crate::PitchOptions::new()
1722 .name("E-4")
1723 .microtone(-0.2)
1724 .build()
1725 .unwrap();
1726 assert_eq!(hair.full_name(), "E-flat in octave 4 (-0c)");
1727 }
1728 use crate::defaults::{FloatType, IntegerType};
1729 use crate::interval::Interval;
1730 use crate::tuningsystem::TuningSystem;
1731
1732 use super::{
1733 Accidental, Microtone, Pitch, convert_harmonic_to_cents, simplify_multiple_enharmonics,
1734 };
1735
1736 #[test]
1737 fn harmonics_match_music21() {
1738 let cases = [
1739 ("C2", 1, "C2", None, 65.406),
1740 ("C2", 2, "C3", None, 130.813),
1741 ("C2", 3, "G3", Some("(+2c)"), 196.224),
1742 ("C2", 4, "C4", None, 261.626),
1743 ("C2", 5, "E4", Some("(-14c)"), 326.973),
1744 ("C2", 6, "G4", Some("(+2c)"), 392.449),
1745 ("C2", 7, "A~4", Some("(+19c)"), 457.891),
1746 ("C2", 8, "C5", None, 523.251),
1747 ("C2", 9, "D5", Some("(+4c)"), 588.688),
1748 ("C2", 11, "F~5", Some("(+1c)"), 719.338),
1749 ("C2", 13, "G#~5", Some("(-9c)"), 850.515),
1750 ("A2", 3, "E4", Some("(+2c)"), 330.009),
1751 ("A2", 5, "C#5", Some("(-14c)"), 549.9),
1752 ("C", 3, "G5", Some("(+2c)"), 784.897),
1753 ("Eb3", 7, "C~6", Some("(+19c)"), 1089.054),
1754 ];
1755 for (fundamental, number, name, microtone, hertz) in cases {
1756 let harmonic = Pitch::from_name(fundamental)
1757 .unwrap()
1758 .harmonic(number)
1759 .unwrap();
1760 assert_eq!(harmonic.name_with_octave(), name, "{fundamental} {number}");
1761 assert_eq!(
1762 harmonic.microtone().map(ToString::to_string).as_deref(),
1763 microtone,
1764 "{fundamental} {number}"
1765 );
1766 assert!(
1767 (harmonic.frequency_hz() - hertz).abs() < 5e-4,
1768 "{fundamental} {number}: {}",
1769 harmonic.frequency_hz()
1770 );
1771 assert_eq!(
1772 harmonic
1773 .fundamental()
1774 .map(Pitch::name_with_octave)
1775 .as_deref(),
1776 (number > 1).then_some(fundamental),
1777 "{fundamental} {number}"
1778 );
1779 }
1780 }
1781
1782 #[test]
1783 fn harmonic_from_fundamental_matches_music21() {
1784 let cases = [
1785 ("G4", "C2", 6, 2.0, "6thH(-2c)/C2"),
1786 ("E5", "C2", 10, -14.0, "10thH(+14c)/C2"),
1787 ("B-4", "C2", 7, -31.0, "7thH(+31c)/C2"),
1788 ("F#5", "C2", 11, -49.0, "11thH(+49c)/C2"),
1789 ("C4", "C2", 4, 0.0, "4thH/C2"),
1790 ("E4", "A2", 3, 2.0, "3rdH(-2c)/A2"),
1791 ("G#4", "A2", 4, 100.0, "4thH(-100c)/A2"),
1792 ("A4", "A2", 4, 0.0, "4thH/A2"),
1793 ("B3", "C2", 4, 100.0, "4thH(-100c)/C2"),
1794 ("D5", "C2", 9, 4.0, "9thH(-4c)/C2"),
1795 ];
1796 for (target, fundamental, number, cents, text) in cases {
1797 let target_pitch = Pitch::from_name(target).unwrap();
1798 let fundamental_pitch = Pitch::from_name(fundamental).unwrap();
1799 let (found, gap) = target_pitch
1800 .harmonic_from_fundamental(&fundamental_pitch)
1801 .unwrap();
1802 assert_eq!(found, number, "{target} over {fundamental}");
1803 assert!(
1804 (gap - cents).abs() < 1e-6,
1805 "{target} over {fundamental}: {gap}"
1806 );
1807 assert_eq!(
1808 target_pitch
1809 .harmonic_string(Some(&fundamental_pitch))
1810 .unwrap(),
1811 text
1812 );
1813 }
1814 let c2 = Pitch::from_name("C2").unwrap();
1815 assert!(
1816 c2.harmonic_from_fundamental(&Pitch::from_name("C4").unwrap())
1817 .is_err()
1818 );
1819 assert!(c2.harmonic_string(None).is_err());
1820 assert_eq!(
1821 c2.harmonic(5).unwrap().harmonic_string(None).unwrap(),
1822 "5thH/C2"
1823 );
1824 }
1825
1826 #[test]
1827 fn enharmonic_helpers_match_music21() {
1828 let names = |pitches: Vec<Pitch>| {
1829 pitches
1830 .iter()
1831 .map(Pitch::name_with_octave)
1832 .collect::<Vec<_>>()
1833 };
1834 let cases = [
1835 (
1836 "C#4",
1837 vec!["Db4", "B##3"],
1838 vec!["Db4"],
1839 vec!["Db4", "Ebbb4", "B##3"],
1840 ),
1841 (
1842 "E-",
1843 vec!["Fbb", "D#"],
1844 vec!["D#"],
1845 vec!["Fbb", "D#", "C###"],
1846 ),
1847 ("B#3", vec!["C4"], vec!["C4"], vec!["C4", "A###3"]),
1848 ("F##4", vec!["G4"], vec!["G4"], vec!["G4", "E###4"]),
1849 (
1850 "C4",
1851 vec!["Dbb4", "B#3"],
1852 vec!["B#3"],
1853 vec!["Dbb4", "B#3", "A###3"],
1854 ),
1855 (
1856 "G-2",
1857 vec!["F#2", "E##2"],
1858 vec!["F#2"],
1859 vec!["Abbb2", "F#2", "E##2"],
1860 ),
1861 (
1862 "A4",
1863 vec!["Bbb4", "G##4"],
1864 vec![],
1865 vec!["Bbb4", "Cbbb5", "G##4"],
1866 ),
1867 (
1868 "B-4",
1869 vec!["Cbb5", "A#4"],
1870 vec!["A#4"],
1871 vec!["Cbb5", "A#4", "G###4"],
1872 ),
1873 ("E#5", vec!["F5"], vec!["F5"], vec!["F5", "D###5"]),
1874 ("Dbb4", vec!["C4"], vec!["C4"], vec!["C4"]),
1875 ];
1876 for (name, limit_two, limit_one, limit_three) in cases {
1877 let pitch = Pitch::from_name(name).unwrap();
1878 assert_eq!(names(pitch.all_common_enharmonics(2)), limit_two, "{name}");
1879 assert_eq!(names(pitch.all_common_enharmonics(1)), limit_one, "{name}");
1880 assert_eq!(
1881 names(pitch.all_common_enharmonics(3)),
1882 limit_three,
1883 "{name}"
1884 );
1885 }
1886
1887 let enharmonic = |left: &str, right: &str| {
1888 Pitch::from_name(left)
1889 .unwrap()
1890 .is_enharmonic(&Pitch::from_name(right).unwrap())
1891 };
1892 assert!(enharmonic("C#4", "D-4"));
1893 assert!(!enharmonic("C#4", "D-5"));
1894 assert!(enharmonic("C#", "D-5"));
1895 assert!(!enharmonic("C#4", "C4"));
1896 assert!(enharmonic("B#3", "C4"));
1897 assert!(enharmonic("B#", "C"));
1898 assert!(enharmonic("C", "C5"));
1899 }
1900
1901 #[test]
1902 fn transposing_to_a_target_matches_music21() {
1903 let g3 = Pitch::from_name("G3").unwrap();
1904 let below = [
1905 ("C4", false, "C3"),
1906 ("C4", true, "C3"),
1907 ("C6", false, "C3"),
1908 ("C6", true, "C3"),
1909 ("G3", false, "G3"),
1910 ("C2", false, "C2"),
1911 ("C2", true, "C3"),
1912 ("F4", true, "F3"),
1913 ("G4", true, "G3"),
1914 ];
1915 for (source, minimize, expected) in below {
1916 let moved = Pitch::from_name(source)
1917 .unwrap()
1918 .transpose_below_target(&g3, minimize)
1919 .unwrap();
1920 assert_eq!(
1921 moved.name_with_octave(),
1922 expected,
1923 "{source} below {minimize}"
1924 );
1925 }
1926 let above = [
1927 ("C4", false, "C4"),
1928 ("C1", false, "C4"),
1929 ("C1", true, "C4"),
1930 ("C6", false, "C6"),
1931 ("C6", true, "C4"),
1932 ("G3", false, "G3"),
1933 ("F#3", true, "F#4"),
1934 ];
1935 for (source, minimize, expected) in above {
1936 let moved = Pitch::from_name(source)
1937 .unwrap()
1938 .transpose_above_target(&g3, minimize)
1939 .unwrap();
1940 assert_eq!(
1941 moved.name_with_octave(),
1942 expected,
1943 "{source} above {minimize}"
1944 );
1945 }
1946 assert!(
1947 Pitch::from_name("C")
1948 .unwrap()
1949 .transpose_below_target(&g3, false)
1950 .is_err()
1951 );
1952 }
1953
1954 #[test]
1955 fn twelve_tone_and_frequency_construction() {
1956 assert!(Pitch::from_name("C4").unwrap().is_twelve_tone());
1957 assert!(Pitch::from_name("C#4").unwrap().is_twelve_tone());
1958 assert!(!Pitch::from_name("C~4").unwrap().is_twelve_tone());
1959 assert!(!Pitch::from_name("C`4").unwrap().is_twelve_tone());
1960 let detuned = Pitch::builder().name("A4").microtone(25).build().unwrap();
1961 assert!(!detuned.is_twelve_tone());
1962 assert_eq!(detuned.ps(), 69.25);
1963
1964 let g4 =
1965 Pitch::from_frequency(440.0 * (2.0 as FloatType).powf((67.02 - 69.0) / 12.0)).unwrap();
1966 assert_eq!(g4.name_with_octave(), "G4");
1967 assert_eq!(g4.microtone().unwrap().to_string(), "(+2c)");
1968 assert!((g4.ps() - 67.02).abs() < 1e-6);
1969
1970 let low = Pitch::from_frequency(100.0).unwrap();
1971 assert_eq!(low.name_with_octave(), "G~2");
1972 assert_eq!(low.microtone().unwrap().to_string(), "(-15c)");
1973 assert!(Pitch::from_frequency(0.0).is_err());
1974 assert_eq!(Pitch::from_name("C4").unwrap().diatonic_note_number(), 29);
1975 assert_eq!(Pitch::from_name("B#3").unwrap().diatonic_note_number(), 28);
1976 }
1977
1978 #[test]
1979 fn language_names_match_music21() {
1980 let cases = [
1981 ("C", "C", "do", "do", "do", "C"),
1982 ("C#", "Cis", "do diesis", "do dièse", "do sostenido", "C♯"),
1983 ("D-", "Des", "re bemolle", "ré bémol", "re bemol", "D♭"),
1984 ("B", "H", "si", "si", "si", "B"),
1985 ("B-", "B", "si bemolle", "si bémol", "si bemol", "B♭"),
1986 ("B#", "His", "si diesis", "si dièse", "si sostenido", "B♯"),
1987 ("E-", "Es", "mi bemolle", "mi bémol", "mi bemol", "E♭"),
1988 ("A-", "As", "la bemolle", "la bémol", "la bemol", "A♭"),
1989 (
1990 "F##",
1991 "Fisis",
1992 "fa doppio diesis",
1993 "fa double dièse",
1994 "fa doble sostenido",
1995 "F𝄪",
1996 ),
1997 (
1998 "G--",
1999 "Geses",
2000 "sol doppio bemolle",
2001 "sol double bémol",
2002 "sol doble bemol",
2003 "G𝄫",
2004 ),
2005 (
2006 "B--",
2007 "Heses",
2008 "si doppio bemolle",
2009 "si double bémol",
2010 "si doble bemol",
2011 "B𝄫",
2012 ),
2013 ("F-", "Fes", "fa bemolle", "fa bémol", "fa bemol", "F♭"),
2014 ("E#", "Eis", "mi diesis", "mi dièse", "mi sostenido", "E♯"),
2015 (
2016 "D##",
2017 "Disis",
2018 "re doppio diesis",
2019 "ré double dièse",
2020 "re doble sostenido",
2021 "D𝄪",
2022 ),
2023 (
2024 "A####",
2025 "Aisisisis",
2026 "la quadruplo diesis",
2027 "la quadruple dièse",
2028 "la cuádruple sostenido",
2029 "A𝄪𝄪",
2030 ),
2031 (
2032 "B----",
2033 "Heseseses",
2034 "si quadruplo bemolle",
2035 "si quadruple bémol",
2036 "si cuádruple bemol",
2037 "B𝄫𝄫",
2038 ),
2039 ];
2040 for (name, german, italian, french, spanish, unicode) in cases {
2041 let pitch = Pitch::from_name(name).unwrap();
2042 assert_eq!(pitch.german().unwrap(), german, "{name}");
2043 assert_eq!(pitch.italian().unwrap(), italian, "{name}");
2044 assert_eq!(pitch.french().unwrap(), french, "{name}");
2045 assert_eq!(pitch.spanish().unwrap(), spanish, "{name}");
2046 assert_eq!(pitch.unicode_name(), unicode, "{name}");
2047 }
2048 assert_eq!(
2049 Pitch::from_name("A#4").unwrap().unicode_name_with_octave(),
2050 "A♯4"
2051 );
2052 assert_eq!(Pitch::from_name("E-5").unwrap().german().unwrap(), "Es");
2053 let quarter_sharp = Pitch::from_name("C~").unwrap();
2054 assert!(quarter_sharp.german().is_err());
2055 assert!(quarter_sharp.italian().is_err());
2056 assert!(quarter_sharp.french().is_err());
2057 assert!(quarter_sharp.spanish().is_err());
2058 }
2059
2060 #[test]
2061 fn fundamental_round_trips_through_the_builder() {
2062 let pitch = Pitch::builder()
2063 .name("E4")
2064 .fundamental(Pitch::from_name("C2").unwrap())
2065 .build()
2066 .unwrap();
2067 assert_eq!(
2068 pitch.fundamental().map(Pitch::name_with_octave),
2069 Some("C2".to_string())
2070 );
2071 assert_eq!(Pitch::from_name("E4").unwrap().fundamental(), None);
2072 }
2073
2074 #[test]
2075 fn simplify_multiple_enharmonics_test() {
2076 let more_than_five = vec![
2077 Pitch::from_number(0.0).unwrap(),
2078 Pitch::from_number(1.0).unwrap(),
2079 Pitch::from_number(2.0).unwrap(),
2080 Pitch::from_number(3.0).unwrap(),
2081 Pitch::from_number(4.0).unwrap(),
2082 Pitch::from_number(5.0).unwrap(),
2083 Pitch::from_number(12.0).unwrap(),
2084 Pitch::from_number(13.0).unwrap(),
2085 ];
2086
2087 let _x = simplify_multiple_enharmonics(&more_than_five, None, None);
2088 let _less_than_five = [
2089 Pitch::from_number(0.0),
2090 Pitch::from_number(1.0),
2091 Pitch::from_number(2.0),
2092 Pitch::from_number(12.0),
2093 Pitch::from_number(13.0),
2094 ];
2095 }
2096
2097 #[test]
2098 fn test_convert_harmonic_to_cents_values() {
2099 assert_eq!(convert_harmonic_to_cents(8), 3600);
2100 assert_eq!(convert_harmonic_to_cents(5), 2786);
2101 assert_eq!(convert_harmonic_to_cents(-2), -1200);
2102 }
2103
2104 #[test]
2105 fn test_pitch_transpose_interval() {
2106 let c4 = Pitch::from_name("C4").unwrap();
2107 let m3 = Interval::from_name("m3").unwrap();
2108 let out = c4.transpose(&m3).unwrap();
2109 assert_eq!(out.name_with_octave(), "Eb4");
2110 }
2111
2112 #[test]
2113 fn test_pitch_frequency_helpers() {
2114 let a4 = Pitch::from_name("A4").unwrap();
2115 assert!((a4.frequency_hz() - 440.0).abs() < 0.0001);
2116
2117 let e4 = Pitch::from_name("E4").unwrap();
2118 assert!((e4.frequency_hz_in(TuningSystem::FiveLimit) - 327.032).abs() < 0.001);
2119 assert!(e4.frequency_hz_in(TuningSystem::FiveLimit) < e4.frequency_hz());
2120 }
2121
2122 #[test]
2123 fn pitch_exposes_accidental_object() {
2124 let custom_accidental = Accidental::new("half-flat").unwrap();
2125 let pitch = Pitch::builder()
2126 .step('D')
2127 .accidental(custom_accidental.clone())
2128 .octave(4)
2129 .build()
2130 .unwrap();
2131
2132 assert_eq!(pitch.name_with_octave(), "D`4");
2133 assert_eq!(pitch.accidental(), &custom_accidental);
2134 assert_eq!(pitch.accidental().name(), "half-flat");
2135 assert_eq!(pitch.accidental().alter(), -0.5);
2136 }
2137
2138 #[test]
2139 fn pitch_exposes_microtone_object() {
2140 let microtone = Microtone::new(-25.0).unwrap();
2141 let pitch = Pitch::builder()
2142 .name("G#4")
2143 .microtone(microtone.clone())
2144 .build()
2145 .unwrap();
2146
2147 assert_eq!(pitch.microtone(), Some(µtone));
2148 assert_eq!(pitch.microtone().unwrap().to_string(), "(-25c)");
2149 assert_eq!(pitch.alter(), 0.75);
2150 }
2151
2152 #[test]
2153 fn pitch_supports_rust_conversion_traits() {
2154 let parsed: Pitch = "C#4".parse().unwrap();
2155 assert_eq!(parsed.to_string(), "C#4");
2156
2157 let midi = Pitch::try_from(60 as IntegerType).unwrap();
2158 assert_eq!(midi.name_with_octave(), "C4");
2159 assert_eq!(midi.midi(), 60);
2160
2161 let pitch_space = Pitch::try_from(61.5).unwrap();
2162 assert_eq!(pitch_space.pitch_space(), 61.5);
2163 assert_eq!(pitch_space.midi(), 62);
2164
2165 let built = Pitch::builder().pitch_space(60.0).build().unwrap();
2166 assert_eq!(built.name_with_octave(), "C4");
2167 }
2168
2169 #[test]
2170 fn pitch_exposes_enharmonic_helpers() {
2171 let c_sharp = Pitch::from_name("C#3").unwrap();
2172 let out = c_sharp.get_higher_enharmonic().unwrap();
2173 assert_eq!(out.name_with_octave(), "Db3");
2174
2175 let mut d_flat = out;
2176 d_flat.get_lower_enharmonic_in_place().unwrap();
2177 assert_eq!(d_flat.name_with_octave(), "C#3");
2178
2179 let d_sharp = Pitch::from_name("D#4").unwrap();
2180 assert_eq!(
2181 d_sharp
2182 .simplify_enharmonic(true)
2183 .unwrap()
2184 .name_with_octave(),
2185 "Eb4"
2186 );
2187 }
2188}