music21_rs/pitch/
harmonics.rs1use super::*;
5
6impl Pitch {
7 pub fn convert_quarter_tones_to_microtones(&self) -> Result<Pitch> {
12 let (alter, shift) = match self.accidental.name() {
13 "half-flat" => (0.0, -50.0),
14 "half-sharp" => (0.0, 50.0),
15 "one-and-a-half-sharp" => (1.0, 50.0),
16 "one-and-a-half-flat" => (-1.0, -50.0),
17 _ => return Ok(self.clone()),
18 };
19 let cents = self.microtone.as_ref().map_or(0.0, Microtone::cents);
20 let mut pitch = self.clone();
21 pitch.accidental_setter(Accidental::new(alter)?);
22 pitch.microtone_setter(Microtone::new(cents + shift)?);
23 Ok(pitch)
24 }
25
26 pub fn convert_microtones_to_quarter_tones(&self) -> Result<Pitch> {
31 let cents = self.microtone.as_ref().map_or(0.0, Microtone::cents);
32 let (shift, remainder) = cents_to_alter_and_cents(cents);
33 let mut pitch = self.clone();
34 pitch.accidental_setter(Accidental::new(self.accidental.alter() + shift)?);
35 pitch.microtone_setter(Microtone::new(remainder)?);
36 Ok(pitch)
37 }
38
39 pub fn harmonic_and_fundamental_from_pitch(&self, fundamental: &Pitch) -> Result<(u32, Pitch)> {
44 let (number, cents) = self.harmonic_from_fundamental(fundamental)?;
45 let cents = -cents;
46 let mut retuned = fundamental.clone();
47 match retuned.microtone.as_ref().map(Microtone::cents) {
48 Some(existing) => retuned.microtone_setter(Microtone::new(existing + cents)?),
49 None if cents != 0.0 => retuned.microtone_setter(Microtone::new(cents)?),
50 None => {}
51 }
52 Ok((number, retuned))
53 }
54
55 pub fn harmonic_and_fundamental_string_from_pitch(
58 &self,
59 fundamental: &Pitch,
60 ) -> Result<String> {
61 let (number, retuned) = self.harmonic_and_fundamental_from_pitch(fundamental)?;
62 let suffix = crate::pitch::microtone::ordinal_suffix(number as IntegerType);
63 Ok(format!(
64 "{number}{suffix}H/{}",
65 retuned.name_with_octave_and_microtone()
66 ))
67 }
68
69 pub fn harmonic(&self, number: u32) -> Result<Pitch> {
74 let cent_shift = convert_harmonic_to_cents(number as IntegerType);
75 if cent_shift == 0 {
76 return Ok(self.clone());
77 }
78 let mut shifted = self.clone();
79 let existing = self.microtone.as_ref().map_or(0.0, Microtone::cents);
80 shifted.microtone_setter(Microtone::new(existing + cent_shift as FloatType)?);
81 let mut harmonic = Pitch::from_frequency(shifted.frequency_hz())?;
82 harmonic.fundamental_setter(self.clone());
83 Ok(harmonic)
84 }
85
86 pub fn harmonic_from_fundamental(&self, fundamental: &Pitch) -> Result<(u32, FloatType)> {
90 if self.ps() <= fundamental.ps() {
91 return Err(Error::Pitch(format!(
92 "cannot find an equivalent harmonic for a fundamental ({fundamental}) that is not above this Pitch ({self})"
93 )));
94 }
95 let mut found = Vec::new();
96 for number in 1..32 {
97 let candidate = fundamental.harmonic(number)?;
98 let above = candidate.ps() > self.ps();
99 found.push((number, candidate));
100 if above {
101 break;
102 }
103 }
104 let (number, gap) = match found.as_slice() {
105 [(number, only)] => (*number, only.ps() - self.ps()),
106 [.., (lower_number, lower), (higher_number, higher)] => {
107 let below = self.ps() - lower.ps();
108 let above = higher.ps() - self.ps();
109 if below <= above {
110 (*lower_number, -below.abs())
111 } else {
112 (*higher_number, above.abs())
113 }
114 }
115 [] => unreachable!("the first harmonic is always collected"),
116 };
117 Ok((
118 number,
119 round_to_digits(gap, PITCH_SPACE_SIGNIFICANT_DIGITS) * 100.0,
120 ))
121 }
122
123 pub fn harmonic_string(&self, fundamental: Option<&Pitch>) -> Result<String> {
126 let fundamental = fundamental.or(self.fundamental()).ok_or_else(|| {
127 Error::Pitch("no fundamental is defined for this Pitch: provide one".to_string())
128 })?;
129 let (number, cents) = self.harmonic_from_fundamental(fundamental)?;
130 let suffix = crate::pitch::microtone::ordinal_suffix(number as IntegerType);
131 let fundamental = fundamental.name_with_octave_and_microtone();
132 if cents == 0.0 {
133 Ok(format!("{number}{suffix}H/{fundamental}"))
134 } else {
135 let microtone = Microtone::new(-cents)?;
136 Ok(format!("{number}{suffix}H{microtone}/{fundamental}"))
137 }
138 }
139}
140
141pub(super) fn convert_harmonic_to_cents(harmonic_shift: IntegerType) -> IntegerType {
142 let mut value = harmonic_shift as FloatType;
143 if value < 0.0 {
144 value = 1.0 / value.abs();
145 }
146 (1200.0 * value.log2()).round() as IntegerType
147}
148
149pub(super) fn cents_to_alter_and_cents(shift: FloatType) -> (FloatType, FloatType) {
155 let mut value = shift;
156 let mut alter_add = 0.0;
157 if value > 150.0 {
158 let increment = (value / 100.0).floor();
159 value -= increment * 100.0;
160 alter_add += increment;
161 } else if value < -150.0 {
162 while value < 100.0 {
163 value += 100.0;
164 alter_add -= 1.0;
165 }
166 }
167 let (alter_shift, cents) = if value < -75.0 {
168 (-1.0, value + 100.0)
169 } else if value < -25.0 {
170 (-0.5, value + 50.0)
171 } else if value <= 25.0 {
172 (0.0, value)
173 } else if value <= 75.0 {
174 (0.5, value - 50.0)
175 } else {
176 (1.0, value - 100.0)
177 };
178 (alter_shift + alter_add, cents)
179}