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/instruments.rs | 485 +++++++++++ src/lib.rs | 279 +++++++ src/main.rs | 214 +++++ src/output/ffmpeg_output.rs | 417 ++++++++++ src/output/mod.rs | 68 ++ src/sheet/mod.rs | 1905 +++++++++++++++++++++++++++++++++++++++++++ src/sheet/play.rs | 314 +++++++ 7 files changed, 3682 insertions(+) create mode 100644 src/instruments.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/output/ffmpeg_output.rs create mode 100644 src/output/mod.rs create mode 100644 src/sheet/mod.rs create mode 100644 src/sheet/play.rs (limited to 'src') diff --git a/src/instruments.rs b/src/instruments.rs new file mode 100644 index 0000000..15ac7d4 --- /dev/null +++ b/src/instruments.rs @@ -0,0 +1,485 @@ +//! This file implements an interface for instruments. +//! +//! An instrument is a type that can produce a `Wave` when given a +//! note in semitone, a sample rate, and a duration in beats. + +use super::*; +use std::{ + f64::consts::PI, + fmt::{self, Display, Formatter}, +}; + +/// The interface of an instrument. +pub trait Instrument { + /// Produce pulses for a given note, sample rate, and duration. + /// + /// This will be separately modified to account for the effect of + /// attack-decay-sustain-release. See + /// [Wikipedia](). + fn play(&mut self, note: T, rate: U, bpm: S, duration: V, volume: R) -> Wave + where + T: Into, + S: Into, + U: Into, + V: Into, + R: Into; +} + +// Some instruments + +#[derive(Default)] +pub struct Sinus {} + +impl Display for Sinus { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "the pure sine instrument") + } +} + +impl Instrument for Sinus { + fn play(&mut self, note: T, rate: U, bpm: S, duration: V, volume: R) -> Wave + where + T: Into, + S: Into, + U: Into, + V: Into, + R: Into, + { + let note = note.into(); + let rate = rate.into(); + let duration = duration.into(); + let volume = volume.into(); + + let bpm = bpm.into(); + let duration_per_beat: Seconds = bpm.into(); + let duration: Seconds = (*duration * *duration_per_beat).into(); + + let nb_samples = (*duration * *rate).floor() as usize; + + let note_h: Hertz = note.into(); + + let step = *note_h * 2f64 * PI / *rate; + + let mut theta = 0f64; + + let mut result: Vec = vec![]; + + let mut current_adsr = 0f64; + + for i in 0..nb_samples { + current_adsr = adsr(i, nb_samples, current_adsr); + + let y: f64 = (*volume * f64::sin(theta)) * current_adsr; + + let y = if y >= 1f64 { + 1f64 + } else if y <= -1f64 { + -1f64 + } else { + y + }; + + result.push(y.into()); + + theta += step; + } + + Wave::new(result) + } +} + +// Other instruments: tangent, and variations of piano. + +// Tangent + +#[derive(Default)] +pub struct Tangent {} + +impl Display for Tangent { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "the pure tangent instrument") + } +} + +impl Instrument for Tangent { + fn play(&mut self, note: T, rate: U, bpm: S, duration: V, volume: R) -> Wave + where + T: Into, + S: Into, + U: Into, + V: Into, + R: Into, + { + let note = note.into(); + let rate = rate.into(); + let duration = duration.into(); + let volume = volume.into(); + + let bpm = bpm.into(); + let duration_per_beat: Seconds = bpm.into(); + let duration: Seconds = (*duration * *duration_per_beat).into(); + + let nb_samples = (*duration * *rate).floor() as usize; + + let note_h: Hertz = note.into(); + + let step = *note_h * 2f64 * PI / *rate; + + let mut theta = 0f64; + + let mut result: Vec = vec![]; + + let mut current_adsr = 0f64; + + for i in 0..nb_samples { + current_adsr = adsr(i, nb_samples, current_adsr); + + let y: f64 = (*volume * f64::tan(theta)) * current_adsr; + + let y = if y >= 1f64 { + 1f64 + } else if y <= -1f64 { + -1f64 + } else { + y + }; + + result.push(y.into()); + + theta += step; + } + + Wave::new(result) + } +} + +// Piano Without overtones + +#[derive(Default)] +pub struct PianoWOver {} + +impl Display for PianoWOver { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "the piano instrument without overtones") + } +} + +impl Instrument for PianoWOver { + fn play(&mut self, note: T, rate: U, bpm: S, duration: V, volume: R) -> Wave + where + T: Into, + S: Into, + U: Into, + V: Into, + R: Into, + { + let note = note.into(); + let rate = rate.into(); + let duration = duration.into(); + let volume = volume.into(); + + let bpm = bpm.into(); + let duration_per_beat: Seconds = bpm.into(); + let duration: Seconds = (*duration * *duration_per_beat).into(); + + let nb_samples = (*duration * *rate).floor() as usize; + + let note_h: Hertz = note.into(); + + let step = *note_h * 2f64 * PI / *rate; + + let mut theta = 0f64; + + let mut result: Vec = vec![]; + + for i in 0..nb_samples { + let mut y = f64::sin(theta); + // let mut y = 0.6f64 * f64::sin(theta) + 0.4f64 * f64::sin(2f64 * theta); + y *= f64::exp((-0.0035f64) * theta); + y += y * y * y; + y *= 1f64 + 16f64 * i as f64 * f64::exp((-6f64) * i as f64); + + y *= *volume; + + result.push(y.into()); + + theta += step; + } + + Wave::new(result) + } +} + +#[derive(Default)] +pub struct PianoVideo {} + +impl Display for PianoVideo { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "the piano instrument that I obtained from a video") + } +} + +impl Instrument for PianoVideo { + fn play(&mut self, note: T, rate: U, bpm: S, duration: V, volume: R) -> Wave + where + T: Into, + S: Into, + U: Into, + V: Into, + R: Into, + { + let note = note.into(); + let rate = rate.into(); + let duration = duration.into(); + let volume = volume.into(); + + let bpm = bpm.into(); + let duration_per_beat: Seconds = bpm.into(); + let duration: Seconds = (*duration * *duration_per_beat).into(); + + let nb_samples = (*duration * *rate).floor() as usize; + + let note_h: Hertz = note.into(); + + let step = *note_h * 2f64 * PI / *rate; + + let mut theta = 0f64; + + let mut result: Vec = vec![]; + + for i in 0..nb_samples { + let mut y = 0.6f64 * f64::sin(theta) + 0.4f64 * f64::sin(2f64 * theta); + y *= f64::exp((-0.0015f64) * theta); + y += y * y * y; + y *= 1f64 + 16f64 * i as f64 * f64::exp((-6f64) * i as f64); + + y *= *volume; + + result.push(y.into()); + + theta += step; + } + + Wave::new(result) + } +} + +#[derive(Default)] +pub struct PianoStackoverflow {} + +impl Display for PianoStackoverflow { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "the piano instrument that I obtained from a stackoverflow answer." + ) + } +} + +impl Instrument for PianoStackoverflow { + fn play(&mut self, note: T, rate: U, bpm: S, duration: V, volume: R) -> Wave + where + T: Into, + S: Into, + U: Into, + V: Into, + R: Into, + { + let note = note.into(); + let rate = rate.into(); + let duration = duration.into(); + let volume = volume.into(); + + let bpm = bpm.into(); + let duration_per_beat: Seconds = bpm.into(); + let duration: Seconds = (*duration * *duration_per_beat).into(); + + let nb_samples = (*duration * *rate).floor() as usize; + + let note_h: Hertz = note.into(); + + // dbg!(note); + // dbg!(note_h); + + let step = *note_h * 2f64 * PI / *rate; + + let mut theta = 0f64; + + let mut result: Vec = vec![]; + + for i in 0..nb_samples { + let mut y = f64::sin(theta) + + (1f64 / 2f64) * f64::sin(2f64 * theta) + + (1f64 / 4f64) * f64::sin(3f64 * theta) + + (1f64 / 8f64) * f64::sin(4f64 * theta) + + (1f64 / 16f64) * f64::sin(5f64 * theta) + + (1f64 / 32f64) * f64::sin(6f64 * theta); + y *= f64::exp((-0.0015f64) * theta); + y += y * y * y; + y *= 1f64 + 16f64 * i as f64 * f64::exp((-6f64) * i as f64); + + y *= *volume; + + result.push(y.into()); + + theta += step; + } + + Wave::new(result) + } +} + +#[allow(dead_code)] +fn adsr(step: usize, total: usize, current: f64) -> f64 { + if step < 4320 { + if current <= 1f64 { + current + 0.0002314814814814815f64 + } else { + 1f64 + } + } else if total - step < 4320 { + if current >= 0f64 { + current - 0.0002314814814814815f64 + } else { + 0f64 + } + } else { + current + } +} + +#[derive(Default)] +pub struct PianoFromC {} + +impl Display for PianoFromC { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "the piano instrument from the last project in C.") + } +} + +impl Instrument for PianoFromC { + fn play(&mut self, note: T, rate: U, bpm: S, duration: V, volume: R) -> Wave + where + T: Into, + S: Into, + U: Into, + V: Into, + R: Into, + { + let note = note.into(); + let rate = rate.into(); + let duration = duration.into(); + let volume = volume.into(); + + let bpm = bpm.into(); + let duration_per_beat: Seconds = bpm.into(); + let duration: Seconds = (*duration * *duration_per_beat).into(); + + let nb_samples = (*duration * *rate).floor() as usize; + + let note_h: Hertz = note.into(); + + // dbg!(note); + // dbg!(note_h); + + let step = *note_h * 2f64 * PI / *rate; + + let mut theta = 0f64; + + let mut result: Vec = vec![]; + + let mut adsr_factor = 1f64; + + #[allow(unused_variables)] + for i in 0..nb_samples { + let mut y = 0.65f64 * f64::sin(theta) + + 0.475f64 * f64::sin(2f64 * theta) + + 0.05f64 * f64::sin(3f64 * theta); + y *= f64::exp((-0.0015f64) * theta); + y += y * y * y; + y *= 1f64 + 16f64 * i as f64 * f64::exp((-6f64) * i as f64); + + adsr_factor = adsr(i, nb_samples, adsr_factor); + y *= adsr_factor; + + y *= *volume; + + // if y >= 1f64 { + // y = 1f64; + // } else if y <= -1f64 { + // y = -1f64; + // } + + result.push(y.into()); + + theta += step; + } + + Wave::new(result) + } +} + +#[derive(Default)] +pub struct Violin {} + +impl Display for Violin { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "an attempt at the violin instrument") + } +} + +impl Instrument for Violin { + fn play(&mut self, note: T, rate: U, bpm: S, duration: V, volume: R) -> Wave + where + T: Into, + S: Into, + U: Into, + V: Into, + R: Into, + { + let note = note.into(); + let rate = rate.into(); + let duration = duration.into(); + let volume = volume.into(); + + let bpm = bpm.into(); + let duration_per_beat: Seconds = bpm.into(); + let duration: Seconds = (*duration * *duration_per_beat).into(); + + let nb_samples = (*duration * *rate).floor() as usize; + + let note_h: Hertz = note.into(); + + let step = *note_h * 2f64 * PI / *rate; + + let mut theta = 0f64; + + let mut result: Vec = vec![]; + + for i in 0..nb_samples { + let mut y = f64::sin(theta) + + f64::sin(2f64 * theta) + + (3f64 / 4f64) * f64::sin(3f64 * theta) + + (1f64 / 8f64) * f64::sin(4f64 * theta) + + (1f64 / 16f64) * f64::sin(5f64 * theta) + + (1f64 / 32f64) * f64::sin(6f64 * theta) + + (1f64 / 64f64) * f64::sin(7f64 * theta) + + (10f64 / 128f64) * f64::sin(8f64 * theta) + + (1f64 / 256f64) * f64::sin(9f64 * theta) + + (1f64 / 512f64) * f64::sin(10f64 * theta) + + (1f64 / 1024f64) * f64::sin(11f64 * theta) + + (1f64 / 2048f64) * f64::sin(12f64 * theta); + y *= f64::exp((-0.00051f64) * theta); + // y += y * y * y; + y *= 1f64 + 16f64 * i as f64 * f64::exp((-6f64) * i as f64); + + y *= *volume; + + result.push(y.into()); + + theta += step; + } + + Wave::new(result) + } +} 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:?}"); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ef4b93f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,214 @@ +use rumu::{self, output::Output}; +use std::env; +use std::path::PathBuf; + +#[derive(Debug)] +struct MainError { + mes: &'static str, +} + +impl std::fmt::Display for MainError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "main error: {}", self.mes) + } +} + +impl std::error::Error for MainError {} + +impl From<&'static str> for MainError { + fn from(mes: &'static str) -> Self { + Self { mes } + } +} + +fn get_root() -> std::io::Result { + let path = env::current_dir()?; + let path_ancestors = path.as_path().ancestors(); + + for p in path_ancestors { + let has_cargo_p = std::fs::read_dir(p)? + .into_iter() + .any(|p| p.unwrap().file_name() == std::ffi::OsString::from("Cargo.toml")); + + if has_cargo_p { + return Ok(PathBuf::from(p)); + } + } + + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Cannot find project root", + )) +} + +fn main() -> Result<(), Box> { + let args: Vec<_> = env::args().skip(1).collect(); + + if args.iter().any(|string| string as &str == "-h") { + println!( + "Usage: cargo run [--features ffmpeg] -- [-h] [sheet filename] [output filename] [encoder]" + ); + + println!(); + + let prefix = " ".repeat(7); + + println!("{prefix}-h: just print this usage and exit"); + + println!(); + + println!("{prefix}sheet filename \t the file to read the sheet from"); + println!("{prefix}output filename \t the name of the output audio file"); + println!("{prefix}encoder \t\t the encoder used to encode the output"); + + println!("{}", "-".repeat(89)); + + println!("{prefix}To skip a paramter, use \"-\" in its place"); + + return Ok(()); + } + + let mut sheet_name = "Ievan Polkka".to_owned(); + + let mut output_name = "Ievan Polkka.opus".to_owned(); + + let mut encoder = if cfg!(feature = "ffmpeg") { + "opus" + } else { + "plain" + }; + + match args.len() { + 0 => {} + 1 => { + if args[0] != "-" { + sheet_name = args[0].to_owned(); + output_name = format!("{sheet_name}.opus"); + } + } + 2 => { + if args[0] != "-" { + sheet_name = args[0].to_owned(); + } + + if args[1] != "-" { + output_name = args[1].to_owned(); + } else { + output_name = format!("{sheet_name}.opus"); + } + } + 3 => { + if args[0] != "-" { + sheet_name = args[0].to_owned(); + } + + let mut output_not_specified = false; + + if args[1] != "-" { + output_name = args[1].to_owned(); + } else { + output_not_specified = true; + output_name = format!("{sheet_name}.{encoder}"); + } + + if args[2] != "-" { + encoder = &args[2]; + + if output_not_specified { + output_name = format!("{sheet_name}.{encoder}"); + } + } + } + _ => { + println!( + "Usage: cargo run [--features ffmpeg] -- \ + [-h] [sheet filename] [output filename] [encoder]" + ); + + println!(); + + let prefix = " ".repeat(7); + + println!("{prefix}-h: just print this usage and exit"); + + println!(); + + println!("{prefix}sheet filename \t the file to read the sheet from"); + println!("{prefix}output filename \t the name of the output audio file"); + println!("{prefix}encoder \t\t the encoder used to encode the output"); + + println!("{}", "-".repeat(89)); + + println!("{prefix}To skip a paramter, use \"-\" in its place"); + + std::process::exit(1); + } + } + + let project_root = get_root()?.to_str().unwrap().to_owned(); + + output_name = format!("{project_root}/audio files/{output_name}"); + + let source = std::fs::read_to_string(format!("{project_root}/songs/{sheet_name}.rumu"))?; + + let mut sheet: rumu::sheet::Sheet = source.parse()?; + + let wave: rumu::Wave = (&mut sheet).into(); + + let rate: rumu::Samples = 44100f64.into(); + + match encoder { + "opus" => { + if !cfg!(feature = "ffmpeg") { + return Err( + "To use the opus encoder one has to enable the \"ffmpeg\" feature.".into(), + ); + } + + #[cfg(feature = "ffmpeg")] + { + let output = rumu::output::ffmpeg_output::OpusOutput::default(); + + output.save(wave, rate, &output_name)?; + } + } + "mp3" => { + if !cfg!(feature = "ffmpeg") { + return Err( + "To use the mp3 encoder one has to enable the \"ffmpeg\" feature.".into(), + ); + } + + #[cfg(feature = "ffmpeg")] + { + let output = rumu::output::ffmpeg_output::MP3Output::default(); + + output.save(wave, rate, &output_name)?; + } + } + "aac" => { + if !cfg!(feature = "ffmpeg") { + return Err( + "To use the aac encoder one has to enable the \"ffmpeg\" feature.".into(), + ); + } + + #[cfg(feature = "ffmpeg")] + { + let output = rumu::output::ffmpeg_output::AACOutput::default(); + + output.save(wave, rate, &output_name)?; + } + } + "plain" => { + let output = rumu::output::PlainOutput::default(); + + output.save(wave, rate, &output_name)?; + } + _ => { + return Err("Unrecognized encoder: {encoder}".into()); + } + } + + Ok(()) +} diff --git a/src/output/ffmpeg_output.rs b/src/output/ffmpeg_output.rs new file mode 100644 index 0000000..2132394 --- /dev/null +++ b/src/output/ffmpeg_output.rs @@ -0,0 +1,417 @@ +//! This file implements the output to save an audio file using ffmpeg +//! libraries. + +use super::*; + +#[allow(unused_imports)] +use std::{fs::File, time::Duration}; + +#[allow(unused_imports)] +use ac_ffmpeg::{ + codec::{ + audio::{ + self, + frame::{ChannelLayout, SampleFormat}, + AudioEncoder, AudioFrameMut, + }, + CodecParameters, Encoder, + }, + format::{ + io::IO, + muxer::{Muxer, OutputFormat}, + }, + time::{TimeBase, Timestamp}, + Error as FFError, +}; + +impl From for OutputError { + fn from(ff: FFError) -> Self { + OutputError::FFMpeg(ff.to_string()) + } +} + +/// Open a given output file. +fn open_output(path: &str, elementary_streams: &[CodecParameters]) -> Result, FFError> { + let output_format = OutputFormat::guess_from_file_name(path) + .ok_or_else(|| FFError::new(format!("unable to guess output format for file: {}", path)))?; + + let output = std::fs::OpenOptions::new() + .create(true) + .write(true) + .open(path) + .map_err(|err| FFError::new(format!("unable to create output file {}: {}", path, err)))?; + + let io = IO::from_seekable_write_stream(output); + + let mut muxer_builder = Muxer::builder(); + + for codec_parameters in elementary_streams { + muxer_builder.add_stream(codec_parameters)?; + } + + muxer_builder.build(io, output_format) +} + +/// A dummy struct to hold a generic function for saving waves. +#[derive(Default)] +pub struct OpusOutput {} + +impl Output for OpusOutput { + fn save(&self, data: Wave, rate: Samples, name: &str) -> Result<(), OutputError> { + if data.is_empty() { + dbg!(); + return Err(OutputError::EmptyWaves); + } + + if std::fs::metadata(name).is_ok() { + std::fs::remove_file(name)?; + } + + let sample_format: SampleFormat = "flt".parse().unwrap(); + + let channel_layout = ChannelLayout::from_channels(2u32).unwrap(); + + let encoder_builder = AudioEncoder::builder("libopus")?; + + let time_base = TimeBase::new(1, 50); + + let duration = Duration::from_nanos((data.len() * *rate as usize) as u64); + + let mut encoder = encoder_builder + .sample_rate(*rate as u32) + .sample_format(sample_format) + .channel_layout(channel_layout) + .bit_rate(96000u64) + .time_base(time_base) + .build()?; + + let codec_parameters = encoder.codec_parameters().into(); + + let mut muxer = open_output(name, &[codec_parameters])?; + + // NOTE: FFMPEG assumes the bytes to be laid out in the + // platform-natural endianness, so this is actually portable, + // in the sense that this must be used with FFMPEG, which + // knows how to deal with this endianness. + let bytes: Vec = data + .iter() + .flat_map(|pulse| (**pulse as f32).to_ne_bytes()) + .collect(); + + let mut frame_idx = 0; + let mut frame_timestamp = Timestamp::new(frame_idx, time_base); + let max_timestamp = Timestamp::from_millis(0) + duration; + + // NOTE: Each frame occupies 20 milliseconds, as recommended + // in the official documentation page for FFMPEG. + let samples_per_frame = (*rate * 0.02f64) as usize; + + let mut bytes_index = 0usize; + + while frame_timestamp < max_timestamp && bytes_index < bytes.len() { + let mut frame = AudioFrameMut::silence( + channel_layout, + sample_format, + *rate as u32, + samples_per_frame, + ) + .with_time_base(time_base) + .with_pts(frame_timestamp); + + let mut planes = frame.planes_mut(); + + let data = planes[0].data_mut(); + + let number_of_bytes = std::cmp::min(samples_per_frame, (bytes.len() - bytes_index) / 4); + + for i in 0..number_of_bytes { + data[i * 8] = bytes[bytes_index + 4 * i]; + data[i * 8 + 1] = bytes[bytes_index + 4 * i + 1]; + data[i * 8 + 2] = bytes[bytes_index + 4 * i + 2]; + data[i * 8 + 3] = bytes[bytes_index + 4 * i + 3]; + data[i * 8 + 4] = bytes[bytes_index + 4 * i]; + data[i * 8 + 5] = bytes[bytes_index + 4 * i + 1]; + data[i * 8 + 6] = bytes[bytes_index + 4 * i + 2]; + data[i * 8 + 7] = bytes[bytes_index + 4 * i + 3]; + } + + let frame = frame.freeze(); + + encoder.push(frame)?; + + while let Some(packet) = encoder.take()? { + muxer.push(packet.with_stream_index(0))?; + } + + frame_idx += 1; + + frame_timestamp = Timestamp::new(frame_idx, time_base); + + bytes_index += samples_per_frame * 4; + } + + encoder.flush()?; + + while let Some(packet) = encoder.take()? { + muxer.push(packet.with_stream_index(0))?; + } + + muxer.flush()?; + + Ok(()) + } +} + +/// A dummy struct to hold a generic function for saving waves. +#[derive(Default)] +pub struct MP3Output {} + +impl Output for MP3Output { + fn save(&self, data: Wave, rate: Samples, name: &str) -> Result<(), OutputError> { + if data.is_empty() { + dbg!(); + return Err(OutputError::EmptyWaves); + } + + if std::fs::metadata(name).is_ok() { + std::fs::remove_file(name)?; + } + + let sample_format: SampleFormat = "fltp".parse().unwrap(); + + let channel_layout = ChannelLayout::from_channels(2u32).unwrap(); + + let encoder_builder = AudioEncoder::builder("libmp3lame")?; + + let time_base = TimeBase::new(1, 50); + + let duration = Duration::from_nanos((data.len() * *rate as usize) as u64); + + let mut encoder = encoder_builder + .sample_rate(*rate as u32) + .sample_format(sample_format) + .channel_layout(channel_layout) + .bit_rate(96000u64) + .time_base(time_base) + .build()?; + + let codec_parameters = encoder.codec_parameters().into(); + + let mut muxer = open_output(name, &[codec_parameters])?; + + // NOTE: FFMPEG assumes the bytes to be laid out in the + // platform-natural endianness, so this is actually portable, + // in the sense that this must be used with FFMPEG, which + // knows how to deal with this endianness. + let bytes: Vec = data + .iter() + .flat_map(|pulse| (**pulse as f32).to_ne_bytes()) + .collect(); + + let mut frame_idx = 0; + let mut frame_timestamp = Timestamp::new(frame_idx, time_base); + let max_timestamp = Timestamp::from_millis(0) + duration; + + // NOTE: Each frame occupies 20 milliseconds, as recommended + // in the official documentation page for FFMPEG. + let samples_per_frame = (*rate * 0.02f64) as usize; + + let mut bytes_index = 0usize; + + while frame_timestamp < max_timestamp && bytes_index < bytes.len() { + let mut frame = AudioFrameMut::silence( + channel_layout, + sample_format, + *rate as u32, + samples_per_frame, + ) + .with_time_base(time_base) + .with_pts(frame_timestamp); + + let mut planes = frame.planes_mut(); + + let number_of_bytes = std::cmp::min(samples_per_frame, (bytes.len() - bytes_index) / 4); + + for i in 0..number_of_bytes { + planes[0].data_mut()[i * 4] = bytes[bytes_index + 4 * i]; + planes[0].data_mut()[i * 4 + 1] = bytes[bytes_index + 4 * i + 1]; + planes[0].data_mut()[i * 4 + 2] = bytes[bytes_index + 4 * i + 2]; + planes[0].data_mut()[i * 4 + 3] = bytes[bytes_index + 4 * i + 3]; + planes[1].data_mut()[i * 4] = bytes[bytes_index + 4 * i]; + planes[1].data_mut()[i * 4 + 1] = bytes[bytes_index + 4 * i + 1]; + planes[1].data_mut()[i * 4 + 2] = bytes[bytes_index + 4 * i + 2]; + planes[1].data_mut()[i * 4 + 3] = bytes[bytes_index + 4 * i + 3]; + } + + let frame = frame.freeze(); + + encoder.push(frame)?; + + while let Some(packet) = encoder.take()? { + muxer.push(packet.with_stream_index(0))?; + } + + frame_idx += 1; + + frame_timestamp = Timestamp::new(frame_idx, time_base); + + bytes_index += samples_per_frame * 4; + } + + encoder.flush()?; + + while let Some(packet) = encoder.take()? { + muxer.push(packet.with_stream_index(0))?; + } + + muxer.flush()?; + + Ok(()) + } +} + +/// A dummy struct to hold a generic function for saving waves. +#[derive(Default)] +pub struct AACOutput {} + +impl Output for AACOutput { + fn save(&self, data: Wave, rate: Samples, name: &str) -> Result<(), OutputError> { + if data.is_empty() { + dbg!(); + return Err(OutputError::EmptyWaves); + } + + if std::fs::metadata(name).is_ok() { + std::fs::remove_file(name)?; + } + + let sample_format: SampleFormat = "fltp".parse().unwrap(); + + let channel_layout = ChannelLayout::from_channels(2u32).unwrap(); + + let encoder_builder = AudioEncoder::builder("aac")?; + + let time_base = TimeBase::new(1, 50); + + let duration = Duration::from_nanos((data.len() * *rate as usize) as u64); + + let mut encoder = encoder_builder + .sample_rate(*rate as u32) + .sample_format(sample_format) + .channel_layout(channel_layout) + .bit_rate(96000u64) + .time_base(time_base) + .build()?; + + let codec_parameters = encoder.codec_parameters().into(); + + let mut muxer = open_output(name, &[codec_parameters])?; + + // NOTE: FFMPEG assumes the bytes to be laid out in the + // platform-natural endianness, so this is actually portable, + // in the sense that this must be used with FFMPEG, which + // knows how to deal with this endianness. + let bytes: Vec = data + .iter() + .flat_map(|pulse| (**pulse as f32).to_ne_bytes()) + .collect(); + + let mut frame_idx = 0; + let mut frame_timestamp = Timestamp::new(frame_idx, time_base); + let max_timestamp = Timestamp::from_millis(0) + duration; + + // NOTE: Each frame occupies 20 milliseconds, as recommended + // in the official documentation page for FFMPEG. + let samples_per_frame = (*rate * 0.02f64) as usize; + + let mut bytes_index = 0usize; + + while frame_timestamp < max_timestamp && bytes_index < bytes.len() { + let mut frame = AudioFrameMut::silence( + channel_layout, + sample_format, + *rate as u32, + samples_per_frame, + ) + .with_time_base(time_base) + .with_pts(frame_timestamp); + + let mut planes = frame.planes_mut(); + + let number_of_bytes = std::cmp::min(samples_per_frame, (bytes.len() - bytes_index) / 4); + + for i in 0..number_of_bytes { + planes[0].data_mut()[i * 4] = bytes[bytes_index + 4 * i]; + planes[0].data_mut()[i * 4 + 1] = bytes[bytes_index + 4 * i + 1]; + planes[0].data_mut()[i * 4 + 2] = bytes[bytes_index + 4 * i + 2]; + planes[0].data_mut()[i * 4 + 3] = bytes[bytes_index + 4 * i + 3]; + planes[1].data_mut()[i * 4] = bytes[bytes_index + 4 * i]; + planes[1].data_mut()[i * 4 + 1] = bytes[bytes_index + 4 * i + 1]; + planes[1].data_mut()[i * 4 + 2] = bytes[bytes_index + 4 * i + 2]; + planes[1].data_mut()[i * 4 + 3] = bytes[bytes_index + 4 * i + 3]; + } + + let frame = frame.freeze(); + + encoder.push(frame)?; + + while let Some(packet) = encoder.take()? { + muxer.push(packet.with_stream_index(0))?; + } + + frame_idx += 1; + + frame_timestamp = Timestamp::new(frame_idx, time_base); + + bytes_index += samples_per_frame * 4; + } + + encoder.flush()?; + + while let Some(packet) = encoder.take()? { + muxer.push(packet.with_stream_index(0))?; + } + + muxer.flush()?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ffmpeg_format() { + let _output_format = OutputFormat::guess_from_file_name("test.opus").unwrap(); + let encoder = AudioEncoder::builder("libopus").unwrap(); + let format: SampleFormat = "flt".parse().unwrap(); + let channel_layout = ChannelLayout::from_channels(2u32).unwrap(); + + encoder + .sample_rate(48000u32) + .sample_format(format) + .channel_layout(channel_layout) + .bit_rate(96000u64) + .time_base(TimeBase::new(1, 25)) + .build() + .unwrap(); + + assert!(!format.is_planar()); + + let mut frame = AudioFrameMut::silence(channel_layout, format, 48000u32, 960); + + let mut planes = frame.planes_mut(); + + println!("number of planes: {}", planes.len()); + + let data = planes[0].data_mut(); + + println!("frame size = {}", data.len()); + + data[0] = 16u8; + + println!("Successfully changed a byte of the silent frame!"); + } +} diff --git a/src/output/mod.rs b/src/output/mod.rs new file mode 100644 index 0000000..520a500 --- /dev/null +++ b/src/output/mod.rs @@ -0,0 +1,68 @@ +//! This file implements the interface to save waves into a file. + +use std::{ + error::Error, + fmt::{self, Display, Formatter}, + io::{self, Write}, +}; + +use super::*; + +#[derive(Debug)] +pub enum OutputError { + Io(std::io::Error), + FFMpeg(String), + EmptyWaves, +} + +impl Display for OutputError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Io(e) => write!(f, "io: {e}"), + Self::EmptyWaves => write!(f, "empty waves"), + OutputError::FFMpeg(s) => write!(f, "FFMPEG error: {s}"), + } + } +} + +impl Error for OutputError {} + +impl From for OutputError { + fn from(io: io::Error) -> Self { + Self::Io(io) + } +} + +/// A type that implements this trait implements a generic function +/// for saving waves into files. +pub trait Output { + /// Save waves DATA with RATE samples per second into a file with + /// NAME. + fn save(&self, data: Wave, rate: Samples, name: &str) -> Result<(), OutputError>; +} + +/// A dummy struct to hold a generic function for saving waves. +#[derive(Default)] +pub struct PlainOutput {} + +impl Output for PlainOutput { + fn save(&self, data: Wave, _rate: Samples, name: &str) -> Result<(), OutputError> { + if data.is_empty() { + return Err(OutputError::EmptyWaves); + } + + let bytes: Vec = data.iter().flat_map(|pulse| pulse.to_le_bytes()).collect(); + + let mut file = std::fs::OpenOptions::new() + .create(true) + .write(true) + .open(name)?; + + file.write_all(&bytes)?; + + Ok(()) + } +} + +#[cfg(feature = "ffmpeg")] +pub mod ffmpeg_output; diff --git a/src/sheet/mod.rs b/src/sheet/mod.rs new file mode 100644 index 0000000..581301c --- /dev/null +++ b/src/sheet/mod.rs @@ -0,0 +1,1905 @@ +#![deny(missing_docs)] +//! This file implements a simple recursive-descent parser for parsing +//! plain text music sheets into some intermediate data structure that +//! can later generate the waves to save to an audio file. +//! +//! The format of the sheet can be described as follows. +//! +//! # 1. Assignments +//! +//! Read a string "\"name\"" followed by a block +//! +//! (assign the block to the name) +//! +//! # 2. Blocks +//! +//! Read block+ enclosed in "{}", nor not, if there is only one block +//! +//! ## 2.1. Groups +//! +//! Read group+ enclosed in "()" or not +//! +//! ### 2.1.1. Unit +//! +//! Read unit* +//! +//! #### 2.1.1.1. Instruction +//! +//! Read an instruction starting with an "i" +//! +//! ##### 2.1.1.1.1. Volume +//! +//! Read a volume "vDIGITS(\.DIGITS)?" +//! +//! ##### 2.1.1.1.2. BPM +//! +//! Read a BPM "bDIGITS" +//! +//! ##### 2.1.1.1.3. Instrument +//! +//! Read an instrument "ipiano"/"iviolin". +//! +//! #### 2.1.1.2. Silence +//! +//! Read a silence "rDIGITS\.*" +//! +//! #### 2.1.1.3. Chord +//! +//! Read a chord = tone (/ tone)* +//! +//! ##### 2.1.1.3.1. Octave +//! +//! Read an octave "(oDIGITS)?|\[><\]*" +//! +//! ##### 2.1.1.3.2. Semitone +//! +//! Read a semitone "\[a-g\](\+|\-)*" +//! +//! ##### 2.1.1.3.3. duration +//! +//! Read a duration "DIGITS\.*|pDIGITS(\.DIGITS)?" +//! +//! ### 2.1.2 Repetition specification +//! +//! Read either a [duration](#21133-duration), to specify a cram +//! expression, and/or "*DIGITS" to specify a repetition of DIGITS times. +//! +//! # 3. name +//! +//! Read a name "\[name\]" that has an assigned value + +use super::*; +use std::error::Error; + +pub mod play; + +/// Possibilities of an instrument. +/// +/// Right now only the piano is usable, whereas the violin is but an +/// experiment. +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] +pub enum Instrument { + #[default] + /// A piano instrument + Piano, + /// An experimental violin instrument + Violin, + /// Pure Sinus instrument + Sinus, + /// Pure Tangent instrument + Tangent, +} + +impl Display for Instrument { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Instrument::Piano => write!(f, "piano"), + Instrument::Violin => write!(f, "violin"), + Instrument::Sinus => write!(f, "sinus"), + Instrument::Tangent => write!(f, "tangent"), + } + } +} + +/// An instruction changing some setting. +/// +/// It changes one of the following: +/// +/// 1. the volume +/// +/// 2. the bpm +/// +/// 3. the instrument +#[derive(Debug, Copy, Clone)] +pub enum Instruction { + /// Change volume + VolumeTo(Volume), + /// Change beats per minute + BPMTo(BPM), + /// Change instrument + InstrumentTo(Instrument), +} + +impl From for Instruction { + fn from(volume: Volume) -> Self { + Self::VolumeTo(volume) + } +} + +impl From for Instruction { + fn from(bpm: BPM) -> Self { + Self::BPMTo(bpm) + } +} + +impl From for Instruction { + fn from(instrument: Instrument) -> Self { + Self::InstrumentTo(instrument) + } +} + +impl Display for Instruction { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Instruction::VolumeTo(v) => write!(f, "volume => {}", **v), + Instruction::BPMTo(bpm) => write!(f, "BPM => {}", **bpm), + Instruction::InstrumentTo(instrument) => write!(f, "Instrument => {instrument}"), + } + } +} + +/// Represents a semitone +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] +pub struct SheetSemitone(isize); + +impl SheetSemitone { + fn shift(&mut self, shift: isize) { + self.0 += shift; + } +} + +impl Display for SheetSemitone { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Represents an octave that is either absolute or relative. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum SheetOctave { + /// Absolute octave specification + Absolute(usize), + /// Relative octave specification + Relative(isize), +} + +impl From for SheetOctave { + fn from(abs: usize) -> Self { + Self::Absolute(abs) + } +} + +impl From for SheetOctave { + fn from(rel: isize) -> Self { + Self::Relative(rel) + } +} + +impl Display for SheetOctave { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Absolute(val) => write!(f, "o{val}"), + Self::Relative(shift) => { + let string = if shift.is_positive() { + str::repeat(">", *shift as usize) + } else { + str::repeat("<", (-*shift) as usize) + }; + + write!(f, "{string}") + } + } + } +} + +impl From<(usize, SheetSemitone)> for Semitones { + fn from((octave, semitone): (usize, SheetSemitone)) -> Self { + let octave_translation = 12 * (octave as isize - 4isize); + + // println!("octave = {octave_translation}"); + // println!("semitone = {}", semitone.0); + + ((semitone.0 + octave_translation - 9) as f64).into() + } +} + +/// Represents a tone and an optional octave. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] +struct SheetTone { + semitone: SheetSemitone, + octave: Option, +} + +impl SheetTone { + fn new(semitone: SheetSemitone, octave: Option) -> Self { + Self { semitone, octave } + } +} + +impl Display for SheetTone { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let semitone = self.semitone; + + if let Some(octave) = self.octave { + write!(f, "{octave} {semitone}") + } else { + write!(f, "{semitone}") + } + } +} + +impl From for SheetTone +where + T: Into, +{ + fn from(semitone: T) -> Self { + let semitone = semitone.into(); + let octave = None; + + Self { semitone, octave } + } +} + +/// A chord is a vector of tones. +#[derive(Debug, Clone, Default)] +pub struct SheetChord(Vec); + +impl Display for SheetChord { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self.0.len() { + 0 => write!(f, ""), + _ => { + let mut iter = self.0.iter(); + let first_tone = *iter.by_ref().take(1).next().unwrap(); + + write!(f, "{first_tone}")?; + + for tone in iter { + write!(f, " / {tone}")?; + } + + Ok(()) + } + } + } +} + +impl From for SheetChord +where + T: Into, +{ + fn from(arg: T) -> Self { + Self(vec![arg.into()]) + } +} + +impl From> for SheetChord +where + T: Into, +{ + fn from(arg: Vec) -> Self { + Self(arg.into_iter().map(Into::into).collect()) + } +} + +/// A duration for a group. +/// +/// This is essentially a float number, except that it also accepts +/// dots. +#[derive(Debug, Copy, Clone, Default)] +pub struct SheetDuration { + beats: Option, + dots: usize, +} + +impl SheetDuration { + /// Return an optional `Beats`. + pub fn beats(&self) -> Option { + self.beats + } + + /// Return the number of dots. + pub fn dots(&self) -> usize { + self.dots + } +} + +impl Display for SheetDuration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(b) = self.beats() { + write!(f, "{}", *b)?; + } + + let dots_str = str::repeat(".", self.dots); + + write!(f, "{dots_str}") + } +} + +impl From for SheetDuration { + fn from(dots: usize) -> Self { + let beats = Default::default(); + Self { beats, dots } + } +} + +impl> From for SheetDuration { + fn from(arg: T) -> Self { + let beats = arg.into(); + let beats = Some(beats); + let dots = Default::default(); + Self { beats, dots } + } +} + +impl> From<(Option, usize)> for SheetDuration { + fn from((arg, dots): (Option, usize)) -> Self { + let beats = arg.map(|b| b.into()); + + Self { beats, dots } + } +} + +/// A unit of the sheet. +/// +/// It is one of the following: +/// +/// 1. an ordinary chord. +/// +/// 2. a silence. +/// +/// 3. an instruction. +#[derive(Debug, Clone)] +pub enum SheetUnit { + /// A combination of a chord and the number of beats. + Tone(SheetChord, SheetDuration), + /// A silence that lasts a number of beats. + Silence(SheetDuration), + /// An [`Instruction`]. + Instruction(Instruction), +} + +impl Default for SheetUnit { + fn default() -> Self { + // The default is silence + let duration: SheetDuration = Default::default(); + duration.into() + } +} + +impl> From for SheetUnit { + fn from(instruction: T) -> Self { + let instruction = instruction.into(); + + Self::Instruction(instruction) + } +} + +impl From for SheetUnit { + fn from(duration: SheetDuration) -> Self { + Self::Silence(duration) + } +} + +impl From for SheetUnit { + fn from(chord: SheetChord) -> Self { + Self::Tone(chord, Default::default()) + } +} + +impl SheetUnit { + /// Adjusting the duration of the chord. + /// + /// # Warning + /// + /// This function does nothing if called on a unit that is not a + /// tone unit. + pub fn set_duration(&mut self, duration: Beats) { + match self { + Self::Tone(chord, _) => { + *self = Self::Tone(chord.clone(), duration.into()); + } + Self::Silence(_) => { + *self = Self::Silence(duration.into()); + } + _ => (), + } + } +} + +impl Display for SheetUnit { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Tone(chord, duration) => write!(f, "{chord},{duration}"), + Self::Silence(duration) => write!(f, "r{duration}"), + Self::Instruction(instruction) => write!(f, "{instruction}"), + } + } +} + +/// A sheet group node either specifies the number of repetitions or is +/// a unit. +#[derive(Debug, Clone)] +pub enum SheetGroupNode { + /// The number of repetition and optionally the crammed duration. + /// + /// Note that the number of repetition can be zero. + /// + /// The crammed duration, if present, will be used to adjust the + /// durations in the notes. + Intermediate(usize, Option), + /// A unit + Unit(SheetUnit), +} + +impl SheetGroupNode { + /// Set the repetition number of an intermediate node. + /// + /// # Panic + /// + /// This function panics if called on a node that is not an + /// intermediate node. + pub fn set_repetition(&mut self, repetition: usize) { + match self { + Self::Intermediate(_, duration) => { + *self = Self::Intermediate(repetition, *duration); + } + _ => { + panic!("setting intermediate on a unit: {self:?}"); + } + } + } + + /// Set the crammed duration of an intermediate node. + /// + /// # Panic + /// + /// This function panics if called on a node that is not an + /// intermediate node. + pub fn set_cram(&mut self, duration: Option) { + match self { + Self::Intermediate(repetition, _) => { + *self = Self::Intermediate(*repetition, duration); + } + _ => { + panic!("setting intermediate on a unit: {self:?}"); + } + } + } + + /// Adjusting the durations of a node to fit within a crammed + /// duration. + pub fn set_duration(&mut self, duration: Option) { + match self { + Self::Unit(unit) => { + if let Some(duration) = duration { + unit.set_duration(duration); + } + } + SheetGroupNode::Intermediate(repetition, _) => { + *self = SheetGroupNode::Intermediate(*repetition, duration.map(Into::into)); + } + } + } +} + +/// A sheet group is a tree whose leafs are units and whose +/// intermediate nodes specify the number of repetitions. +#[derive(Debug, Clone, Default)] +pub struct SheetGroup { + nodes: Vec, + edges: Vec>, +} + +impl SheetGroup { + /// Construct a new group. + pub fn new(nodes: Vec, edges: Vec>) -> Self { + Self { nodes, edges } + } + + /// Return the node at position N. + pub fn get_node(&self, n: usize) -> &SheetGroupNode { + self.nodes.get(n).unwrap() + } + + /// Return the children of the node at position N. + pub fn get_children(&self, n: usize) -> &[usize] { + self.edges.get(n).unwrap() + } + + /// Adjust durations for each chord in the group. + fn do_cram(&mut self, start_duration: &mut Beats) { + if self.nodes.is_empty() { + return; + } + + // First calculate the durations of each node, so that we can + // find out their respective proportions later on. + + let mut durations_array: Vec = vec![0f64.into(); self.nodes.len()]; + let mut seen = vec![false; self.nodes.len()]; + + let current_duration = start_duration; + + let mut stack = vec![0]; + + let mut cram_result: Vec<(usize, Beats)> = vec![]; + + while let Some(top) = stack.pop() { + match self.nodes[top] { + SheetGroupNode::Intermediate(repetition, Some(cram)) => { + match repetition { + 0 => { + continue; + } + 1 => { + durations_array[top] = (&cram, *current_duration).into(); + } + _ => { + durations_array[top] = (&cram, *current_duration).into(); + + durations_array[top] = + (*durations_array[top] * repetition as f64).into(); + } + } + + for child in self.edges[top].iter().rev() { + stack.push(*child); + } + } + SheetGroupNode::Intermediate(repetition, _) => { + if repetition == 0 { + continue; + } + + if !seen[top] { + stack.push(top); + + for child in self.edges[top].iter().rev().copied() { + stack.push(child); + } + } else { + let summation: f64 = self.edges[top] + .iter() + .map(|node| *durations_array[*node]) + .sum(); + + match repetition { + 0 => unreachable!(), + 1 => { + durations_array[top] = summation.into(); + } + _ => { + durations_array[top] = summation.into(); + + durations_array[top] = + (*durations_array[top] * repetition as f64).into(); + } + } + } + } + SheetGroupNode::Unit(SheetUnit::Tone(_, duration)) => { + let final_duration: Beats = (&duration, *current_duration).into(); + + durations_array[top] = final_duration; + + *current_duration = final_duration; + + cram_result.push((top, final_duration)); + } + SheetGroupNode::Unit(SheetUnit::Silence(duration)) => { + let final_duration: Beats = (&duration, *current_duration).into(); + + durations_array[top] = final_duration; + + *current_duration = final_duration; + + cram_result.push((top, final_duration)); + } + // Instructions have no duration. + _ => {} + } + + seen[top] = true; + } + + for (child, duration) in cram_result { + self.nodes[child].set_duration(Some(duration)); + } + + // Then do a depth first traversal to "cram" the durations, + // based on their respective proportions. + + let mut stack = vec![( + 0, + matches!(self.nodes[0], SheetGroupNode::Intermediate(_, Some(_))), + )]; + + while let Some((top, top_cram_p)) = stack.pop() { + if let SheetGroupNode::Intermediate(repetition, cram) = self.nodes[top] { + if repetition == 0 { + continue; + } + + let new_cram_p = top_cram_p || cram.is_some(); + + let top_duration = *durations_array[top]; + + let total_duration: f64 = self.edges[top] + .iter() + .copied() + .map(|child| *durations_array[child]) + .sum(); + + if new_cram_p { + let mut cram_result: Vec<(usize, Beats)> = vec![]; + + // println!("top = {top}"); + + for child in self.edges[top].iter().copied() { + if matches!( + self.nodes[child], + SheetGroupNode::Unit(SheetUnit::Instruction(_)) + ) { + continue; + } + // println!("child = {child}"); + + let child_duration = *durations_array[child]; + + let mut crammed_duration: Beats = + ((child_duration / total_duration) * top_duration).into(); + + if let SheetGroupNode::Intermediate(repetition, _) = self.nodes[child] { + match repetition { + 0 => { + continue; + } + 1 => { + // nothing to do + } + _ => { + crammed_duration = + (*crammed_duration / repetition as f64).into(); + } + } + } + + cram_result.push((child, crammed_duration)); + } + + for (child, crammed_duration) in cram_result { + self.nodes[child].set_duration(Some(crammed_duration)); + // durations_array[child] = crammed_duration; + } + } + + stack.append( + &mut self.edges[top] + .iter() + .copied() + .filter_map(|child| match self.nodes[child] { + SheetGroupNode::Intermediate(repetition, _) if repetition != 0 => { + Some((child, new_cram_p)) + } + _ => None, + }) + .collect(), + ); + } + } + } +} + +/// A sheet block is a sequence of groups. +/// +/// A block is delimited by curly braces. +#[derive(Debug, Clone, Default)] +pub struct SheetBlock { + groups: Vec, +} + +impl SheetBlock { + /// A plain constructor. + pub fn new(groups: Vec) -> Self { + Self { groups } + } +} + +impl Deref for SheetBlock { + type Target = [SheetGroup]; + + fn deref(&self) -> &Self::Target { + &self.groups + } +} + +/// A sheet consists of a vector of blocks. +/// +/// If a sheet does not have any blocks, a block is automatically +/// inferred to contain the entire sheet. +/// +/// If a sheet has multiple blocks, every block will be played at the +/// same time. +#[derive(Debug, Clone, Default)] +pub struct Sheet { + blocks: Vec, +} + +impl Sheet { + /// A plain constructor. + pub fn new(blocks: Vec) -> Self { + Self { blocks } + } +} + +impl Deref for Sheet { + type Target = [SheetBlock]; + + fn deref(&self) -> &Self::Target { + &self.blocks + } +} + +/// A parsing error. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum SheetParseError { + /// Empty sheet is not allowed. + EmptySheet, + /// Encounters an invalid character in a comment. + InvalidComment(usize), + /// Expecting a closing block. + HangingBlock(usize), + /// Invalid block. + InvalidBlock(usize), + /// Expecting a closing group. + HangingGroup(usize), + /// Invalid group. + InvalidGroup(usize), + /// Invalid character + InvalidChar(usize), + /// Invalid instruction + InvalidInstruction(usize), + /// Invalid instrument instruction + InvalidInstrument(usize), + /// Invalid volume instruction + InvalidVolume(usize), + /// Invalid bpm instruction + InvalidBPM(usize), + /// Invalid tone + InvalidTone(usize), + /// Invalid octave + InvalidOctave(usize), + /// Invalid duration + InvalidDuration(usize), + /// Invalid repetition + InvalidRepetition(usize), +} + +impl Display for SheetParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + SheetParseError::EmptySheet => write!(f, "found an empty sheet"), + SheetParseError::InvalidComment(pos) => write!(f, "invalid comment at {pos}"), + SheetParseError::HangingBlock(pos) => write!(f, "expecting closing block at {pos}"), + SheetParseError::InvalidBlock(pos) => write!(f, "invalid block at {pos}"), + SheetParseError::HangingGroup(pos) => write!(f, "expecting closing group at {pos}"), + SheetParseError::InvalidGroup(pos) => write!(f, "invalid group at {pos}"), + SheetParseError::InvalidChar(pos) => write!(f, "invalid character at {pos}"), + SheetParseError::InvalidInstruction(pos) => write!(f, "invalid instruction at {pos}"), + SheetParseError::InvalidInstrument(pos) => write!(f, "invalid instrument at {pos}"), + SheetParseError::InvalidVolume(pos) => write!(f, "invalid volume at {pos}"), + SheetParseError::InvalidBPM(pos) => write!(f, "invalid BPM at {pos}"), + SheetParseError::InvalidTone(pos) => write!(f, "invalid tone at {pos}"), + SheetParseError::InvalidOctave(pos) => write!(f, "invalid octave at {pos}"), + SheetParseError::InvalidDuration(pos) => write!(f, "invalid duration at {pos}"), + SheetParseError::InvalidRepetition(pos) => write!(f, "invalid repetition at {pos}"), + } + } +} + +impl Error for SheetParseError {} + +impl SheetParseError { + /// Translate the positions in the error by a certain amount. + pub fn shift_pos(&self, shift_len: usize) -> Self { + match self { + SheetParseError::EmptySheet => self.clone(), + SheetParseError::InvalidComment(pos) => Self::InvalidComment(pos + shift_len), + SheetParseError::HangingBlock(pos) => Self::HangingBlock(pos + shift_len), + SheetParseError::InvalidBlock(pos) => Self::InvalidBlock(pos + shift_len), + SheetParseError::HangingGroup(pos) => Self::HangingGroup(pos + shift_len), + SheetParseError::InvalidGroup(pos) => Self::InvalidGroup(pos + shift_len), + SheetParseError::InvalidChar(pos) => Self::InvalidChar(pos + shift_len), + SheetParseError::InvalidInstruction(pos) => Self::InvalidInstruction(pos + shift_len), + SheetParseError::InvalidInstrument(pos) => Self::InvalidInstrument(pos + shift_len), + SheetParseError::InvalidVolume(pos) => Self::InvalidVolume(pos + shift_len), + SheetParseError::InvalidBPM(pos) => Self::InvalidBPM(pos + shift_len), + SheetParseError::InvalidTone(pos) => Self::InvalidTone(pos + shift_len), + SheetParseError::InvalidOctave(pos) => Self::InvalidOctave(pos + shift_len), + SheetParseError::InvalidDuration(pos) => Self::InvalidDuration(pos + shift_len), + SheetParseError::InvalidRepetition(pos) => Self::InvalidRepetition(pos + shift_len), + } + } +} + +fn skip_by_fn(s: &str, f: fn(char) -> bool) -> usize { + for (index, c) in s.chars().enumerate() { + if !f(c) { + return index; + } + } + + s.len() +} + +fn is_wsp(c: char) -> bool { + c == ' ' || c == '\t' +} + +fn is_wsp_or_newline(c: char) -> bool { + is_wsp(c) || c == '\n' || c == '\r' +} + +fn is_v_char(c: char) -> bool { + ('!'..='~').contains(&c) +} + +fn is_wsp_or_v_char(c: char) -> bool { + is_wsp(c) || is_v_char(c) +} + +// fn is_alpha(c: char) -> bool { +// ('A'..='Z').contains(&c) || ('a'..='z').contains(&c) +// } + +fn is_decimal_digit(c: char) -> bool { + ('0'..='9').contains(&c) +} + +// fn is_binary_digit(c: char) -> bool { +// c == '0' || c == '1' +// } + +// fn is_hexadecimal_digit(c: char) -> bool { +// is_decimal_digit(c) || ('A'..='F').contains(&c) || ('a'..='f').contains(&c) +// } + +// fn is_alpha_or_digit_or_hyphen(c: char) -> bool { +// is_alpha(c) || is_decimal_digit(c) || c == '-' +// } + +// #[allow(unused)] +// fn is_char_not_quote(c: char) -> bool { +// (' '..='~').contains(&c) && c != '"' +// } + +/// Skip comments and newlines. +fn skip_c_nl(s: &str) -> Result, SheetParseError> { + if s.is_empty() { + return Ok(Some(0)); + } + + let s_len = s.len(); + + match s.chars().next().unwrap() { + ';' => { + let after_wsp_and_v_char = skip_by_fn(&s[1..], is_wsp_or_v_char); + + if 1 + after_wsp_and_v_char >= s_len { + Ok(Some(s_len)) + } else { + match s.chars().nth(1 + after_wsp_and_v_char).unwrap() { + '\n' | '\r' => Ok(Some(after_wsp_and_v_char + 2)), + _ => Err(SheetParseError::InvalidComment(1 + after_wsp_and_v_char)), + } + } + } + '\n' | '\r' => Ok(Some(1)), + _ => Ok(None), + } +} + +/// Skip whitespaces, newlines, and comments. +fn skip_wsp_and_newline_and_comments(s: &str) -> Result { + let mut s = s; + let mut continue_skipping = true; + + let mut index = 0; + + while continue_skipping { + continue_skipping = false; + + let skip_wsp = skip_by_fn(s, is_wsp_or_newline); + + if skip_wsp > 0 { + continue_skipping = true; + + index += skip_wsp; + + if skip_wsp >= s.len() { + return Ok(index); + } + + s = &s[skip_wsp..]; + } + + // skip comments + match skip_c_nl(s).map_err(|e| e.shift_pos(index))? { + Some(skipped_size) if skipped_size > 0 => { + continue_skipping = true; + + index += skipped_size; + + if skipped_size >= s.len() { + return Ok(index); + } + + s = &s[skipped_size..]; + } + _ => {} + } + } + + Ok(index) +} + +/// Skip white spaces, tabs, newlines, and comments altogether. +macro_rules! skip_ws { + ($s:ident, $index:ident, $fallback:expr) => { + let skip_result = skip_wsp_and_newline_and_comments($s).map_err(|e| e.shift_pos($index))?; + + $index += skip_result; + + if skip_result >= $s.len() { + $fallback + } else { + $s = &$s[skip_result..]; + } + }; +} + +#[derive(Debug, Copy, Clone, Default)] +struct CramRepetition(Option, Option); + +impl CramRepetition { + fn repetition(&self) -> usize { + self.1.unwrap_or(1usize) + } + + fn cram(&self) -> Option { + self.0 + } + + fn set_cram(&mut self, cram: Option) { + self.0 = cram; + } + + fn set_repetition(&mut self, repetition: Option) { + self.1 = repetition; + } +} + +fn parse_cram_repetition(s: &str) -> Result<(usize, CramRepetition), SheetParseError> { + if s.is_empty() { + return Ok((0, Default::default())); + } + + let mut s = s; + + let mut index = 0; + + let mut result: CramRepetition = Default::default(); + + // The index is zero, so we do not need to translate the error. + let (duration_len, duration) = parse_one_duration(s)?; + + if duration_len != 0 { + index += duration_len; + + result.set_cram(Some(duration)); + + if duration_len >= s.len() { + return Ok((index, result)); + } + + s = &s[duration_len..]; + + skip_ws!(s, index, { + return Ok((index, result)); + }); + } + + if s.starts_with('*') { + index += 1; + s = &s[1..]; + + skip_ws!(s, index, { + return Err(SheetParseError::InvalidRepetition(index)); + }); + + let digits_len = skip_by_fn(s, is_decimal_digit); + + if digits_len != 0 { + index += digits_len; + + let num: usize = s[..digits_len].parse().unwrap(); + + result.set_repetition(Some(num)); + } + } + + Ok((index, result)) +} + +fn parse_one_instruction(s: &str) -> Result<(usize, SheetUnit), SheetParseError> { + if s.is_empty() { + return Err(SheetParseError::InvalidInstruction(0)); + } + + let mut s = s; + let mut index = 0; + + if s.starts_with('i') { + index += 1; + s = &s[1..]; + + if s.starts_with("piano") { + index += 5; + + Ok((index, Instrument::Piano.into())) + } else if s.starts_with("violin") { + index += 6; + + Ok((index, Instrument::Violin.into())) + } else if s.starts_with("sinus") { + index += 5; + + Ok((index, Instrument::Sinus.into())) + } else if s.starts_with("tangent") { + index += 7; + + Ok((index, Instrument::Tangent.into())) + } else { + Err(SheetParseError::InvalidInstrument(index)) + } + } else if s.starts_with('b') { + index += 1; + s = &s[1..]; + + let digits_len = skip_by_fn(s, is_decimal_digit); + + if digits_len == 0 { + return Err(SheetParseError::InvalidBPM(index)); + } + + // This should never fail because the string is supposed to + // contain only decimal digits. + let num: usize = (s[..digits_len]).parse().unwrap(); + + if num == 0 { + return Err(SheetParseError::InvalidBPM(index)); + } + + index += digits_len; + + let bpm: BPM = (num as f64).into(); + + Ok((index, bpm.into())) + } else if s.starts_with('v') { + index += 1; + s = &s[1..]; + + let digits_len = skip_by_fn(s, is_decimal_digit); + + let num: usize = if digits_len == 0 { + 0 + } else { + // This should never fail because the string is supposed + // to contain only decimal digits. + (s[..digits_len]).parse().unwrap() + }; + + let mut frac: usize = 0; + + index += digits_len; + s = &s[digits_len..]; + + if s.starts_with('.') { + index += 1; + s = &s[1..]; + + let frac_digits_len = skip_by_fn(s, is_decimal_digit); + + if frac_digits_len != 0 { + index += frac_digits_len; + + frac = (s[..frac_digits_len]).parse().unwrap(); + } else if digits_len == 0 { + return Err(SheetParseError::InvalidVolume(index)); + } + } + + // This should never fail + let volume: f64 = format!("{num}.{frac}").parse().unwrap(); + + let volume: Volume = volume.into(); + + Ok((index, volume.into())) + } else { + Err(SheetParseError::InvalidInstruction(index)) + } +} + +// This never fails, since a silence "r" cannot determine that the +// following is invalid. +fn parse_one_silence(s: &str) -> (usize, SheetUnit) { + if s.is_empty() { + return (0, Default::default()); + } + + let mut s = s; + let mut index = 0; + + let mut beats: Option = None; + + let digits_len = skip_by_fn(s, is_decimal_digit); + + if digits_len != 0 { + // this will not fail + let num: usize = s[..digits_len].parse().unwrap(); + + let beats_beats: Beats = (1f64 / num as f64).into(); + + beats = Some(beats_beats); + + if digits_len >= s.len() { + let duration: SheetDuration = beats_beats.into(); + return (index + digits_len, duration.into()); + } + + index += digits_len; + s = &s[digits_len..]; + } + + let dots_len = skip_by_fn(s, |c| c == '.'); + + index += dots_len; + + let duration: SheetDuration = (beats, dots_len).into(); + + (index, duration.into()) +} + +fn parse_one_duration(s: &str) -> Result<(usize, SheetDuration), SheetParseError> { + if s.is_empty() { + return Ok((0, Default::default())); + } + + let mut s = s; + + let mut index = 0; + + if s.starts_with('p') { + // a precise specification + + index += 1; + s = &s[1..]; + + let digits_len = skip_by_fn(s, is_decimal_digit); + + if digits_len == 0 { + return Err(SheetParseError::InvalidDuration(index)); + } + + index += digits_len; + + let num: usize = s[..digits_len].parse().unwrap(); + + if digits_len >= s.len() { + return Ok((index, (num as f64).into())); + } + + s = &s[digits_len..]; + + let mut frac: usize = 0; + + if s.starts_with('.') { + index += 1; + s = &s[1..]; + + let frac_len = skip_by_fn(s, is_decimal_digit); + + index += frac_len; + + if frac_len != 0 { + frac = s[..frac_len].parse().unwrap(); + } + } + + let duration_f64: f64 = format!("{num}.{frac}").parse().unwrap(); + + Ok((index, duration_f64.into())) + } else { + let digits_len = skip_by_fn(s, is_decimal_digit); + + let mut duration_f64 = None; + + let mut dots_len = 0; + + if digits_len != 0 { + index += digits_len; + + let num: usize = s[..digits_len].parse().unwrap(); + + duration_f64 = Some(1f64 / num as f64); + + if digits_len < s.len() { + s = &s[digits_len..]; + } + } + + if s.starts_with('.') { + dots_len = skip_by_fn(s, |c| c == '.'); + + index += dots_len; + } + + Ok((index, (duration_f64, dots_len).into())) + } +} + +/// Parse a chord. +/// +/// # Panic +/// +/// It assumes that the string starts with a valid letter. If the +/// assumption is violated, this function panics. +fn parse_one_chord(s: &str) -> Result<(usize, SheetUnit), SheetParseError> { + if s.is_empty() { + return Err(SheetParseError::InvalidTone(0)); + } + + let mut s = s; + let mut index = 0; + + let mut tones: Vec = Vec::new(); + + let mut sheet_duration = SheetDuration::default(); + + loop { + let mut octave: Option = None; + + if s.starts_with('o') { + index += 1; + s = &s[1..]; + + let digits_len = skip_by_fn(s, is_decimal_digit); + + if digits_len == 0 { + return Err(SheetParseError::InvalidOctave(index)); + } else if digits_len >= s.len() { + return Err(SheetParseError::InvalidTone(index + digits_len)); + } + + let num: usize = s[..digits_len].parse().unwrap(); + octave = Some(num.into()); + + index += digits_len; + s = &s[digits_len..]; + } else if s.starts_with('<') { + let less_len = skip_by_fn(s, |c| c == '<'); + + if less_len >= s.len() { + return Err(SheetParseError::InvalidTone(index + less_len)); + } + + octave = Some((-(less_len as isize)).into()); + + index += less_len; + s = &s[less_len..]; + } else if s.starts_with('>') { + let greater_len = skip_by_fn(s, |c| c == '>'); + + if greater_len >= s.len() { + return Err(SheetParseError::InvalidTone(index + greater_len)); + } + + octave = Some((greater_len as isize).into()); + + index += greater_len; + s = &s[greater_len..]; + } + + skip_ws!(s, index, { + return Err(SheetParseError::InvalidTone(index)); + }); + + let first_char = s.chars().next().unwrap(); + + let mut tone = match first_char { + 'c' | 'C' => SheetSemitone(0isize), + 'd' | 'D' => SheetSemitone(2isize), + 'e' | 'E' => SheetSemitone(4isize), + 'f' | 'F' => SheetSemitone(5isize), + 'g' | 'G' => SheetSemitone(7isize), + 'a' | 'A' => SheetSemitone(9isize), + 'b' | 'B' => SheetSemitone(11isize), + _ => { + return Err(SheetParseError::InvalidTone(index)); + } + }; + + index += 1; + + if s.len() == 1 { + tones.push(SheetTone::new(tone, octave)); + + break; + } + + s = &s[1..]; + + if s.starts_with('+') { + let plus_len = skip_by_fn(s, |c| c == '+'); + tone.shift(plus_len as isize); + + index += plus_len; + + if plus_len >= s.len() { + tones.push(SheetTone::new(tone, octave)); + + let chord: SheetChord = tones.into(); + + return Ok((index, chord.into())); + } + + s = &s[plus_len..]; + } else if s.starts_with('-') { + let minus_len = skip_by_fn(s, |c| c == '-'); + tone.shift(-(minus_len as isize)); + + index += minus_len; + + if minus_len >= s.len() { + tones.push(SheetTone::new(tone, octave)); + + let chord: SheetChord = tones.into(); + + return Ok((index, chord.into())); + } + + s = &s[minus_len..]; + } + + tones.push(SheetTone::new(tone, octave)); + + skip_ws!(s, index, { + let chord: SheetChord = tones.into(); + + return Ok((index, chord.into())); + }); + + let (duration_len, duration) = parse_one_duration(s).map_err(|e| e.shift_pos(index))?; + + if duration_len != 0 { + index += duration_len; + + sheet_duration = duration; + + break; + } + + // If there is a forward slash then continue else just break + + if !s.starts_with('/') { + break; + } + + index += 1; + s = &s[1..]; + + skip_ws!(s, index, { + return Err(SheetParseError::InvalidTone(index)); + }); + } + + let chord: SheetChord = tones.into(); + + let unit = SheetUnit::Tone(chord, sheet_duration); + + Ok((index, unit)) +} + +fn parse_one_unit(s: &str) -> Result<(usize, SheetUnit), SheetParseError> { + if s.is_empty() { + return Ok((0, Default::default())); + } + + let mut s = s; + + let mut index = 0; + + let mut peek_iter = s.chars().peekable(); + let first_char = peek_iter.peek().unwrap(); + + match first_char { + 'i' => { + s = &s[1..]; + index += 1; + + parse_one_instruction(s) + .map(|(len, unit)| (len + index, unit)) + .map_err(|e| e.shift_pos(index)) + } + 'r' => { + s = &s[1..]; + index += 1; + + let (len, unit) = parse_one_silence(s); + + Ok((index + len, unit)) + } + 'o' | '<' | '>' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' => parse_one_chord(s), + _ => Ok((0, Default::default())), + } +} + +fn parse_one_group(s: &str) -> Result<(usize, SheetGroup), SheetParseError> { + // read units + + let mut s = s; + + let mut explicit_group = true; + + let mut index = 0; + + let mut result = SheetGroup::default(); + + result.nodes.push(SheetGroupNode::Intermediate(1, None)); + result.edges.push(Vec::new()); + + let mut peek_iter = s.chars().peekable(); + let first_char = peek_iter.peek(); + + match first_char { + Some('(') => { + // an explicit block + + let mut stack = vec![0]; + let mut current_parent = 0; + + index += 1; + s = &s[1..]; + + skip_ws!(s, index, { + dbg!(); + return Err(SheetParseError::HangingGroup(index)); + }); + + loop { + let (parsed_length, parsed_unit) = + parse_one_unit(s).map_err(|e| e.shift_pos(index))?; + + if parsed_length == 0 { + let mut peekable = s.chars().peekable(); + + let first_char = peekable.peek().unwrap(); + + match first_char { + '(' => { + result.nodes.push(SheetGroupNode::Intermediate(1, None)); + result.edges.push(Vec::new()); + + let result_len = result.nodes.len(); + + result.edges[current_parent].push(result_len - 1); + + stack.push(current_parent); + current_parent = result_len - 1; + + index += 1; + s = &s[1..]; + + skip_ws!(s, index, { + return Err(SheetParseError::HangingGroup(index)); + }); + + continue; + } + ')' => { + if stack.len() <= 1 { + break; + } + + index += 1; + s = &s[1..]; + + skip_ws!(s, index, { + return Err(SheetParseError::HangingGroup(index)); + }); + + let (parsed_length, parsed_cram_repetition) = + parse_cram_repetition(s).map_err(|e| e.shift_pos(index))?; + + if parsed_length != 0 { + index += parsed_length; + s = &s[parsed_length..]; + + result.nodes[current_parent] + .set_repetition(parsed_cram_repetition.repetition()); + + result.nodes[current_parent] + .set_cram(parsed_cram_repetition.cram()); + + skip_ws!(s, index, { + return Err(SheetParseError::HangingGroup(index)); + }); + } + + current_parent = stack.pop().unwrap(); + + continue; + } + _ => { + break; + } + } + } + + index += parsed_length; + s = &s[parsed_length..]; + + let leaf_node = SheetGroupNode::Unit(parsed_unit); + + let nodes_len = result.nodes.len(); + + result.nodes.push(leaf_node); + result.edges.push(Vec::new()); + + result.edges[current_parent].push(nodes_len); + + skip_ws!(s, index, { + dbg!(); + return Err(SheetParseError::HangingGroup(index)); + }); + } + + if s.starts_with(')') { + index += 1; + s = &s[1..]; + + skip_ws!(s, index, { + return Ok((index, result)); + }); + } else { + dbg!(); + return Err(SheetParseError::HangingGroup(index)); + } + } + + Some(')') => { + dbg!(); + return Err(SheetParseError::HangingGroup(index)); + } + Some(_) => { + // an implicit group: we read a unit only + + explicit_group = false; + + let (parsed_length, parsed_unit) = parse_one_unit(s).map_err(|e| e.shift_pos(index))?; + + if parsed_length != 0 { + index += parsed_length; + + let leaf_node = SheetGroupNode::Unit(parsed_unit); + + let nodes_len = result.nodes.len(); + + result.nodes.push(leaf_node); + result.edges.push(Vec::new()); + + result.edges[0].push(nodes_len); + + if parsed_length >= s.len() { + return Ok((index, result)); + } + + s = &s[parsed_length..]; + + skip_ws!(s, index, { + return Ok((index, result)); + }); + } + } + None => { + // empty group + } + } + + if explicit_group && index == 1 { + dbg!(); + return Err(SheetParseError::HangingGroup(index)); + } // else if !explicit_group && index == 0 { + // dbg!(); + // return Err(SheetParseError::InvalidGroup(index)); + // } + + let (parsed_length, parsed_cram_repetition) = + parse_cram_repetition(s).map_err(|e| e.shift_pos(index))?; + + if parsed_length != 0 { + index += parsed_length; + s = &s[parsed_length..]; + + result.nodes[0].set_repetition(parsed_cram_repetition.repetition()); + result.nodes[0].set_cram(parsed_cram_repetition.cram()); + } + + let skip_result = skip_wsp_and_newline_and_comments(s).map_err(|e| e.shift_pos(index))?; + + index += skip_result; + + // let mut peek_iter = s.chars().peekable(); + // let first_char = peek_iter.peek(); + + // if explicit_group && first_char.copied() != Some(')') { + // return Err(SheetParseError::HangingGroup(index)); + // } + + // if explicit_group { + // index += 1; + // } + + Ok((index, result)) +} + +fn parse_one_block(s: &str) -> Result<(usize, SheetBlock), SheetParseError> { + // read groups + + let mut s = s; + + // let mut explicit_block = true; + + let mut index = 0; + + let mut result = SheetBlock::default(); + + let mut peek_iter = s.chars().peekable(); + let first_char = peek_iter.peek(); + + match first_char { + Some('{') => { + // an explicit block + + index += 1; + s = &s[1..]; + + skip_ws!(s, index, { + return Err(SheetParseError::HangingBlock(index)); + }); + + loop { + let (parsed_length, parsed_group) = + parse_one_group(s).map_err(|e| e.shift_pos(index))?; + + if parsed_length == 0 { + break; + } + + index += parsed_length; + s = &s[parsed_length..]; + + result.groups.push(parsed_group); + } + + skip_ws!(s, index, { + return Err(SheetParseError::HangingBlock(index)); + }); + + if s.starts_with('}') { + index += 1; + } else { + return Err(SheetParseError::HangingBlock(index)); + } + } + Some('}') => { + dbg!(); + return Err(SheetParseError::HangingBlock(index)); + } + Some(_) => { + // an implicit block: we read till the end + + // explicit_block = false; + + loop { + let (parsed_length, parsed_group) = + parse_one_group(s).map_err(|e| e.shift_pos(index))?; + + if parsed_length == 0 { + break; + } + + index += parsed_length; + s = &s[parsed_length..]; + + result.groups.push(parsed_group); + } + } + None => { + // empty block + } + } + + // let mut peek_iter = s.chars().peekable(); + // let first_char = peek_iter.peek(); + + // if explicit_block && first_char.copied() != Some('}') { + // return Err(SheetParseError::HangingBlock(index)); + // } else if !explicit_block && !s.is_empty() { + // return Err(SheetParseError::InvalidBlock(index)); + // } + + // if explicit_block { + // index += 1; + // } + + Ok((index, result)) +} + +impl std::str::FromStr for Sheet { + type Err = SheetParseError; + + fn from_str(s: &str) -> Result { + if s.is_empty() { + return Err(SheetParseError::EmptySheet); + } + + let mut s = s; + + let mut index: usize = 0; + + let mut result = Sheet::default(); + + loop { + skip_ws!(s, index, break); + + // s is guaranteed to be non-empty at this point + + // look ahead one character to determine the situation + if s.starts_with('\"') { + // an assignment expression + todo!(); + // first skip whitespaces again + } else { + // a block + + let (block_len, block) = parse_one_block(s).map_err(|e| e.shift_pos(index))?; + + index += block_len; + + result.blocks.push(block); + + if block_len >= s.len() { + break; + } + + s = &s[block_len..]; + } + } + + Ok(result) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_sheet() -> Result<(), Box> { + let s: &str = + &String::from("{ (o4 a+ / < b- 4... o3 c+ ( < e-- 4 r8 ) 1 * 2 ) 4... * 12 > d. }"); + + println!("input = {s}"); + + let sheet: Sheet = s.parse().map_err(|e: SheetParseError| e.to_string())?; + + println!("sheet = {sheet:?}"); + + Ok(()) + } + + #[test] + fn test_group_do_cram() -> Result<(), String> { + let s: &str = &String::from("(o4 a+ / < b- 4... o3 c+) 4... * 12"); + + println!("input = {s}"); + + let (index, mut group) = parse_one_group(s).map_err(|e| e.to_string())?; + + println!("{group:?}"); + + assert_eq!(index, s.len()); + + group.do_cram(&mut Beats::default()); + + println!("{group:?}"); + + match group.nodes[1] { + SheetGroupNode::Unit(SheetUnit::Tone(_, duration)) => { + assert_eq!(*duration.beats.unwrap(), 0.234375f64); + } + _ => { + panic!("wrong node"); + } + } + + match group.nodes[2] { + SheetGroupNode::Unit(SheetUnit::Tone(_, duration)) => { + assert_eq!(*duration.beats.unwrap(), 0.234375f64); + } + _ => { + panic!("wrong node"); + } + } + + Ok(()) + } + + #[test] + fn test_parse_group() -> Result<(), String> { + let s: &str = &String::from("(o4 a+ / < b- 4... o3 c+) 4... * 12"); + + println!("input = {s}"); + + let (index, unit) = parse_one_group(s).map_err(|e| e.to_string())?; + + println!("{unit:?}"); + + assert_eq!(index, s.len()); + + Ok(()) + } + + #[test] + fn test_parse_chord() -> Result<(), SheetParseError> { + let s: &str = &String::from("o4 a+ / < b- 4..."); + + let (index, unit) = parse_one_chord(s)?; + + assert_eq!(index, s.len()); + assert_eq!(format!("{unit}"), "o4 10 / < 10,0.25..."); + + Ok(()) + } + + #[test] + fn test_skip_wsp_nl_c() -> Result<(), SheetParseError> { + let mut s: &str = + &String::from("\n\n\r\r \t; haha this is a comment\nthis is not a comment"); + + let s_len = s.len(); + + let mut index = 0; + + skip_ws!(s, index, ()); + + assert_eq!(index, 33); + assert_eq!(s_len, 54); + + assert_eq!(s, "this is not a comment"); + + Ok(()) + } +} diff --git a/src/sheet/play.rs b/src/sheet/play.rs new file mode 100644 index 0000000..c2a632d --- /dev/null +++ b/src/sheet/play.rs @@ -0,0 +1,314 @@ +#![warn(missing_docs)] +//! This file implements the interface to turn a parsed representation +//! of a sheet into pulses. + +use super::*; + +#[allow(unused_imports)] +use crate::instruments::{self, Instrument as IInstrument}; + +#[derive(Debug, Clone, Copy)] +/// The environment when generating waves from a sheet. +struct SheetEnv { + volume: Volume, + bpm: BPM, + instrument: Instrument, + duration: Beats, + octave: usize, + rate: Samples, +} + +impl SheetEnv { + fn set_volume(&mut self, volume: Volume) { + self.volume = volume; + } + fn set_bpm(&mut self, bpm: BPM) { + self.bpm = bpm; + } + fn set_instrument(&mut self, instrument: Instrument) { + self.instrument = instrument; + } + fn set_duration(&mut self, duration: Beats) { + self.duration = duration; + } + fn set_octave(&mut self, octave: usize) { + self.octave = octave; + } +} + +impl Default for SheetEnv { + fn default() -> Self { + let volume = Volume::default(); + let bpm = BPM::default(); + let instrument = Instrument::default(); + let duration = Beats::default(); + let octave = 4usize; + let rate = Samples::default(); + + Self { + volume, + bpm, + instrument, + duration, + octave, + rate, + } + } +} + +impl From<(&SheetDuration, Beats)> for Beats { + fn from((duration, env_duration): (&SheetDuration, Beats)) -> Self { + let duration_base: Beats = if let Some(base) = duration.beats { + base + } else { + env_duration + }; + + let mut final_duration = duration_base; + + for i in (0..duration.dots).map(|n| n + 1) { + let power = 2usize.pow(i as u32) as f64; + + final_duration = (*final_duration + *duration_base / power).into(); + } + + final_duration + } +} + +impl From<(&SheetUnit, &mut SheetEnv)> for Wave { + fn from((unit, env): (&SheetUnit, &mut SheetEnv)) -> Self { + let mut result = Vec::new(); + + // Unfortunately our trait is wrongly designed and that is not + // dynamically dispatchable, so we need separate variables for + // each instrument. + let mut piano = instruments::PianoFromC::default(); + let mut violin = instruments::Violin::default(); + let mut sinus = instruments::Sinus::default(); + let mut tangent = instruments::Tangent::default(); + + match unit { + SheetUnit::Tone(chord, duration) => { + let mut local_waves = Vec::new(); + + for tone in chord.0.iter() { + let octave = if let Some(abs_or_rel) = tone.octave { + match abs_or_rel { + SheetOctave::Absolute(absolute_octave) => absolute_octave, + SheetOctave::Relative(relative_octave) => { + let result: isize = relative_octave + env.octave as isize; + + assert!(!result.is_negative()); + + result as usize + } + } + } else { + env.octave + }; + + env.set_octave(octave); + + let semitone: Semitones = (octave, tone.semitone).into(); + + let final_duration: Beats = (duration, env.duration).into(); + + env.set_duration(final_duration); + + let wave = match env.instrument { + Instrument::Piano => { + piano.play(semitone, env.rate, env.bpm, final_duration, env.volume) + } + Instrument::Violin => { + violin.play(semitone, env.rate, env.bpm, final_duration, env.volume) + } + Instrument::Sinus => { + sinus.play(semitone, env.rate, env.bpm, final_duration, env.volume) + } + Instrument::Tangent => { + tangent.play(semitone, env.rate, env.bpm, final_duration, env.volume) + } + }; + + local_waves.push(wave); + } + + let local_result = mix_waves_exact(local_waves); + + result.append(&mut local_result.to_vec()); + } + SheetUnit::Silence(duration) => { + let duration_per_beat: Seconds = env.bpm.into(); + + let final_duration: Beats = (duration, env.duration).into(); + + env.set_duration(final_duration); + + let duration: Seconds = (*final_duration * *duration_per_beat).into(); + let nb_samples = (*duration * *env.rate).floor() as usize; + + result.append(&mut vec![0f64.into(); nb_samples]); + } + SheetUnit::Instruction(instruction) => match instruction { + Instruction::VolumeTo(volume) => { + env.set_volume(*volume); + } + Instruction::BPMTo(bpm) => { + env.set_bpm(*bpm); + } + Instruction::InstrumentTo(instrument) => { + env.set_instrument(*instrument); + } + }, + } + + Wave::new(result) + } +} + +impl From<(&mut SheetGroup, &mut SheetEnv)> for Wave { + fn from((group, env): (&mut SheetGroup, &mut SheetEnv)) -> Self { + group.do_cram(&mut env.duration); + + // if group.nodes.len() == 19 { + // println!("group={group:?}"); + // } + + // After we crammed the expressions, we are sure that all + // tones have the correct durations, so we can safely ignore + // all cram expressions afterwards. + + let mut stack = vec![0usize]; + let mut seen = vec![false; group.nodes.len()]; + let mut stack_args: Vec = vec![]; + + while let Some(top) = stack.pop() { + match &group.nodes[top] { + SheetGroupNode::Intermediate(repetition, _) if *repetition != 0 => { + if !seen[top] { + stack.push(top); + + stack.append( + &mut group.edges[top] + .iter() + .copied() + .rev() + .filter(|child| match group.nodes[*child] { + SheetGroupNode::Intermediate(child_repetition, _) + if child_repetition != 0 => + { + true + } + SheetGroupNode::Unit(_) => true, + _ => false, + }) + .collect(), + ); + } else { + match repetition { + 0 => { + unreachable!() + } + // 1 => { + // for _ in 0..group.edges[top] + // .iter() + // .filter(|child| match group.nodes[**child] { + // SheetGroupNode::Intermediate(child_repetition, _) + // if child_repetition != 0 => + // { + // true + // } + // SheetGroupNode::Unit(_) => true, + // _ => false, + // }) + // .count() + // { + // result.append(&mut stack_args.pop().unwrap().to_vec()); + // } + // } + _ => { + let mut local_stack_args = vec![]; + let mut local_results = vec![]; + let mut final_local_result = vec![]; + + for _ in 0..group.edges[top] + .iter() + .filter(|child| match group.nodes[**child] { + SheetGroupNode::Intermediate(child_repetition, _) + if child_repetition != 0 => + { + true + } + SheetGroupNode::Unit(_) => true, + _ => false, + }) + .count() + { + local_stack_args.push(stack_args.pop().unwrap()); + } + + for local_wave in local_stack_args.into_iter().rev() { + local_results.append(&mut local_wave.to_vec()); + } + + for _ in 0..(repetition - 1) { + final_local_result.append(&mut local_results.clone()); + } + + final_local_result.append(&mut local_results); + + stack_args.push(Wave::new(final_local_result)); + } + } + } + } + SheetGroupNode::Unit(unit) => { + stack_args.push((unit, &mut *env).into()); + } + _ => { + continue; + } + } + + seen[top] = true; + } + + let result = stack_args + .iter() + .flat_map(|vec| vec.iter()) + .copied() + .collect(); + + Wave::new(result) + } +} + +// Note: The rest are but ordinary house-keeping codes to glue +// everything together. + +impl From<&mut SheetBlock> for Wave { + fn from(block: &mut SheetBlock) -> Self { + let mut result = Vec::new(); + let mut env = SheetEnv::default(); + + for group in block.groups.iter_mut() { + let wave: Wave = (group, &mut env).into(); + + result.append(&mut wave.to_vec()); + } + + Self::new(result) + } +} + +impl From<&mut Sheet> for Wave { + fn from(sheet: &mut Sheet) -> Self { + mix_waves_exact(sheet.blocks.iter_mut().map(Into::::into).collect()) + } +} + +#[cfg(test)] +mod tests { + // use super::*; +} -- cgit v1.2.3-18-g5258