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:
parent
57ffa3c690
commit
aed3598ef7
2 changed files with 109 additions and 49 deletions
10
src/main.rs
10
src/main.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
148
src/music.rs
148
src/music.rs
|
@ -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;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue