/*
* Copyright (c) 2021, PockelHockel.
* This software is licensed under the GNU General Public License v3.0
*/
use std::num::ParseIntError;
use std::str::FromStr;
use std::time::Duration;
use rodio::OutputStreamHandle;
/// Enum of all types of notes in the english naming convention. See [here](https://en.wikipedia.org/wiki/Musical_note#12-tone_chromatic_scale).
#[derive(Debug)]
pub enum NoteType {
/// [A](https://en.wikipedia.org/wiki/A_(musical_note))
A,
/// [A♯ / A-sharp / B♭ / B-flat](https://en.wikipedia.org/wiki/A%E2%99%AF_(musical_note))
ASharp,
/// [B](https://en.wikipedia.org/wiki/B_(musical_note))
B,
/// [C](https://en.wikipedia.org/wiki/C_(musical_note))
C,
/// [C♯ / C-sharp / D♭ / D-flat](https://en.wikipedia.org/wiki/C%E2%99%AF_(musical_note))
CSharp,
/// [D](https://en.wikipedia.org/wiki/D_(musical_note))
D,
/// [D♯ / D-sharp / E♭ / E-flat](https://en.wikipedia.org/wiki/D%E2%99%AF_(musical_note))
DSharp,
/// [E](https://en.wikipedia.org/wiki/E_(musical_note))
E,
/// [F](https://en.wikipedia.org/wiki/F_(musical_note))
F,
/// [F♯ / F-sharp / G♭ / G-flat](https://en.wikipedia.org/wiki/F%E2%99%AF_(musical_note))
FSharp,
/// [G](https://en.wikipedia.org/wiki/G_(musical_note))
G,
/// [G♯ / G-sharp / A♭ / A-flat](https://en.wikipedia.org/wiki/G%E2%99%AF_(musical_note))
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,
}
}
}
/// Represents the pitch of a note as defined [here](https://en.wikipedia.org/wiki/Pitch_(music)).
#[derive(Debug)]
pub struct NotePitch {
typ: NoteType,
n: i32,
}
impl NotePitch {
pub fn new(typ: NoteType, n: i32) -> Self {
NotePitch { typ, n }
}
/// Converts this [`NotePitch`](struct.NotePitch.html) to its frequency according to the [list of piano key frequencies](https://en.wikipedia.org/wiki/Piano_key_frequencies#List).
/// # Example
/// ```rust
/// use simple_tones::NotePitch;
///
/// let note_pitch: NotePitch = "A4".parse().unwrap();
/// assert_eq!(note_pitch.frequency(), 440);
/// ```
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
}
}
/// Represents an error that can happen while parsing a [`NotePitch`](struct.NotePitch.html) from a str
#[derive(Debug)]
pub enum ParseNotePitchError {
/// The length of the given str is invalid
InvalidLength,
/// The type of note is invalid
InvalidType,
/// The octave of the note is invalid
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)),
}
}
}
/// Represents the relative duration of a note as defined [here](https://en.wikipedia.org/wiki/Note_value#List)
#[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,
}
}
}
/// Is implemented by all playable types. (e.g. [`Note`](struct.Note.html), [`Rest`](struct.Rest.html))
pub trait Play {
/// Plays on the given `stream_handle` for `duration` time.
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);
}
}
/// Represents a musical note.
#[derive(Debug)]
pub struct Note {
pitch: NotePitch,
duration: f64,
}
impl Note {
/// Constructs a new Note with a given [`NotePitch`](struct.NotePitch.html) and a duration which represents the value of the note as a fraction.
/// [`NoteDuration`](enum.NoteDuration.html) can be used as a helper enum.
/// # Example
/// ```rust
/// use simple_tones::{Note, NoteDuration};
///
/// let note = Note::new("A4".parse().unwrap(), NoteDuration::Whole);
/// ```
pub fn new<D: Into<f64>>(pitch: NotePitch, duration: D) -> Self {
Note {
pitch,
duration: duration.into(),
}
}
}
impl Play for Note {
/// Plays this note on the given `stream_handle` for `duration` time. The sound is generated by a sine wave.
/// # Example
/// ```rust
/// use std::time;
/// use simple_tones::{Note, NoteType, NotePitch, Play};
///
/// let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
/// let note = Note::new(NotePitch::new(NoteType::A, 4), 1);
/// note.play(&stream_handle, time::Duration::from_secs(2));
/// ```
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();
}
}
/// Represents a [musical rest](https://en.wikipedia.org/wiki/Rest_(music)).
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()));
}
}