Reorganize repository
This commit is contained in:
956
crates/forth/src/vm.rs
Normal file
956
crates/forth/src/vm.rs
Normal file
@@ -0,0 +1,956 @@
|
||||
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<Vec<(f64, f64)>>,
|
||||
iteration_index: Option<usize>,
|
||||
}
|
||||
|
||||
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<Value> {
|
||||
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<Vec<String>, String> {
|
||||
self.evaluate_impl(script, ctx, None)
|
||||
}
|
||||
|
||||
pub fn evaluate_with_trace(
|
||||
&self,
|
||||
script: &str,
|
||||
ctx: &StepContext,
|
||||
trace: &mut ExecutionTrace,
|
||||
) -> Result<Vec<String>, String> {
|
||||
self.evaluate_impl(script, ctx, Some(trace))
|
||||
}
|
||||
|
||||
fn evaluate_impl(
|
||||
&self,
|
||||
script: &str,
|
||||
ctx: &StepContext,
|
||||
trace: Option<&mut ExecutionTrace>,
|
||||
) -> Result<Vec<String>, 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<Vec<String>, String> {
|
||||
let mut stack = self.stack.lock().unwrap();
|
||||
let mut outputs: Vec<String> = Vec::new();
|
||||
let mut time_stack: Vec<TimeContext> = 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<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
time_stack: &mut Vec<TimeContext>,
|
||||
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),
|
||||
"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")?.as_float()?;
|
||||
let min = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
let val = self.rng.lock().unwrap().gen_range(min..max);
|
||||
stack.push(Value::Float(val, None));
|
||||
}
|
||||
Op::Rrand => {
|
||||
let max = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
let min = stack.pop().ok_or("stack underflow")?.as_int()?;
|
||||
let val = self.rng.lock().unwrap().gen_range(min..=max);
|
||||
stack.push(Value::Int(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<Value> = 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<Value> = 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<Value> = 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::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::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<F>(stack: &mut Vec<Value>, 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<F>(stack: &mut Vec<Value>, 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<String> = pairs.iter().map(|(k, v)| format!("{k}/{v}")).collect();
|
||||
format!("/{}", parts.join("/"))
|
||||
}
|
||||
Reference in New Issue
Block a user