From e954df3f896bd18494cd27d77b26bbb2005de8a7 Mon Sep 17 00:00:00 2001 From: JSDurand Date: Wed, 24 Aug 2022 23:54:13 +0800 Subject: First commit Now the project is in a somewhat complete state, ready for future enhancements. --- src/lib.rs | 279 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 src/lib.rs (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5fa7e79 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,279 @@ +//! This crate implements a simple utility to make music as pure +//! numbers. + +use std::{ + fmt::{self, Display, Formatter}, + ops::{Deref, DerefMut}, +}; + +macro_rules! deftype { + ($($x:ident),*) => { + $( + #[derive(Debug, Copy, Clone)] + pub struct $x(f64); + + impl From for $x { + fn from(n: f64) -> Self { + Self(n) + } + } + + impl Deref for $x { + type Target = f64; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl DerefMut for $x { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + )* + }; +} + +deftype!(Volume, Samples, Hertz, Seconds, Pulse, BPM, Beats, Semitones); + +impl Display for Semitones { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let translated = **self + 9f64; + + let quotient = translated.div_euclid(12f64) as i64; + + let octave_string = (quotient + 4).to_string(); + + let remainder = translated.rem_euclid(12f64) as i64; + + // Due to round-off error, there is the possibility that the + // remainder is equal to the divider's absolute value, + // according to the official documentation, so one more + // conversion is necessary. + let remainder = if remainder == 12i64 { 0i64 } else { remainder }; + + let note_string = match remainder { + 0i64 => String::from("C"), + 1i64 => String::from("C#"), + 2i64 => String::from("D"), + 3i64 => String::from("D#"), + 4i64 => String::from("E"), + 5i64 => String::from("F"), + 6i64 => String::from("F#"), + 7i64 => String::from("G"), + 8i64 => String::from("G#"), + 9i64 => String::from("A"), + 10i64 => String::from("A#"), + 11i64 => String::from("B"), + _ => panic!("remainder not in the range [0, 11]: {remainder}"), + }; + + write!(f, "{note_string}{octave_string}") + } +} + +#[allow(clippy::derivable_impls)] +impl Default for Pulse { + fn default() -> Self { + Self(f64::default()) + } +} + +#[derive(Debug, Clone, Default)] +pub struct Wave { + pulses: Vec, +} + +impl Wave { + /// Construct a new wave + pub fn new(pulses: Vec) -> Self { + Self { pulses } + } + + /// Return the vector contained inside it + pub fn to_vec(self) -> Vec { + self.pulses + } +} + +#[allow(dead_code)] +fn mix_waves_no_divide(waves: Vec) -> Wave { + let mut index = 0; + + let mut result_pulses = Vec::new(); + + loop { + let mut count = 0usize; + let mut local_result = 0f64; + + for wave in waves.iter() { + if index < wave.len() { + local_result += *wave[index]; + count += 1; + } + } + + match count { + 0 => { + break; + } + _ => { + result_pulses.push(local_result.into()); + } + } + + index += 1; + } + + Wave::new(result_pulses) +} + +#[allow(dead_code)] +fn mix_waves_exact(waves: Vec) -> Wave { + let mut index = 0; + + let mut result_pulses = Vec::new(); + + let waves_len = waves.len(); + + loop { + let mut count = 0usize; + let mut local_result = 0f64; + + for wave in waves.iter() { + if index < wave.len() { + local_result += *wave[index]; + count += 1; + } + } + + match count { + 0 => { + break; + } + _ => { + result_pulses.push((local_result / waves_len as f64).into()); + } + } + + index += 1; + } + + Wave::new(result_pulses) +} + +#[allow(dead_code)] +fn mix_waves(waves: Vec) -> Wave { + let mut index = 0; + + let mut result_pulses = Vec::new(); + + loop { + let mut count = 0usize; + let mut local_result = 0f64; + + for wave in waves.iter() { + if index < wave.len() { + local_result += *wave[index]; + count += 1; + } + } + + match count { + 0 => { + break; + } + 1 => { + result_pulses.push(local_result.into()); + } + _ => { + result_pulses.push((local_result / count as f64).into()); + } + } + + index += 1; + } + + Wave::new(result_pulses) +} + +impl Deref for Wave { + type Target = [Pulse]; + + fn deref(&self) -> &Self::Target { + &self.pulses + } +} + +// The twelveth root of two. +const STD_BASE: Hertz = Hertz(1.059463094359295f64); + +impl Default for Volume { + fn default() -> Self { + 1f64.into() + } +} + +impl Default for BPM { + fn default() -> Self { + 120f64.into() + } +} + +impl Default for Beats { + fn default() -> Self { + 1f64.into() + } +} + +impl From for Seconds { + fn from(b: BPM) -> Self { + (60f64 / *b).into() + } +} + +impl Default for Samples { + fn default() -> Self { + 48000f64.into() + } +} + +impl Default for Hertz { + fn default() -> Self { + 440f64.into() + } +} + +impl From for Hertz { + fn from(st: Semitones) -> Self { + (*Self::default() * STD_BASE.powf(*st)).into() + } +} + +pub mod instruments; +pub mod output; +pub mod sheet; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_stoh() { + let st: Semitones = 1f64.into(); + + let hertz: Hertz = st.into(); + + println!("hertz = {hertz:?}"); + } + + #[test] + fn test_btos() { + let b = BPM::default(); + + let s: Seconds = b.into(); + + println!("second = {s:?}"); + } +} -- cgit v1.2.3-18-g5258