use std::time::Duration;
use once_cell::sync::Lazy;
use super::helpers::mel;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub trait HasPitch {
fn pitch(&self) -> Pitch;
}
pub trait HasBaseFrequency {
fn base_frequency(&self) -> f32;
}
pub trait HasFrequency {
fn frequency(&self) -> f32;
fn frequency_range(&self) -> (f32, f32) {
let frequency = self.frequency();
(frequency * (1.0 - 1.0 / 17.462 / 2.0), frequency * (1.0 + 1.0 / 16.8196 / 2.0))
}
fn tight_frequency_range(&self) -> (f32, f32) {
let frequency = self.frequency();
(frequency * (1.0 - 1.0 / 17.462 / 8.0), frequency * (1.0 + 1.0 / 16.8196 / 8.0))
}
}
pub trait HasMel: HasFrequency {
fn mel(&self) -> f32 {
mel(self.frequency())
}
}
#[cfg(feature = "audio")]
use super::base::{Playable, PlaybackHandle, Res};
#[cfg(feature = "audio")]
impl<T: HasFrequency> Playable for T {
fn play(&self, delay: Duration, length: Duration, fade_in: Duration) -> Res<PlaybackHandle> {
use rodio::{source::SineWave, OutputStream, Sink, Source};
let (stream, stream_handle) = OutputStream::try_default()?;
let sink = Sink::try_new(&stream_handle)?;
let source = SineWave::new(self.frequency()).take_duration(length - delay).buffered().delay(delay).fade_in(fade_in).amplify(0.20);
sink.append(source);
Ok(PlaybackHandle::new(stream, stream_handle, vec![sink]))
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug, Ord, PartialOrd)]
#[repr(u8)]
pub enum Pitch {
C,
DFlat,
D,
EFlat,
E,
F,
GFlat,
G,
AFlat,
A,
BFlat,
B,
}
impl HasBaseFrequency for Pitch {
#[coverage(off)]
fn base_frequency(&self) -> f32 {
match self {
Pitch::C => 16.35,
Pitch::DFlat => 17.32,
Pitch::D => 18.35,
Pitch::EFlat => 19.45,
Pitch::E => 20.60,
Pitch::F => 21.83,
Pitch::GFlat => 23.12,
Pitch::G => 24.50,
Pitch::AFlat => 25.96,
Pitch::A => 27.50,
Pitch::BFlat => 29.14,
Pitch::B => 30.87,
}
}
}
impl HasPitch for Pitch {
fn pitch(&self) -> Pitch {
*self
}
}
impl TryFrom<u8> for Pitch {
type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Pitch::C),
1 => Ok(Pitch::DFlat),
2 => Ok(Pitch::D),
3 => Ok(Pitch::EFlat),
4 => Ok(Pitch::E),
5 => Ok(Pitch::F),
6 => Ok(Pitch::GFlat),
7 => Ok(Pitch::G),
8 => Ok(Pitch::AFlat),
9 => Ok(Pitch::A),
10 => Ok(Pitch::BFlat),
11 => Ok(Pitch::B),
_ => Err("Invalid pitch"),
}
}
}
pub static ALL_PITCHES: Lazy<[Pitch; 12]> = Lazy::new(|| {
[
Pitch::C,
Pitch::DFlat,
Pitch::D,
Pitch::EFlat,
Pitch::E,
Pitch::F,
Pitch::GFlat,
Pitch::G,
Pitch::AFlat,
Pitch::A,
Pitch::BFlat,
Pitch::B,
]
});
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_properties() {
assert_eq!(Pitch::G.pitch(), Pitch::G);
assert_eq!(Pitch::G.base_frequency(), 24.50);
}
}