Skip to content

Feature/cycle mapping #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 13, 2024
17 changes: 9 additions & 8 deletions examples/assets/kick.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
return rhythm {
unit = "1/16",
pattern = function()
local pulses = table.create({ 0, 6, 10 })
---@param context PatternContext
return function(context)
return pulses:find((context.pulse_step - 1) % 16) ~= nil
unit = "1/4",
resolution = 16,
emit = cycle("bd [~ bd] ~ ~ bd [~ bd] _ ~ bd [~ bd] ~ ~ bd [~ bd] [_ bd2] [~ bd _ ~]"):map(
function(context, value)
-- print(context.channel, context.step, context.step_length, "->", value)
if value == "bd" or value == "bd2" then
return { key = 48, volume = value == "bd" and 0.9 or 0.5 }
end
end
end,
emit = { 60, 60, note { 60, { key = 96, volume = 0.135 } } },
)
}
29 changes: 6 additions & 23 deletions examples/play.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,31 +71,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
};

// generate a few phrases
let cycle =
new_cycle_event("bd [~ bd] ~ ~ bd [~ bd] _ ~ bd [~ bd] ~ ~ bd [~ bd] [_ bd2] [~ bd _ ~]")?
.with_mappings(&[("bd", new_note("c4")), ("bd2", new_note(("c4", None, 0.5)))]);

let kick_pattern = beat_time
.every_nth_beat(1.0)
.every_nth_beat(16.0)
.with_instrument(KICK)
.with_pattern(
vec![
Pulse::from(1.0),
Pulse::from(vec![0.0, 1.0]),
Pulse::from(0.0),
Pulse::from(0.0),
Pulse::from(1.0),
Pulse::from(vec![0.0, 1.0]),
Pulse::from(0.0),
Pulse::from(0.0),
Pulse::from(1.0),
Pulse::from(vec![0.0, 1.0]),
Pulse::from(0.0),
Pulse::from(0.0),
Pulse::from(1.0),
Pulse::from(vec![0.0, 1.0]),
Pulse::from(vec![0.0, 1.0]),
Pulse::from(vec![0.0, 1.0, 0.0, 0.0]),
]
.to_pattern(),
)
.trigger(new_note_event("C_5"));
.trigger(cycle);

let snare_pattern = beat_time
.every_nth_beat(2.0)
Expand Down
7 changes: 5 additions & 2 deletions src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ mod timeout;
mod unwrap;

// public re-exports
pub use callback::{clear_lua_callback_errors, has_lua_callback_errors, lua_callback_errors};
pub use callback::{
add_lua_callback_error, clear_lua_callback_errors, has_lua_callback_errors, lua_callback_errors,
};

// internal re-exports
pub(crate) use callback::LuaCallback;
pub(crate) use timeout::LuaTimeoutHook;
pub(crate) use unwrap::{
gate_trigger_from_value, note_events_from_value, pattern_pulse_from_value,
gate_trigger_from_value, note_event_from_value, note_events_from_value,
pattern_pulse_from_value,
};

// ---------------------------------------------------------------------------------------------
Expand Down
109 changes: 77 additions & 32 deletions src/bindings/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ pub fn clear_lua_callback_errors() {
.clear();
}

/// Add/signal a new Lua callback errors.
///
/// ### Panics
/// Panics if accessing the global lua callback error vector failed.
pub fn add_lua_callback_error(name: &str, err: &LuaError) {
log::warn!("Lua callback '{}' failed to evaluate:\n{}", name, err);
LUA_CALLBACK_ERRORS
.write()
.expect("Failed to lock Lua callback error vector")
.push(err.clone());
}

// -------------------------------------------------------------------------------------------------

/// Lazily evaluates a lua function the first time it's called, to either use it as a iterator,
Expand Down Expand Up @@ -77,12 +89,17 @@ pub(crate) struct LuaCallback {
}

impl LuaCallback {
/// Create a new Callback from an unowned lua function.
pub fn new(lua: &Lua, function: LuaFunction) -> LuaResult<Self> {
Self::with_owned(lua, function.into_owned())
}

/// Create a new Callback from an owned lua function.
pub fn with_owned(lua: &Lua, function: LuaOwnedFunction) -> LuaResult<Self> {
// create an empty context and memorize the function without calling it
let context = lua.create_table()?.into_owned();
let environment = function.environment().map(LuaTable::into_owned);
let environment = function.to_ref().environment().map(LuaTable::into_owned);
let generator = None;
let function = function.into_owned();
let initialized = false;
Ok(Self {
environment,
Expand Down Expand Up @@ -138,7 +155,21 @@ impl LuaCallback {
Ok(())
}

/// Sets the emitter context for the callback. Only used for function callbacks.
/// Sets the cycle context step value for the callback.
pub fn set_context_cycle_step(
&mut self,
channel: usize,
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)?;
Ok(())
}

/// Sets the emitter context for the callback.
pub fn set_pattern_context(
&mut self,
time_base: &BeatTimeBase,
Expand All @@ -150,7 +181,7 @@ impl LuaCallback {
Ok(())
}

/// Sets the gate context for the callback. Only used for function callbacks.
/// Sets the gate context for the callback.
pub fn set_gate_context(
&mut self,
time_base: &BeatTimeBase,
Expand All @@ -163,7 +194,7 @@ impl LuaCallback {
Ok(())
}

/// Sets the emitter context for the callback. Only used for function callbacks.
/// Sets the emitter context for the callback.
pub fn set_emitter_context(
&mut self,
time_base: &BeatTimeBase,
Expand All @@ -172,16 +203,24 @@ impl LuaCallback {
pulse_time_step: f64,
step: usize,
) -> LuaResult<()> {
self.set_gate_context(
time_base,
pulse,
pulse_step,
pulse_time_step,
)?;
self.set_gate_context(time_base, pulse, pulse_step, pulse_time_step)?;
self.set_context_step(step)?;
Ok(())
}

/// Sets the cycle context for the callback.
pub fn set_cycle_context(
&mut self,
time_base: &BeatTimeBase,
channel: usize,
step: usize,
step_length: f64,
) -> LuaResult<()> {
self.set_context_time_base(time_base)?;
self.set_context_cycle_step(channel, step, step_length)?;
Ok(())
}

/// Name of the inner function for errors. Usually will be an annonymous function.
pub fn name(&self) -> String {
self.function
Expand All @@ -193,42 +232,48 @@ impl LuaCallback {

/// Invoke the Lua function callback or generator.
pub fn call(&mut self) -> LuaResult<LuaValue> {
if !self.initialized {
self.call_with_arg(LuaValue::Nil)
}

/// Invoke the Lua function callback or generator with an additional argument.
pub fn call_with_arg<'lua, A: IntoLua<'lua> + Clone>(
&'lua mut self,
arg: A,
) -> LuaResult<LuaValue<'lua>> {
if self.initialized {
self.function.call((self.context.to_ref(), arg))
} else {
self.initialized = true;
let function = self.function.clone();
let result = function.call::<_, LuaValue>(self.context.to_ref())?;
if let Some(inner_function) = result.as_function() {
// function returned a function -> is an iterator. use inner function instead.
let function_environment = self
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()))?
};
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.
let environment = self
.function
.to_ref()
.environment()
.map(LuaTable::into_owned);
let function_generator = Some(self.function.clone());
self.environment = function_environment;
self.generator = function_generator;
self.function = inner_function.clone().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))
} else {
// function returned not a function. use this function directly.
// function returned some value. use this function directly.
self.environment = None;
self.generator = None;
Ok(result)
}
}
self.function.call(self.context.to_ref())
}

/// Report a Lua callback errors. The error will be logged and usually cleared after
/// the next callback call.
pub fn handle_error(&self, err: &LuaError) {
log::warn!(
"Lua callback '{}' failed to evaluate:\n{}",
self.name(),
err
);
LUA_CALLBACK_ERRORS
.write()
.expect("Failed to lock Lua callback error vector")
.push(err.clone());
add_lua_callback_error(&self.name(), err)
}

/// Reset the callback function or iterator to its initial state.
Expand Down
Loading