diff --git a/docs/src/API/cycle.md b/docs/src/API/cycle.md index bec3a03..4e5240a 100644 --- a/docs/src/API/cycle.md +++ b/docs/src/API/cycle.md @@ -222,6 +222,10 @@ > channel/voice index within the cycle. each channel in the cycle gets emitted and thus mapped > separately, starting with the first channel index 1. +### iteration : [`integer`](../API/builtins/integer.md) +> Iteration counter for the cycle that increases once per the whole cycle's output +> Starts from 1 when the cycle starts running or after it got reset. + ### step : [`integer`](../API/builtins/integer.md) > Continues step counter for each channel, incrementing with each new mapped value in the cycle. > Starts from 1 when the cycle starts running or after it got reset. @@ -229,6 +233,9 @@ ### step_length : [`number`](../API/builtins/number.md) > step length fraction within the cycle, where 1 is the total duration of a single cycle run. +### step_time : [`number`](../API/builtins/number.md) +> step start fraction within the cycle, where 1 is the total duration of a single cycle run. + ### trigger : [`Note`](../API/note.md#Note)[`?`](../API/builtins/nil.md) > Note that triggered the pattern, if any. Usually will be a monophonic note. > To access the raw note number value use: `context.trigger.notes[1].key` diff --git a/src/bindings/callback.rs b/src/bindings/callback.rs index e2d153a..ad637a2 100644 --- a/src/bindings/callback.rs +++ b/src/bindings/callback.rs @@ -230,6 +230,16 @@ impl LuaCallback { .unwrap_or("anonymous function".to_string()) } + /// Applies a function to itself and handles the error that may occur. + pub fn handle(&mut self, fun: F) + where + F: FnOnce(&mut Self) -> LuaResult<()>, + { + if let Err(err) = fun(self) { + self.handle_error(&err); + } + } + /// Sets the emitters playback state for the callback. pub fn set_context_playback_state( &mut self, @@ -306,11 +316,13 @@ impl LuaCallback { channel: usize, step: usize, step_length: f64, + step_time: f64, ) -> LuaResult<()> { let values = &mut self.context.borrow_mut::()?.values; values.insert(b"channel", (channel + 1).into()); values.insert(b"step", step.wrapping_add(1).into()); values.insert(b"step_length", step_length.into()); + values.insert(b"step_time", step_time.into()); Ok(()) } @@ -363,17 +375,17 @@ impl LuaCallback { } /// Sets the cycle context for the mapping callbacks. - pub fn set_cycle_map_context( - &mut self, - playback_state: ContextPlaybackState, - time_base: &BeatTimeBase, - channel: usize, - step: usize, - step_length: f64, - ) -> LuaResult<()> { + pub fn init_cycle_map_context(&mut self, time_base: &BeatTimeBase) -> LuaResult<()> { + let playback_state = ContextPlaybackState::Running; + let channel = 0; + let step = 0; + let step_length = 0.0; + let step_time = 0.0; + let iteration = 1; + self.set_context_cycle_iteration(iteration)?; self.set_context_playback_state(playback_state)?; self.set_context_time_base(time_base)?; - self.set_context_cycle_step(channel, step, step_length)?; + self.set_context_cycle_step(channel, step, step_length, step_time)?; Ok(()) } diff --git a/src/emitter/scripted.rs b/src/emitter/scripted.rs index 74f7ba9..e379459 100644 --- a/src/emitter/scripted.rs +++ b/src/emitter/scripted.rs @@ -111,27 +111,22 @@ impl Emitter for ScriptedEmitter { // reset timeout self.timeout_hook.reset(); // update function context with the new time base - if let Err(err) = self.callback.set_context_time_base(time_base) { - self.callback.handle_error(&err); - } + self.callback.handle(|c| c.set_context_time_base(time_base)); } fn set_trigger_event(&mut self, event: &Event) { // reset timeout self.timeout_hook.reset(); // update function context from the new time base - if let Err(err) = self.callback.set_context_trigger_event(event) { - self.callback.handle_error(&err); - } + self.callback.handle(|c| c.set_context_trigger_event(event)) } fn set_parameters(&mut self, parameters: ParameterSet) { // reset timeout self.timeout_hook.reset(); // update function context with the new parameters - if let Err(err) = self.callback.set_context_parameters(¶meters) { - self.callback.handle_error(&err); - } + self.callback + .handle(|c| c.set_context_parameters(¶meters)); } fn run(&mut self, pulse: RhythmEvent, emit_event: bool) -> Option> { @@ -176,22 +171,14 @@ impl Emitter for ScriptedEmitter { self.timeout_hook.reset(); // reset step counter self.step = 0; - if let Err(err) = self.callback.set_context_step(self.step) { - self.callback.handle_error(&err); - } + self.callback.handle(|c| c.set_context_step(self.step)); // reset pulse counter self.pulse_step = 0; self.pulse_time_step = 0.0; - if let Err(err) = self - .callback - .set_context_pulse_step(self.pulse_step, self.pulse_time_step) - { - self.callback.handle_error(&err); - } + self.callback + .handle(|c| c.set_context_pulse_step(self.pulse_step, self.pulse_time_step)); // restore function - if let Err(err) = self.callback.reset() { - self.callback.handle_error(&err); - } + self.callback.handle(|c| c.reset()); // reset last event self.note_event_state.clear(); } diff --git a/src/emitter/scripted_cycle.rs b/src/emitter/scripted_cycle.rs index fe12949..2ecb1bb 100644 --- a/src/emitter/scripted_cycle.rs +++ b/src/emitter/scripted_cycle.rs @@ -131,18 +131,8 @@ impl ScriptedCycleEmitter { timeout_hook.reset(); let timeout_hook = Some(timeout_hook); // initialize emitter context for the function - let playback_state = ContextPlaybackState::Running; - let channel = 0; - let step = 0; - let step_length = 0.0; let mut mapping_callback = mapping_callback; - mapping_callback.set_cycle_map_context( - playback_state, - time_base, - channel, - step, - step_length, - )?; + mapping_callback.init_cycle_map_context(time_base)?; let mappings = ScriptedCycleMapping::Function(mapping_callback); let channel_steps = vec![]; Ok(Self { @@ -159,6 +149,7 @@ impl ScriptedCycleEmitter { channel_index: usize, channel_step: usize, step_length: f64, + step_time: f64, event: CycleEvent, ) -> LuaResult>> { let mut note_events = { @@ -169,6 +160,7 @@ impl ScriptedCycleEmitter { channel_index, channel_step, step_length, + step_time, )?; // call mapping function let result = mapping_callback.call_with_arg(event.as_str().as_ref())?; @@ -216,6 +208,13 @@ impl ScriptedCycleEmitter { // inject var callback values into cycle, if present self.apply_variables_callback(); + // set mapping callback playback state + if let ScriptedCycleMapping::Function(callback) = &mut self.mappings { + callback.handle(|c| { + c.set_context_playback_state(ContextPlaybackState::Running) + .and(c.set_context_cycle_iteration(self.cycle.iteration())) + }); + } // run the cycle event generator let events = { match self.cycle.generate() { @@ -235,13 +234,6 @@ impl ScriptedCycleEmitter { } }; - // set mapping callback playback state - if let ScriptedCycleMapping::Function(callback) = &mut self.mappings { - if let Err(err) = callback.set_context_playback_state(ContextPlaybackState::Running) { - callback.handle_error(&err); - } - } - // convert possibly mapped cycle channel items to a list of note events let mut timed_note_events = CycleNoteEvents::new(); for (channel_index, channel_events) in events.into_iter().enumerate() { @@ -256,7 +248,14 @@ impl ScriptedCycleEmitter { let start = event.span().start(); let length = event.span().length(); let step_length = length.to_f64().unwrap_or(0.0); - match self.cycle_to_note_event(channel_index, channel_step, step_length, event) { + let step_time = start.to_f64().unwrap_or(0.0); + match self.cycle_to_note_event( + channel_index, + channel_step, + step_length, + step_time, + event, + ) { Err(err) => { if let ScriptedCycleMapping::Function(callback) = &self.mappings { callback.handle_error(&err) @@ -330,11 +329,9 @@ impl ScriptedCycleEmitter { match &mut self.mappings { ScriptedCycleMapping::Function(mapping_callback) => { // set playback state - if let Err(err) = - mapping_callback.set_context_playback_state(ContextPlaybackState::Seeking) - { - mapping_callback.handle_error(&err); - } + mapping_callback + .handle(|c| c.set_context_playback_state(ContextPlaybackState::Seeking)); + if mapping_callback.is_stateful().unwrap_or(true) { // run stateful callbacks but ignore results for (channel_index, channel_events) in events.into_iter().enumerate() { @@ -347,10 +344,12 @@ impl ScriptedCycleEmitter { self.channel_steps[channel_index] += 1; // update step in context let step_length = event.span().length().to_f64().unwrap_or(0.0); + let step_time = event.span().start().to_f64().unwrap_or(0.0); if let Err(err) = mapping_callback.set_context_cycle_step( channel_index, channel_step, step_length, + step_time, ) { mapping_callback.handle_error(&err); return; @@ -408,15 +407,13 @@ impl ScriptedCycleEmitter { fn apply_variables_callback(&mut self) { if let ScriptedCycleMapping::Function(callback) = &mut self.variables { // update context - if let Err(err) = callback - .set_context_playback_state(ContextPlaybackState::Running) - .and(callback.set_context_parameters( - &self.parameters.iter().map(|(p, _)| Rc::clone(p)).collect(), - )) - .and(callback.set_context_cycle_iteration(self.cycle.iteration())) - { - callback.handle_error(&err); - } + callback.handle(|c| { + c.set_context_playback_state(ContextPlaybackState::Running) + .and(c.set_context_parameters( + &self.parameters.iter().map(|(p, _)| Rc::clone(p)).collect(), + )) + .and(c.set_context_cycle_iteration(self.cycle.iteration())) + }); // run match callback.call() { Err(err) => { @@ -457,14 +454,10 @@ impl Emitter for ScriptedCycleEmitter { } // pass time base to callbacks if let ScriptedCycleMapping::Function(callback) = &mut self.variables { - if let Err(err) = callback.set_context_time_base(time_base) { - callback.handle_error(&err); - } + callback.handle(|c| c.set_context_time_base(time_base)); } if let ScriptedCycleMapping::Function(callback) = &mut self.mappings { - if let Err(err) = callback.set_context_time_base(time_base) { - callback.handle_error(&err); - } + callback.handle(|c| c.set_context_time_base(time_base)); } } @@ -475,14 +468,10 @@ impl Emitter for ScriptedCycleEmitter { } // pass event to callbacks if let ScriptedCycleMapping::Function(callback) = &mut self.variables { - if let Err(err) = callback.set_context_trigger_event(event) { - callback.handle_error(&err); - } + callback.handle(|c| c.set_context_trigger_event(event)); } if let ScriptedCycleMapping::Function(callback) = &mut self.mappings { - if let Err(err) = callback.set_context_trigger_event(event) { - callback.handle_error(&err); - } + callback.handle(|c| c.set_context_trigger_event(event)); } } @@ -523,16 +512,12 @@ impl Emitter for ScriptedCycleEmitter { // pass parameters to the variables callback context if let ScriptedCycleMapping::Function(callback) = &mut self.variables { - if let Err(err) = callback.set_context_parameters(¶meters) { - callback.handle_error(&err); - } + callback.handle(|c| c.set_context_parameters(¶meters)); } // pass parameters to the mapping callback context if let ScriptedCycleMapping::Function(callback) = &mut self.mappings { - if let Err(err) = callback.set_context_parameters(¶meters) { - callback.handle_error(&err); - } + callback.handle(|c| c.set_context_parameters(¶meters)); } } @@ -567,27 +552,20 @@ impl Emitter for ScriptedCycleEmitter { if let ScriptedCycleMapping::Function(callback) = &mut self.variables { // reset iteration counter let iteration = 0; - if let Err(err) = callback.set_context_cycle_iteration(iteration) { - callback.handle_error(&err); - } + callback.handle(|c| c.set_context_cycle_iteration(iteration)); // restore function - if let Err(err) = callback.reset() { - callback.handle_error(&err); - } + callback.handle(|c| c.reset()); } if let ScriptedCycleMapping::Function(callback) = &mut self.mappings { // reset step counter let channel = 0; let step = 0; let step_length = 0.0; + let step_time = 0.0; self.channel_steps.clear(); - if let Err(err) = callback.set_context_cycle_step(channel, step, step_length) { - callback.handle_error(&err); - } + callback.handle(|c| c.set_context_cycle_step(channel, step, step_length, step_time)); // restore function - if let Err(err) = callback.reset() { - callback.handle_error(&err); - } + callback.handle(|c| c.reset()); } } } diff --git a/src/gate/scripted.rs b/src/gate/scripted.rs index 56b111d..408f9f6 100644 --- a/src/gate/scripted.rs +++ b/src/gate/scripted.rs @@ -70,27 +70,22 @@ impl Gate for ScriptedGate { // reset timeout self.timeout_hook.reset(); // update function context from the new time base - if let Err(err) = self.callback.set_context_time_base(time_base) { - self.callback.handle_error(&err); - } + self.callback.handle(|c| c.set_context_time_base(time_base)); } fn set_trigger_event(&mut self, event: &Event) { // reset timeout self.timeout_hook.reset(); // update function context from the new time base - if let Err(err) = self.callback.set_context_trigger_event(event) { - self.callback.handle_error(&err); - } + self.callback.handle(|c| c.set_context_trigger_event(event)); } fn set_parameters(&mut self, parameters: ParameterSet) { // reset timeout self.timeout_hook.reset(); // update function context with the new parameters - if let Err(err) = self.callback.set_context_parameters(¶meters) { - self.callback.handle_error(&err); - } + self.callback + .handle(|c| c.set_context_parameters(¶meters)); } fn run(&mut self, pulse: &RhythmEvent) -> bool { @@ -120,19 +115,9 @@ impl Gate for ScriptedGate { self.pulse_step = 0; self.pulse_time_step = 0.0; // update step in context - if let Err(err) = self - .callback - .set_context_pulse_step(self.pulse_step, self.pulse_time_step) - { - self.callback.handle_error(&err); - } - // reset function - if let Err(err) = self.callback.reset() { - self.callback.handle_error(&err); - } + self.callback + .handle(|c| c.set_context_pulse_step(self.pulse_step, self.pulse_time_step)); // reset function - if let Err(err) = self.callback.reset() { - self.callback.handle_error(&err); - } + self.callback.handle(|c| c.reset()); } } diff --git a/src/rhythm/scripted.rs b/src/rhythm/scripted.rs index 5952067..b770753 100644 --- a/src/rhythm/scripted.rs +++ b/src/rhythm/scripted.rs @@ -141,27 +141,22 @@ impl Rhythm for ScriptedRhythm { // reset timeout self.timeout_hook.reset(); // update function context from the new time base - if let Err(err) = self.callback.set_context_time_base(time_base) { - self.callback.handle_error(&err); - } + self.callback.handle(|c| c.set_context_time_base(time_base)); } fn set_trigger_event(&mut self, event: &crate::Event) { // reset timeout self.timeout_hook.reset(); // update function context from the new time base - if let Err(err) = self.callback.set_context_trigger_event(event) { - self.callback.handle_error(&err); - } + self.callback.handle(|c| c.set_context_trigger_event(event)); } fn set_parameters(&mut self, parameters: ParameterSet) { // reset timeout self.timeout_hook.reset(); // update function context with the new parameters - if let Err(err) = self.callback.set_context_parameters(¶meters) { - self.callback.handle_error(&err); - } + self.callback + .handle(|c| c.set_context_parameters(¶meters)); } fn set_repeat_count(&mut self, count: Option) { @@ -181,16 +176,10 @@ impl Rhythm for ScriptedRhythm { self.pulse_step = 0; self.pulse_time_step = 0.0; // update step in context - if let Err(err) = self - .callback - .set_context_pulse_step(self.pulse_step, self.pulse_time_step) - { - self.callback.handle_error(&err); - } + self.callback + .handle(|c| c.set_context_pulse_step(self.pulse_step, self.pulse_time_step)); // reset function - if let Err(err) = self.callback.reset() { - self.callback.handle_error(&err); - } + self.callback.handle(|c| c.reset()); // reset pulse and pulse iter self.pulse = None; self.pulse_iter = None; diff --git a/types/pattrns/library/cycle.lua b/types/pattrns/library/cycle.lua index a6a98b1..df3ac3e 100644 --- a/types/pattrns/library/cycle.lua +++ b/types/pattrns/library/cycle.lua @@ -14,11 +14,16 @@ error("Do not try to execute this file. It's just a type definition file.") ---channel/voice index within the cycle. each channel in the cycle gets emitted and thus mapped ---separately, starting with the first channel index 1. ---@field channel integer +---Iteration counter for the cycle that increases once per the whole cycle's output +---Starts from 1 when the cycle starts running or after it got reset. +---@field iteration integer ---Continues step counter for each channel, incrementing with each new mapped value in the cycle. ---Starts from 1 when the cycle starts running or after it got reset. ---@field step integer ---step length fraction within the cycle, where 1 is the total duration of a single cycle run. ---@field step_length number +---step start fraction within the cycle, where 1 is the total duration of a single cycle run. +---@field step_time number ---Context passed to 'cycle:var` functions. ---@class CycleVarContext : TimeContext