summaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs279
1 files changed, 279 insertions, 0 deletions
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<f64> 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<Pulse>,
+}
+
+impl Wave {
+ /// Construct a new wave
+ pub fn new(pulses: Vec<Pulse>) -> Self {
+ Self { pulses }
+ }
+
+ /// Return the vector contained inside it
+ pub fn to_vec(self) -> Vec<Pulse> {
+ self.pulses
+ }
+}
+
+#[allow(dead_code)]
+fn mix_waves_no_divide(waves: Vec<Wave>) -> 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>) -> 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>) -> 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<BPM> 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<Semitones> 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:?}");
+ }
+}