1use std::sync::LazyLock;
6
7use super::helpers::mel;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12pub trait HasPitch {
14 fn pitch(&self) -> Pitch;
16}
17
18pub trait HasBaseFrequency {
20 fn base_frequency(&self) -> f32;
22}
23
24pub trait HasFrequency {
26 fn frequency(&self) -> f32;
28
29 fn frequency_range(&self) -> (f32, f32) {
32 let frequency = self.frequency();
33
34 (frequency * (1.0 - 1.0 / 17.462 / 2.0), frequency * (1.0 + 1.0 / 16.8196 / 2.0))
35 }
36
37 fn tight_frequency_range(&self) -> (f32, f32) {
40 let frequency = self.frequency();
41
42 (frequency * (1.0 - 1.0 / 17.462 / 8.0), frequency * (1.0 + 1.0 / 16.8196 / 8.0))
43 }
44}
45
46pub trait HasMel: HasFrequency {
48 fn mel(&self) -> f32 {
50 mel(self.frequency())
51 }
52}
53
54#[cfg(feature = "audio")]
55use super::base::{Playable, PlaybackHandle, Res};
56
57#[cfg(feature = "audio")]
58impl<T: HasFrequency> Playable for T {
59 fn play(&self, delay: std::time::Duration, length: std::time::Duration, fade_in: std::time::Duration) -> Res<PlaybackHandle> {
60 use rodio::{source::SineWave, OutputStreamBuilder, Sink, Source};
61
62 let stream = OutputStreamBuilder::open_default_stream()?;
63 let sink = Sink::connect_new(stream.mixer());
64 let source = SineWave::new(self.frequency()).take_duration(length - delay).buffered().delay(delay).fade_in(fade_in).amplify(0.20);
65 sink.append(source);
66
67 Ok(PlaybackHandle::new(stream, vec![sink]))
68 }
69}
70
71#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
78#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug, Ord, PartialOrd)]
79#[repr(u8)]
80pub enum Pitch {
81 C,
83 DFlat,
85 D,
87 EFlat,
89 E,
91 F,
93 GFlat,
95 G,
97 AFlat,
99 A,
101 BFlat,
103 B,
105}
106
107impl HasBaseFrequency for Pitch {
110 #[coverage(off)]
111 fn base_frequency(&self) -> f32 {
112 match self {
113 Pitch::C => 16.35,
114 Pitch::DFlat => 17.32,
115 Pitch::D => 18.35,
116 Pitch::EFlat => 19.45,
117 Pitch::E => 20.60,
118 Pitch::F => 21.83,
119 Pitch::GFlat => 23.12,
120 Pitch::G => 24.50,
121 Pitch::AFlat => 25.96,
122 Pitch::A => 27.50,
123 Pitch::BFlat => 29.14,
124 Pitch::B => 30.87,
125 }
126 }
127}
128
129impl HasPitch for Pitch {
130 fn pitch(&self) -> Pitch {
131 *self
132 }
133}
134
135impl TryFrom<u8> for Pitch {
136 type Error = &'static str;
137
138 fn try_from(value: u8) -> Result<Self, Self::Error> {
139 match value {
140 0 => Ok(Pitch::C),
141 1 => Ok(Pitch::DFlat),
142 2 => Ok(Pitch::D),
143 3 => Ok(Pitch::EFlat),
144 4 => Ok(Pitch::E),
145 5 => Ok(Pitch::F),
146 6 => Ok(Pitch::GFlat),
147 7 => Ok(Pitch::G),
148 8 => Ok(Pitch::AFlat),
149 9 => Ok(Pitch::A),
150 10 => Ok(Pitch::BFlat),
151 11 => Ok(Pitch::B),
152 _ => Err("Invalid pitch"),
153 }
154 }
155}
156
157pub static ALL_PITCHES: LazyLock<[Pitch; 12]> = LazyLock::new(|| {
161 [
162 Pitch::C,
163 Pitch::DFlat,
164 Pitch::D,
165 Pitch::EFlat,
166 Pitch::E,
167 Pitch::F,
168 Pitch::GFlat,
169 Pitch::G,
170 Pitch::AFlat,
171 Pitch::A,
172 Pitch::BFlat,
173 Pitch::B,
174 ]
175});
176
177#[cfg(test)]
180mod tests {
181 use super::*;
182 use pretty_assertions::assert_eq;
183
184 #[test]
185 fn test_properties() {
186 assert_eq!(Pitch::G.pitch(), Pitch::G);
187 assert_eq!(Pitch::G.base_frequency(), 24.50);
188 }
189}