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/sheet/mod.rs | 1905 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1905 insertions(+) create mode 100644 src/sheet/mod.rs (limited to 'src/sheet/mod.rs') 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(()) + } +} -- cgit v1.2.3-18-g5258