simple_tones 0.1.0

Easy to use crate to play music notes.
Documentation
/*
 * 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()));
    }
}