Feat: polyphony + iterator reset
Some checks failed
Deploy Website / deploy (push) Failing after 4m48s

This commit is contained in:
2026-02-02 00:33:46 +01:00
parent adee8d0d57
commit 6b95f31afd
9 changed files with 132 additions and 145 deletions

View File

@@ -58,7 +58,6 @@ pub enum Op {
Seed,
Cycle,
PCycle,
TCycle,
Choose,
ChanceExec,
ProbExec,

View File

@@ -154,6 +154,14 @@ impl CmdRegister {
&self.deltas
}
pub(super) fn sound(&self) -> Option<&Value> {
self.sound.as_ref()
}
pub(super) fn params(&self) -> &[(String, Value)] {
&self.params
}
pub(super) fn snapshot(&self) -> Option<CmdSnapshot<'_>> {
if self.sound.is_some() || !self.params.is_empty() {
Some((self.sound.as_ref(), self.params.as_slice()))

View File

@@ -152,6 +152,23 @@ impl Forth {
select_and_run(selected, stack, outputs, cmd)
};
let compute_poly_count = |cmd: &CmdRegister| -> usize {
let sound_len = match cmd.sound() {
Some(Value::CycleList(items)) => items.len(),
_ => 1,
};
let param_max = cmd
.params()
.iter()
.map(|(_, v)| match v {
Value::CycleList(items) => items.len(),
_ => 1,
})
.max()
.unwrap_or(1);
sound_len.max(param_max)
};
let emit_with_cycling = |cmd: &CmdRegister,
emit_idx: usize,
delta_secs: f64,
@@ -363,38 +380,56 @@ impl Forth {
}
Op::NewCmd => {
let val = stack.pop().ok_or("stack underflow")?;
if stack.is_empty() {
return Err("stack underflow".into());
}
let values = std::mem::take(stack);
let val = if values.len() == 1 {
values.into_iter().next().unwrap()
} else {
Value::CycleList(values)
};
cmd.set_sound(val);
}
Op::SetParam(param) => {
let val = stack.pop().ok_or("stack underflow")?;
if stack.is_empty() {
return Err("stack underflow".into());
}
let values = std::mem::take(stack);
let val = if values.len() == 1 {
values.into_iter().next().unwrap()
} else {
Value::CycleList(values)
};
cmd.set_param(param.clone(), val);
}
Op::Emit => {
let poly_count = compute_poly_count(cmd);
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() {
for poly_idx in 0..poly_count {
for delta_val in deltas.iter() {
let delta_frac = delta_val.as_float()?;
let delta_secs = ctx.nudge_secs + delta_frac * ctx.step_duration();
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, poly_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);
}
}
}
}
}
}
@@ -503,19 +538,6 @@ 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 {
@@ -692,27 +714,10 @@ impl Forth {
}
Op::At => {
let top = stack.pop().ok_or("stack underflow")?;
let deltas = match &top {
Value::Float(..) => vec![top],
Value::Int(n, _) => {
let count = *n as usize;
if stack.len() < count {
return Err(format!(
"at: stack underflow, expected {} values but got {}",
count,
stack.len()
));
}
let mut vals = Vec::with_capacity(count);
for _ in 0..count {
vals.push(stack.pop().ok_or("stack underflow")?);
}
vals.reverse();
vals
}
_ => return Err("at expects float or int count".into()),
};
if stack.is_empty() {
return Err("stack underflow".into());
}
let deltas = std::mem::take(stack);
cmd.set_deltas(deltas);
}

View File

@@ -563,16 +563,6 @@ pub const WORDS: &[Word] = &[
compile: Simple,
varargs: true,
},
Word {
name: "tcycle",
aliases: &[],
category: "Probability",
stack: "(v1..vn n -- CycleList)",
desc: "Create cycle list for emit-time resolution",
example: "60 64 67 3 tcycle note",
compile: Simple,
varargs: true,
},
Word {
name: "every",
aliases: &[],
@@ -1231,9 +1221,9 @@ pub const WORDS: &[Word] = &[
name: "at",
aliases: &[],
category: "Time",
stack: "(v1..vn n --)",
stack: "(v1..vn --)",
desc: "Set delta context for emit timing",
example: "0 0.5 2 at kick s . => emits at 0 and 0.5 of step",
example: "0 0.5 at kick s . => emits at 0 and 0.5 of step",
compile: Simple,
varargs: true,
},
@@ -2819,7 +2809,6 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
"seed" => Op::Seed,
"cycle" => Op::Cycle,
"pcycle" => Op::PCycle,
"tcycle" => Op::TCycle,
"choose" => Op::Choose,
"every" => Op::Every,
"chance" => Op::ChanceExec,