From aafe3179207a46672b660afdc8c5e8a63bba5c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduard=20M=C3=BCller?= Date: Mon, 1 Jul 2024 13:06:33 +0200 Subject: [PATCH 1/5] add new `omit` function to EventIter and a new `skip_until_time` impl to GenericRhythm --- benches/benchmarks/rhythm.rs | 2 +- benches/benchmarks/scripted.rs | 2 +- src/event.rs | 10 ++++- src/event/cycle.rs | 14 +++++++ src/event/fixed.rs | 12 ++++-- src/event/mutated.rs | 16 +++----- src/event/scripted.rs | 32 ++++++++++++++- src/event/scripted_cycle.rs | 14 +++++++ src/phrase.rs | 25 ++++++------ src/rhythm.rs | 13 +++--- src/rhythm/generic.rs | 72 +++++++++++++++++++++++++++++++--- src/tidal/cycle.rs | 25 +++++++++--- 12 files changed, 187 insertions(+), 50 deletions(-) diff --git a/benches/benchmarks/rhythm.rs b/benches/benchmarks/rhythm.rs index 9066c37..53a397e 100644 --- a/benches/benchmarks/rhythm.rs +++ b/benches/benchmarks/rhythm.rs @@ -194,7 +194,7 @@ pub fn seek(c: &mut Criterion) { phrase.reset(); 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); + phrase.skip_until_time(sample_time); sample_time += seek_step * samples_per_sec as SampleTime; } }) diff --git a/benches/benchmarks/scripted.rs b/benches/benchmarks/scripted.rs index 83e004f..3c122d5 100644 --- a/benches/benchmarks/scripted.rs +++ b/benches/benchmarks/scripted.rs @@ -185,7 +185,7 @@ pub fn seek(c: &mut Criterion) { phrase.reset(); 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); + phrase.skip_until_time(sample_time); sample_time += seek_step * samples_per_sec as SampleTime; } }) diff --git a/src/event.rs b/src/event.rs index 140a2c8..96c497a 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 returning 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 omit(&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..d843163 100644 --- a/src/event/cycle.rs +++ b/src/event/cycle.rs @@ -248,6 +248,14 @@ impl CycleEventIter { // convert timed note events into EventIterItems timed_note_events.into_event_iter_items() } + + /// Generate next batch of events from the next cycle run but ignore the results. + fn omit_events(&mut self) { + // run the cycle event generator + if let Err(err) = self.cycle.omit() { + panic!("Cycle runtime error: {err}"); + } + } } impl EventIter for CycleEventIter { @@ -267,6 +275,12 @@ impl EventIter for CycleEventIter { } } + fn omit(&mut self, _pulse: PulseIterItem, emit_event: bool) { + if emit_event { + self.omit_events() + } + } + fn duplicate(&self) -> Box { Box::new(self.clone()) } diff --git a/src/event/fixed.rs b/src/event/fixed.rs index 0a14343..623a680 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 omit(&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..d57e6bb 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_event(&mut self, pulse: PulseIterItem) -> LuaResult>> { // reset timeout self.timeout_hook.reset(); // update function context @@ -64,6 +64,19 @@ impl ScriptedEventIter { // return as EventIterItem Ok(Some(vec![EventIterItem::new(event)])) } + + fn omit_event(&mut self, pulse: PulseIterItem) -> LuaResult<()> { + // 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(()) + } } impl Clone for ScriptedEventIter { @@ -99,7 +112,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_event(pulse) { Ok(event) => event, Err(err) => { self.callback.handle_error(&err); @@ -117,6 +130,21 @@ impl EventIter for ScriptedEventIter { } } + fn omit(&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.omit_event(pulse) { + self.callback.handle_error(&err); + } + self.step += 1; + self.pulse_step += 1; + self.pulse_time_step += pulse.step_time; + } else { + 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..34c795c 100644 --- a/src/event/scripted_cycle.rs +++ b/src/event/scripted_cycle.rs @@ -170,6 +170,14 @@ impl ScriptedCycleEventIter { // convert timed note events into EventIterItems timed_note_events.into_event_iter_items() } + + /// Generate next batch of events from the next cycle run but ignore the results. + fn omit_events(&mut self) { + // run the cycle event generator + if let Err(err) = self.cycle.omit() { + add_lua_callback_error("cycle", &LuaError::RuntimeError(err)); + } + } } impl EventIter for ScriptedCycleEventIter { @@ -203,6 +211,12 @@ impl EventIter for ScriptedCycleEventIter { } } + fn omit(&mut self, _pulse: PulseIterItem, emit_event: bool) { + if emit_event { + self.omit_events() + } + } + fn duplicate(&self) -> Box { Box::new(self.clone()) } diff --git a/src/phrase.rs b/src/phrase.rs index 50d2054..03555ca 100644 --- a/src/phrase.rs +++ b/src/phrase.rs @@ -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 seek 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().skip_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,7 +246,7 @@ impl RhythmIter for Phrase { .map(|(_, event)| event) } - fn seek_until_time(&mut self, sample_time: SampleTime) { + fn skip_until_time(&mut self, sample_time: SampleTime) { self.skip_events_until_time(sample_time) } } diff --git a/src/rhythm.rs b/src/rhythm.rs index 539a14e..e5ae704 100644 --- a/src/rhythm.rs +++ b/src/rhythm.rs @@ -70,12 +70,13 @@ pub trait RhythmIter: Debug { /// event from the event iter and returns it. Else returns `None` when the pattern finished playing. 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, dry run *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 skip_until_time(&mut self, sample_time: SampleTime) { + while self.run_until_time(sample_time).is_some() { + // continue } } } diff --git a/src/rhythm/generic.rs b/src/rhythm/generic.rs index 981a830..e40f1d1 100644 --- a/src/rhythm/generic.rs +++ b/src/rhythm/generic.rs @@ -256,7 +256,7 @@ impl RhythmIter // 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() { // generate a pulse from the pattern and pass the pulse to the gate let (new_pulse_item, emit_event) = { @@ -270,8 +270,7 @@ impl RhythmIter }; 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 { + if let Some(slice) = self.event_iter.run(new_pulse_item, emit_event) { self.event_iter_items = VecDeque::from(slice); } else { self.event_iter_items.clear(); @@ -283,13 +282,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 @@ -305,6 +304,7 @@ impl RhythmIter } else { // and return a timed None event 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 @@ -317,6 +317,66 @@ impl RhythmIter }) } } + + fn skip_until_time(&mut self, sample_time: SampleTime) { + loop { + // 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: we're done + break; + } + // fetch new event iter items, if necessary + if self.event_iter_items.is_empty() { + // 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) + } else { + // pattern playback finished + break; + } + }; + self.event_iter_pulse_item = new_pulse_item; + + // test if the event iter crosses the target time + let step_duration = self.current_steps_sample_duration(); + let next_step_time = + (self.event_iter_next_sample_time + step_duration) as SampleTime; + if next_step_time < sample_time { + self.event_iter.omit(new_pulse_item, emit_event); + self.event_iter_next_sample_time += step_duration; + continue; + } else { + // generate new events from the gated pulse + if let Some(slice) = self.event_iter.run(new_pulse_item, emit_event) { + self.event_iter_items = VecDeque::from(slice); + } else { + self.event_iter_items.clear(); + } + } + } + // fetch a new event item from the event iter item deque + if let Some(event_item) = self.event_iter_items.pop_front() { + // 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); + break; + } + // advance to the next pulse in the next iteration when all events got consumed + if self.event_iter_items.is_empty() { + self.event_iter_next_sample_time += self.current_steps_sample_duration(); + } + } else { + self.event_iter_next_sample_time += self.current_steps_sample_duration(); + } + } + } } impl Rhythm diff --git a/src/tidal/cycle.rs b/src/tidal/cycle.rs index 30d336d..d965d04 100644 --- a/src/tidal/cycle.rs +++ b/src/tidal/cycle.rs @@ -96,12 +96,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. @@ -117,6 +115,23 @@ impl Cycle { Ok(events.export()) } + /// Calculate next iteration of output without actually generating events. + /// + /// For stateful inputs, this generates output events, discarding resulting events, + /// for stateless inputs, this simply moves the iteration counter. + /// + /// Returns error when the number of generated events exceed the configured event limit. + pub fn omit(&mut self) -> Result<(), String> { + let cycle = self.state.iteration; + self.state.events = 0; + self.state.step = 0; + if self.is_stateful() { + let _ = Self::output(&self.root, &mut self.state, cycle, self.event_limit)?; + } + self.state.iteration += 1; + Ok(()) + } + /// reset state to initial state pub fn reset(&mut self) { self.state.iteration = 0; From b4b064a56c4ddc3c4db7dc7391201a6f1afb6680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduard=20M=C3=BCller?= Date: Tue, 2 Jul 2024 09:40:24 +0200 Subject: [PATCH 2/5] refactor generic rhythm to reuse code in skip & regular run impls --- src/rhythm/generic.rs | 197 ++++++++++++++++++++++++------------------ 1 file changed, 115 insertions(+), 82 deletions(-) diff --git a/src/rhythm/generic.rs b/src/rhythm/generic.rs index e40f1d1..f7c1098 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 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)> { + 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 @@ -258,22 +239,22 @@ impl RhythmIter } // 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((new_pulse_item, emit_event)) = self.run_pattern() { + // generate new events from the gated pulse + if let Some(slice) = self.event_iter.run(new_pulse_item, 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 - if let Some(slice) = self.event_iter.run(new_pulse_item, emit_event) { - 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 @@ -302,7 +283,7 @@ 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; @@ -317,36 +298,89 @@ 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() + } +} + +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 skip_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; + } + // batch omit events in whole steps loop { + // quickly check if pattern playback finished + if self.pattern_playback_finished { + return; + } // 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: we're done - break; + return; } - // fetch new event iter items, if necessary - if self.event_iter_items.is_empty() { - // 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) - } else { - // pattern playback finished - break; - } - }; - self.event_iter_pulse_item = new_pulse_item; - + // generate a pulse from the pattern and pass the pulse to the gate + if let Some((new_pulse_item, emit_event)) = self.run_pattern() { // test if the event iter crosses the target time let step_duration = self.current_steps_sample_duration(); let next_step_time = (self.event_iter_next_sample_time + step_duration) as SampleTime; if next_step_time < sample_time { + // omit all events from the gated pulse self.event_iter.omit(new_pulse_item, emit_event); self.event_iter_next_sample_time += step_duration; continue; @@ -354,26 +388,24 @@ impl RhythmIter // generate new events from the gated pulse if let Some(slice) = self.event_iter.run(new_pulse_item, 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 } } - } - // fetch a new event item from the event iter item deque - if let Some(event_item) = self.event_iter_items.pop_front() { - // 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); - break; - } - // advance to the next pulse in the next iteration when all events got consumed - if self.event_iter_items.is_empty() { - self.event_iter_next_sample_time += self.current_steps_sample_duration(); - } } else { - self.event_iter_next_sample_time += self.current_steps_sample_duration(); + // pattern playback finished + 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; } } } @@ -428,6 +460,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(); From 63cc4e6b14e46e87cc94030bcf762b5751eb195c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduard=20M=C3=BCller?= Date: Tue, 2 Jul 2024 09:42:04 +0200 Subject: [PATCH 3/5] add phrase seek tests --- src/phrase.rs | 207 ++++++++++++++++++++++++++++++++++++++++++ src/rhythm/generic.rs | 36 ++++---- 2 files changed, 225 insertions(+), 18 deletions(-) diff --git a/src/phrase.rs b/src/phrase.rs index 03555ca..c3918ac 100644 --- a/src/phrase.rs +++ b/src/phrase.rs @@ -308,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_omitting(phrase: &mut Phrase, time: SampleTime) { + phrase.skip_events_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, seek_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_omitting(&mut phrase2, sample_time); + } + + assert_eq!(events1, events2); + + Ok(()) + } +} diff --git a/src/rhythm/generic.rs b/src/rhythm/generic.rs index f7c1098..ee133f0 100644 --- a/src/rhythm/generic.rs +++ b/src/rhythm/generic.rs @@ -213,6 +213,10 @@ impl GenericRhythm 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; @@ -244,9 +248,9 @@ impl GenericRhythm RhythmIter if !self.event_iter_items.is_empty() { return; } - // batch omit events in whole steps + // quickly check if pattern playback finished + if self.pattern_playback_finished { + return; + } + // batch omit events in whole steps, if possible loop { - // quickly check if pattern playback finished - if self.pattern_playback_finished { - return; - } // quickly check if the next event is due before the given target time - let next_sample_time = - self.sample_offset + self.event_iter_next_sample_time as SampleTime; - if next_sample_time >= sample_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((new_pulse_item, emit_event)) = self.run_pattern() { + 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(); - let next_step_time = - (self.event_iter_next_sample_time + step_duration) as SampleTime; - if next_step_time < sample_time { + if ((next_sample_time + step_duration) as SampleTime) < sample_time { // omit all events from the gated pulse - self.event_iter.omit(new_pulse_item, emit_event); + self.event_iter.omit(pulse, emit_event); self.event_iter_next_sample_time += step_duration; - continue; } else { // generate new events from the gated pulse - if let Some(slice) = self.event_iter.run(new_pulse_item, emit_event) { + 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 { @@ -396,7 +396,7 @@ impl RhythmIter } } } else { - // pattern playback finished + // pattern playback finished: we're done here self.pattern_playback_finished = true; return; } From d8c3354c79fe93f3d5e896d68dc3cd674b3a511f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduard=20M=C3=BCller?= Date: Tue, 2 Jul 2024 20:25:54 +0200 Subject: [PATCH 4/5] reorganized cycle tests & removed unused step from state --- src/tidal/cycle.rs | 94 +++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/tidal/cycle.rs b/src/tidal/cycle.rs index d965d04..b19348f 100644 --- a/src/tidal/cycle.rs +++ b/src/tidal/cycle.rs @@ -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()), @@ -99,7 +98,7 @@ impl Cycle { /// Check if a cycle may give different outputs between cycles. pub fn is_stateful(&self) -> bool { // TODO improve: * and / can change the output, <1> does not etc.. - self.input.contains(['<', '{', '|', '?', '/']) + self.input.contains(['<', '{', '|', '?', '/', '*']) } /// Query for the next iteration of output. @@ -108,7 +107,6 @@ impl Cycle { pub fn generate(&mut self) -> Result>, String> { let cycle = self.state.iteration; self.state.events = 0; - self.state.step = 0; let mut events = Self::output(&self.root, &mut self.state, cycle, self.event_limit)?; self.state.iteration += 1; events.transform_spans(&Span::default()); @@ -124,7 +122,6 @@ impl Cycle { pub fn omit(&mut self) -> Result<(), String> { let cycle = self.state.iteration; self.state.events = 0; - self.state.step = 0; if self.is_stateful() { let _ = Self::output(&self.root, &mut self.state, cycle, self.event_limit)?; } @@ -135,7 +132,6 @@ impl Cycle { /// 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())); @@ -1444,7 +1440,6 @@ impl CycleParser { struct CycleState { iteration: u32, rng: Xoshiro256PlusPlus, - step: u32, events: usize, } @@ -1729,6 +1724,7 @@ impl Cycle { #[cfg(test)] mod test { use super::*; + type F = fraction::Fraction; fn assert_cycles(input: &str, outputs: Vec>>) -> Result<(), String> { @@ -1740,13 +1736,50 @@ 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(()) + } + + #[test] + + 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()); - pub fn cycle() -> Result<(), String> { + 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()?, [[ @@ -1927,15 +1960,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![ @@ -2125,42 +2149,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( @@ -2180,27 +2189,18 @@ 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(()) } } From 774fde627423af171b7329c1ac20eb2c757cc956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduard=20M=C3=BCller?= Date: Tue, 2 Jul 2024 20:31:59 +0200 Subject: [PATCH 5/5] optimize seeking in cycles - add a new is_omittable function to quickly check if cycles must be run in order to skip an iteration - when running a non emittable cycle, avoid collecting and processing events where possible to avoid overhead --- src/tidal/cycle.rs | 162 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 125 insertions(+), 37 deletions(-) diff --git a/src/tidal/cycle.rs b/src/tidal/cycle.rs index b19348f..0bb7b6e 100644 --- a/src/tidal/cycle.rs +++ b/src/tidal/cycle.rs @@ -101,14 +101,26 @@ impl Cycle { self.input.contains(['<', '{', '|', '?', '/', '*']) } + /// Check if a cycle run can be omitted without breaking the internal state + pub fn is_omittable(&self) -> bool { + !self.input.contains(['<', '|', '?']) + } + /// Query for the next iteration of output. /// /// Returns error when the number of generated events exceed the configured event limit. pub fn generate(&mut self) -> Result>, String> { let cycle = self.state.iteration; - self.state.events = 0; - let mut events = Self::output(&self.root, &mut self.state, cycle, self.event_limit)?; self.state.iteration += 1; + self.state.events = 0; + let omit_events = false; + let mut events = Self::output( + &self.root, + &mut self.state, + cycle, + self.event_limit, + omit_events, + )?; events.transform_spans(&Span::default()); Ok(events.export()) } @@ -120,12 +132,23 @@ impl Cycle { /// /// Returns error when the number of generated events exceed the configured event limit. pub fn omit(&mut self) -> Result<(), String> { - let cycle = self.state.iteration; - self.state.events = 0; - if self.is_stateful() { - let _ = Self::output(&self.root, &mut self.state, cycle, self.event_limit)?; + if self.is_omittable() { + // no need to run, just skip an iteration + self.state.iteration += 1; + } else { + // dry run to maintain internal state + let cycle = self.state.iteration; + self.state.iteration += 1; + self.state.events = 0; + let omit_events = true; + Self::output( + &self.root, + &mut self.state, + cycle, + self.event_limit, + omit_events, + )?; } - self.state.iteration += 1; Ok(()) } @@ -1449,20 +1472,26 @@ impl Cycle { state: &mut CycleState, span: &Span, limit: usize, + omit_events: bool, ) -> Result { let range = span.whole_range(); let mut cycles = vec![]; for cycle in range { - let mut events = Self::output(step, state, cycle, limit)?; - events.transform_spans(&Span::new(Fraction::from(cycle), Fraction::from(cycle + 1))); - cycles.push(events) + let mut events = Self::output(step, state, cycle, limit, omit_events)?; + if !omit_events { + let span = Span::new(Fraction::from(cycle), Fraction::from(cycle + 1)); + events.transform_spans(&span); + cycles.push(events) + } } let mut events = Events::Multi(MultiEvents { span: span.clone(), length: span.length(), events: cycles, }); - events.crop(span); + if !omit_events { + events.crop(span); + } Ok(events) } @@ -1472,13 +1501,16 @@ impl Cycle { cycle: u32, mult: Fraction, limit: usize, + omit_events: bool, ) -> Result { let span = Span::new( Fraction::from(cycle) * mult, Fraction::from(cycle + 1) * mult, ); - let mut events = Self::output_span(step, state, &span, limit)?; - events.normalize_spans(&span); + let mut events = Self::output_span(step, state, &span, limit, omit_events)?; + if !omit_events { + events.normalize_spans(&span); + } Ok(events) } @@ -1488,6 +1520,7 @@ impl Cycle { state: &mut CycleState, cycle: u32, limit: usize, + omit_events: bool, ) -> Result { let events = match step { // repeats only make it here if they had no preceding value @@ -1516,11 +1549,14 @@ impl Cycle { } else { let mut events = vec![]; for s in &sd.steps { - let e = Self::output(s, state, cycle, limit)?; - events.push(e) + let e = Self::output(s, state, cycle, limit, omit_events)?; + if !omit_events { + events.push(e) + } + } + if !omit_events { + Events::subdivide_lengths(&mut events); } - - Events::subdivide_lengths(&mut events); Events::Multi(MultiEvents { span: Span::default(), length: Fraction::one(), @@ -1535,15 +1571,16 @@ impl Cycle { let length = a.steps.len() as u32; let current = cycle % length; if let Some(step) = a.steps.get(current as usize) { - Self::output(step, state, cycle / length, limit)? + Self::output(step, state, cycle / length, limit, omit_events)? } else { Events::empty() // unreachable } } } Step::Choices(cs) => { + // TODO seed the rng properly 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, omit_events)? } Step::Polymeter(pm) => { let step = pm.steps.as_ref(); @@ -1553,7 +1590,7 @@ impl Cycle { _ => 1, }; let mult = Fraction::from(count) / Fraction::from(length); - Self::output_multiplied(step, state, cycle, mult, limit)? + Self::output_multiplied(step, state, cycle, mult, limit, omit_events)? } Step::Stack(st) => { if st.stack.is_empty() { @@ -1561,7 +1598,10 @@ impl Cycle { } else { let mut channels = vec![]; for s in &st.stack { - channels.push(Self::output(s, state, cycle, limit)?) + let events = Self::output(s, state, cycle, limit, omit_events)?; + if !omit_events { + channels.push(events); + } } Events::Poly(PolyEvents { span: Span::default(), @@ -1573,12 +1613,16 @@ impl Cycle { Step::StaticExpression(e) => { match e.op { StaticOp::Target() => { - let mut out = Self::output(e.left.as_ref(), state, cycle, limit)?; - out.mutate_events(&mut |event| event.target = e.right.to_target()); + let mut out = + Self::output(e.left.as_ref(), state, cycle, limit, omit_events)?; + if !omit_events { + out.mutate_events(&mut |event| event.target = e.right.to_target()); + } out } StaticOp::Degrade() => { - let mut out = Self::output(e.left.as_ref(), state, cycle, limit)?; + let mut out = + Self::output(e.left.as_ref(), state, cycle, limit, omit_events)?; out.mutate_events(&mut |event: &mut Event| { if let Some(chance) = e.right.to_chance() { // TODO seed the rng properly @@ -1606,7 +1650,14 @@ impl Cycle { } else { right }); - Self::output_multiplied(e.left.as_ref(), state, cycle, mult, limit)? + Self::output_multiplied( + e.left.as_ref(), + state, + cycle, + mult, + limit, + omit_events, + )? } else { Events::empty() } @@ -1633,17 +1684,25 @@ impl Cycle { }; if let Some(steps) = steps_single.value.to_integer() { if let Some(pulses) = pulses_single.value.to_integer() { - let out = - Self::output(b.left.as_ref(), state, cycle, limit)?; - for pulse in euclidean( - steps.max(0) as u32, - pulses.max(0) as u32, - rotation.unwrap_or(0), - ) { - if pulse { - events.push(out.clone()) - } else { - events.push(Events::empty()) + let out = Self::output( + b.left.as_ref(), + state, + cycle, + limit, + omit_events, + )?; + if !omit_events { + events.reserve(pulses.max(0) as usize); + for pulse in euclidean( + steps.max(0) as u32, + pulses.max(0) as u32, + rotation.unwrap_or(0), + ) { + if pulse { + events.push(out.clone()) + } else { + events.push(Events::empty()) + } } } } @@ -1654,7 +1713,9 @@ impl Cycle { } _ => (), // TODO support something other than Step::Single as pulses } - Events::subdivide_lengths(&mut events); + if !omit_events { + Events::subdivide_lengths(&mut events); + } Events::Multi(MultiEvents { span: Span::default(), length: Fraction::one(), @@ -1744,6 +1805,20 @@ mod test { Ok(()) } + fn assert_cycle_seeking(input: &str) -> Result<(), String> { + let seed = rand::thread_rng().gen(); + for number_of_seeks 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_seeks { + let _ = cycle1.generate()?; + cycle2.omit()?; + } + assert_eq!(cycle1.generate()?, cycle2.generate()?); + } + Ok(()) + } + #[test] fn span() -> Result<(), String> { @@ -2203,4 +2278,17 @@ mod test { .is_ok()); Ok(()) } + + #[test] + fn seeking() -> Result<(), String> { + assert_cycle_seeking("[a b c d]")?; // stateless + assert_cycle_seeking("[a b], [c d]")?; + assert_cycle_seeking("{a b}%2 {a b}*5")?; // stateful + assert_cycle_seeking("[a b]*5 [a b]/5")?; + assert_cycle_seeking("[a b c d]")?; + assert_cycle_seeking("a ")?; + assert_cycle_seeking("[a b? c d]|[c? d?]")?; + assert_cycle_seeking("[{a b}/2 c d], e? {a b}*2")?; + Ok(()) + } }