diff --git a/README.md b/README.md index cdc6e03..7e7589b 100644 Binary files a/README.md and b/README.md differ diff --git a/examples/assets/hihat.lua b/examples/assets/hihat.lua index 9fb7bdb..52394b4 100644 --- a/examples/assets/hihat.lua +++ b/examples/assets/hihat.lua @@ -14,6 +14,9 @@ return rhythm { end end end, + gate = function (context) + return context.pulse_value > math.random() + end, emit = function(context) local rand = math.randomstate(0x8879) return function(context) diff --git a/examples/assets/snare.lua b/examples/assets/snare.lua index b4898b5..b4efc47 100644 --- a/examples/assets/snare.lua +++ b/examples/assets/snare.lua @@ -3,6 +3,9 @@ math.randomseed(0x13ee127) return rhythm { unit = "1/16", pattern = pattern.from { 0, 0, 0, 0, 1, 0, 0.075, 0 } * 7 + { 0, 0, 0, 1, 0, 0, 0.5, 0 }, + gate = function (context) + return context.pulse_value > math.random() + end, emit = function(context) return { key = "C5", volume = (context.pulse_value == 1) and 0.8 or 0.5 } end, diff --git a/examples/play-script.rs b/examples/play-script.rs index 5e44649..8a2b36f 100644 --- a/examples/play-script.rs +++ b/examples/play-script.rs @@ -132,7 +132,6 @@ fn main() -> Result<(), Box> { Rc::new(RefCell::new(BeatTimeRhythm::new( beat_time, BeatTimeStep::Beats(1.0), - None ))) }) }; diff --git a/examples/play.rs b/examples/play.rs index 66c0fe5..26ecc7e 100644 --- a/examples/play.rs +++ b/examples/play.rs @@ -111,6 +111,8 @@ fn main() -> Result<(), Box> { .every_nth_sixteenth(2.0) .with_offset(BeatTimeStep::Sixteenth(1.0)) .with_instrument(HIHAT) + .with_pattern([1.0, 0.5].to_pattern()) + .with_gate(ProbabilityGate::new(None)) .trigger(new_note_event("C_5").mutate({ let mut vel_step = 0; let mut note_step = 0; diff --git a/src/bindings.rs b/src/bindings.rs index e83542c..0581b29 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -282,17 +282,11 @@ fn register_global_bindings( Ok(unit) => matches!(unit.as_str(), "seconds" | "ms"), Err(_) => false, }; - // NB: don't keep borrowing app_data_ref here: Rhythm constructors may use random functions - let rand_seed = { - lua.app_data_ref::() - .expect("Failed to access Lua app data") - .rand_seed - }; if second_time_unit { - SecondTimeRhythm::from_table(lua, &timeout_hook, &time_base, &table, rand_seed)? + SecondTimeRhythm::from_table(lua, &timeout_hook, &time_base, &table)? .into_lua(lua) } else { - BeatTimeRhythm::from_table(lua, &timeout_hook, &time_base, &table, rand_seed)? + BeatTimeRhythm::from_table(lua, &timeout_hook, &time_base, &table)? .into_lua(lua) } } diff --git a/src/bindings/rhythm/beat_time.rs b/src/bindings/rhythm/beat_time.rs index af9b23f..0ffe9a8 100644 --- a/src/bindings/rhythm/beat_time.rs +++ b/src/bindings/rhythm/beat_time.rs @@ -23,7 +23,6 @@ impl BeatTimeRhythm { timeout_hook: &LuaTimeoutHook, time_base: &BeatTimeBase, table: &LuaTable, - rand_seed: Option, ) -> LuaResult { // resolution let mut resolution = 1.0; @@ -56,7 +55,7 @@ impl BeatTimeRhythm { } } // create a new BeatTimeRhythm with the given time base and step - let mut rhythm = BeatTimeRhythm::new(*time_base, step, rand_seed); + let mut rhythm = BeatTimeRhythm::new(*time_base, step); // offset if table.contains_key("offset")? { let offset = table.get::<_, f32>("offset")?; diff --git a/src/bindings/rhythm/second_time.rs b/src/bindings/rhythm/second_time.rs index 50f609f..5624e76 100644 --- a/src/bindings/rhythm/second_time.rs +++ b/src/bindings/rhythm/second_time.rs @@ -23,7 +23,6 @@ impl SecondTimeRhythm { timeout_hook: &LuaTimeoutHook, time_base: &BeatTimeBase, table: &LuaTable, - rand_seed: Option, ) -> LuaResult { // resolution let mut resolution = 1.0; @@ -49,7 +48,7 @@ impl SecondTimeRhythm { } } // create a new SecondTimeRhythm with the given time base and step - let mut rhythm = SecondTimeRhythm::new(*time_base, resolution, rand_seed); + let mut rhythm = SecondTimeRhythm::new(*time_base, resolution); // offset if table.contains_key("offset")? { let offset = table.get::<_, f32>("offset")? as SecondTimeStep; diff --git a/src/gate.rs b/src/gate.rs index 4f38151..31c6642 100644 --- a/src/gate.rs +++ b/src/gate.rs @@ -9,6 +9,7 @@ use crate::{BeatTimeBase, InputParameterSet, PulseIterItem}; pub mod probability; #[cfg(feature = "scripting")] pub mod scripted; +pub mod threshold; // ------------------------------------------------------------------------------------------------- diff --git a/src/gate/threshold.rs b/src/gate/threshold.rs new file mode 100644 index 0000000..29d7c3e --- /dev/null +++ b/src/gate/threshold.rs @@ -0,0 +1,53 @@ +use std::borrow::Cow; + +use crate::{BeatTimeBase, Gate, InputParameterSet, PulseIterItem}; + +// ------------------------------------------------------------------------------------------------- + +/// Gate implementation which passes all pulse values > a specified threshold value (by default 0). +#[derive(Debug, Clone)] +pub struct ThresholdGate { + threshold: f32, +} + +impl ThresholdGate { + pub fn new() -> Self { + Self::with_threshold(0.0) + } + + pub fn with_threshold(threshold: f32) -> Self { + Self { threshold } + } +} + +impl Default for ThresholdGate { + fn default() -> Self { + Self::new() + } +} + +impl Gate for ThresholdGate { + fn set_time_base(&mut self, _time_base: &BeatTimeBase) { + // nothing to do + } + + fn set_external_context(&mut self, _data: &[(Cow, f64)]) { + // nothing to do + } + + fn set_input_parameters(&mut self, _parameters: InputParameterSet) { + // nothing to do + } + + fn run(&mut self, pulse: &PulseIterItem) -> bool { + pulse.value > self.threshold + } + + fn duplicate(&self) -> Box { + Box::new(self.clone()) + } + + fn reset(&mut self) { + // nothing to do + } +} diff --git a/src/prelude.rs b/src/prelude.rs index 78b0c3a..6f73675 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -18,7 +18,7 @@ pub use super::{ new_parameter_change_event, new_polyphonic_note_event, new_polyphonic_note_sequence_event, unique_instrument_id, InstrumentId, NoteEvent, ParameterChangeEvent, ParameterId, }, - gate::probability::ProbabilityGate, + gate::{probability::ProbabilityGate, threshold::ThresholdGate}, pattern::{euclidean, fixed::ToFixedPattern}, rhythm::{beat_time::BeatTimeRhythm, second_time::SecondTimeRhythm}, time::{BeatTimeStep, SecondTimeStep}, diff --git a/src/rhythm/beat_time.rs b/src/rhythm/beat_time.rs index d9a4486..29abe3e 100644 --- a/src/rhythm/beat_time.rs +++ b/src/rhythm/beat_time.rs @@ -48,7 +48,7 @@ macro_rules! generate_step_funcs { /// Shortcuts for creating beat-time based patterns. impl BeatTimeBase { pub fn every_nth_step(&self, step: BeatTimeStep) -> BeatTimeRhythm { - BeatTimeRhythm::new(*self, step, None) + BeatTimeRhythm::new(*self, step) } generate_step_funcs!(sixteenth, BeatTimeStep::Sixteenth); generate_step_funcs!(eighth, BeatTimeStep::Eighth); diff --git a/src/rhythm/generic.rs b/src/rhythm/generic.rs index fb626eb..300a7f1 100644 --- a/src/rhythm/generic.rs +++ b/src/rhythm/generic.rs @@ -15,7 +15,7 @@ use std::borrow::BorrowMut; use crate::{ event::{fixed::FixedEventIter, Event, EventIter, EventIterItem, InstrumentId}, - gate::probability::ProbabilityGate, + gate::threshold::ThresholdGate, pattern::{fixed::FixedPattern, Pattern}, time::{BeatTimeBase, SampleTimeDisplay}, Gate, InputParameter, InputParameterSet, PulseIterItem, Rhythm, RhythmIter, RhythmIterItem, @@ -63,14 +63,14 @@ pub struct GenericRhythm GenericRhythm { /// Create a new pattern based rhythm which emits `value` every `beat_time` `step`, /// and an optional seed for the random number generator. - pub fn new(time_base: BeatTimeBase, step: Step, seed: Option) -> Self { + pub fn new(time_base: BeatTimeBase, step: Step) -> Self { let offset = Offset::default_offset(); let instrument = None; let input_parameters = InputParameterSet::new(); let pattern = Box::::default(); let pattern_repeat_count = None; let pattern_playback_finished = false; - let gate = Box::new(ProbabilityGate::new(seed)); + let gate = Box::new(ThresholdGate::new()); let event_iter = Box::::default(); let event_iter_sample_time = 0; let event_iter_next_sample_time = offset.to_samples(&time_base); @@ -181,15 +181,13 @@ impl GenericRhythm(self, gate: T) -> Self { self.with_gate_dyn(Box::new(gate)) } - /// Return a new rhythm instance which uses the given dyn [`Gate`] instead of the default - /// probability gate. + /// Return a new rhythm instance which uses the given dyn [`Gate`] instead of the default gate. #[must_use] pub fn with_gate_dyn(self, gate: Box) -> Self { let time_base = self.time_base; diff --git a/src/rhythm/second_time.rs b/src/rhythm/second_time.rs index a4a83d7..a1f8692 100644 --- a/src/rhythm/second_time.rs +++ b/src/rhythm/second_time.rs @@ -33,9 +33,9 @@ pub type SecondTimeRhythm = GenericRhythm; // ------------------------------------------------------------------------------------------------- -/// Shortcuts for creating sample-time based rhythms. +/// Shortcuts for creating second-time based rhythms. impl BeatTimeBase { pub fn every_nth_seconds(&self, step: SecondTimeStep) -> SecondTimeRhythm { - SecondTimeRhythm::new(*self, step, None) + SecondTimeRhythm::new(*self, step) } } diff --git a/types/nerdo/library/rhythm.lua b/types/nerdo/library/rhythm.lua index e6009e1..d3e0351 100644 --- a/types/nerdo/library/rhythm.lua +++ b/types/nerdo/library/rhythm.lua @@ -19,7 +19,7 @@ error("Do not try to execute this file. It's just a type definition file.") ---@field trigger_offset integer? --- ---Current input parameter values, using parameter ids as keys ----and the actual parameter value as value. +---and the actual parameter value as value. ---@see InputParameter ---@field inputs table @@ -120,13 +120,13 @@ error("Do not try to execute this file. It's just a type definition file.") ---``` ---@field offset number? --- ----Define optional input parameters for the rhythm. Input parameters can dynamically ----change a rhythms behavior everywhere where `context`s are passed, e.g. in pattern, +---Define optional input parameters for the rhythm. Input parameters can dynamically +---change a rhythms behavior everywhere where `context`s are passed, e.g. in pattern, ---gate, emitter or cycle map generator functions. --- ---## examples: ---```lua ------ trigger a single note as specified by input parameter 'note' +----- trigger a single note as specified by input parameter 'note' ----- when input parameter 'enabled' is true, else triggers nothing. --- inputs = { --- parameter.boolean("enabled", true), @@ -206,12 +206,19 @@ error("Do not try to execute this file. It's just a type definition file.") ---``` ---@field repeats (integer|boolean)? --- ----Set optional pulse train filter between pattern and emitter. By default a probability ----gate is used, which passes 1s directly, skips 0s, and applies values in range (0 - 1) using ----the pulse value as probability, like: +---Optional pulse train filter function or generator function which filters events between +---the pattern and emitter. By default a threshold gate, which passes all pulse values +---greater than zero. +--- +---Custom function should returns true when a pattern pulse value should be passed, +---and false when the emitter should be skipped. +--- +---### examples: ---```lua +----- probability gate: skips all 0s, passes all 1s. pulse alues in range (0, 1) are +----- maybe passed, using the pulse value as probablility. ---gate = function(context) ---- return context.pulse_value >= 1 or context.pulse_value > math.random() +--- return context.pulse_value > math.random() ---end ---``` ---@field gate Pulse[]|(fun(context: GateContext):boolean)|(fun(context: GateContext):fun(context: GateContext):boolean)?