This commit is contained in:
@@ -68,6 +68,7 @@ thread-priority = "1"
|
|||||||
ringbuf = "0.4"
|
ringbuf = "0.4"
|
||||||
arc-swap = "1"
|
arc-swap = "1"
|
||||||
midir = "0.10"
|
midir = "0.10"
|
||||||
|
parking_lot = "0.12"
|
||||||
|
|
||||||
# Desktop-only dependencies (behind feature flag)
|
# Desktop-only dependencies (behind feature flag)
|
||||||
egui = { version = "0.33", optional = true }
|
egui = { version = "0.33", optional = true }
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ desktop = []
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
parking_lot = "0.12"
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
|||||||
match &tokens[i] {
|
match &tokens[i] {
|
||||||
Token::Int(n, span) => {
|
Token::Int(n, span) => {
|
||||||
let key = n.to_string();
|
let key = n.to_string();
|
||||||
if let Some(body) = dict.lock().unwrap().get(&key).cloned() {
|
if let Some(body) = dict.lock().get(&key).cloned() {
|
||||||
ops.extend(body);
|
ops.extend(body);
|
||||||
} else {
|
} else {
|
||||||
ops.push(Op::PushInt(*n, Some(*span)));
|
ops.push(Op::PushInt(*n, Some(*span)));
|
||||||
@@ -114,7 +114,7 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
|||||||
}
|
}
|
||||||
Token::Float(f, span) => {
|
Token::Float(f, span) => {
|
||||||
let key = f.to_string();
|
let key = f.to_string();
|
||||||
if let Some(body) = dict.lock().unwrap().get(&key).cloned() {
|
if let Some(body) = dict.lock().get(&key).cloned() {
|
||||||
ops.extend(body);
|
ops.extend(body);
|
||||||
} else {
|
} else {
|
||||||
ops.push(Op::PushFloat(*f, Some(*span)));
|
ops.push(Op::PushFloat(*f, Some(*span)));
|
||||||
@@ -137,7 +137,7 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
|||||||
} else if word == ":" {
|
} else if word == ":" {
|
||||||
let (consumed, name, body) = compile_colon_def(&tokens[i + 1..], dict)?;
|
let (consumed, name, body) = compile_colon_def(&tokens[i + 1..], dict)?;
|
||||||
i += consumed;
|
i += consumed;
|
||||||
dict.lock().unwrap().insert(name, body);
|
dict.lock().insert(name, body);
|
||||||
} else if word == ";" {
|
} else if word == ";" {
|
||||||
return Err("unexpected ;".into());
|
return Err("unexpected ;".into());
|
||||||
} else if word == "if" {
|
} else if word == "if" {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
use parking_lot::Mutex;
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::ops::Op;
|
use super::ops::Op;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use parking_lot::Mutex;
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
use rand::{Rng as RngTrait, SeedableRng};
|
use rand::{Rng as RngTrait, SeedableRng};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
@@ -19,7 +20,7 @@ pub struct Forth {
|
|||||||
impl Forth {
|
impl Forth {
|
||||||
pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
|
pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
|
stack: Arc::new(Mutex::new(Vec::new())),
|
||||||
vars,
|
vars,
|
||||||
dict,
|
dict,
|
||||||
rng,
|
rng,
|
||||||
@@ -28,12 +29,12 @@ impl Forth {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn stack(&self) -> Vec<Value> {
|
pub fn stack(&self) -> Vec<Value> {
|
||||||
self.stack.lock().unwrap().clone()
|
self.stack.lock().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn clear_stack(&self) {
|
pub fn clear_stack(&self) {
|
||||||
self.stack.lock().unwrap().clear();
|
self.stack.lock().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result<Vec<String>, String> {
|
pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result<Vec<String>, String> {
|
||||||
@@ -69,7 +70,7 @@ impl Forth {
|
|||||||
ctx: &StepContext,
|
ctx: &StepContext,
|
||||||
trace: Option<&mut ExecutionTrace>,
|
trace: Option<&mut ExecutionTrace>,
|
||||||
) -> Result<Vec<String>, String> {
|
) -> Result<Vec<String>, String> {
|
||||||
let mut stack = self.stack.lock().unwrap();
|
let mut stack = self.stack.lock();
|
||||||
let mut outputs: Vec<String> = Vec::with_capacity(8);
|
let mut outputs: Vec<String> = Vec::with_capacity(8);
|
||||||
let mut cmd = CmdRegister::new();
|
let mut cmd = CmdRegister::new();
|
||||||
|
|
||||||
@@ -474,7 +475,7 @@ impl Forth {
|
|||||||
Op::Get => {
|
Op::Get => {
|
||||||
let name = stack.pop().ok_or("stack underflow")?;
|
let name = stack.pop().ok_or("stack underflow")?;
|
||||||
let name = name.as_str()?;
|
let name = name.as_str()?;
|
||||||
let vars = self.vars.lock().unwrap();
|
let vars = self.vars.lock();
|
||||||
let val = vars.get(name).cloned().unwrap_or(Value::Int(0, None));
|
let val = vars.get(name).cloned().unwrap_or(Value::Int(0, None));
|
||||||
stack.push(val);
|
stack.push(val);
|
||||||
}
|
}
|
||||||
@@ -482,7 +483,7 @@ impl Forth {
|
|||||||
let name = stack.pop().ok_or("stack underflow")?;
|
let name = stack.pop().ok_or("stack underflow")?;
|
||||||
let name = name.as_str()?.to_string();
|
let name = name.as_str()?.to_string();
|
||||||
let val = stack.pop().ok_or("stack underflow")?;
|
let val = stack.pop().ok_or("stack underflow")?;
|
||||||
self.vars.lock().unwrap().insert(name, val);
|
self.vars.lock().insert(name, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::GetContext(name) => {
|
Op::GetContext(name) => {
|
||||||
@@ -520,7 +521,7 @@ impl Forth {
|
|||||||
} else {
|
} else {
|
||||||
(*b_i, *a_i)
|
(*b_i, *a_i)
|
||||||
};
|
};
|
||||||
let val = self.rng.lock().unwrap().gen_range(lo..=hi);
|
let val = self.rng.lock().gen_range(lo..=hi);
|
||||||
stack.push(Value::Int(val, None));
|
stack.push(Value::Int(val, None));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@@ -530,7 +531,7 @@ impl Forth {
|
|||||||
let val = if (hi - lo).abs() < f64::EPSILON {
|
let val = if (hi - lo).abs() < f64::EPSILON {
|
||||||
lo
|
lo
|
||||||
} else {
|
} else {
|
||||||
self.rng.lock().unwrap().gen_range(lo..hi)
|
self.rng.lock().gen_range(lo..hi)
|
||||||
};
|
};
|
||||||
stack.push(Value::Float(val, None));
|
stack.push(Value::Float(val, None));
|
||||||
}
|
}
|
||||||
@@ -543,7 +544,7 @@ impl Forth {
|
|||||||
return Err("exprand requires positive values".into());
|
return Err("exprand requires positive values".into());
|
||||||
}
|
}
|
||||||
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
|
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
|
||||||
let u: f64 = self.rng.lock().unwrap().gen();
|
let u: f64 = self.rng.lock().gen();
|
||||||
let val = lo * (hi / lo).powf(u);
|
let val = lo * (hi / lo).powf(u);
|
||||||
stack.push(Value::Float(val, None));
|
stack.push(Value::Float(val, None));
|
||||||
}
|
}
|
||||||
@@ -554,13 +555,13 @@ impl Forth {
|
|||||||
return Err("logrand requires positive values".into());
|
return Err("logrand requires positive values".into());
|
||||||
}
|
}
|
||||||
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
|
let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) };
|
||||||
let u: f64 = self.rng.lock().unwrap().gen();
|
let u: f64 = self.rng.lock().gen();
|
||||||
let val = hi * (lo / hi).powf(u);
|
let val = hi * (lo / hi).powf(u);
|
||||||
stack.push(Value::Float(val, None));
|
stack.push(Value::Float(val, None));
|
||||||
}
|
}
|
||||||
Op::Seed => {
|
Op::Seed => {
|
||||||
let s = stack.pop().ok_or("stack underflow")?.as_int()?;
|
let s = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||||
*self.rng.lock().unwrap() = StdRng::seed_from_u64(s as u64);
|
*self.rng.lock() = StdRng::seed_from_u64(s as u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::Cycle | Op::PCycle => {
|
Op::Cycle | Op::PCycle => {
|
||||||
@@ -580,14 +581,14 @@ impl Forth {
|
|||||||
if count == 0 {
|
if count == 0 {
|
||||||
return Err("choose count must be > 0".into());
|
return Err("choose count must be > 0".into());
|
||||||
}
|
}
|
||||||
let idx = self.rng.lock().unwrap().gen_range(0..count);
|
let idx = self.rng.lock().gen_range(0..count);
|
||||||
drain_select_run(count, idx, stack, outputs, cmd)?;
|
drain_select_run(count, idx, stack, outputs, cmd)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::ChanceExec | Op::ProbExec => {
|
Op::ChanceExec | Op::ProbExec => {
|
||||||
let threshold = stack.pop().ok_or("stack underflow")?.as_float()?;
|
let threshold = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||||
let quot = stack.pop().ok_or("stack underflow")?;
|
let quot = stack.pop().ok_or("stack underflow")?;
|
||||||
let val: f64 = self.rng.lock().unwrap().gen();
|
let val: f64 = self.rng.lock().gen();
|
||||||
let limit = match &ops[pc] {
|
let limit = match &ops[pc] {
|
||||||
Op::ChanceExec => threshold,
|
Op::ChanceExec => threshold,
|
||||||
_ => threshold / 100.0,
|
_ => threshold / 100.0,
|
||||||
@@ -598,7 +599,7 @@ impl Forth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Op::Coin => {
|
Op::Coin => {
|
||||||
let val: f64 = self.rng.lock().unwrap().gen();
|
let val: f64 = self.rng.lock().gen();
|
||||||
stack.push(Value::Int(if val < 0.5 { 1 } else { 0 }, None));
|
stack.push(Value::Int(if val < 0.5 { 1 } else { 0 }, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,7 +712,6 @@ impl Forth {
|
|||||||
let clamped = tempo.clamp(20.0, 300.0);
|
let clamped = tempo.clamp(20.0, 300.0);
|
||||||
self.vars
|
self.vars
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.insert("__tempo__".to_string(), Value::Float(clamped, None));
|
.insert("__tempo__".to_string(), Value::Float(clamped, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,7 +720,6 @@ impl Forth {
|
|||||||
let clamped = speed.clamp(0.125, 8.0);
|
let clamped = speed.clamp(0.125, 8.0);
|
||||||
self.vars
|
self.vars
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.insert(ctx.speed_key.to_string(), Value::Float(clamped, None));
|
.insert(ctx.speed_key.to_string(), Value::Float(clamped, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,7 +735,7 @@ impl Forth {
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut val = String::with_capacity(8);
|
let mut val = String::with_capacity(8);
|
||||||
let _ = write!(&mut val, "{bank}:{pattern}");
|
let _ = write!(&mut val, "{bank}:{pattern}");
|
||||||
self.vars.lock().unwrap().insert(ctx.chain_key.to_string(), Value::Str(Arc::from(val), None));
|
self.vars.lock().insert(ctx.chain_key.to_string(), Value::Str(Arc::from(val), None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -852,7 +851,6 @@ impl Forth {
|
|||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
self.vars
|
self.vars
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
|
||||||
.insert("i".to_string(), Value::Int(i, None));
|
.insert("i".to_string(), Value::Int(i, None));
|
||||||
run_quotation(quot.clone(), stack, outputs, cmd)?;
|
run_quotation(quot.clone(), stack, outputs, cmd)?;
|
||||||
}
|
}
|
||||||
@@ -954,7 +952,7 @@ impl Forth {
|
|||||||
}
|
}
|
||||||
Op::Forget => {
|
Op::Forget => {
|
||||||
let name = stack.pop().ok_or("stack underflow")?.as_str()?.to_string();
|
let name = stack.pop().ok_or("stack underflow")?.as_str()?.to_string();
|
||||||
self.dict.lock().unwrap().remove(&name);
|
self.dict.lock().remove(&name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pc += 1;
|
pc += 1;
|
||||||
|
|||||||
@@ -3067,7 +3067,7 @@ pub(super) fn compile_word(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User-defined words from dictionary
|
// User-defined words from dictionary
|
||||||
if let Some(body) = dict.lock().unwrap().get(name) {
|
if let Some(body) = dict.lock().get(name) {
|
||||||
ops.extend(body.iter().cloned());
|
ops.extend(body.iter().cloned());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
use parking_lot::Mutex;
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
|
|
||||||
@@ -605,8 +606,8 @@ impl App {
|
|||||||
link.set_tempo(tempo);
|
link.set_tempo(tempo);
|
||||||
|
|
||||||
self.playback.clear_queues();
|
self.playback.clear_queues();
|
||||||
self.variables.lock().unwrap().clear();
|
self.variables.lock().clear();
|
||||||
self.dict.lock().unwrap().clear();
|
self.dict.lock().clear();
|
||||||
|
|
||||||
for (bank, pattern) in playing {
|
for (bank, pattern) in playing {
|
||||||
self.playback.queued_changes.push(StagedChange {
|
self.playback.queued_changes.push(StagedChange {
|
||||||
|
|||||||
@@ -494,7 +494,6 @@ pub(crate) struct TickOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct StepResult {
|
struct StepResult {
|
||||||
completed_iterations: Vec<PatternId>,
|
|
||||||
any_step_fired: bool,
|
any_step_fired: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,6 +556,7 @@ pub(crate) struct SequencerState {
|
|||||||
buf_audio_commands: Vec<TimestampedCommand>,
|
buf_audio_commands: Vec<TimestampedCommand>,
|
||||||
buf_activated: Vec<PatternId>,
|
buf_activated: Vec<PatternId>,
|
||||||
buf_stopped: Vec<PatternId>,
|
buf_stopped: Vec<PatternId>,
|
||||||
|
buf_completed_iterations: Vec<PatternId>,
|
||||||
cc_access: Option<Arc<dyn CcAccess>>,
|
cc_access: Option<Arc<dyn CcAccess>>,
|
||||||
active_notes: HashMap<(u8, u8, u8), ActiveNote>,
|
active_notes: HashMap<(u8, u8, u8), ActiveNote>,
|
||||||
muted: std::collections::HashSet<(usize, usize)>,
|
muted: std::collections::HashSet<(usize, usize)>,
|
||||||
@@ -580,11 +580,12 @@ impl SequencerState {
|
|||||||
dropped_events: 0,
|
dropped_events: 0,
|
||||||
script_engine,
|
script_engine,
|
||||||
variables,
|
variables,
|
||||||
speed_overrides: HashMap::new(),
|
speed_overrides: HashMap::with_capacity(MAX_PATTERNS),
|
||||||
key_cache: KeyCache::new(),
|
key_cache: KeyCache::new(),
|
||||||
buf_audio_commands: Vec::with_capacity(32),
|
buf_audio_commands: Vec::with_capacity(32),
|
||||||
buf_activated: Vec::with_capacity(16),
|
buf_activated: Vec::with_capacity(16),
|
||||||
buf_stopped: Vec::with_capacity(16),
|
buf_stopped: Vec::with_capacity(16),
|
||||||
|
buf_completed_iterations: Vec::with_capacity(16),
|
||||||
cc_access,
|
cc_access,
|
||||||
active_notes: HashMap::new(),
|
active_notes: HashMap::new(),
|
||||||
muted: std::collections::HashSet::new(),
|
muted: std::collections::HashSet::new(),
|
||||||
@@ -710,7 +711,7 @@ impl SequencerState {
|
|||||||
input.mouse_down,
|
input.mouse_down,
|
||||||
);
|
);
|
||||||
|
|
||||||
let vars = self.read_variables(&steps.completed_iterations, steps.any_step_fired);
|
let vars = self.read_variables(&self.buf_completed_iterations, steps.any_step_fired);
|
||||||
self.apply_chain_transitions(vars.chain_transitions);
|
self.apply_chain_transitions(vars.chain_transitions);
|
||||||
|
|
||||||
self.audio_state.prev_beat = beat;
|
self.audio_state.prev_beat = beat;
|
||||||
@@ -813,14 +814,14 @@ impl SequencerState {
|
|||||||
#[cfg(feature = "desktop")] mouse_down: f64,
|
#[cfg(feature = "desktop")] mouse_down: f64,
|
||||||
) -> StepResult {
|
) -> StepResult {
|
||||||
self.buf_audio_commands.clear();
|
self.buf_audio_commands.clear();
|
||||||
|
self.buf_completed_iterations.clear();
|
||||||
let mut result = StepResult {
|
let mut result = StepResult {
|
||||||
completed_iterations: Vec::new(),
|
|
||||||
any_step_fired: false,
|
any_step_fired: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.speed_overrides.clear();
|
self.speed_overrides.clear();
|
||||||
{
|
{
|
||||||
let vars = self.variables.lock().unwrap();
|
let vars = self.variables.lock();
|
||||||
for id in self.audio_state.active_patterns.keys() {
|
for id in self.audio_state.active_patterns.keys() {
|
||||||
let key = self.key_cache.speed_key(id.bank, id.pattern);
|
let key = self.key_cache.speed_key(id.bank, id.pattern);
|
||||||
if let Some(v) = vars.get(key).and_then(|v| v.as_float().ok()) {
|
if let Some(v) = vars.get(key).and_then(|v| v.as_float().ok()) {
|
||||||
@@ -921,7 +922,7 @@ impl SequencerState {
|
|||||||
let next_step = active.step_index + 1;
|
let next_step = active.step_index + 1;
|
||||||
if next_step >= pattern.length {
|
if next_step >= pattern.length {
|
||||||
active.iter += 1;
|
active.iter += 1;
|
||||||
result.completed_iterations.push(PatternId {
|
self.buf_completed_iterations.push(PatternId {
|
||||||
bank: active.bank,
|
bank: active.bank,
|
||||||
pattern: active.pattern,
|
pattern: active.pattern,
|
||||||
});
|
});
|
||||||
@@ -947,7 +948,7 @@ impl SequencerState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut vars = self.variables.lock().unwrap();
|
let mut vars = self.variables.lock();
|
||||||
let new_tempo = vars.remove("__tempo__").and_then(|v| v.as_float().ok());
|
let new_tempo = vars.remove("__tempo__").and_then(|v| v.as_float().ok());
|
||||||
|
|
||||||
let mut chain_transitions = Vec::new();
|
let mut chain_transitions = Vec::new();
|
||||||
@@ -1073,7 +1074,7 @@ fn sequencer_loop(
|
|||||||
let mut seq_state = SequencerState::new(variables, dict, rng, cc_access);
|
let mut seq_state = SequencerState::new(variables, dict, rng, cc_access);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut commands = Vec::new();
|
let mut commands = Vec::with_capacity(8);
|
||||||
while let Ok(cmd) = cmd_rx.try_recv() {
|
while let Ok(cmd) = cmd_rx.try_recv() {
|
||||||
if matches!(cmd, SeqCommand::Shutdown) {
|
if matches!(cmd, SeqCommand::Shutdown) {
|
||||||
return;
|
return;
|
||||||
@@ -1205,7 +1206,16 @@ fn sequencer_loop(
|
|||||||
|
|
||||||
shared_state.store(Arc::new(output.shared_state));
|
shared_state.store(Arc::new(output.shared_state));
|
||||||
|
|
||||||
thread::sleep(Duration::from_micros(200));
|
// Adaptive sleep: calculate time until next substep boundary
|
||||||
|
// At max speed (8x), substeps occur every beat/32
|
||||||
|
// Sleep for most of that time, leaving 500μs margin for processing
|
||||||
|
let beats_per_sec = tempo / 60.0;
|
||||||
|
let max_speed = 8.0; // Maximum speed multiplier from speed.clamp()
|
||||||
|
let secs_per_substep = 1.0 / (beats_per_sec * 4.0 * max_speed);
|
||||||
|
let substep_us = (secs_per_substep * 1_000_000.0) as u64;
|
||||||
|
// Sleep for most of the substep duration, clamped to reasonable bounds
|
||||||
|
let sleep_us = substep_us.saturating_sub(500).clamp(50, 2000);
|
||||||
|
thread::sleep(Duration::from_micros(sleep_us));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1317,7 +1327,7 @@ fn parse_midi_command(cmd: &str) -> Option<(MidiCommand, Option<f64>)> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::sync::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
fn make_state() -> SequencerState {
|
fn make_state() -> SequencerState {
|
||||||
let variables: Variables = Arc::new(Mutex::new(HashMap::new()));
|
let variables: Variables = Arc::new(Mutex::new(HashMap::new()));
|
||||||
@@ -1497,7 +1507,7 @@ mod tests {
|
|||||||
|
|
||||||
// Set chain variable
|
// Set chain variable
|
||||||
{
|
{
|
||||||
let mut vars = state.variables.lock().unwrap();
|
let mut vars = state.variables.lock();
|
||||||
vars.insert(
|
vars.insert(
|
||||||
"__chain_0_0__".to_string(),
|
"__chain_0_0__".to_string(),
|
||||||
Value::Str(std::sync::Arc::from("0:1"), None),
|
Value::Str(std::sync::Arc::from("0:1"), None),
|
||||||
@@ -1736,7 +1746,7 @@ mod tests {
|
|||||||
|
|
||||||
// Set chain: 0:0 -> 0:1
|
// Set chain: 0:0 -> 0:1
|
||||||
{
|
{
|
||||||
let mut vars = state.variables.lock().unwrap();
|
let mut vars = state.variables.lock();
|
||||||
vars.insert(
|
vars.insert(
|
||||||
"__chain_0_0__".to_string(),
|
"__chain_0_0__".to_string(),
|
||||||
Value::Str(std::sync::Arc::from("0:1"), None),
|
Value::Str(std::sync::Arc::from("0:1"), None),
|
||||||
@@ -1979,7 +1989,7 @@ mod tests {
|
|||||||
|
|
||||||
// Script fires at beat 1.0 (step 0). Set __tempo__ as if the script did.
|
// Script fires at beat 1.0 (step 0). Set __tempo__ as if the script did.
|
||||||
{
|
{
|
||||||
let mut vars = state.variables.lock().unwrap();
|
let mut vars = state.variables.lock();
|
||||||
vars.insert("__tempo__".to_string(), Value::Float(140.0, None));
|
vars.insert("__tempo__".to_string(), Value::Float(140.0, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
src/midi.rs
20
src/midi.rs
@@ -1,4 +1,5 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use parking_lot::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use midir::{MidiInput, MidiOutput};
|
use midir::{MidiInput, MidiOutput};
|
||||||
|
|
||||||
@@ -28,9 +29,8 @@ impl CcMemory {
|
|||||||
/// Set a CC value (for testing)
|
/// Set a CC value (for testing)
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn set_cc(&self, device: usize, channel: usize, cc: usize, value: u8) {
|
pub fn set_cc(&self, device: usize, channel: usize, cc: usize, value: u8) {
|
||||||
if let Ok(mut mem) = self.0.lock() {
|
let mut mem = self.0.lock();
|
||||||
mem[device.min(MAX_MIDI_DEVICES - 1)][channel.min(15)][cc.min(127)] = value;
|
mem[device.min(MAX_MIDI_DEVICES - 1)][channel.min(15)][cc.min(127)] = value;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,11 +42,8 @@ impl Default for CcMemory {
|
|||||||
|
|
||||||
impl CcAccess for CcMemory {
|
impl CcAccess for CcMemory {
|
||||||
fn get_cc(&self, device: usize, channel: usize, cc: usize) -> u8 {
|
fn get_cc(&self, device: usize, channel: usize, cc: usize) -> u8 {
|
||||||
self.0
|
let mem = self.0.lock();
|
||||||
.lock()
|
mem[device.min(MAX_MIDI_DEVICES - 1)][channel.min(15)][cc.min(127)]
|
||||||
.ok()
|
|
||||||
.map(|mem| mem[device.min(MAX_MIDI_DEVICES - 1)][channel.min(15)][cc.min(127)])
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,9 +151,8 @@ impl MidiState {
|
|||||||
let data2 = message[2];
|
let data2 = message[2];
|
||||||
if (status & 0xF0) == 0xB0 && data1 < 128 {
|
if (status & 0xF0) == 0xB0 && data1 < 128 {
|
||||||
let channel = (status & 0x0F) as usize;
|
let channel = (status & 0x0F) as usize;
|
||||||
if let Ok(mut mem) = cc_mem.lock() {
|
let mut mem = cc_mem.lock();
|
||||||
mem[*slot][channel][data1] = data2;
|
mem[*slot][channel][data1] = data2;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
use parking_lot::Mutex;
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use cagire::forth::{Dictionary, Forth, Rng, StepContext, Value, Variables};
|
use cagire::forth::{Dictionary, Forth, Rng, StepContext, Value, Variables};
|
||||||
|
use parking_lot::Mutex;
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn default_ctx() -> StepContext<'static> {
|
pub fn default_ctx() -> StepContext<'static> {
|
||||||
StepContext {
|
StepContext {
|
||||||
|
|||||||
Reference in New Issue
Block a user