WIP: optimizations for linux
This commit is contained in:
@@ -68,6 +68,7 @@ thread-priority = "1"
|
||||
ringbuf = "0.4"
|
||||
arc-swap = "1"
|
||||
midir = "0.10"
|
||||
parking_lot = "0.12"
|
||||
|
||||
# Desktop-only dependencies (behind feature flag)
|
||||
egui = { version = "0.33", optional = true }
|
||||
|
||||
@@ -13,3 +13,4 @@ desktop = []
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8"
|
||||
parking_lot = "0.12"
|
||||
|
||||
@@ -106,7 +106,7 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
match &tokens[i] {
|
||||
Token::Int(n, span) => {
|
||||
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);
|
||||
} else {
|
||||
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) => {
|
||||
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);
|
||||
} else {
|
||||
ops.push(Op::PushFloat(*f, Some(*span)));
|
||||
@@ -137,7 +137,7 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
|
||||
} else if word == ":" {
|
||||
let (consumed, name, body) = compile_colon_def(&tokens[i + 1..], dict)?;
|
||||
i += consumed;
|
||||
dict.lock().unwrap().insert(name, body);
|
||||
dict.lock().insert(name, body);
|
||||
} else if word == ";" {
|
||||
return Err("unexpected ;".into());
|
||||
} else if word == "if" {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use parking_lot::Mutex;
|
||||
use rand::rngs::StdRng;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::ops::Op;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use parking_lot::Mutex;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::{Rng as RngTrait, SeedableRng};
|
||||
use std::borrow::Cow;
|
||||
@@ -19,7 +20,7 @@ pub struct Forth {
|
||||
impl Forth {
|
||||
pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
|
||||
Self {
|
||||
stack: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
|
||||
stack: Arc::new(Mutex::new(Vec::new())),
|
||||
vars,
|
||||
dict,
|
||||
rng,
|
||||
@@ -28,12 +29,12 @@ impl Forth {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn stack(&self) -> Vec<Value> {
|
||||
self.stack.lock().unwrap().clone()
|
||||
self.stack.lock().clone()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
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> {
|
||||
@@ -69,7 +70,7 @@ impl Forth {
|
||||
ctx: &StepContext,
|
||||
trace: Option<&mut ExecutionTrace>,
|
||||
) -> 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 cmd = CmdRegister::new();
|
||||
|
||||
@@ -474,7 +475,7 @@ impl Forth {
|
||||
Op::Get => {
|
||||
let name = stack.pop().ok_or("stack underflow")?;
|
||||
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));
|
||||
stack.push(val);
|
||||
}
|
||||
@@ -482,7 +483,7 @@ impl Forth {
|
||||
let name = stack.pop().ok_or("stack underflow")?;
|
||||
let name = name.as_str()?.to_string();
|
||||
let val = stack.pop().ok_or("stack underflow")?;
|
||||
self.vars.lock().unwrap().insert(name, val);
|
||||
self.vars.lock().insert(name, val);
|
||||
}
|
||||
|
||||
Op::GetContext(name) => {
|
||||
@@ -520,7 +521,7 @@ impl Forth {
|
||||
} else {
|
||||
(*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));
|
||||
}
|
||||
_ => {
|
||||
@@ -530,7 +531,7 @@ impl Forth {
|
||||
let val = if (hi - lo).abs() < f64::EPSILON {
|
||||
lo
|
||||
} else {
|
||||
self.rng.lock().unwrap().gen_range(lo..hi)
|
||||
self.rng.lock().gen_range(lo..hi)
|
||||
};
|
||||
stack.push(Value::Float(val, None));
|
||||
}
|
||||
@@ -543,7 +544,7 @@ impl Forth {
|
||||
return Err("exprand requires positive values".into());
|
||||
}
|
||||
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);
|
||||
stack.push(Value::Float(val, None));
|
||||
}
|
||||
@@ -554,13 +555,13 @@ impl Forth {
|
||||
return Err("logrand requires positive values".into());
|
||||
}
|
||||
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);
|
||||
stack.push(Value::Float(val, None));
|
||||
}
|
||||
Op::Seed => {
|
||||
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 => {
|
||||
@@ -580,14 +581,14 @@ impl Forth {
|
||||
if count == 0 {
|
||||
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)?;
|
||||
}
|
||||
|
||||
Op::ChanceExec | Op::ProbExec => {
|
||||
let threshold = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
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] {
|
||||
Op::ChanceExec => threshold,
|
||||
_ => threshold / 100.0,
|
||||
@@ -598,7 +599,7 @@ impl Forth {
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -711,7 +712,6 @@ impl Forth {
|
||||
let clamped = tempo.clamp(20.0, 300.0);
|
||||
self.vars
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert("__tempo__".to_string(), Value::Float(clamped, None));
|
||||
}
|
||||
|
||||
@@ -720,7 +720,6 @@ impl Forth {
|
||||
let clamped = speed.clamp(0.125, 8.0);
|
||||
self.vars
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(ctx.speed_key.to_string(), Value::Float(clamped, None));
|
||||
}
|
||||
|
||||
@@ -736,7 +735,7 @@ impl Forth {
|
||||
use std::fmt::Write;
|
||||
let mut val = String::with_capacity(8);
|
||||
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 {
|
||||
self.vars
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert("i".to_string(), Value::Int(i, None));
|
||||
run_quotation(quot.clone(), stack, outputs, cmd)?;
|
||||
}
|
||||
@@ -954,7 +952,7 @@ impl Forth {
|
||||
}
|
||||
Op::Forget => {
|
||||
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;
|
||||
|
||||
@@ -3067,7 +3067,7 @@ pub(super) fn compile_word(
|
||||
}
|
||||
|
||||
// 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());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use parking_lot::Mutex;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crossbeam_channel::Sender;
|
||||
|
||||
@@ -605,8 +606,8 @@ impl App {
|
||||
link.set_tempo(tempo);
|
||||
|
||||
self.playback.clear_queues();
|
||||
self.variables.lock().unwrap().clear();
|
||||
self.dict.lock().unwrap().clear();
|
||||
self.variables.lock().clear();
|
||||
self.dict.lock().clear();
|
||||
|
||||
for (bank, pattern) in playing {
|
||||
self.playback.queued_changes.push(StagedChange {
|
||||
|
||||
@@ -494,7 +494,6 @@ pub(crate) struct TickOutput {
|
||||
}
|
||||
|
||||
struct StepResult {
|
||||
completed_iterations: Vec<PatternId>,
|
||||
any_step_fired: bool,
|
||||
}
|
||||
|
||||
@@ -557,6 +556,7 @@ pub(crate) struct SequencerState {
|
||||
buf_audio_commands: Vec<TimestampedCommand>,
|
||||
buf_activated: Vec<PatternId>,
|
||||
buf_stopped: Vec<PatternId>,
|
||||
buf_completed_iterations: Vec<PatternId>,
|
||||
cc_access: Option<Arc<dyn CcAccess>>,
|
||||
active_notes: HashMap<(u8, u8, u8), ActiveNote>,
|
||||
muted: std::collections::HashSet<(usize, usize)>,
|
||||
@@ -580,11 +580,12 @@ impl SequencerState {
|
||||
dropped_events: 0,
|
||||
script_engine,
|
||||
variables,
|
||||
speed_overrides: HashMap::new(),
|
||||
speed_overrides: HashMap::with_capacity(MAX_PATTERNS),
|
||||
key_cache: KeyCache::new(),
|
||||
buf_audio_commands: Vec::with_capacity(32),
|
||||
buf_activated: Vec::with_capacity(16),
|
||||
buf_stopped: Vec::with_capacity(16),
|
||||
buf_completed_iterations: Vec::with_capacity(16),
|
||||
cc_access,
|
||||
active_notes: HashMap::new(),
|
||||
muted: std::collections::HashSet::new(),
|
||||
@@ -710,7 +711,7 @@ impl SequencerState {
|
||||
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.audio_state.prev_beat = beat;
|
||||
@@ -813,14 +814,14 @@ impl SequencerState {
|
||||
#[cfg(feature = "desktop")] mouse_down: f64,
|
||||
) -> StepResult {
|
||||
self.buf_audio_commands.clear();
|
||||
self.buf_completed_iterations.clear();
|
||||
let mut result = StepResult {
|
||||
completed_iterations: Vec::new(),
|
||||
any_step_fired: false,
|
||||
};
|
||||
|
||||
self.speed_overrides.clear();
|
||||
{
|
||||
let vars = self.variables.lock().unwrap();
|
||||
let vars = self.variables.lock();
|
||||
for id in self.audio_state.active_patterns.keys() {
|
||||
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()) {
|
||||
@@ -921,7 +922,7 @@ impl SequencerState {
|
||||
let next_step = active.step_index + 1;
|
||||
if next_step >= pattern.length {
|
||||
active.iter += 1;
|
||||
result.completed_iterations.push(PatternId {
|
||||
self.buf_completed_iterations.push(PatternId {
|
||||
bank: active.bank,
|
||||
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 mut chain_transitions = Vec::new();
|
||||
@@ -1073,7 +1074,7 @@ fn sequencer_loop(
|
||||
let mut seq_state = SequencerState::new(variables, dict, rng, cc_access);
|
||||
|
||||
loop {
|
||||
let mut commands = Vec::new();
|
||||
let mut commands = Vec::with_capacity(8);
|
||||
while let Ok(cmd) = cmd_rx.try_recv() {
|
||||
if matches!(cmd, SeqCommand::Shutdown) {
|
||||
return;
|
||||
@@ -1205,7 +1206,16 @@ fn sequencer_loop(
|
||||
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Mutex;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
fn make_state() -> SequencerState {
|
||||
let variables: Variables = Arc::new(Mutex::new(HashMap::new()));
|
||||
@@ -1497,7 +1507,7 @@ mod tests {
|
||||
|
||||
// Set chain variable
|
||||
{
|
||||
let mut vars = state.variables.lock().unwrap();
|
||||
let mut vars = state.variables.lock();
|
||||
vars.insert(
|
||||
"__chain_0_0__".to_string(),
|
||||
Value::Str(std::sync::Arc::from("0:1"), None),
|
||||
@@ -1736,7 +1746,7 @@ mod tests {
|
||||
|
||||
// Set chain: 0:0 -> 0:1
|
||||
{
|
||||
let mut vars = state.variables.lock().unwrap();
|
||||
let mut vars = state.variables.lock();
|
||||
vars.insert(
|
||||
"__chain_0_0__".to_string(),
|
||||
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.
|
||||
{
|
||||
let mut vars = state.variables.lock().unwrap();
|
||||
let mut vars = state.variables.lock();
|
||||
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};
|
||||
|
||||
@@ -28,9 +29,8 @@ impl CcMemory {
|
||||
/// Set a CC value (for testing)
|
||||
#[allow(dead_code)]
|
||||
pub fn set_cc(&self, device: usize, channel: usize, cc: usize, value: u8) {
|
||||
if let Ok(mut mem) = self.0.lock() {
|
||||
mem[device.min(MAX_MIDI_DEVICES - 1)][channel.min(15)][cc.min(127)] = value;
|
||||
}
|
||||
let mut mem = self.0.lock();
|
||||
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 {
|
||||
fn get_cc(&self, device: usize, channel: usize, cc: usize) -> u8 {
|
||||
self.0
|
||||
.lock()
|
||||
.ok()
|
||||
.map(|mem| mem[device.min(MAX_MIDI_DEVICES - 1)][channel.min(15)][cc.min(127)])
|
||||
.unwrap_or(0)
|
||||
let mem = self.0.lock();
|
||||
mem[device.min(MAX_MIDI_DEVICES - 1)][channel.min(15)][cc.min(127)]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,9 +151,8 @@ impl MidiState {
|
||||
let data2 = message[2];
|
||||
if (status & 0xF0) == 0xB0 && data1 < 128 {
|
||||
let channel = (status & 0x0F) as usize;
|
||||
if let Ok(mut mem) = cc_mem.lock() {
|
||||
mem[*slot][channel][data1] = data2;
|
||||
}
|
||||
let mut mem = cc_mem.lock();
|
||||
mem[*slot][channel][data1] = data2;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
use rand::rngs::StdRng;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use cagire::forth::{Dictionary, Forth, Rng, StepContext, Value, Variables};
|
||||
use parking_lot::Mutex;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::SeedableRng;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn default_ctx() -> StepContext<'static> {
|
||||
StepContext {
|
||||
|
||||
Reference in New Issue
Block a user