diff --git a/benches/benchmarks/rhythm.rs b/benches/benchmarks/rhythm.rs index 873d764..21c65e8 100644 --- a/benches/benchmarks/rhythm.rs +++ b/benches/benchmarks/rhythm.rs @@ -191,7 +191,7 @@ pub fn run(c: &mut Criterion) { pub fn seek(c: &mut Criterion) { let mut group = c.benchmark_group("Rust Phrase"); let phrase = create_phrase(); - let samples_per_sec = phrase.time_base().samples_per_sec; + let samples_per_sec = phrase.time_base().samples_per_sec as SampleTime; let seek_step = 10; let seek_time = 60 * 60; group.bench_function("Seek", |b| { @@ -202,10 +202,10 @@ pub fn seek(c: &mut Criterion) { phrase }, |mut phrase| { - let mut sample_time = samples_per_sec as SampleTime; - while sample_time < (seek_time * samples_per_sec) as SampleTime { - phrase.seek_until_time(sample_time); - sample_time += seek_step * samples_per_sec as SampleTime; + let mut sample_time = samples_per_sec; + while sample_time < seek_time * samples_per_sec { + phrase.advance_until_time(sample_time); + sample_time += seek_step * samples_per_sec; } }, criterion::BatchSize::SmallInput, diff --git a/benches/benchmarks/scripted.rs b/benches/benchmarks/scripted.rs index 0a874e8..24abc85 100644 --- a/benches/benchmarks/scripted.rs +++ b/benches/benchmarks/scripted.rs @@ -180,7 +180,7 @@ pub fn run(c: &mut Criterion) { pub fn seek(c: &mut Criterion) { let mut group = c.benchmark_group("Scripted Phrase"); let phrase = create_phrase(); - let samples_per_sec = phrase.time_base().samples_per_sec; + let samples_per_sec = phrase.time_base().samples_per_sec as SampleTime; let seek_step = 10; let seek_time = 60 * 60; group.bench_function("Seek", |b| { @@ -191,10 +191,10 @@ pub fn seek(c: &mut Criterion) { phrase }, |mut phrase| { - let mut sample_time = samples_per_sec as SampleTime; - while sample_time < (seek_time * samples_per_sec) as SampleTime { - phrase.seek_until_time(sample_time); - sample_time += seek_step * samples_per_sec as SampleTime; + let mut sample_time = samples_per_sec; + while sample_time < seek_time * samples_per_sec { + phrase.advance_until_time(sample_time); + sample_time += seek_step * samples_per_sec; } }, criterion::BatchSize::SmallInput, diff --git a/src/bindings/callback.rs b/src/bindings/callback.rs index d998f33..e3bfba6 100644 --- a/src/bindings/callback.rs +++ b/src/bindings/callback.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, fmt::Debug}; +use std::{borrow::Cow, collections::HashMap, fmt::Debug}; use mlua::prelude::*; @@ -71,18 +71,18 @@ pub fn add_lua_callback_error(name: &str, err: &LuaError) { /// with values before it's called. /// /// Errors from callbacks should be handled by calling `self.handle_error` so external clients -/// can deal with them later, as apropriate. +/// can deal with them later, as appropriate. /// /// By memorizing the original generator function and environment, it also can be reset to its /// initial state by calling the original generator function again to fetch a new freshly /// initialized function. /// -/// TODO: Upvalues of generators or simple functions could actuially be collected and restored +/// TODO: Upvalues of generators or simple functions could actually be collected and restored /// too, but this uses debug functionality and may break some upvalues. #[derive(Debug, Clone)] pub(crate) struct LuaCallback { environment: Option, - context: LuaOwnedTable, + context: LuaOwnedAnyUserData, generator: Option, function: LuaOwnedFunction, initialized: bool, @@ -96,8 +96,15 @@ impl LuaCallback { /// Create a new Callback from an owned lua function. pub fn with_owned(lua: &Lua, function: LuaOwnedFunction) -> LuaResult { - // create an empty context and memorize the function without calling it - let context = lua.create_table()?.into_owned(); + // create and register the callback context + LuaCallbackContext::register(lua)?; + let context = lua + .create_any_userdata(LuaCallbackContext { + values: HashMap::new(), + external_values: HashMap::new(), + })? + .into_owned(); + // and memorize the function without calling it let environment = function.to_ref().environment().map(LuaTable::into_owned); let generator = None; let initialized = false; @@ -112,27 +119,31 @@ impl LuaCallback { /// Sets the emitter time base context for the callback. pub fn set_context_time_base(&mut self, time_base: &BeatTimeBase) -> LuaResult<()> { - let table = self.context.to_ref(); - table.raw_set("beats_per_min", time_base.beats_per_min)?; - table.raw_set("beats_per_bar", time_base.beats_per_bar)?; - table.raw_set("samples_per_sec", time_base.samples_per_sec)?; + let values = &mut self.context.borrow_mut::()?.values; + values.insert(b"beats_per_min", time_base.beats_per_min as LuaNumber); + values.insert(b"beats_per_min", time_base.beats_per_min as LuaNumber); + values.insert(b"beats_per_bar", time_base.beats_per_bar as LuaNumber); + values.insert(b"samples_per_sec", time_base.samples_per_sec as LuaNumber); Ok(()) } /// Sets external emitter context for the callback. pub fn set_context_external_data(&mut self, data: &[(Cow, f64)]) -> LuaResult<()> { - let table = self.context.to_ref(); + let external_values = &mut self + .context + .borrow_mut::()? + .external_values; for (key, value) in data { - table.raw_set(key as &str, *value)?; + external_values.insert(key.to_string(), *value as LuaNumber); } Ok(()) } /// Sets the pulse value emitter context for the callback. pub fn set_context_pulse_value(&mut self, pulse: PulseIterItem) -> LuaResult<()> { - let table = self.context.to_ref(); - table.raw_set("pulse_value", pulse.value)?; - table.raw_set("pulse_time", pulse.step_time)?; + let values = &mut self.context.borrow_mut::()?.values; + values.insert(b"pulse_value", pulse.value as LuaNumber); + values.insert(b"pulse_time", pulse.step_time as LuaNumber); Ok(()) } @@ -142,16 +153,16 @@ impl LuaCallback { pulse_step: usize, pulse_time_step: f64, ) -> LuaResult<()> { - let table = self.context.to_ref(); - table.raw_set("pulse_step", pulse_step + 1)?; - table.raw_set("pulse_time_step", pulse_time_step)?; + let values = &mut self.context.borrow_mut::()?.values; + values.insert(b"pulse_step", (pulse_step + 1) as LuaNumber); + values.insert(b"pulse_time_step", pulse_time_step as LuaNumber); Ok(()) } /// Sets the step emitter context for the callback. pub fn set_context_step(&mut self, step: usize) -> LuaResult<()> { - let table = self.context.to_ref(); - table.raw_set("step", step + 1)?; + let values = &mut self.context.borrow_mut::()?.values; + values.insert(b"step", (step + 1) as LuaNumber); Ok(()) } @@ -162,10 +173,10 @@ impl LuaCallback { step: usize, step_length: f64, ) -> LuaResult<()> { - let table = self.context.to_ref(); - table.raw_set("channel", channel + 1)?; - table.raw_set("step", step + 1)?; - table.raw_set("step_length", step_length)?; + let values = &mut self.context.borrow_mut::()?.values; + values.insert(b"channel", (channel + 1) as LuaNumber); + values.insert(b"step", (step + 1) as LuaNumber); + values.insert(b"step_length", step_length as LuaNumber); Ok(()) } @@ -221,34 +232,45 @@ impl LuaCallback { Ok(()) } - /// Name of the inner function for errors. Usually will be an annonymous function. + /// Name of the inner function for errors. Usually will be an anonymous function. pub fn name(&self) -> String { self.function .to_ref() .info() .name - .unwrap_or("annonymous function".to_string()) + .unwrap_or("anonymous function".to_string()) } - /// Invoke the Lua function callback or generator. + /// Returns true if the callback is a generator. + /// + /// To test this, the callback must have run at least once, so it returns None if it never has. + pub fn is_stateful(&self) -> Option { + if self.initialized { + Some(self.generator.is_some()) + } else { + None + } + } + + /// Invoke the Lua function or generator and return its result as LuaValue. pub fn call(&mut self) -> LuaResult { self.call_with_arg(LuaValue::Nil) } - /// Invoke the Lua function callback or generator with an additional argument. + /// Invoke the Lua function or generator with an additional argument and return its result as LuaValue. pub fn call_with_arg<'lua, A: IntoLua<'lua> + Clone>( &'lua mut self, arg: A, ) -> LuaResult> { if self.initialized { - self.function.call((self.context.to_ref(), arg)) + self.function.call((&self.context, arg)) } else { self.initialized = true; let result = { // HACK: don't borrow self here, so we can borrow mut again to assign the generator function // see https://stackoverflow.com/questions/73641155/how-to-force-rust-to-drop-a-mutable-borrow let function = unsafe { &*(&self.function as *const LuaOwnedFunction) }; - function.call::<_, LuaValue>((self.context.to_ref(), arg.clone()))? + function.call::<_, LuaValue>((&self.context, arg.clone()))? }; if let Some(inner_function) = result.as_function().cloned().map(|f| f.into_owned()) { // function returned a function -> is a generator. use the inner function instead. @@ -259,8 +281,7 @@ impl LuaCallback { .map(LuaTable::into_owned); self.environment = environment; self.generator = Some(std::mem::replace(&mut self.function, inner_function)); - self.function - .call::<_, LuaValue>((self.context.to_ref(), arg)) + self.function.call::<_, LuaValue>((&self.context, arg)) } else { // function returned some value. use this function directly. self.environment = None; @@ -288,7 +309,7 @@ impl LuaCallback { // then fetch a new fresh function from the generator let value = function_generator .to_ref() - .call::<_, LuaValue>(self.context.to_ref())?; + .call::<_, LuaValue>(&self.context)?; if let Some(function) = value.as_function() { self.function = function.clone().into_owned(); } else { @@ -305,6 +326,43 @@ impl LuaCallback { } } +// ------------------------------------------------------------------------------------------------- + +/// Memorizes an optional set of values that are passed along as context with the callback. +#[derive(Debug, Clone)] +struct LuaCallbackContext { + values: HashMap<&'static [u8], LuaNumber>, + external_values: HashMap, +} + +impl LuaCallbackContext { + fn register(lua: &Lua) -> LuaResult<()> { + // NB: registering for a specific engine is faster than implementing the UserData impl + // See https://github.com/mlua-rs/mlua/discussions/283 + lua.register_userdata_type::(|reg| { + reg.add_meta_field_with("__index", |lua| { + lua.create_function( + |lua, (this, key): (LuaUserDataRef, LuaString)| { + if let Some(value) = this.values.get(key.as_bytes()) { + // fast path (string bytes) + value.into_lua(lua) + } else if let Some(value) = + this.external_values.get(key.to_string_lossy().as_ref()) + { + // slower path (string ) + value.into_lua(lua) + } else { + Err(mlua::Error::RuntimeError( + "no such field in context".to_string(), + )) + } + }, + ) + }) + }) + } +} + // -------------------------------------------------------------------------------------------------- #[cfg(test)] diff --git a/src/event.rs b/src/event.rs index 140a2c8..5795849 100644 --- a/src/event.rs +++ b/src/event.rs @@ -395,7 +395,15 @@ pub trait EventIter: Debug { /// Returns an optional stack of event iter items, which should be emitted for the given pulse. fn run(&mut self, pulse: PulseIterItem, emit_event: bool) -> Option>; - /// Create a new cloned instance of this event iter. This actualy is a clone(), wrapped into + /// Move iterator with the given pulse value forward without generating an event. + /// + /// This can be used to optimize iterator skipping in some EventIter implementations, but by + /// default calls `run` and simply discards the generated event return value. + fn advance(&mut self, pulse: PulseIterItem, emit_event: bool) { + let _ = self.run(pulse, emit_event); + } + + /// Create a new cloned instance of this event iter. This actually is a clone(), wrapped into /// a `Box`, but called 'duplicate' to avoid conflicts with possible /// Clone impls. fn duplicate(&self) -> Box; diff --git a/src/event/cycle.rs b/src/event/cycle.rs index d096c7c..c98356f 100644 --- a/src/event/cycle.rs +++ b/src/event/cycle.rs @@ -192,7 +192,7 @@ impl CycleEventIter { } /// Generate a note event from a single cycle event, applying mappings if necessary - fn note_events(&mut self, event: CycleEvent) -> Result>, String> { + fn map_note_event(&mut self, event: CycleEvent) -> Result>, String> { let mut note_events = { if let Some(note_events) = self.mappings.get(event.string()) { // apply custom note mappings @@ -215,7 +215,7 @@ impl CycleEventIter { /// Generate next batch of events from the next cycle run. /// Converts cycle events to note events and flattens channels into note columns. - fn generate_events(&mut self) -> Vec { + fn generate(&mut self) -> Vec { // run the cycle event generator let events = { match self.cycle.generate() { @@ -232,7 +232,7 @@ impl CycleEventIter { for event in channel_events.into_iter() { let start = event.span().start(); let length = event.span().length(); - match self.note_events(event) { + match self.map_note_event(event) { Ok(note_events) => { if !note_events.is_empty() { timed_note_events.add(channel_index, start, length, note_events); @@ -261,12 +261,18 @@ impl EventIter for CycleEventIter { fn run(&mut self, _pulse: PulseIterItem, emit_event: bool) -> Option> { if emit_event { - Some(self.generate_events()) + Some(self.generate()) } else { None } } + fn advance(&mut self, _pulse: PulseIterItem, emit_event: bool) { + if emit_event { + self.cycle.advance(); + } + } + fn duplicate(&self) -> Box { Box::new(self.clone()) } diff --git a/src/event/fixed.rs b/src/event/fixed.rs index 0a14343..7bd776e 100644 --- a/src/event/fixed.rs +++ b/src/event/fixed.rs @@ -103,13 +103,17 @@ impl EventIter for FixedEventIter { return None; } let event = self.events[self.event_index].clone(); - self.event_index += 1; - if self.event_index >= self.events.len() { - self.event_index = 0; - } + self.event_index = (self.event_index + 1) % self.events.len(); Some(vec![EventIterItem::new(event)]) } + fn advance(&mut self, _pulse: PulseIterItem, emit_event: bool) { + if !emit_event || self.events.is_empty() { + return; + } + self.event_index = (self.event_index + 1) % self.events.len(); + } + fn duplicate(&self) -> Box { Box::new(self.clone()) } diff --git a/src/event/mutated.rs b/src/event/mutated.rs index c67b297..aa621ee 100644 --- a/src/event/mutated.rs +++ b/src/event/mutated.rs @@ -73,17 +73,13 @@ impl EventIter for MutatedEventIter { } fn run(&mut self, _pulse: PulseIterItem, emit_event: bool) -> Option> { - if emit_event { - let event = self.events[self.event_index].clone(); - self.events[self.event_index] = Self::mutate(event.clone(), &mut self.map); - self.event_index += 1; - if self.event_index >= self.events.len() { - self.event_index = 0; - } - Some(vec![EventIterItem::new(event)]) - } else { - None + if !emit_event || self.events.is_empty() { + return None; } + let event = self.events[self.event_index].clone(); + self.events[self.event_index] = Self::mutate(event.clone(), &mut self.map); + self.event_index = (self.event_index + 1) % self.events.len(); + Some(vec![EventIterItem::new(event)]) } fn duplicate(&self) -> Box { diff --git a/src/event/scripted.rs b/src/event/scripted.rs index 5ba487e..5d61c04 100644 --- a/src/event/scripted.rs +++ b/src/event/scripted.rs @@ -48,7 +48,7 @@ impl ScriptedEventIter { }) } - fn next_event(&mut self, pulse: PulseIterItem) -> LuaResult>> { + fn generate(&mut self, pulse: PulseIterItem) -> LuaResult>> { // reset timeout self.timeout_hook.reset(); // update function context @@ -64,6 +64,23 @@ impl ScriptedEventIter { // return as EventIterItem Ok(Some(vec![EventIterItem::new(event)])) } + + fn advance(&mut self, pulse: PulseIterItem) -> LuaResult<()> { + if self.callback.is_stateful().unwrap_or(true) { + // reset timeout + self.timeout_hook.reset(); + // update function context + self.callback.set_context_pulse_value(pulse)?; + self.callback + .set_context_pulse_step(self.pulse_step, self.pulse_time_step)?; + self.callback.set_context_step(self.step)?; + // invoke callback and ignore the result + self.callback.call()?; + Ok(()) + } else { + Ok(()) + } + } } impl Clone for ScriptedEventIter { @@ -99,7 +116,7 @@ impl EventIter for ScriptedEventIter { fn run(&mut self, pulse: PulseIterItem, emit_event: bool) -> Option> { // generate a new event and move or only update pulse counters if emit_event { - let event = match self.next_event(pulse) { + let event = match self.generate(pulse) { Ok(event) => event, Err(err) => { self.callback.handle_error(&err); @@ -117,6 +134,18 @@ impl EventIter for ScriptedEventIter { } } + fn advance(&mut self, pulse: PulseIterItem, emit_event: bool) { + // generate a new event and move or only update pulse counters + if emit_event { + if let Err(err) = self.advance(pulse) { + self.callback.handle_error(&err); + } + self.step += 1; + } + self.pulse_step += 1; + self.pulse_time_step += pulse.step_time; + } + fn duplicate(&self) -> Box { Box::new(self.clone()) } diff --git a/src/event/scripted_cycle.rs b/src/event/scripted_cycle.rs index 858cee3..8def519 100644 --- a/src/event/scripted_cycle.rs +++ b/src/event/scripted_cycle.rs @@ -128,7 +128,7 @@ impl ScriptedCycleEventIter { /// Generate next batch of events from the next cycle run. /// Converts cycle events to note events and flattens channels into note columns. - fn generate_events(&mut self) -> Vec { + fn generate(&mut self) -> Vec { // run the cycle event generator let events = { match self.cycle.generate() { @@ -197,12 +197,18 @@ impl EventIter for ScriptedCycleEventIter { fn run(&mut self, _pulse: PulseIterItem, emit_event: bool) -> Option> { if emit_event { - Some(self.generate_events()) + Some(self.generate()) } else { None } } + fn advance(&mut self, _pulse: PulseIterItem, emit_event: bool) { + if emit_event { + self.cycle.advance(); + } + } + fn duplicate(&self) -> Box { Box::new(self.clone()) } diff --git a/src/phrase.rs b/src/phrase.rs index 50d2054..5333dc2 100644 --- a/src/phrase.rs +++ b/src/phrase.rs @@ -115,8 +115,8 @@ impl Phrase { } } - /// Seek rhythms until a given sample time is reached, ignoring all events until that time. - pub fn skip_events_until_time(&mut self, sample_time: SampleTime) { + /// Move rhythms until a given sample time is reached, ignoring all events until that time. + pub fn advance_until_time(&mut self, sample_time: SampleTime) { // skip next events in all rhythms for (rhythm_slot, next_event) in self .rhythm_slots @@ -124,17 +124,15 @@ impl Phrase { .zip(self.next_events.iter_mut()) { // skip cached, next due events - if let Some((rhythm_index, event)) = next_event.take() { + if let Some((_, event)) = next_event { if event.time >= sample_time { - // no yet due: put it back - *next_event = Some((rhythm_index, event)); + // cached event is not yet due: no need to advance the slot + continue; } + *next_event = None; } - // when there's no cached event, seek the rhythm - if next_event.is_none() { - if let RhythmSlot::Rhythm(rhythm) = rhythm_slot { - rhythm.borrow_mut().seek_until_time(sample_time); - } + if let RhythmSlot::Rhythm(rhythm) = rhythm_slot { + rhythm.borrow_mut().advance_until_time(sample_time); } } } @@ -142,10 +140,10 @@ impl Phrase { /// reset playback status and shift events to the given sample position. /// Further take over rhythms from the passed previously playing phrase for `RhythmSlot::Continue` slots. pub fn reset_with_offset(&mut self, sample_offset: SampleTime, previous_phrase: &Phrase) { - // reset rhythm iters, unless they are in continue mode. in contine mode, copy the slot + // reset rhythm iters, unless they are in continue mode. in continue mode, copy the slot // from the previously playing phrase and adjust sample offsets to fit. - for rhythm_index in 0..self.rhythm_slots.len() { - match &mut self.rhythm_slots[rhythm_index] { + for (rhythm_index, rhythm_slot) in self.rhythm_slots.iter_mut().enumerate() { + match rhythm_slot { RhythmSlot::Rhythm(rhythm) => { { let mut rhythm = rhythm.borrow_mut(); @@ -162,8 +160,7 @@ impl Phrase { self.next_events[rhythm_index] .clone_from(&previous_phrase.next_events[rhythm_index]); // take over rhythm - self.rhythm_slots[rhythm_index] - .clone_from(&previous_phrase.rhythm_slots[rhythm_index]); + rhythm_slot.clone_from(&previous_phrase.rhythm_slots[rhythm_index]); } } } @@ -249,8 +246,8 @@ impl RhythmIter for Phrase { .map(|(_, event)| event) } - fn seek_until_time(&mut self, sample_time: SampleTime) { - self.skip_events_until_time(sample_time) + fn advance_until_time(&mut self, sample_time: SampleTime) { + self.advance_until_time(sample_time) } } @@ -311,3 +308,210 @@ impl Rhythm for Phrase { } } } + +// -------------------------------------------------------------------------------------------------- + +#[cfg(test)] +mod test { + use crate::prelude::*; + + fn create_phrase() -> Result { + let beat_time = BeatTimeBase { + samples_per_sec: 44100, + beats_per_min: 130.0, + beats_per_bar: 4, + }; + + let seed_bytes = 12312312312_u64.to_le_bytes(); + let mut seed = [0; 32]; + for i in 0..32 { + seed[i] = seed_bytes[i % 8]; + } + + let kick_cycle = new_cycle_event_with_seed( + "bd? [~ bd] ~ ~ bd [~ bd] _ ~ bd? [~ bd] ~ ~ bd [~ bd] [_ bd2] [~ bd _ ~]", + seed, + )?; + let mut kick_pattern = beat_time.every_nth_beat(16.0).trigger(kick_cycle); + kick_pattern.set_sample_offset(20); // test with offsets + + let snare_pattern = beat_time + .every_nth_beat(2.0) + .with_offset(BeatTimeStep::Beats(1.0)) + .trigger(new_note_event("C_5")); + + let hihat_pattern = + beat_time + .every_nth_sixteenth(2.0) + .trigger(new_note_event("C_5").mutate({ + let mut step = 0; + move |mut event| { + if let Event::NoteEvents(notes) = &mut event { + for note in notes.iter_mut().flatten() { + note.volume = 1.0 / (step + 1) as f32; + step += 1; + if step >= 3 { + step = 0; + } + } + } + event + } + })); + + let hihat_pattern2 = beat_time + .every_nth_sixteenth(2.0) + .with_offset(BeatTimeStep::Sixteenth(1.0)) + .trigger(new_note_event("C_5").mutate({ + let mut vel_step = 0; + let mut note_step = 0; + move |mut event| { + if let Event::NoteEvents(notes) = &mut event { + for note in notes.iter_mut().flatten() { + note.volume = 1.0 / (vel_step + 1) as f32 * 0.5; + vel_step += 1; + if vel_step >= 3 { + vel_step = 0; + } + note.note = Note::from((Note::C4 as u8) + 32 - note_step); + note_step += 1; + if note_step >= 32 { + note_step = 0; + } + } + } + event + } + })); + + let hihat_rhythm = Phrase::new( + beat_time, + vec![hihat_pattern, hihat_pattern2], + BeatTimeStep::Bar(4.0), + ); + + let bass_notes = Scale::try_from((Note::C5, "aeolian")).unwrap().notes(); + let bass_pattern = beat_time + .every_nth_eighth(1.0) + .with_pattern([1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1].to_pattern()) + .trigger(new_note_event_sequence(vec![ + new_note((bass_notes[0], None, 0.5)), + new_note((bass_notes[2], None, 0.5)), + new_note((bass_notes[3], None, 0.5)), + new_note((bass_notes[0], None, 0.5)), + new_note((bass_notes[2], None, 0.5)), + new_note((bass_notes[3], None, 0.5)), + new_note((bass_notes[6].transposed(-12), None, 0.5)), + ])); + + let synth_pattern = + beat_time + .every_nth_bar(4.0) + .trigger(new_polyphonic_note_sequence_event(vec![ + vec![ + new_note(("C 4", None, 0.3)), + new_note(("D#4", None, 0.3)), + new_note(("G 4", None, 0.3)), + ], + vec![ + new_note(("C 4", None, 0.3)), + new_note(("D#4", None, 0.3)), + new_note(("F 4", None, 0.3)), + ], + vec![ + new_note(("C 4", None, 0.3)), + new_note(("D#4", None, 0.3)), + new_note(("G 4", None, 0.3)), + ], + vec![ + new_note(("C 4", None, 0.3)), + new_note(("D#4", None, 0.3)), + new_note(("A#4", None, 0.3)), + ], + ])); + + let fx_pattern = + beat_time + .every_nth_seconds(8.0) + .trigger(new_polyphonic_note_sequence_event(vec![ + vec![new_note(("C 4", None, 0.2)), None, None], + vec![None, new_note(("C 4", None, 0.2)), None], + vec![None, None, new_note(("F 4", None, 0.2))], + ])); + + Ok(Phrase::new( + beat_time, + vec![ + RhythmSlot::from(kick_pattern), + RhythmSlot::from(snare_pattern), + RhythmSlot::from(hihat_rhythm), + RhythmSlot::from(bass_pattern), + RhythmSlot::from(fx_pattern), + RhythmSlot::from(synth_pattern), + ], + BeatTimeStep::Bar(8.0), + )) + } + + fn run_phrase(phrase: &mut Phrase, time: SampleTime) -> Vec { + let mut events = Vec::new(); + while let Some(event) = phrase.run_until_time(time) { + events.push(event) + } + events + } + + // slow skip using run_until_time + fn skip_phrase_by_running(phrase: &mut Phrase, time: SampleTime) { + while phrase.run_until_time(time).is_some() { + // ignore event + } + } + + // fast skip using skip_events_until_time + fn skip_phrase_by_advancing(phrase: &mut Phrase, time: SampleTime) { + phrase.advance_until_time(time) + } + + #[test] + fn skip_events() -> Result<(), String> { + let sample_offset = 2345676; + + let mut phrase1 = create_phrase()?; + phrase1.set_sample_offset(sample_offset); + let mut events1 = Vec::new(); + + let mut phrase2 = create_phrase()?; + phrase2.set_sample_offset(sample_offset); + let mut events2 = Vec::new(); + + // run_time, advance_time + let run_steps = [ + (1024, 1), + (2000, 555432), + (5000, 666), + (200, 211), + (100, 10200), + (1024, 122), + (8000, 5577432), + (50000, 66), + (20020, 2121), + (1000, 100), + ]; + + let mut sample_time = sample_offset; + for (run_time, seek_time) in run_steps { + sample_time += run_time; + events1.append(&mut run_phrase(&mut phrase1, sample_time)); + events2.append(&mut run_phrase(&mut phrase2, sample_time)); + + sample_time += seek_time; + skip_phrase_by_running(&mut phrase1, sample_time); + skip_phrase_by_advancing(&mut phrase2, sample_time); + } + + assert_eq!(events1, events2); + + Ok(()) + } +} diff --git a/src/player.rs b/src/player.rs index 95068bc..4019b60 100644 --- a/src/player.rs +++ b/src/player.rs @@ -236,10 +236,10 @@ impl SamplePlayer { // match playing notes state to the passed rhythm self.playing_notes .resize(sequence.phrase_rhythm_slot_count(), HashMap::new()); - // seek new phase to our previously played time - sequence.skip_events_until_time(self.emitted_sample_time); + // move new phase to our previously played time + sequence.advance_until_time(self.emitted_sample_time); log::debug!(target: "Player", - "Seek sequence to time {:.2}", + "Advance sequence to time {:.2}", time_base.samples_to_seconds(self.emitted_sample_time) ); } diff --git a/src/rhythm.rs b/src/rhythm.rs index 539a14e..551bde8 100644 --- a/src/rhythm.rs +++ b/src/rhythm.rs @@ -60,22 +60,20 @@ pub trait RhythmIter: Debug { /// Set a new custom sample offset value. fn set_sample_offset(&mut self, sample_offset: SampleTime); - /// Step iter: runs pattern to generate a new pulse, then generates an event from the event iter. - /// Returns `None` when the pattern finished playing. - fn run(&mut self) -> Option { - self.run_until_time(SampleTime::MAX) - } - /// Sample time iter: Generates the next due event but running the pattern to generate a new + /// Sample time iter: Generate a single next due event but running the pattern to generate a new /// pulse, if the pulse's sample time is smaller than the given sample time. Then generates an - /// event from the event iter and returns it. Else returns `None` when the pattern finished playing. + /// event from the event iter and returns it. + /// + /// Returns `None` when no event is due of when the pattern finished playing, else Some event. fn run_until_time(&mut self, sample_time: SampleTime) -> Option; - /// Skip, dry run *all events* until the given target time is reached. Depending on the rhythm - /// impl this may be faster than using `run_until_time`, fetching, then discarding events. - fn seek_until_time(&mut self, sample_time: SampleTime) { - // default impl fetches and ignores all events until we've reached the given sample_time - while let Some(event) = self.run_until_time(sample_time) { - debug_assert!(event.time < sample_time); + /// Skip *all events* until the given target time is reached. + /// + /// This calls `run_until_time` by default, until the target time is reached and + /// discards all generated events, but may be overridden to optimize run time. + fn advance_until_time(&mut self, sample_time: SampleTime) { + while self.run_until_time(sample_time).is_some() { + // continue } } } @@ -87,7 +85,7 @@ impl Iterator for dyn RhythmIter { type Item = RhythmIterItem; fn next(&mut self) -> Option { - self.run() + self.run_until_time(SampleTime::MAX) } } diff --git a/src/rhythm/beat_time.rs b/src/rhythm/beat_time.rs index 429b156..d9a4486 100644 --- a/src/rhythm/beat_time.rs +++ b/src/rhythm/beat_time.rs @@ -9,16 +9,19 @@ use crate::{ // ------------------------------------------------------------------------------------------------- impl GenericRhythmTimeStep for BeatTimeStep { + #[inline] fn default_offset() -> Self { Self::Beats(0.0) } + #[inline] fn default_step() -> Self { Self::Beats(1.0) } - fn to_samples(&self, time_base: &crate::BeatTimeBase) -> f64 { - self.to_samples(time_base) + #[inline] + fn to_samples(&self, time_base: &BeatTimeBase) -> f64 { + BeatTimeStep::to_samples(self, time_base) } } diff --git a/src/rhythm/generic.rs b/src/rhythm/generic.rs index 981a830..921f4cd 100644 --- a/src/rhythm/generic.rs +++ b/src/rhythm/generic.rs @@ -47,6 +47,7 @@ pub struct GenericRhythm, pattern: Box, + pattern_playback_finished: bool, gate: Box, event_iter: Box, event_iter_sample_time: SampleTime, @@ -63,6 +64,7 @@ impl GenericRhythm::default(); + let pattern_playback_finished = false; let gate = Box::new(ProbabilityGate::new(seed)); let event_iter = Box::::default(); let event_iter_sample_time = 0; @@ -76,6 +78,7 @@ impl GenericRhythm GenericRhythm f64 { self.step.to_samples(&self.time_base) * self.event_iter_pulse_item.step_time } /// Return start sample time of the given event iter item + #[inline] fn event_iter_item_start_time(&self, start: &Fraction) -> SampleTime { let step_time = self.current_steps_sample_duration(); let event_iter_time = self.sample_offset as f64 + self.event_iter_next_sample_time; @@ -191,6 +196,7 @@ impl GenericRhythm SampleTime { let step_time = self.current_steps_sample_duration(); let length = length.to_f64().unwrap_or(1.0); @@ -208,73 +214,54 @@ impl GenericRhythm Clone - for GenericRhythm -{ - fn clone(&self) -> Self { - Self { - pattern: self.pattern.duplicate(), - event_iter: self.event_iter.duplicate(), - event_iter_items: self.event_iter_items.clone(), - gate: self.gate.duplicate(), - ..*self + fn run_pattern(&mut self) -> Option<(PulseIterItem, bool)> { + debug_assert!( + self.event_iter_items.is_empty(), + "Should only run patterns when there are no pending event iter items" + ); + if let Some(pulse) = self.pattern.run() { + let emit_event = self.gate.run(&pulse); + self.event_iter_pulse_item = pulse; + Some((pulse, emit_event)) + } else { + None } } -} -impl Iterator - for GenericRhythm -{ - type Item = RhythmIterItem; - - fn next(&mut self) -> Option { - self.run() - } -} - -impl RhythmIter - for GenericRhythm -{ - fn sample_time_display(&self) -> Box { - Box::new(self.time_base) - } - - fn sample_offset(&self) -> SampleTime { - self.sample_offset - } - fn set_sample_offset(&mut self, sample_offset: SampleTime) { - self.sample_offset = sample_offset; - } - - fn run_until_time(&mut self, sample_time: SampleTime) -> Option { + fn run_rhythm( + &mut self, + sample_time: SampleTime, + fetch_new_event_iter_items: bool, + ) -> Option { + // quickly check if pattern playback finished + if self.pattern_playback_finished { + return None; + } // quickly check if the next event is due before the given target time - self.event_iter_sample_time = sample_time; let next_sample_time = self.sample_offset + self.event_iter_next_sample_time as SampleTime; if next_sample_time >= sample_time { // next event is not yet due return None; } - // fetch new event iter items, if neccessary + // fetch new event iter items, if necessary if self.event_iter_items.is_empty() { + if !fetch_new_event_iter_items { + // if we should not fetch new event iter items we're done here + return None; + } // generate a pulse from the pattern and pass the pulse to the gate - let (new_pulse_item, emit_event) = { - if let Some(pulse) = self.pattern.run() { - let emit_event = self.gate.run(&pulse); - (pulse, emit_event) + if let Some((pulse, emit_event)) = self.run_pattern() { + // generate new events from the gated pulse + if let Some(slice) = self.event_iter.run(pulse, emit_event) { + self.event_iter_items = VecDeque::from(slice); } else { - // pattern playback finished - return None; + self.event_iter_items.clear(); } - }; - self.event_iter_pulse_item = new_pulse_item; - // generate new events from the gated pulse - let slice = self.event_iter.run(new_pulse_item, emit_event); - if let Some(slice) = slice { - self.event_iter_items = VecDeque::from(slice); } else { - self.event_iter_items.clear(); + // pattern playback finished + self.pattern_playback_finished = true; + return None; } } // fetch a new event item from the event iter item deque @@ -283,13 +270,13 @@ impl RhythmIter .pop_front() .map(|event| self.event_with_default_instrument(event)) { - if self.event_iter_item_start_time(&event_item.start) >= sample_time { + // return event as sample timed rhythm iter item + let time = self.event_iter_item_start_time(&event_item.start); + if time >= sample_time { // the given event iter item is not yet due: put it back self.event_iter_items.push_front(event_item); return None; } - // return event as sample timed rhythm iter item - let time = self.event_iter_item_start_time(&event_item.start); let event = Some(event_item.event); let duration = self.event_iter_item_duration(&event_item.length); // advance to the next pulse in the next iteration when all events got consumed @@ -303,8 +290,9 @@ impl RhythmIter duration, }) } else { - // and return a timed None event + // return 'None' event as sample timed rhythm iter item let time = self.event_iter_item_start_time(&Fraction::ZERO); + debug_assert!(time < sample_time, "Event should be due here"); let event = None; let duration = self.event_iter_item_duration(&Fraction::ONE); // advance to the next pulse in the next iteration @@ -319,6 +307,113 @@ impl RhythmIter } } +impl Clone + for GenericRhythm +{ + fn clone(&self) -> Self { + Self { + pattern: self.pattern.duplicate(), + event_iter: self.event_iter.duplicate(), + event_iter_items: self.event_iter_items.clone(), + gate: self.gate.duplicate(), + ..*self + } + } +} + +impl Iterator + for GenericRhythm +{ + type Item = RhythmIterItem; + + fn next(&mut self) -> Option { + self.run_until_time(SampleTime::MAX) + } +} + +impl RhythmIter + for GenericRhythm +{ + fn sample_time_display(&self) -> Box { + Box::new(self.time_base) + } + + fn sample_offset(&self) -> SampleTime { + self.sample_offset + } + fn set_sample_offset(&mut self, sample_offset: SampleTime) { + self.sample_offset = sample_offset; + } + + fn run_until_time(&mut self, sample_time: SampleTime) -> Option { + // memorize current time + self.event_iter_sample_time = sample_time; + // fetch events + let fetch_new_items = true; + self.run_rhythm(sample_time, fetch_new_items) + } + + fn advance_until_time(&mut self, sample_time: SampleTime) { + // memorize current time + self.event_iter_sample_time = sample_time; + // clear pending event iter items with regular runs + while !self.event_iter_items.is_empty() { + let fetch_new_items = false; + if self.run_rhythm(sample_time, fetch_new_items).is_none() { + break; + } + } + // when the are still pending events, they are not yet due, so we are done + if !self.event_iter_items.is_empty() { + return; + } + // quickly check if pattern playback finished + if self.pattern_playback_finished { + return; + } + // batch advance event iter in full pulse steps + loop { + // quickly check if the next event is due before the given target time + let next_sample_time = self.sample_offset as f64 + self.event_iter_next_sample_time; + if (next_sample_time as SampleTime) >= sample_time { + // next event is not yet due: we're done + return; + } + // generate a pulse from the pattern and pass the pulse to the gate + if let Some((pulse, emit_event)) = self.run_pattern() { + // test if the event iter crosses the target time + let step_duration = self.current_steps_sample_duration(); + if ((next_sample_time + step_duration) as SampleTime) < sample_time { + // skip all events from the gated pulse + self.event_iter.advance(pulse, emit_event); + self.event_iter_next_sample_time += step_duration; + } else { + // generate new events from the gated pulse + if let Some(slice) = self.event_iter.run(pulse, emit_event) { + self.event_iter_items = VecDeque::from(slice); + break; // clear remaining items with regular runs + } else { + self.event_iter_items.clear(); + self.event_iter_next_sample_time += self.current_steps_sample_duration(); + return; // remaining step is empty: we can finish this run + } + } + } else { + // pattern playback finished: we're done here + self.pattern_playback_finished = true; + return; + } + } + // clear remaining event iter items with regular runs + while !self.event_iter_items.is_empty() { + let fetch_new_items = true; + if self.run_rhythm(sample_time, fetch_new_items).is_none() { + break; + } + } + } +} + impl Rhythm for GenericRhythm { @@ -368,6 +463,7 @@ impl Rhythm self.sample_offset = 0; // reset pattern and gate self.pattern.reset(); + self.pattern_playback_finished = false; self.gate.reset(); // reset iterator state self.event_iter.reset(); diff --git a/src/rhythm/second_time.rs b/src/rhythm/second_time.rs index 0f8094d..a4a83d7 100644 --- a/src/rhythm/second_time.rs +++ b/src/rhythm/second_time.rs @@ -10,16 +10,19 @@ use crate::{ // ------------------------------------------------------------------------------------------------- impl GenericRhythmTimeStep for SecondTimeStep { + #[inline] fn default_offset() -> Self { 0.0 } + #[inline] fn default_step() -> Self { 1.0 } + #[inline] fn to_samples(&self, time_base: &BeatTimeBase) -> f64 { - time_base.seconds_to_samples_exact(*self) + *self * time_base.samples_per_second() as f64 } } diff --git a/src/sequence.rs b/src/sequence.rs index 060a5ab..b5498aa 100644 --- a/src/sequence.rs +++ b/src/sequence.rs @@ -59,17 +59,16 @@ impl Sequence { /// Run rhythms until a given sample time is reached, calling the given `visitor` /// function for all emitted events to consume them. - pub fn consume_events_until_time(&mut self, run_until_time: SampleTime, consumer: &mut F) + pub fn consume_events_until_time(&mut self, sample_time: SampleTime, consumer: &mut F) where F: FnMut(RhythmIndex, SampleTime, Option, SampleTime), { debug_assert!( - run_until_time >= self.sample_position, + sample_time >= self.sample_position, "can not rewind playback here" ); - while run_until_time - self.sample_position > 0 { - let (next_phrase_start, samples_to_run) = - self.samples_until_next_phrase(run_until_time); + while sample_time - self.sample_position > 0 { + let (next_phrase_start, samples_to_run) = self.samples_until_next_phrase(sample_time); if next_phrase_start <= samples_to_run { // run current phrase until it ends let sample_position = self.sample_position; @@ -97,20 +96,19 @@ impl Sequence { } } - /// Seek sequence until a given sample time is reached, ignoring all events. - pub fn skip_events_until_time(&mut self, run_until_time: SampleTime) { + /// Move sequence playback head to the given sample time, ignoring all events. + pub fn advance_until_time(&mut self, sample_time: SampleTime) { debug_assert!( - run_until_time >= self.sample_position, + sample_time >= self.sample_position, "can not rewind playback here" ); - while run_until_time - self.sample_position > 0 { - let (next_phrase_start, samples_to_run) = - self.samples_until_next_phrase(run_until_time); + while sample_time - self.sample_position > 0 { + let (next_phrase_start, samples_to_run) = self.samples_until_next_phrase(sample_time); if next_phrase_start <= samples_to_run { // run current phrase until it ends let sample_position = self.sample_position; self.current_phrase_mut() - .skip_events_until_time(sample_position + next_phrase_start); + .advance_until_time(sample_position + next_phrase_start); // select next phrase in the sequence let previous_phrase = self.current_phrase_mut().clone(); self.phrase_index = (self.phrase_index + 1) % self.phrases().len(); @@ -126,7 +124,7 @@ impl Sequence { // keep running the current phrase let sample_position = self.sample_position; self.current_phrase_mut() - .skip_events_until_time(sample_position + samples_to_run); + .advance_until_time(sample_position + samples_to_run); self.sample_position_in_phrase += samples_to_run; self.sample_position += samples_to_run; } diff --git a/src/tidal/cycle.rs b/src/tidal/cycle.rs index 30d336d..f6ffa94 100644 --- a/src/tidal/cycle.rs +++ b/src/tidal/cycle.rs @@ -21,7 +21,7 @@ pub struct Cycle { root: Step, event_limit: usize, input: String, - seed: Option<[u8; 32]>, + seed: Option, state: CycleState, } impl Cycle { @@ -44,7 +44,6 @@ impl Cycle { let input = input.to_string(); let root = CycleParser::step(mini)?; let state = CycleState { - step: 0, events: 0, iteration: 0, rng: Xoshiro256PlusPlus::from_seed(thread_rng().gen()), @@ -78,11 +77,10 @@ impl Cycle { self.state.iteration == 0, "Should not reconfigure seed of running cycle" ); + // create a u64 seed value from the given seed array + // TODO: should pass a u64 here directly + let seed: u64 = Xoshiro256PlusPlus::from_seed(seed).gen(); Self { - state: CycleState { - rng: Xoshiro256PlusPlus::from_seed(seed), - ..self.state - }, seed: Some(seed), ..self } @@ -96,12 +94,10 @@ impl Cycle { } } - // TODO remove this or improve, * and / can change the output, <1> does not etc.. - /// check if a cycle will give different outputs between cycles + /// Check if a cycle may give different outputs between cycles. pub fn is_stateful(&self) -> bool { - ['<', '{', '|', '?', '/'] - .iter() - .any(|&c| self.input.contains(c)) + // TODO improve: * and / can change the output, <1> does not etc.. + self.input.contains(['<', '{', '|', '?', '/', '*']) } /// Query for the next iteration of output. @@ -110,20 +106,24 @@ impl Cycle { pub fn generate(&mut self) -> Result>, String> { let cycle = self.state.iteration; self.state.events = 0; - self.state.step = 0; + if let Some(seed) = self.seed { + self.state.rng = Xoshiro256PlusPlus::seed_from_u64(seed.wrapping_add(cycle as u64)); + } let mut events = Self::output(&self.root, &mut self.state, cycle, self.event_limit)?; self.state.iteration += 1; events.transform_spans(&Span::default()); Ok(events.export()) } + /// Move cycle iteration without generating any events. + pub fn advance(&mut self) { + self.state.iteration += 1; + } + /// reset state to initial state pub fn reset(&mut self) { self.state.iteration = 0; - self.state.step = 0; self.state.events = 0; - self.state.rng = - Xoshiro256PlusPlus::from_seed(self.seed.unwrap_or_else(|| thread_rng().gen())); } } @@ -1429,7 +1429,6 @@ impl CycleParser { struct CycleState { iteration: u32, rng: Xoshiro256PlusPlus, - step: u32, events: usize, } @@ -1533,7 +1532,7 @@ impl Cycle { } Step::Choices(cs) => { let choice = state.rng.gen_range(0..cs.choices.len()); - Self::output(&cs.choices[choice], state, cycle, limit)? // TODO seed the rng properly + Self::output(&cs.choices[choice], state, cycle, limit)? } Step::Polymeter(pm) => { let step = pm.steps.as_ref(); @@ -1571,7 +1570,6 @@ impl Cycle { let mut out = Self::output(e.left.as_ref(), state, cycle, limit)?; out.mutate_events(&mut |event: &mut Event| { if let Some(chance) = e.right.to_chance() { - // TODO seed the rng properly if chance < state.rng.gen_range(0.0..1.0) { event.value = Value::Rest } @@ -1714,6 +1712,7 @@ impl Cycle { #[cfg(test)] mod test { use super::*; + type F = fraction::Fraction; fn assert_cycles(input: &str, outputs: Vec>>) -> Result<(), String> { @@ -1725,13 +1724,64 @@ mod test { } fn assert_cycle_equality(a: &str, b: &str) -> Result<(), String> { - assert_eq!(Cycle::from(a)?.generate()?, Cycle::from(b)?.generate()?,); + let seed = rand::thread_rng().gen(); + assert_eq!( + Cycle::from(a)?.with_seed(seed).generate()?, + Cycle::from(b)?.with_seed(seed).generate()?, + ); + Ok(()) + } + + fn assert_cycle_advancing(input: &str) -> Result<(), String> { + let seed = rand::thread_rng().gen(); + for number_of_runs in 1..9 { + let mut cycle1 = Cycle::from(input)?.with_seed(seed); + let mut cycle2 = Cycle::from(input)?.with_seed(seed); + for _ in 0..number_of_runs { + let _ = cycle1.generate()?; + cycle2.advance(); + } + assert_eq!(cycle1.generate()?, cycle2.generate()?); + } Ok(()) } #[test] - pub fn cycle() -> Result<(), String> { + fn span() -> Result<(), String> { + assert!(Span::new(F::new(0u8, 1u8), F::new(1u8, 1u8)) + .includes(&Span::new(F::new(1u8, 2u8), F::new(2u8, 1u8)))); + Ok(()) + } + + #[test] + fn parse() -> Result<(), String> { + assert!(Cycle::from("a b c [d").is_err()); + assert!(Cycle::from("a b/ c [d").is_err()); + assert!(Cycle::from("a b--- c [d").is_err()); + assert!(Cycle::from("*a b c [d").is_err()); + assert!(Cycle::from("a {{{}}").is_err()); + assert!(Cycle::from("a*[]").is_err()); + assert!(Cycle::from("] a z [").is_err()); + assert!(Cycle::from("->err").is_err()); + assert!(Cycle::from("(a, b)").is_err()); + assert!(Cycle::from("#(12, 32)").is_err()); + assert!(Cycle::from("#c $").is_err()); + + assert!(Cycle::from("c44'mode").is_err()); + assert!(Cycle::from("c4'!mode").is_err()); + assert!(Cycle::from("y'mode").is_err()); + assert!(Cycle::from("c4'mo'de").is_err()); + + assert!(Cycle::from("c4'mode").is_ok()); + assert!(Cycle::from("c'm7#\u{0394}").is_ok()); + assert!(Cycle::from("[[[[[[[[]]]]]][[[[[]][[[]]]]]][[[][[[]]]]][[[[]]]]]]").is_ok()); + + Ok(()) + } + + #[test] + fn generate() -> Result<(), String> { assert_eq!( Cycle::from("a b c d")?.generate()?, [[ @@ -1912,15 +1962,6 @@ mod test { ]] ); - assert!(Cycle::from("c44'mode").is_err()); - assert!(Cycle::from("c4'!mode").is_err()); - assert!(Cycle::from("y'mode").is_err()); - assert!(Cycle::from("c4'mo'de").is_err()); - - assert!(Cycle::from("c4'mode").is_ok()); - assert!(Cycle::from("c'm7#\u{0394}").is_ok()); - assert!(Cycle::from("[[[[[[[[]]]]]][[[[[]][[[]]]]]][[[][[[]]]]][[[[]]]]]]").is_ok()); - assert_cycles( "", vec![ @@ -2110,42 +2151,27 @@ mod test { ]] ); - assert_eq!(Cycle::from("a? b?")?.root, Cycle::from("a?0.5 b?0.5")?.root); - + assert_cycle_equality("a? b?", "a?0.5 b?0.5")?; assert_cycle_equality("[a b c](3,8,9)", "[a b c](3,8,1)")?; - assert_cycle_equality("[a b c](3,8,7)", "[a b c](3,8,-1)")?; - assert_cycle_equality("[a a a a]", "[a ! ! !]")?; - assert_cycle_equality("[! ! a !]", "[~ ~ a a]")?; - assert_cycle_equality("a ~ ~ ~", "a - - -")?; - assert_cycle_equality("[a b] ! ! !", "[a b] [a b] [a b] ")?; - assert_cycle_equality("{a b!2 c}%3", "{a b b c}%3")?; - assert_cycle_equality("a b, {c d e}%2", "{a b, c d e}")?; - assert_cycle_equality("0..3", "0 1 2 3")?; - assert_cycle_equality("-5..-8", "-5 -6 -7 -8")?; - assert_cycle_equality("a b . c d", "[a b] [c d]")?; - assert_cycle_equality( "a b . c d e . f g h i [j k . l m]", "[a b] [c d e] [f g h i [[j k] [l m]]]", )?; - assert_cycle_equality( "a b . c d e , f g h i . j k, l m", "[a b] [c d e], [[f g h i] [j k]], [l m]", )?; - assert_cycle_equality("{a b . c d . f g h}%2", "{[a b] [c d] [f g h]}%2")?; - assert_cycle_equality("", "<[a b] [c d] [f g h]>")?; assert_cycles( @@ -2165,27 +2191,31 @@ mod test { ], )?; - assert!(Span::new(F::new(0u8, 1u8), F::new(1u8, 1u8)) - .includes(&Span::new(F::new(1u8, 2u8), F::new(2u8, 1u8)))); - // TODO test random outputs // parse_with_debug("[a b c d]?0.5"); + Ok(()) + } + + #[test] + fn event_limit() -> Result<(), String> { assert!(Cycle::from("[[a b c d]*100]*100")?.generate().is_err()); assert!(Cycle::from("[[a b c d]*100]*100")? .with_event_limit(0x10000) .generate() .is_ok()); - assert!(Cycle::from("a b c [d").is_err()); - assert!(Cycle::from("a b/ c [d").is_err()); - assert!(Cycle::from("a b--- c [d").is_err()); - assert!(Cycle::from("*a b c [d").is_err()); - assert!(Cycle::from("a {{{}}").is_err()); - assert!(Cycle::from("a*[]").is_err()); - assert!(Cycle::from("] a z [").is_err()); - assert!(Cycle::from("->err").is_err()); - assert!(Cycle::from("(a, b)").is_err()); - assert!(Cycle::from("#(12, 32)").is_err()); - assert!(Cycle::from("#c $").is_err()); + Ok(()) + } + + #[test] + fn advancing() -> Result<(), String> { + assert_cycle_advancing("[a b c d]")?; // stateless + assert_cycle_advancing("[a b], [c d]")?; + assert_cycle_advancing("{a b}%2 {a b}*5")?; // stateful + assert_cycle_advancing("[a b]*5 [a b]/5")?; + assert_cycle_advancing("[a b c d]")?; + assert_cycle_advancing("a ")?; + assert_cycle_advancing("[a b? c d]|[c? d?]")?; + assert_cycle_advancing("[{a b}/2 c d], e? {a b}*2")?; Ok(()) } } diff --git a/src/time.rs b/src/time.rs index 9c7f0e0..3c7dc75 100644 --- a/src/time.rs +++ b/src/time.rs @@ -36,7 +36,4 @@ pub trait TimeBase: Debug { fn seconds_to_samples(&self, seconds: f64) -> SampleTime { (seconds * self.samples_per_second() as f64).trunc() as SampleTime } - fn seconds_to_samples_exact(&self, seconds: f64) -> f64 { - seconds * self.samples_per_second() as f64 - } } diff --git a/src/time/beats.rs b/src/time/beats.rs index 3418d24..8a206ac 100644 --- a/src/time/beats.rs +++ b/src/time/beats.rs @@ -15,10 +15,12 @@ pub struct BeatTimeBase { impl BeatTimeBase { /// Time base's samples per beat, in order to convert beat to sample time and vice versa. + #[inline] pub fn samples_per_beat(&self) -> f64 { self.samples_per_sec as f64 * 60.0 / self.beats_per_min as f64 } /// Time base's samples per bar, in order to convert bar to sample time and vice versa. + #[inline] pub fn samples_per_bar(&self) -> f64 { self.samples_per_sec as f64 * 60.0 / self.beats_per_min as f64 * self.beats_per_bar as f64 } @@ -33,6 +35,7 @@ impl From for SecondTimeBase { } impl TimeBase for BeatTimeBase { + #[inline] fn samples_per_second(&self) -> u32 { self.samples_per_sec } @@ -43,10 +46,10 @@ impl SampleTimeDisplay for BeatTimeBase { fn display(&self, sample_time: SampleTime) -> String { let total_beats = sample_time / self.samples_per_beat() as u64; let total_beats_f = sample_time as f64 / self.samples_per_beat(); - let beat_frations = total_beats_f - total_beats as f64; + let beat_fractions = total_beats_f - total_beats as f64; let bars = total_beats / self.beats_per_bar as u64; let beats = total_beats - self.beats_per_bar as u64 * bars; - let ppq = (beat_frations * 960.0 + 0.5) as u64; + let ppq = (beat_fractions * 960.0 + 0.5) as u64; format!("{}.{}.{:03}", bars + 1, beats + 1, ppq) } } @@ -69,7 +72,6 @@ pub enum BeatTimeStep { impl BeatTimeStep { /// Get number of steps in the current time resolution. pub fn steps(&self) -> f32 { - #[allow(clippy::match_same_arms)] match *self { BeatTimeStep::SixtyFourth(amount) => amount, BeatTimeStep::ThirtySecond(amount) => amount, @@ -109,6 +111,7 @@ impl BeatTimeStep { } } /// Convert a beat or bar step to samples for the given beat time base. + #[inline] pub fn to_samples(&self, time_base: &BeatTimeBase) -> f64 { self.steps() as f64 * self.samples_per_step(time_base) } diff --git a/src/time/seconds.rs b/src/time/seconds.rs index a12eee8..04fd40b 100644 --- a/src/time/seconds.rs +++ b/src/time/seconds.rs @@ -12,6 +12,7 @@ pub struct SecondTimeBase { } impl TimeBase for SecondTimeBase { + #[inline] fn samples_per_second(&self) -> u32 { self.samples_per_sec }