Skip to main content

music21/pitch/
mod.rs

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