From aed3598ef751a07cc7c57eeedc77ac0b22fcb20a Mon Sep 17 00:00:00 2001 From: alemidev Date: Tue, 27 Dec 2022 19:02:52 +0100 Subject: [PATCH] feat: allow tuning to arbitrary octave --- src/main.rs | 10 ++-- src/music.rs | 148 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 109 insertions(+), 49 deletions(-) diff --git a/src/main.rs b/src/main.rs index 437ff0b..f47dca4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,7 +51,7 @@ struct Args { /// Tune buffer size to be in tune with given note (overrides buffer option) #[arg(long, value_name = "NOTE")] - tune: Option, + tune: Option, /// Sample rate to use #[arg(long, value_name = "HZ", default_value_t = 44100)] @@ -73,12 +73,14 @@ struct Args { fn main() -> Result<(), io::Error> { let mut args = Args::parse(); - if let Some(note) = &args.tune { // TODO make it less jank - if note != &Note::INVALID { - args.buffer = note.tune_buffer_size(0, args.sample_rate); + if let Some(txt) = &args.tune { // TODO make it less jank + if let Ok(note) = txt.parse::() { + args.buffer = note.tune_buffer_size(args.sample_rate); while args.buffer % 4 != 0 { args.buffer += 1; // TODO jank but otherwise it doesn't align } + } else { + eprintln!("[!] Unrecognized note '{}', ignoring option", txt); } } diff --git a/src/music.rs b/src/music.rs index ae6d96c..f616a58 100644 --- a/src/music.rs +++ b/src/music.rs @@ -1,46 +1,117 @@ +use std::{str::FromStr, num::ParseIntError}; + #[derive(Debug, PartialEq, Clone)] -pub enum Note { - C, Db, D, Eb, E, F, Gb, G, Ab, A, Bb, B, - INVALID +pub enum Tone { + C, Db, D, Eb, E, F, Gb, G, Ab, A, Bb, B } -impl From:: for Note { - fn from(txt: String) -> Self { - match txt.as_str() { - "C" => Note::C, - "Db" => Note::Db, - "D" => Note::D, - "Eb" => Note::Eb, - "E" => Note::E, - "F" => Note::F, - "Gb" => Note::Gb, - "G" => Note::G, - "Ab" => Note::Ab, - "A" => Note::A, - "Bb" => Note::Bb, - "B" => Note::B, - _ => Note::INVALID, +pub struct ToneError {} + +#[derive(Debug, PartialEq, Clone)] +pub struct Note { + tone: Tone, + octave: u32, +} + +pub enum NoteError { + InvalidOctave(ParseIntError), + InalidNote(ToneError), +} + +impl From:: for NoteError { + fn from(e: ToneError) -> Self { + NoteError::InalidNote(e) + } +} + +impl From:: for NoteError { + fn from(e: ParseIntError) -> Self { + NoteError::InvalidOctave(e) + } +} + +impl FromStr for Note { + type Err = NoteError; + + fn from_str(txt: &str) -> Result { + let trimmed = txt.trim(); + let mut split = 0; + for c in trimmed.chars() { + if !c.is_ascii_digit() { + split += 1; + } else { + break; + } + } + Ok( + Note { + tone: trimmed[..split].parse::()?, + octave: trimmed[split..].parse::().unwrap_or(0), + } + ) + } +} + +// impl TryFrom:: for Note { +// type Error = NoteError; +// fn try_from(value: String) -> Result { +// value.as_str().parse::() +// } +// } + +impl FromStr for Tone { + type Err = ToneError; + + fn from_str(txt: &str) -> Result { + match txt { + "C" => Ok(Tone::C ), + "C#" | "Db" => Ok(Tone::Db), + "D" => Ok(Tone::D ), + "D#" | "Eb" => Ok(Tone::Eb), + "E" => Ok(Tone::E ), + "F" => Ok(Tone::F ), + "F#" | "Gb" => Ok(Tone::Gb), + "G" => Ok(Tone::G ), + "G#" | "Ab" => Ok(Tone::Ab), + "A" => Ok(Tone::A ), + "A#" | "Bb" => Ok(Tone::Bb), + "B" => Ok(Tone::B ), + _ => Err(ToneError { }) } } } impl Note { + pub fn tune_buffer_size(&self, sample_rate: u32) -> u32 { + let t = 1.0 / self.tone.freq(self.octave); // periodo ? + let buf = (sample_rate as f32) * t; + return (buf * 4.0).round() as u32; + } + + // pub fn tune_sample_rate(&self, octave:u32, buffer_size: u32) -> u32 { + // // TODO does it just work the same way? + // let t = 1.0 / self.freq(octave); // periodo ? + // let buf = (buffer_size as f32) * t; + // return (buf * 4.0).round() as u32; + // } +} + +impl Tone { pub fn freq(&self, octave: u32) -> f32 { match octave { 0 => match self { - Note::C => 16.35, - Note::Db => 17.32, - Note::D => 18.35, - Note::Eb => 19.45, - Note::E => 20.60, - Note::F => 21.83, - Note::Gb => 23.12, - Note::G => 24.50, - Note::Ab => 25.96, - Note::A => 27.50, - Note::Bb => 29.14, - Note::B => 30.87, - Note::INVALID => 0.0, + Tone::C => 16.35, + Tone::Db => 17.32, + Tone::D => 18.35, + Tone::Eb => 19.45, + Tone::E => 20.60, + Tone::F => 21.83, + Tone::Gb => 23.12, + Tone::G => 24.50, + Tone::Ab => 25.96, + Tone::A => 27.50, + Tone::Bb => 29.14, + Tone::B => 30.87, }, _ => { let mut freq = self.freq(0); @@ -55,18 +126,5 @@ impl Note { // pub fn all() -> Vec { // vec![Note::C, Note::Db, Note::D, Note::Eb, Note::E, Note::F, Note::Gb, Note::G, Note::Ab, Note::A, Note::Bb, Note::B] // } - - pub fn tune_buffer_size(&self, octave:u32, sample_rate: u32) -> u32 { - let t = 1.0 / self.freq(octave); // periodo ? - let buf = (sample_rate as f32) * t; - return (buf * 4.0).round() as u32; - } - - // pub fn tune_sample_rate(&self, octave:u32, buffer_size: u32) -> u32 { - // // TODO does it just work the same way? - // let t = 1.0 / self.freq(octave); // periodo ? - // let buf = (buffer_size as f32) * t; - // return (buf * 4.0).round() as u32; - // } }