WIP simplify

This commit is contained in:
2026-01-29 09:38:41 +01:00
parent f1f1b28b31
commit 4d0d837e14
11 changed files with 434 additions and 291 deletions

View File

@@ -145,35 +145,26 @@ impl Forth {
select_and_run(selected, stack, outputs, cmd)
};
let drain_list_select_run = |idx_source: usize,
err_msg: &str,
stack: &mut Vec<Value>,
outputs: &mut Vec<String>,
cmd: &mut CmdRegister|
-> Result<(), String> {
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(err_msg.into());
}
values.reverse();
let idx = idx_source % values.len();
let selected = values[idx].clone();
select_and_run(selected, stack, outputs, cmd)
};
let emit_once = |cmd: &CmdRegister, outputs: &mut Vec<String>| -> Result<Option<Value>, String> {
let emit_with_cycling = |cmd: &CmdRegister, emit_idx: usize, delta_secs: f64, outputs: &mut Vec<String>| -> Result<Option<Value>, String> {
let (sound_val, params) = cmd.snapshot().ok_or("no sound set")?;
let sound = sound_val.as_str()?.to_string();
let resolved_sound_val = resolve_cycling(&sound_val, emit_idx);
// Note: sound span is recorded by Op::Emit, not here
let sound = resolved_sound_val.as_str()?.to_string();
let resolved_params: Vec<(String, String)> =
params.iter().map(|(k, v)| (k.clone(), v.to_param_string())).collect();
emit_output(&sound, &resolved_params, ctx.step_duration(), ctx.nudge_secs, outputs);
Ok(Some(sound_val))
params.iter().map(|(k, v)| {
let resolved = resolve_cycling(v, emit_idx);
// Record selected span for params if they came from a CycleList
if let Value::CycleList(_) = v {
if let Some(span) = resolved.span() {
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
trace.selected_spans.push(span);
}
}
}
(k.clone(), resolved.to_param_string())
}).collect();
emit_output(&sound, &resolved_params, ctx.step_duration(), delta_secs, outputs);
Ok(Some(resolved_sound_val))
};
while pc < ops.len() {
@@ -361,12 +352,28 @@ impl Forth {
}
Op::Emit => {
if let Some(sound_val) = emit_once(cmd, outputs)? {
if let Some(span) = sound_val.span() {
let deltas = if cmd.deltas().is_empty() {
vec![Value::Float(0.0, None)]
} else {
cmd.deltas().to_vec()
};
for (emit_idx, delta_val) in deltas.iter().enumerate() {
let delta_frac = delta_val.as_float()?;
let delta_secs = ctx.nudge_secs + delta_frac * ctx.step_duration();
// Record delta span for highlighting
if let Some(span) = delta_val.span() {
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
trace.selected_spans.push(span);
}
}
if let Some(sound_val) = emit_with_cycling(cmd, emit_idx, delta_secs, outputs)? {
if let Some(span) = sound_val.span() {
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
trace.selected_spans.push(span);
}
}
}
}
}
@@ -438,6 +445,19 @@ impl Forth {
drain_select_run(count, idx, stack, outputs, cmd)?;
}
Op::TCycle => {
let count = stack.pop().ok_or("stack underflow")?.as_int()? as usize;
if count == 0 {
return Err("tcycle 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();
stack.push(Value::CycleList(values));
}
Op::Choose => {
let count = stack.pop().ok_or("stack underflow")?.as_int()? as usize;
if count == 0 {
@@ -606,37 +626,23 @@ impl Forth {
cmd.set_param("dur".into(), Value::Float(dur, 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;
Op::At => {
let top = stack.pop().ok_or("stack underflow")?;
let deltas = match &top {
Value::Float(..) => vec![top],
Value::Int(n, _) if *n > 0 && stack.len() >= *n as usize => {
let count = *n as usize;
let mut vals = Vec::with_capacity(count);
for _ in 0..count {
vals.push(stack.pop().ok_or("stack underflow")?);
}
vals.reverse();
vals
}
values.push(v);
count += 1;
}
values.reverse();
for v in values {
stack.push(v);
}
stack.push(Value::Int(count, None));
}
Op::ListEndCycle | Op::ListEndPCycle => {
let idx_source = match &ops[pc] {
Op::ListEndCycle => ctx.runs,
_ => ctx.iter,
Value::Int(..) => vec![top],
_ => return Err("at expects number or list".into()),
};
let err_msg = match &ops[pc] {
Op::ListEndCycle => "empty cycle list",
_ => "empty pattern cycle list",
};
drain_list_select_run(idx_source, err_msg, stack, outputs, cmd)?;
cmd.set_deltas(deltas);
}
Op::Adsr => {
@@ -699,8 +705,8 @@ impl Forth {
if n < 0 {
return Err("emit count must be >= 0".into());
}
for _ in 0..n {
emit_once(cmd, outputs)?;
for i in 0..n as usize {
emit_with_cycling(cmd, i, ctx.nudge_secs, outputs)?;
}
}
}
@@ -846,3 +852,12 @@ fn format_cmd(pairs: &[(String, String)]) -> String {
let parts: Vec<String> = pairs.iter().map(|(k, v)| format!("{k}/{v}")).collect();
format!("/{}", parts.join("/"))
}
fn resolve_cycling(val: &Value, emit_idx: usize) -> Value {
match val {
Value::CycleList(items) if !items.is_empty() => {
items[emit_idx % items.len()].clone()
}
other => other.clone(),
}
}