1
0
Fork 0
mirror of https://github.com/alemidev/scope-tui.git synced 2024-11-23 22:24:48 +01:00

feat: allow tuning to arbitrary octave

This commit is contained in:
əlemi 2022-12-27 19:02:52 +01:00
parent 57ffa3c690
commit aed3598ef7
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
2 changed files with 109 additions and 49 deletions

View file

@ -51,7 +51,7 @@ struct Args {
/// Tune buffer size to be in tune with given note (overrides buffer option) /// Tune buffer size to be in tune with given note (overrides buffer option)
#[arg(long, value_name = "NOTE")] #[arg(long, value_name = "NOTE")]
tune: Option<Note>, tune: Option<String>,
/// Sample rate to use /// Sample rate to use
#[arg(long, value_name = "HZ", default_value_t = 44100)] #[arg(long, value_name = "HZ", default_value_t = 44100)]
@ -73,12 +73,14 @@ struct Args {
fn main() -> Result<(), io::Error> { fn main() -> Result<(), io::Error> {
let mut args = Args::parse(); let mut args = Args::parse();
if let Some(note) = &args.tune { // TODO make it less jank if let Some(txt) = &args.tune { // TODO make it less jank
if note != &Note::INVALID { if let Ok(note) = txt.parse::<Note>() {
args.buffer = note.tune_buffer_size(0, args.sample_rate); args.buffer = note.tune_buffer_size(args.sample_rate);
while args.buffer % 4 != 0 { while args.buffer % 4 != 0 {
args.buffer += 1; // TODO jank but otherwise it doesn't align args.buffer += 1; // TODO jank but otherwise it doesn't align
} }
} else {
eprintln!("[!] Unrecognized note '{}', ignoring option", txt);
} }
} }

View file

@ -1,46 +1,117 @@
use std::{str::FromStr, num::ParseIntError};
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum Note { pub enum Tone {
C, Db, D, Eb, E, F, Gb, G, Ab, A, Bb, B, C, Db, D, Eb, E, F, Gb, G, Ab, A, Bb, B
INVALID
} }
impl From::<String> for Note { pub struct ToneError {}
fn from(txt: String) -> Self {
match txt.as_str() { #[derive(Debug, PartialEq, Clone)]
"C" => Note::C, pub struct Note {
"Db" => Note::Db, tone: Tone,
"D" => Note::D, octave: u32,
"Eb" => Note::Eb, }
"E" => Note::E,
"F" => Note::F, pub enum NoteError {
"Gb" => Note::Gb, InvalidOctave(ParseIntError),
"G" => Note::G, InalidNote(ToneError),
"Ab" => Note::Ab, }
"A" => Note::A,
"Bb" => Note::Bb, impl From::<ToneError> for NoteError {
"B" => Note::B, fn from(e: ToneError) -> Self {
_ => Note::INVALID, NoteError::InalidNote(e)
}
}
impl From::<ParseIntError> for NoteError {
fn from(e: ParseIntError) -> Self {
NoteError::InvalidOctave(e)
}
}
impl FromStr for Note {
type Err = NoteError;
fn from_str(txt: &str) -> Result<Self, Self::Err> {
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::<Tone>()?,
octave: trimmed[split..].parse::<u32>().unwrap_or(0),
}
)
}
}
// impl TryFrom::<String> for Note {
// type Error = NoteError;
// fn try_from(value: String) -> Result<Self, Self::Error> {
// value.as_str().parse::<Note>()
// }
// }
impl FromStr for Tone {
type Err = ToneError;
fn from_str(txt: &str) -> Result<Self, Self::Err> {
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 { 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 { pub fn freq(&self, octave: u32) -> f32 {
match octave { match octave {
0 => match self { 0 => match self {
Note::C => 16.35, Tone::C => 16.35,
Note::Db => 17.32, Tone::Db => 17.32,
Note::D => 18.35, Tone::D => 18.35,
Note::Eb => 19.45, Tone::Eb => 19.45,
Note::E => 20.60, Tone::E => 20.60,
Note::F => 21.83, Tone::F => 21.83,
Note::Gb => 23.12, Tone::Gb => 23.12,
Note::G => 24.50, Tone::G => 24.50,
Note::Ab => 25.96, Tone::Ab => 25.96,
Note::A => 27.50, Tone::A => 27.50,
Note::Bb => 29.14, Tone::Bb => 29.14,
Note::B => 30.87, Tone::B => 30.87,
Note::INVALID => 0.0,
}, },
_ => { _ => {
let mut freq = self.freq(0); let mut freq = self.freq(0);
@ -55,18 +126,5 @@ impl Note {
// pub fn all() -> Vec<Note> { // pub fn all() -> Vec<Note> {
// 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] // 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;
// }
} }