use rand::rngs::StdRng; use rand::{Rng as RngTrait, SeedableRng}; use super::compiler::compile_script; use super::ops::Op; use super::types::{ CmdRegister, Dictionary, ExecutionTrace, Rng, Stack, StepContext, Value, Variables, }; #[derive(Clone, Debug)] struct TimeContext { start: f64, duration: f64, subdivisions: Option>, iteration_index: Option, } pub struct Forth { stack: Stack, vars: Variables, dict: Dictionary, rng: Rng, } impl Forth { pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self { Self { stack: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())), vars, dict, rng, } } #[allow(dead_code)] pub fn stack(&self) -> Vec { self.stack.lock().unwrap().clone() } #[allow(dead_code)] pub fn clear_stack(&self) { self.stack.lock().unwrap().clear(); } pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result, String> { self.evaluate_impl(script, ctx, None) } pub fn evaluate_with_trace( &self, script: &str, ctx: &StepContext, trace: &mut ExecutionTrace, ) -> Result, String> { self.evaluate_impl(script, ctx, Some(trace)) } fn evaluate_impl( &self, script: &str, ctx: &StepContext, trace: Option<&mut ExecutionTrace>, ) -> Result, String> { if script.trim().is_empty() { return Err("empty script".into()); } let ops = compile_script(script, &self.dict)?; self.execute(&ops, ctx, trace) } fn execute( &self, ops: &[Op], ctx: &StepContext, trace: Option<&mut ExecutionTrace>, ) -> Result, String> { let mut stack = self.stack.lock().unwrap(); let mut outputs: Vec = Vec::new(); let mut time_stack: Vec = vec![TimeContext { start: 0.0, duration: ctx.step_duration() * 4.0, subdivisions: None, iteration_index: None, }]; let mut cmd = CmdRegister::default(); self.execute_ops( ops, ctx, &mut stack, &mut outputs, &mut time_stack, &mut cmd, trace, )?; Ok(outputs) } #[allow(clippy::too_many_arguments)] fn execute_ops( &self, ops: &[Op], ctx: &StepContext, stack: &mut Vec, outputs: &mut Vec, time_stack: &mut Vec, cmd: &mut CmdRegister, trace: Option<&mut ExecutionTrace>, ) -> Result<(), String> { let mut pc = 0; let trace_cell = std::cell::RefCell::new(trace); while pc < ops.len() { match &ops[pc] { Op::PushInt(n, span) => stack.push(Value::Int(*n, *span)), Op::PushFloat(f, span) => stack.push(Value::Float(*f, *span)), Op::PushStr(s, span) => stack.push(Value::Str(s.clone(), *span)), Op::Dup => { let v = stack.last().ok_or("stack underflow")?.clone(); stack.push(v); } Op::Dupn => { let n = stack.pop().ok_or("stack underflow")?.as_int()?; let v = stack.pop().ok_or("stack underflow")?; for _ in 0..n { stack.push(v.clone()); } } Op::Drop => { stack.pop().ok_or("stack underflow")?; } Op::Swap => { let len = stack.len(); if len < 2 { return Err("stack underflow".into()); } stack.swap(len - 1, len - 2); } Op::Over => { let len = stack.len(); if len < 2 { return Err("stack underflow".into()); } let v = stack[len - 2].clone(); stack.push(v); } Op::Rot => { let len = stack.len(); if len < 3 { return Err("stack underflow".into()); } let v = stack.remove(len - 3); stack.push(v); } Op::Nip => { let len = stack.len(); if len < 2 { return Err("stack underflow".into()); } stack.remove(len - 2); } Op::Tuck => { let len = stack.len(); if len < 2 { return Err("stack underflow".into()); } let v = stack[len - 1].clone(); stack.insert(len - 2, v); } Op::Add => binary_op(stack, |a, b| a + b)?, Op::Sub => binary_op(stack, |a, b| a - b)?, Op::Mul => binary_op(stack, |a, b| a * b)?, Op::Div => binary_op(stack, |a, b| a / b)?, Op::Mod => { let b = stack.pop().ok_or("stack underflow")?.as_int()?; let a = stack.pop().ok_or("stack underflow")?.as_int()?; stack.push(Value::Int(a % b, None)); } Op::Neg => { let v = stack.pop().ok_or("stack underflow")?; match v { Value::Int(i, s) => stack.push(Value::Int(-i, s)), Value::Float(f, s) => stack.push(Value::Float(-f, s)), _ => return Err("expected number".into()), } } Op::Abs => { let v = stack.pop().ok_or("stack underflow")?; match v { Value::Int(i, s) => stack.push(Value::Int(i.abs(), s)), Value::Float(f, s) => stack.push(Value::Float(f.abs(), s)), _ => return Err("expected number".into()), } } Op::Floor => { let v = stack.pop().ok_or("stack underflow")?.as_float()?; stack.push(Value::Int(v.floor() as i64, None)); } Op::Ceil => { let v = stack.pop().ok_or("stack underflow")?.as_float()?; stack.push(Value::Int(v.ceil() as i64, None)); } Op::Round => { let v = stack.pop().ok_or("stack underflow")?.as_float()?; stack.push(Value::Int(v.round() as i64, None)); } Op::Min => binary_op(stack, |a, b| a.min(b))?, Op::Max => binary_op(stack, |a, b| a.max(b))?, Op::Eq => cmp_op(stack, |a, b| (a - b).abs() < f64::EPSILON)?, Op::Ne => cmp_op(stack, |a, b| (a - b).abs() >= f64::EPSILON)?, Op::Lt => cmp_op(stack, |a, b| a < b)?, Op::Gt => cmp_op(stack, |a, b| a > b)?, Op::Le => cmp_op(stack, |a, b| a <= b)?, Op::Ge => cmp_op(stack, |a, b| a >= b)?, Op::And => { let b = stack.pop().ok_or("stack underflow")?.is_truthy(); let a = stack.pop().ok_or("stack underflow")?.is_truthy(); stack.push(Value::Int(if a && b { 1 } else { 0 }, None)); } Op::Or => { let b = stack.pop().ok_or("stack underflow")?.is_truthy(); let a = stack.pop().ok_or("stack underflow")?.is_truthy(); stack.push(Value::Int(if a || b { 1 } else { 0 }, None)); } Op::Not => { let v = stack.pop().ok_or("stack underflow")?.is_truthy(); stack.push(Value::Int(if v { 0 } else { 1 }, None)); } Op::BranchIfZero(offset, then_span, else_span) => { let v = stack.pop().ok_or("stack underflow")?; if !v.is_truthy() { if let Some(span) = else_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(*span); } } pc += offset; } else if let Some(span) = then_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(*span); } } } Op::Branch(offset) => { pc += offset; } Op::NewCmd => { let name = stack.pop().ok_or("stack underflow")?; let name = name.as_str()?; cmd.set_sound(name.to_string()); } Op::SetParam(param) => { let val = stack.pop().ok_or("stack underflow")?; cmd.set_param(param.clone(), val.to_param_string()); } Op::Emit => { let (sound, mut params) = cmd.take().ok_or("no sound set")?; let mut pairs = vec![("sound".into(), sound)]; pairs.append(&mut params); let time_ctx = time_stack.last().ok_or("time stack underflow")?; if time_ctx.start > 0.0 { pairs.push(("delta".into(), time_ctx.start.to_string())); } if !pairs.iter().any(|(k, _)| k == "dur") { pairs.push(("dur".into(), time_ctx.duration.to_string())); } if let Some(idx) = pairs.iter().position(|(k, _)| k == "delaytime") { let ratio: f64 = pairs[idx].1.parse().unwrap_or(1.0); pairs[idx].1 = (ratio * time_ctx.duration).to_string(); } else { pairs.push(("delaytime".into(), time_ctx.duration.to_string())); } outputs.push(format_cmd(&pairs)); } Op::Get => { let name = stack.pop().ok_or("stack underflow")?; let name = name.as_str()?; let vars = self.vars.lock().unwrap(); let val = vars.get(name).cloned().unwrap_or(Value::Int(0, None)); stack.push(val); } Op::Set => { 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); } Op::GetContext(name) => { let val = match name.as_str() { "step" => Value::Int(ctx.step as i64, None), "beat" => Value::Float(ctx.beat, None), "bank" => Value::Int(ctx.bank as i64, None), "pattern" => Value::Int(ctx.pattern as i64, None), "tempo" => Value::Float(ctx.tempo, None), "phase" => Value::Float(ctx.phase, None), "slot" => Value::Int(ctx.slot as i64, None), "runs" => Value::Int(ctx.runs as i64, None), "iter" => Value::Int(ctx.iter as i64, None), "speed" => Value::Float(ctx.speed, None), "stepdur" => Value::Float(ctx.step_duration(), None), "fill" => Value::Int(if ctx.fill { 1 } else { 0 }, None), _ => Value::Int(0, None), }; stack.push(val); } Op::Rand => { let max = stack.pop().ok_or("stack underflow")?; let min = stack.pop().ok_or("stack underflow")?; match (&min, &max) { (Value::Int(min_i, _), Value::Int(max_i, _)) => { let val = self.rng.lock().unwrap().gen_range(*min_i..=*max_i); stack.push(Value::Int(val, None)); } _ => { let min_f = min.as_float()?; let max_f = max.as_float()?; let val = self.rng.lock().unwrap().gen_range(min_f..max_f); 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); } Op::Cycle => { let count = stack.pop().ok_or("stack underflow")?.as_int()? as usize; if count == 0 { return Err("cycle count must be > 0".into()); } if stack.len() < count { return Err("stack underflow".into()); } let start = stack.len() - count; let values: Vec = stack.drain(start..).collect(); let idx = ctx.runs % count; let selected = values[idx].clone(); if let Some(span) = selected.span() { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.selected_spans.push(span); } } if let Value::Quotation(quot_ops, body_span) = selected { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?; *trace_cell.borrow_mut() = trace_opt; } else { stack.push(selected); } } Op::PCycle => { let count = stack.pop().ok_or("stack underflow")?.as_int()? as usize; if count == 0 { return Err("pcycle count must be > 0".into()); } if stack.len() < count { return Err("stack underflow".into()); } let start = stack.len() - count; let values: Vec = stack.drain(start..).collect(); let idx = ctx.iter % count; let selected = values[idx].clone(); if let Some(span) = selected.span() { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.selected_spans.push(span); } } if let Value::Quotation(quot_ops, body_span) = selected { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?; *trace_cell.borrow_mut() = trace_opt; } else { stack.push(selected); } } Op::Choose => { let count = stack.pop().ok_or("stack underflow")?.as_int()? as usize; if count == 0 { return Err("choose count must be > 0".into()); } if stack.len() < count { return Err("stack underflow".into()); } let start = stack.len() - count; let values: Vec = stack.drain(start..).collect(); let idx = self.rng.lock().unwrap().gen_range(0..count); let selected = values[idx].clone(); if let Some(span) = selected.span() { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.selected_spans.push(span); } } if let Value::Quotation(quot_ops, body_span) = selected { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?; *trace_cell.borrow_mut() = trace_opt; } else { stack.push(selected); } } Op::ChanceExec => { let prob = stack.pop().ok_or("stack underflow")?.as_float()?; let quot = stack.pop().ok_or("stack underflow")?; let val: f64 = self.rng.lock().unwrap().gen(); if val < prob { match quot { Value::Quotation(quot_ops, body_span) => { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?; *trace_cell.borrow_mut() = trace_opt; } _ => return Err("expected quotation".into()), } } } Op::ProbExec => { let pct = stack.pop().ok_or("stack underflow")?.as_float()?; let quot = stack.pop().ok_or("stack underflow")?; let val: f64 = self.rng.lock().unwrap().gen(); if val < pct / 100.0 { match quot { Value::Quotation(quot_ops, body_span) => { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?; *trace_cell.borrow_mut() = trace_opt; } _ => return Err("expected quotation".into()), } } } Op::Coin => { let val: f64 = self.rng.lock().unwrap().gen(); stack.push(Value::Int(if val < 0.5 { 1 } else { 0 }, None)); } Op::Every => { let n = stack.pop().ok_or("stack underflow")?.as_int()?; if n <= 0 { return Err("every count must be > 0".into()); } let result = ctx.iter as i64 % n == 0; stack.push(Value::Int(if result { 1 } else { 0 }, None)); } Op::Quotation(quote_ops, body_span) => { stack.push(Value::Quotation(quote_ops.clone(), *body_span)); } Op::When => { let cond = stack.pop().ok_or("stack underflow")?; let quot = stack.pop().ok_or("stack underflow")?; if cond.is_truthy() { match quot { Value::Quotation(quot_ops, body_span) => { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?; *trace_cell.borrow_mut() = trace_opt; } _ => return Err("expected quotation".into()), } } } Op::Unless => { let cond = stack.pop().ok_or("stack underflow")?; let quot = stack.pop().ok_or("stack underflow")?; if !cond.is_truthy() { match quot { Value::Quotation(quot_ops, body_span) => { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?; *trace_cell.borrow_mut() = trace_opt; } _ => return Err("expected quotation".into()), } } } Op::Mtof => { let note = stack.pop().ok_or("stack underflow")?.as_float()?; let freq = 440.0 * 2.0_f64.powf((note - 69.0) / 12.0); stack.push(Value::Float(freq, None)); } Op::Ftom => { let freq = stack.pop().ok_or("stack underflow")?.as_float()?; let note = 69.0 + 12.0 * (freq / 440.0).log2(); stack.push(Value::Float(note, None)); } Op::Degree(pattern) => { let degree = stack.pop().ok_or("stack underflow")?.as_int()?; let len = pattern.len() as i64; let octave_offset = degree.div_euclid(len); let idx = degree.rem_euclid(len) as usize; let midi = 60 + octave_offset * 12 + pattern[idx]; stack.push(Value::Int(midi, None)); } Op::Oct => { let shift = stack.pop().ok_or("stack underflow")?.as_int()?; let note = stack.pop().ok_or("stack underflow")?.as_int()?; stack.push(Value::Int(note + shift * 12, None)); } Op::At => { let pos = stack.pop().ok_or("stack underflow")?.as_float()?; let parent = time_stack.last().ok_or("time stack underflow")?; let new_start = parent.start + parent.duration * pos; time_stack.push(TimeContext { start: new_start, duration: parent.duration * (1.0 - pos), subdivisions: None, iteration_index: parent.iteration_index, }); } Op::Window => { let end = stack.pop().ok_or("stack underflow")?.as_float()?; let start_pos = stack.pop().ok_or("stack underflow")?.as_float()?; let parent = time_stack.last().ok_or("time stack underflow")?; let new_start = parent.start + parent.duration * start_pos; let new_duration = parent.duration * (end - start_pos); time_stack.push(TimeContext { start: new_start, duration: new_duration, subdivisions: None, iteration_index: parent.iteration_index, }); } Op::Scale => { let factor = stack.pop().ok_or("stack underflow")?.as_float()?; let parent = time_stack.last().ok_or("time stack underflow")?; time_stack.push(TimeContext { start: parent.start, duration: parent.duration * factor, subdivisions: None, iteration_index: parent.iteration_index, }); } Op::Pop => { if time_stack.len() <= 1 { return Err("cannot pop root time context".into()); } time_stack.pop(); } Op::Subdivide => { let n = stack.pop().ok_or("stack underflow")?.as_int()? as usize; if n == 0 { return Err("subdivide count must be > 0".into()); } let time_ctx = time_stack.last_mut().ok_or("time stack underflow")?; let sub_duration = time_ctx.duration / n as f64; let mut subs = Vec::with_capacity(n); for i in 0..n { subs.push((time_ctx.start + sub_duration * i as f64, sub_duration)); } time_ctx.subdivisions = Some(subs); } Op::Each => { let (sound, params) = cmd.take().ok_or("no sound set")?; let time_ctx = time_stack.last().ok_or("time stack underflow")?; let subs = time_ctx .subdivisions .as_ref() .ok_or("each requires subdivide first")?; for (sub_start, sub_dur) in subs { let mut pairs = vec![("sound".into(), sound.clone())]; pairs.extend(params.iter().cloned()); if *sub_start > 0.0 { pairs.push(("delta".into(), sub_start.to_string())); } if !pairs.iter().any(|(k, _)| k == "dur") { pairs.push(("dur".into(), sub_dur.to_string())); } if let Some(idx) = pairs.iter().position(|(k, _)| k == "delaytime") { let ratio: f64 = pairs[idx].1.parse().unwrap_or(1.0); pairs[idx].1 = (ratio * sub_dur).to_string(); } else { pairs.push(("delaytime".into(), sub_dur.to_string())); } outputs.push(format_cmd(&pairs)); } } Op::SetTempo => { let tempo = stack.pop().ok_or("stack underflow")?.as_float()?; let clamped = tempo.clamp(20.0, 300.0); self.vars .lock() .unwrap() .insert("__tempo__".to_string(), Value::Float(clamped, None)); } Op::Chain => { let pattern = stack.pop().ok_or("stack underflow")?.as_int()? - 1; let bank = stack.pop().ok_or("stack underflow")?.as_int()? - 1; if bank < 0 || pattern < 0 { return Err("chain: bank and pattern must be >= 1".into()); } if bank as usize == ctx.bank && pattern as usize == ctx.pattern { // chaining to self is a no-op } else { let key = format!("__chain_{}_{}__", ctx.bank, ctx.pattern); let val = format!("{bank}:{pattern}"); self.vars .lock() .unwrap() .insert(key, Value::Str(val, None)); } } Op::Loop => { let beats = stack.pop().ok_or("stack underflow")?.as_float()?; let dur = beats * 60.0 / ctx.tempo / ctx.speed; cmd.set_param("fit".into(), dur.to_string()); cmd.set_param("dur".into(), dur.to_string()); } Op::ListStart => { stack.push(Value::Marker); } Op::ListEnd => { let mut count = 0; let mut values = Vec::new(); while let Some(v) = stack.pop() { if v.is_marker() { break; } values.push(v); count += 1; } values.reverse(); for v in values { stack.push(v); } stack.push(Value::Int(count, None)); } Op::ListEndCycle => { let mut values = Vec::new(); while let Some(v) = stack.pop() { if v.is_marker() { break; } values.push(v); } if values.is_empty() { return Err("empty cycle list".into()); } values.reverse(); let idx = ctx.runs % values.len(); let selected = values[idx].clone(); if let Some(span) = selected.span() { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.selected_spans.push(span); } } if let Value::Quotation(quot_ops, body_span) = selected { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?; *trace_cell.borrow_mut() = trace_opt; } else { stack.push(selected); } } Op::ListEndPCycle => { let mut values = Vec::new(); while let Some(v) = stack.pop() { if v.is_marker() { break; } values.push(v); } if values.is_empty() { return Err("empty pattern cycle list".into()); } values.reverse(); let idx = ctx.iter % values.len(); let selected = values[idx].clone(); if let Some(span) = selected.span() { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.selected_spans.push(span); } } if let Value::Quotation(quot_ops, body_span) = selected { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?; *trace_cell.borrow_mut() = trace_opt; } else { stack.push(selected); } } Op::Adsr => { let r = stack.pop().ok_or("stack underflow")?; let s = stack.pop().ok_or("stack underflow")?; let d = stack.pop().ok_or("stack underflow")?; let a = stack.pop().ok_or("stack underflow")?; cmd.set_param("attack".into(), a.to_param_string()); cmd.set_param("decay".into(), d.to_param_string()); cmd.set_param("sustain".into(), s.to_param_string()); cmd.set_param("release".into(), r.to_param_string()); } Op::Ad => { let d = stack.pop().ok_or("stack underflow")?; let a = stack.pop().ok_or("stack underflow")?; cmd.set_param("attack".into(), a.to_param_string()); cmd.set_param("decay".into(), d.to_param_string()); cmd.set_param("sustain".into(), "0".into()); } Op::Stack => { let n = stack.pop().ok_or("stack underflow")?.as_int()? as usize; if n == 0 { return Err("stack count must be > 0".into()); } let time_ctx = time_stack.last_mut().ok_or("time stack underflow")?; let sub_duration = time_ctx.duration / n as f64; let mut subs = Vec::with_capacity(n); for _ in 0..n { subs.push((time_ctx.start, sub_duration)); } time_ctx.subdivisions = Some(subs); } Op::Echo => { let n = stack.pop().ok_or("stack underflow")?.as_int()? as usize; if n == 0 { return Err("echo count must be > 0".into()); } let time_ctx = time_stack.last_mut().ok_or("time stack underflow")?; // Geometric series: d1 * (2 - 2^(1-n)) = total let d1 = time_ctx.duration / (2.0 - 2.0_f64.powi(1 - n as i32)); let mut subs = Vec::with_capacity(n); for i in 0..n { let dur = d1 / 2.0_f64.powi(i as i32); let start = if i == 0 { time_ctx.start } else { time_ctx.start + d1 * (2.0 - 2.0_f64.powi(1 - i as i32)) }; subs.push((start, dur)); } time_ctx.subdivisions = Some(subs); } Op::Necho => { let n = stack.pop().ok_or("stack underflow")?.as_int()? as usize; if n == 0 { return Err("necho count must be > 0".into()); } let time_ctx = time_stack.last_mut().ok_or("time stack underflow")?; // Reverse geometric: d1 + 2*d1 + 4*d1 + ... = d1 * (2^n - 1) = total let d1 = time_ctx.duration / (2.0_f64.powi(n as i32) - 1.0); let mut subs = Vec::with_capacity(n); for i in 0..n { let dur = d1 * 2.0_f64.powi(i as i32); let start = if i == 0 { time_ctx.start } else { // Sum of previous durations: d1 * (2^i - 1) time_ctx.start + d1 * (2.0_f64.powi(i as i32) - 1.0) }; subs.push((start, dur)); } time_ctx.subdivisions = Some(subs); } Op::For => { let quot = stack.pop().ok_or("stack underflow")?; let time_ctx = time_stack.last().ok_or("time stack underflow")?; let subs = time_ctx .subdivisions .clone() .ok_or("for requires subdivide first")?; match quot { Value::Quotation(quot_ops, body_span) => { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } for (i, (sub_start, sub_dur)) in subs.iter().enumerate() { time_stack.push(TimeContext { start: *sub_start, duration: *sub_dur, subdivisions: None, iteration_index: Some(i), }); let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops( "_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut(), )?; *trace_cell.borrow_mut() = trace_opt; time_stack.pop(); } } _ => return Err("expected quotation".into()), } } Op::LocalCycleEnd => { let mut values = Vec::new(); while let Some(v) = stack.pop() { if v.is_marker() { break; } values.push(v); } if values.is_empty() { return Err("empty local cycle list".into()); } values.reverse(); let time_ctx = time_stack.last().ok_or("time stack underflow")?; let idx = time_ctx.iteration_index.unwrap_or(0) % values.len(); let selected = values[idx].clone(); if let Some(span) = selected.span() { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.selected_spans.push(span); } } if let Value::Quotation(quot_ops, body_span) = selected { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?; *trace_cell.borrow_mut() = trace_opt; } else { stack.push(selected); } } Op::Apply => { let quot = stack.pop().ok_or("stack underflow")?; match quot { Value::Quotation(quot_ops, body_span) => { if let Some(span) = body_span { if let Some(trace) = trace_cell.borrow_mut().as_mut() { trace.executed_spans.push(span); } } let mut trace_opt = trace_cell.borrow_mut().take(); self.execute_ops("_ops, ctx, stack, outputs, time_stack, cmd, trace_opt.as_deref_mut())?; *trace_cell.borrow_mut() = trace_opt; } _ => return Err("expected quotation".into()), } } Op::Ramp => { let curve = stack.pop().ok_or("stack underflow")?.as_float()?; let freq = stack.pop().ok_or("stack underflow")?.as_float()?; let phase = (freq * ctx.beat).fract(); let phase = if phase < 0.0 { phase + 1.0 } else { phase }; let val = phase.powf(curve); stack.push(Value::Float(val, None)); } Op::Range => { let max = stack.pop().ok_or("stack underflow")?.as_float()?; let min = stack.pop().ok_or("stack underflow")?.as_float()?; let val = stack.pop().ok_or("stack underflow")?.as_float()?; stack.push(Value::Float(min + val * (max - min), None)); } Op::Noise => { let freq = stack.pop().ok_or("stack underflow")?.as_float()?; let val = perlin_noise_1d(freq * ctx.beat); stack.push(Value::Float(val, None)); } } pc += 1; } Ok(()) } } fn perlin_grad(hash_input: i64) -> f64 { let mut h = (hash_input as u64).wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); h ^= h >> 33; h = h.wrapping_mul(0xff51afd7ed558ccd); h ^= h >> 33; if h & 1 == 0 { 1.0 } else { -1.0 } } fn perlin_noise_1d(x: f64) -> f64 { let x0 = x.floor() as i64; let t = x - x0 as f64; let s = t * t * (3.0 - 2.0 * t); let d0 = perlin_grad(x0) * t; let d1 = perlin_grad(x0 + 1) * (t - 1.0); (d0 + s * (d1 - d0)) * 0.5 + 0.5 } fn binary_op(stack: &mut Vec, f: F) -> Result<(), String> where F: Fn(f64, f64) -> f64, { let b = stack.pop().ok_or("stack underflow")?.as_float()?; let a = stack.pop().ok_or("stack underflow")?.as_float()?; let result = f(a, b); if result.fract() == 0.0 && result.abs() < i64::MAX as f64 { stack.push(Value::Int(result as i64, None)); } else { stack.push(Value::Float(result, None)); } Ok(()) } fn cmp_op(stack: &mut Vec, f: F) -> Result<(), String> where F: Fn(f64, f64) -> bool, { let b = stack.pop().ok_or("stack underflow")?.as_float()?; let a = stack.pop().ok_or("stack underflow")?.as_float()?; stack.push(Value::Int(if f(a, b) { 1 } else { 0 }, None)); Ok(()) } fn format_cmd(pairs: &[(String, String)]) -> String { let parts: Vec = pairs.iter().map(|(k, v)| format!("{k}/{v}")).collect(); format!("/{}", parts.join("/")) }