use std::num::ParseIntError;
use std::str::FromStr;
use std::time::Duration;
use rodio::OutputStreamHandle;
#[derive(Debug)]
pub enum NoteType {
A,
ASharp,
B,
C,
CSharp,
D,
DSharp,
E,
F,
FSharp,
G,
GSharp,
}
impl NoteType {
fn number(&self) -> u32 {
match self {
NoteType::A => 0,
NoteType::ASharp => 1,
NoteType::B => 2,
NoteType::C => 3,
NoteType::CSharp => 4,
NoteType::D => 5,
NoteType::DSharp => 6,
NoteType::E => 7,
NoteType::F => 8,
NoteType::FSharp => 9,
NoteType::G => 10,
NoteType::GSharp => 11,
}
}
}
#[derive(Debug)]
pub struct NotePitch {
typ: NoteType,
n: i32,
}
impl NotePitch {
pub fn new(typ: NoteType, n: i32) -> Self {
NotePitch { typ, n }
}
pub fn frequency(&self) -> u32 {
let x = (self.n - 4) * 12 + self.typ.number() as i32;
(2.0f64.powf(x as f64 / 12.0) * 440.0).round() as u32
}
}
#[derive(Debug)]
pub enum ParseNotePitchError {
InvalidLength,
InvalidType,
InvalidOctave(ParseIntError),
}
impl FromStr for NotePitch {
type Err = ParseNotePitchError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() < 2 {
return Err(ParseNotePitchError::InvalidLength);
}
let s = s.split_at(s.len() - 1);
let mut add = 0;
let typ = match s.0 {
"Ab" => {
add += -1;
NoteType::GSharp
}
"A" => NoteType::A,
"A#" => NoteType::ASharp,
"Bb" => NoteType::ASharp,
"B" => NoteType::B,
"C" => NoteType::C,
"C#" => NoteType::CSharp,
"Db" => NoteType::CSharp,
"D" => NoteType::D,
"D#" => NoteType::DSharp,
"Eb" => NoteType::DSharp,
"E" => NoteType::E,
"F" => NoteType::F,
"F#" => NoteType::FSharp,
"Gb" => NoteType::FSharp,
"G" => NoteType::G,
"G#" => NoteType::GSharp,
_ => return Err(ParseNotePitchError::InvalidType),
};
match s.1.parse::<i32>() {
Ok(n) => Ok(Self::new(typ, n + add)),
Err(e) => Err(ParseNotePitchError::InvalidOctave(e)),
}
}
}
#[derive(Copy, Clone)]
pub enum NoteDuration {
WholeDotted,
Whole,
HalfDotted,
Half,
QuarterDotted,
Quarter,
EighthDotted,
Eighth,
SixteenthDotted,
Sixteenth,
ThirtySecondDotted,
ThirtySecond,
SixtyFourthDotted,
SixtyFourth,
}
impl Into<f64> for NoteDuration {
fn into(self) -> f64 {
match self {
NoteDuration::WholeDotted => 3.0 / 2.0,
NoteDuration::Whole => 1.0,
NoteDuration::HalfDotted => 3.0 / 4.0,
NoteDuration::Half => 1.0 / 2.0,
NoteDuration::QuarterDotted => 3.0 / 8.0,
NoteDuration::Quarter => 1.0 / 4.0,
NoteDuration::EighthDotted => 3.0 / 16.0,
NoteDuration::Eighth => 1.0 / 8.0,
NoteDuration::SixteenthDotted => 3.0 / 32.0,
NoteDuration::Sixteenth => 1.0 / 16.0,
NoteDuration::ThirtySecondDotted => 3.0 / 64.0,
NoteDuration::ThirtySecond => 1.0 / 32.0,
NoteDuration::SixtyFourthDotted => 3.0 / 128.0,
NoteDuration::SixtyFourth => 1.0 / 64.0,
}
}
}
pub trait Play {
fn play(&self, stream_handle: &OutputStreamHandle, bar_duration: Duration);
}
impl<'a, T> Play for &'a T
where
T: Play,
{
fn play(&self, stream_handle: &OutputStreamHandle, bar_duration: Duration) {
(*self).play(&stream_handle, bar_duration);
}
}
#[derive(Debug)]
pub struct Note {
pitch: NotePitch,
duration: f64,
}
impl Note {
pub fn new<D: Into<f64>>(pitch: NotePitch, duration: D) -> Self {
Note {
pitch,
duration: duration.into(),
}
}
}
impl Play for Note {
fn play(&self, stream_handle: &OutputStreamHandle, bar_duration: Duration) {
let sink = rodio::Sink::try_new(stream_handle).unwrap();
sink.set_volume(0.5);
let source = rodio::source::SineWave::new(self.pitch.frequency());
sink.append(source);
std::thread::sleep(bar_duration.mul_f64(self.duration));
sink.stop();
}
}
pub struct Rest(NoteDuration);
impl Play for Rest {
fn play(&self, _stream_handle: &OutputStreamHandle, bar_duration: Duration) {
std::thread::sleep(bar_duration.mul_f64(self.0.into()));
}
}