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