From 4365d2196aa2d548ab280b5878c292725c009f77 Mon Sep 17 00:00:00 2001 From: Matthew Maxwell Date: Wed, 30 Oct 2024 21:58:06 -0500 Subject: [PATCH] Separate functionality into different files --- src/commands/freq.rs | 25 ++------------ src/commands/midi.rs | 32 ++--------------- src/commands/note.rs | 79 ++---------------------------------------- src/convert.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ src/parse.rs | 42 +++++++++++++++++++++++ 6 files changed, 135 insertions(+), 127 deletions(-) create mode 100644 src/convert.rs create mode 100644 src/parse.rs diff --git a/src/commands/freq.rs b/src/commands/freq.rs index b8bc8ed..37fcda0 100644 --- a/src/commands/freq.rs +++ b/src/commands/freq.rs @@ -1,10 +1,10 @@ use async_trait::async_trait; -use regex::Regex; use crate::{ cli::Command, - commands::midi::{midi_note_number_to_frequency, midi_note_number_to_music_note}, - error::{FNoteError, FNoteResult}, + convert::{frequency_to_midi_note, midi_note_number_to_frequency, midi_note_number_to_music_note}, + error::FNoteResult, + parse::try_frequency_from_str, }; /// Extracts the (nearest) MIDI note number and music note from a frequency. @@ -37,22 +37,3 @@ impl Command for FreqCommand { Ok(()) } } - -/// Converts a frequency to the nearest MIDI note number. -pub(crate) fn frequency_to_midi_note(frequency: f32) -> Option { - let midi_note = 69.0 + 12.0 * (frequency / 440.0).log2(); - if (0..=127).contains(&(midi_note.round() as i32)) { - Some(midi_note.round() as u8) - } else { - None - } -} - -fn try_frequency_from_str(arg: &str) -> FNoteResult { - let regex = Regex::new(r"^\d+(\.\d+)?$").unwrap(); - if regex.is_match(arg) { - Ok(arg.parse::().unwrap()) - } else { - Err(FNoteError::InvalidFrequency(arg.to_string())) - } -} diff --git a/src/commands/midi.rs b/src/commands/midi.rs index ad7ecba..418cefa 100644 --- a/src/commands/midi.rs +++ b/src/commands/midi.rs @@ -1,9 +1,10 @@ use async_trait::async_trait; -use regex::Regex; use crate::{ cli::Command, - error::{FNoteError, FNoteResult}, + convert::{midi_note_number_to_frequency, midi_note_number_to_music_note}, + error::FNoteResult, + parse::try_midi_note_number_from_str, }; /// Extracts the frequency and music note from a MIDI note number (0-127). @@ -27,30 +28,3 @@ impl Command for MidiCommand { Ok(()) } } - -/// Converts a MIDI note number to a music note. -pub(crate) fn midi_note_number_to_music_note(midi_note_number: u8) -> Option { - let semitone_to_note = ["C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"]; - - let octave = (midi_note_number / 12) as i8 - 1; - let semitone_offset = (midi_note_number % 12) as usize; - let note = semitone_to_note.get(semitone_offset).unwrap(); - - Some(format!("{}{}", note, octave)) -} - -/// Converts a MIDI note number to a frequency (Hz). -pub(crate) fn midi_note_number_to_frequency(midi_note_number: u8) -> f32 { - let frequency = 440.0 * 2.0f32.powf((midi_note_number as f32 - 69.0) / 12.0); - (frequency * 100.0).round() / 100.0 -} - -/// Parses a MIDI note number from a string. -fn try_midi_note_number_from_str(arg: &str) -> FNoteResult { - let regex = Regex::new(r"^(?:\d|[1-9]\d|1[01]\d|12[0-7])$").unwrap(); - if regex.is_match(arg) { - Ok(arg.parse::().unwrap()) - } else { - Err(FNoteError::InvalidMidiNoteNumber(arg.to_string())) - } -} diff --git a/src/commands/note.rs b/src/commands/note.rs index 390b2df..0c74790 100644 --- a/src/commands/note.rs +++ b/src/commands/note.rs @@ -1,10 +1,10 @@ use async_trait::async_trait; -use regex::Regex; use crate::{ cli::Command, - commands::midi::midi_note_number_to_frequency, - error::{FNoteError, FNoteResult}, + convert::{midi_note_number_to_frequency, music_note_to_midi_note_number}, + error::FNoteResult, + parse::try_music_note_from_str, }; /// Extracts the frequency and MIDI note number from a music note (e.g. C5). @@ -28,76 +28,3 @@ impl Command for NoteCommand { Ok(()) } } - -/// Converts a music note to a MIDI note number. -fn music_note_to_midi_note_number(music_note: &str) -> FNoteResult { - let note_to_semitone = [ - ("Cb", -1), // -1 instead of 11 because Cb = B, meaning decrement octave by 1 - ("C", 0), - ("C#", 1), - ("Db", 1), - ("D", 2), - ("D#", 3), - ("Eb", 3), - ("E", 4), - ("Fb", 4), - ("F", 5), - ("E#", 5), - ("F#", 6), - ("Gb", 6), - ("G", 7), - ("G#", 8), - ("Ab", 8), - ("A", 9), - ("A#", 10), - ("Bb", 10), - ("B", 11), - ("B#", 12), // 12 instead of 0 because B# = C, meaning increment octave by 1 - ] - .iter() - .cloned() - .collect::>(); - - if music_note.len() < 2 || music_note.len() > 4 { - Err(FNoteError::InvalidMusicNote(music_note.to_string())) - } else { - let base_note = if music_note.chars().nth(1) == Some('#') || music_note.chars().nth(1) == Some('b') { - &music_note[..2] - } else { - &music_note[..1] - }; - - let octave_str = &music_note[base_note.len()..]; - let octave: i8 = octave_str.parse().unwrap(); - if !(-1..=9).contains(&octave) { - return Err(FNoteError::InvalidMusicNote(music_note.to_string())); - } - - let semitone_offset = *note_to_semitone.get(base_note).unwrap(); - let midi_number = ((octave + 1) * 12 + semitone_offset as i8) as i16; - if (0..=127).contains(&midi_number) { - Ok(midi_number as u8) - } else { - Err(FNoteError::InvalidMusicNote(music_note.to_string())) - } - } -} - -/// Parses a music note from a string. -fn try_music_note_from_str(arg: &str) -> FNoteResult { - let regex = Regex::new(r"^[A-Ga-g][#b]?(-1|[0-9])$").unwrap(); - if regex.is_match(arg) { - let mut chars = arg.chars(); - let mut result = String::new(); - - if let Some(note_char) = chars.next() { - result.push(note_char.to_ascii_uppercase()); - } - - result.push_str(chars.as_str()); - - Ok(result) - } else { - Err(FNoteError::InvalidMusicNote(arg.to_string())) - } -} diff --git a/src/convert.rs b/src/convert.rs new file mode 100644 index 0000000..d4c6c51 --- /dev/null +++ b/src/convert.rs @@ -0,0 +1,82 @@ +use crate::error::{FNoteError, FNoteResult}; + +/// Converts a frequency to the nearest MIDI note number. +pub(crate) fn frequency_to_midi_note(frequency: f32) -> Option { + let midi_note = 69.0 + 12.0 * (frequency / 440.0).log2(); + if (0..=127).contains(&(midi_note.round() as i32)) { + Some(midi_note.round() as u8) + } else { + None + } +} + +/// Converts a MIDI note number to a frequency (Hz). +pub(crate) fn midi_note_number_to_frequency(midi_note_number: u8) -> f32 { + let frequency = 440.0 * 2.0f32.powf((midi_note_number as f32 - 69.0) / 12.0); + (frequency * 100.0).round() / 100.0 +} + +/// Converts a MIDI note number to a music note. +pub(crate) fn midi_note_number_to_music_note(midi_note_number: u8) -> Option { + let semitone_to_note = ["C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"]; + + let octave = (midi_note_number / 12) as i8 - 1; + let semitone_offset = (midi_note_number % 12) as usize; + let note = semitone_to_note.get(semitone_offset).unwrap(); + + Some(format!("{}{}", note, octave)) +} + +/// Converts a music note to a MIDI note number. +pub(crate) fn music_note_to_midi_note_number(music_note: &str) -> FNoteResult { + let note_to_semitone = [ + ("Cb", -1), // -1 instead of 11 because Cb = B, meaning decrement octave by 1 + ("C", 0), + ("C#", 1), + ("Db", 1), + ("D", 2), + ("D#", 3), + ("Eb", 3), + ("E", 4), + ("Fb", 4), + ("F", 5), + ("E#", 5), + ("F#", 6), + ("Gb", 6), + ("G", 7), + ("G#", 8), + ("Ab", 8), + ("A", 9), + ("A#", 10), + ("Bb", 10), + ("B", 11), + ("B#", 12), // 12 instead of 0 because B# = C, meaning increment octave by 1 + ] + .iter() + .cloned() + .collect::>(); + + if music_note.len() < 2 || music_note.len() > 4 { + Err(FNoteError::InvalidMusicNote(music_note.to_string())) + } else { + let base_note = if music_note.chars().nth(1) == Some('#') || music_note.chars().nth(1) == Some('b') { + &music_note[..2] + } else { + &music_note[..1] + }; + + let octave_str = &music_note[base_note.len()..]; + let octave: i8 = octave_str.parse().unwrap(); + if !(-1..=9).contains(&octave) { + return Err(FNoteError::InvalidMusicNote(music_note.to_string())); + } + + let semitone_offset = *note_to_semitone.get(base_note).unwrap(); + let midi_number = ((octave + 1) * 12 + semitone_offset as i8) as i16; + if (0..=127).contains(&midi_number) { + Ok(midi_number as u8) + } else { + Err(FNoteError::InvalidMusicNote(music_note.to_string())) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 489700c..c8f6f3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,3 +2,5 @@ pub mod cli; pub mod error; pub(crate) mod commands; +pub(crate) mod convert; +pub(crate) mod parse; diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..fd2329a --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,42 @@ +use regex::Regex; + +use crate::error::{FNoteError, FNoteResult}; + +/// Parses a frequency from a string. +pub(crate) fn try_frequency_from_str(arg: &str) -> FNoteResult { + let regex = Regex::new(r"^\d+(\.\d+)?$").unwrap(); + if regex.is_match(arg) { + Ok(arg.parse::().unwrap()) + } else { + Err(FNoteError::InvalidFrequency(arg.to_string())) + } +} + +/// Parses a MIDI note number from a string. +pub(crate) fn try_midi_note_number_from_str(arg: &str) -> FNoteResult { + let regex = Regex::new(r"^(?:\d|[1-9]\d|1[01]\d|12[0-7])$").unwrap(); + if regex.is_match(arg) { + Ok(arg.parse::().unwrap()) + } else { + Err(FNoteError::InvalidMidiNoteNumber(arg.to_string())) + } +} + +/// Parses a music note from a string. +pub(crate) fn try_music_note_from_str(arg: &str) -> FNoteResult { + let regex = Regex::new(r"^[A-Ga-g][#b]?(-1|[0-9])$").unwrap(); + if regex.is_match(arg) { + let mut chars = arg.chars(); + let mut result = String::new(); + + if let Some(note_char) = chars.next() { + result.push(note_char.to_ascii_uppercase()); + } + + result.push_str(chars.as_str()); + + Ok(result) + } else { + Err(FNoteError::InvalidMusicNote(arg.to_string())) + } +}