Cleaning old temporal model
This commit is contained in:
@@ -4,8 +4,7 @@ use rand::{Rng as RngTrait, SeedableRng};
|
||||
use super::compiler::compile_script;
|
||||
use super::ops::Op;
|
||||
use super::types::{
|
||||
CmdRegister, Dictionary, ExecutionTrace, PendingEmission, ResolvedEmission, Rng, ScopeContext,
|
||||
Stack, StepContext, Value, Variables,
|
||||
CmdRegister, Dictionary, ExecutionTrace, Rng, Stack, StepContext, Value, Variables,
|
||||
};
|
||||
|
||||
pub struct Forth {
|
||||
@@ -70,24 +69,9 @@ impl Forth {
|
||||
) -> Result<Vec<String>, String> {
|
||||
let mut stack = self.stack.lock().unwrap();
|
||||
let mut outputs: Vec<String> = Vec::new();
|
||||
let root_duration = ctx.step_duration() * 4.0;
|
||||
let mut scope_stack: Vec<ScopeContext> = vec![ScopeContext::new(0.0, root_duration)];
|
||||
let mut cmd = CmdRegister::default();
|
||||
|
||||
self.execute_ops(
|
||||
ops,
|
||||
ctx,
|
||||
&mut stack,
|
||||
&mut outputs,
|
||||
&mut scope_stack,
|
||||
&mut cmd,
|
||||
trace,
|
||||
)?;
|
||||
|
||||
// Resolve root scope at end of script
|
||||
if let Some(scope) = scope_stack.pop() {
|
||||
resolve_scope(&scope, ctx.step_duration(), ctx.nudge_secs, &mut outputs);
|
||||
}
|
||||
self.execute_ops(ops, ctx, &mut stack, &mut outputs, &mut cmd, trace)?;
|
||||
|
||||
Ok(outputs)
|
||||
}
|
||||
@@ -100,70 +84,56 @@ impl Forth {
|
||||
ctx: &StepContext,
|
||||
stack: &mut Vec<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
scope_stack: &mut Vec<ScopeContext>,
|
||||
cmd: &mut CmdRegister,
|
||||
trace: Option<&mut ExecutionTrace>,
|
||||
) -> Result<(), String> {
|
||||
let mut pc = 0;
|
||||
let trace_cell = std::cell::RefCell::new(trace);
|
||||
|
||||
// Executes a quotation value, handling trace recording and recursive dispatch.
|
||||
let run_quotation = |quot: Value,
|
||||
stack: &mut Vec<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
scope_stack: &mut Vec<ScopeContext>,
|
||||
cmd: &mut CmdRegister|
|
||||
-> Result<(), String> {
|
||||
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 run_quotation =
|
||||
|quot: Value, stack: &mut Vec<Value>, outputs: &mut Vec<String>, cmd: &mut CmdRegister| -> Result<(), String> {
|
||||
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,
|
||||
cmd,
|
||||
trace_opt.as_deref_mut(),
|
||||
)?;
|
||||
*trace_cell.borrow_mut() = trace_opt;
|
||||
Ok(())
|
||||
}
|
||||
let mut trace_opt = trace_cell.borrow_mut().take();
|
||||
self.execute_ops(
|
||||
"_ops,
|
||||
ctx,
|
||||
stack,
|
||||
outputs,
|
||||
scope_stack,
|
||||
cmd,
|
||||
trace_opt.as_deref_mut(),
|
||||
)?;
|
||||
*trace_cell.borrow_mut() = trace_opt;
|
||||
_ => Err("expected quotation".into()),
|
||||
}
|
||||
};
|
||||
|
||||
let select_and_run =
|
||||
|selected: Value, stack: &mut Vec<Value>, outputs: &mut Vec<String>, cmd: &mut CmdRegister| -> Result<(), String> {
|
||||
if let Some(span) = selected.span() {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.selected_spans.push(span);
|
||||
}
|
||||
}
|
||||
if matches!(selected, Value::Quotation(..)) {
|
||||
run_quotation(selected, stack, outputs, cmd)
|
||||
} else {
|
||||
stack.push(selected);
|
||||
Ok(())
|
||||
}
|
||||
_ => Err("expected quotation".into()),
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Selects a value from a list, records trace, and either executes (quotation) or pushes (other).
|
||||
let select_and_run = |selected: Value,
|
||||
stack: &mut Vec<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
scope_stack: &mut Vec<ScopeContext>,
|
||||
cmd: &mut CmdRegister|
|
||||
-> Result<(), String> {
|
||||
if let Some(span) = selected.span() {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.selected_spans.push(span);
|
||||
}
|
||||
}
|
||||
if matches!(selected, Value::Quotation(..)) {
|
||||
run_quotation(selected, stack, outputs, scope_stack, cmd)
|
||||
} else {
|
||||
stack.push(selected);
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
// Drains `count` values from the stack, selects one by index, and runs it.
|
||||
let drain_select_run = |count: usize,
|
||||
idx: usize,
|
||||
stack: &mut Vec<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
scope_stack: &mut Vec<ScopeContext>,
|
||||
cmd: &mut CmdRegister|
|
||||
-> Result<(), String> {
|
||||
if stack.len() < count {
|
||||
@@ -172,15 +142,13 @@ impl Forth {
|
||||
let start = stack.len() - count;
|
||||
let values: Vec<Value> = stack.drain(start..).collect();
|
||||
let selected = values[idx].clone();
|
||||
select_and_run(selected, stack, outputs, scope_stack, cmd)
|
||||
select_and_run(selected, stack, outputs, cmd)
|
||||
};
|
||||
|
||||
// Pops all values until a marker, selects one by index, and runs it.
|
||||
let drain_list_select_run = |idx_source: usize,
|
||||
err_msg: &str,
|
||||
stack: &mut Vec<Value>,
|
||||
outputs: &mut Vec<String>,
|
||||
scope_stack: &mut Vec<ScopeContext>,
|
||||
cmd: &mut CmdRegister|
|
||||
-> Result<(), String> {
|
||||
let mut values = Vec::new();
|
||||
@@ -196,26 +164,15 @@ impl Forth {
|
||||
values.reverse();
|
||||
let idx = idx_source % values.len();
|
||||
let selected = values[idx].clone();
|
||||
select_and_run(selected, stack, outputs, scope_stack, cmd)
|
||||
select_and_run(selected, stack, outputs, cmd)
|
||||
};
|
||||
|
||||
// Emits one sound event from the current command register into the current scope.
|
||||
let emit_once = |cmd: &CmdRegister,
|
||||
scope_stack: &mut Vec<ScopeContext>|
|
||||
-> Result<Option<Value>, String> {
|
||||
let emit_once = |cmd: &CmdRegister, 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_params: Vec<(String, String)> = params
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.to_param_string()))
|
||||
.collect();
|
||||
let scope = scope_stack.last_mut().ok_or("scope stack underflow")?;
|
||||
let slot_idx = scope.claim_slot();
|
||||
scope.pending.push(PendingEmission {
|
||||
sound,
|
||||
params: resolved_params,
|
||||
slot_index: slot_idx,
|
||||
});
|
||||
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))
|
||||
};
|
||||
|
||||
@@ -404,7 +361,7 @@ impl Forth {
|
||||
}
|
||||
|
||||
Op::Emit => {
|
||||
if let Some(sound_val) = emit_once(cmd, scope_stack)? {
|
||||
if let Some(sound_val) = emit_once(cmd, outputs)? {
|
||||
if let Some(span) = sound_val.span() {
|
||||
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
|
||||
trace.selected_spans.push(span);
|
||||
@@ -413,17 +370,6 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
|
||||
Op::Silence => {
|
||||
let scope = scope_stack.last_mut().ok_or("scope stack underflow")?;
|
||||
scope.claim_slot();
|
||||
}
|
||||
|
||||
Op::Scale => {
|
||||
let factor = stack.pop().ok_or("stack underflow")?.as_float()?;
|
||||
let scope = scope_stack.last_mut().ok_or("scope stack underflow")?;
|
||||
scope.weight = factor;
|
||||
}
|
||||
|
||||
Op::Get => {
|
||||
let name = stack.pop().ok_or("stack underflow")?;
|
||||
let name = name.as_str()?;
|
||||
@@ -489,7 +435,7 @@ impl Forth {
|
||||
Op::Cycle => ctx.runs,
|
||||
_ => ctx.iter,
|
||||
} % count;
|
||||
drain_select_run(count, idx, stack, outputs, scope_stack, cmd)?;
|
||||
drain_select_run(count, idx, stack, outputs, cmd)?;
|
||||
}
|
||||
|
||||
Op::Choose => {
|
||||
@@ -498,7 +444,7 @@ impl Forth {
|
||||
return Err("choose count must be > 0".into());
|
||||
}
|
||||
let idx = self.rng.lock().unwrap().gen_range(0..count);
|
||||
drain_select_run(count, idx, stack, outputs, scope_stack, cmd)?;
|
||||
drain_select_run(count, idx, stack, outputs, cmd)?;
|
||||
}
|
||||
|
||||
Op::ChanceExec | Op::ProbExec => {
|
||||
@@ -510,7 +456,7 @@ impl Forth {
|
||||
_ => threshold / 100.0,
|
||||
};
|
||||
if val < limit {
|
||||
run_quotation(quot, stack, outputs, scope_stack, cmd)?;
|
||||
run_quotation(quot, stack, outputs, cmd)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,7 +486,7 @@ impl Forth {
|
||||
_ => !cond.is_truthy(),
|
||||
};
|
||||
if should_run {
|
||||
run_quotation(quot, stack, outputs, scope_stack, cmd)?;
|
||||
run_quotation(quot, stack, outputs, cmd)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,7 +499,7 @@ impl Forth {
|
||||
} else {
|
||||
false_quot
|
||||
};
|
||||
run_quotation(quot, stack, outputs, scope_stack, cmd)?;
|
||||
run_quotation(quot, stack, outputs, cmd)?;
|
||||
}
|
||||
|
||||
Op::Pick => {
|
||||
@@ -580,7 +526,7 @@ impl Forth {
|
||||
quots.len()
|
||||
));
|
||||
}
|
||||
run_quotation(quots.swap_remove(idx), stack, outputs, scope_stack, cmd)?;
|
||||
run_quotation(quots.swap_remove(idx), stack, outputs, cmd)?;
|
||||
}
|
||||
|
||||
Op::Mtof => {
|
||||
@@ -690,7 +636,7 @@ impl Forth {
|
||||
Op::ListEndCycle => "empty cycle list",
|
||||
_ => "empty pattern cycle list",
|
||||
};
|
||||
drain_list_select_run(idx_source, err_msg, stack, outputs, scope_stack, cmd)?;
|
||||
drain_list_select_run(idx_source, err_msg, stack, outputs, cmd)?;
|
||||
}
|
||||
|
||||
Op::Adsr => {
|
||||
@@ -714,7 +660,7 @@ impl Forth {
|
||||
|
||||
Op::Apply => {
|
||||
let quot = stack.pop().ok_or("stack underflow")?;
|
||||
run_quotation(quot, stack, outputs, scope_stack, cmd)?;
|
||||
run_quotation(quot, stack, outputs, cmd)?;
|
||||
}
|
||||
|
||||
Op::Ramp => {
|
||||
@@ -744,36 +690,6 @@ impl Forth {
|
||||
stack.push(Value::Float(val, None));
|
||||
}
|
||||
|
||||
Op::DivStart => {
|
||||
let parent = scope_stack.last().ok_or("scope stack underflow")?;
|
||||
let mut new_scope = ScopeContext::new(parent.start, parent.duration);
|
||||
new_scope.weight = parent.weight;
|
||||
scope_stack.push(new_scope);
|
||||
}
|
||||
|
||||
Op::DivEnd => {
|
||||
if scope_stack.len() <= 1 {
|
||||
return Err("unmatched ~ (no div/stack to close)".into());
|
||||
}
|
||||
let child = scope_stack.pop().unwrap();
|
||||
|
||||
if child.stacked {
|
||||
resolve_scope(&child, ctx.step_duration(), ctx.nudge_secs, outputs);
|
||||
} else {
|
||||
let parent = scope_stack.last_mut().ok_or("scope stack underflow")?;
|
||||
let parent_slot = parent.claim_slot();
|
||||
resolve_scope_to_parent(&child, parent_slot, parent);
|
||||
}
|
||||
}
|
||||
|
||||
Op::StackStart => {
|
||||
let parent = scope_stack.last().ok_or("scope stack underflow")?;
|
||||
let mut new_scope = ScopeContext::new(parent.start, parent.duration);
|
||||
new_scope.weight = parent.weight;
|
||||
new_scope.stacked = true;
|
||||
scope_stack.push(new_scope);
|
||||
}
|
||||
|
||||
Op::ClearCmd => {
|
||||
cmd.clear();
|
||||
}
|
||||
@@ -784,7 +700,7 @@ impl Forth {
|
||||
return Err("emit count must be >= 0".into());
|
||||
}
|
||||
for _ in 0..n {
|
||||
emit_once(cmd, scope_stack)?;
|
||||
emit_once(cmd, outputs)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -795,106 +711,6 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_scope(
|
||||
scope: &ScopeContext,
|
||||
step_duration: f64,
|
||||
nudge_secs: f64,
|
||||
outputs: &mut Vec<String>,
|
||||
) {
|
||||
let slot_dur = if scope.slot_count == 0 {
|
||||
scope.duration * scope.weight
|
||||
} else {
|
||||
scope.duration * scope.weight / scope.slot_count as f64
|
||||
};
|
||||
|
||||
struct Emission {
|
||||
delta: f64,
|
||||
sound: String,
|
||||
params: Vec<(String, String)>,
|
||||
dur: f64,
|
||||
}
|
||||
|
||||
let mut emissions: Vec<Emission> = Vec::new();
|
||||
|
||||
for em in &scope.pending {
|
||||
let delta = scope.start + slot_dur * em.slot_index as f64;
|
||||
emissions.push(Emission {
|
||||
delta,
|
||||
sound: em.sound.clone(),
|
||||
params: em.params.clone(),
|
||||
dur: slot_dur,
|
||||
});
|
||||
}
|
||||
|
||||
for em in &scope.resolved {
|
||||
let slot_start = slot_dur * em.parent_slot as f64;
|
||||
let delta = scope.start + slot_start + em.offset_in_slot * slot_dur;
|
||||
let dur = em.dur * slot_dur;
|
||||
emissions.push(Emission {
|
||||
delta,
|
||||
sound: em.sound.clone(),
|
||||
params: em.params.clone(),
|
||||
dur,
|
||||
});
|
||||
}
|
||||
|
||||
emissions.sort_by(|a, b| {
|
||||
a.delta
|
||||
.partial_cmp(&b.delta)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
|
||||
for em in emissions {
|
||||
emit_output(
|
||||
&em.sound,
|
||||
&em.params,
|
||||
em.delta,
|
||||
em.dur,
|
||||
step_duration,
|
||||
nudge_secs,
|
||||
outputs,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_scope_to_parent(child: &ScopeContext, parent_slot: usize, parent: &mut ScopeContext) {
|
||||
if child.slot_count == 0 && child.pending.is_empty() && child.resolved.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let child_slot_count = child.slot_count.max(1);
|
||||
|
||||
// Store offsets and durations as fractions of the parent slot
|
||||
// Child's internal structure: slot_count slots, each slot is 1/slot_count of the whole
|
||||
for em in &child.pending {
|
||||
let offset_fraction = em.slot_index as f64 / child_slot_count as f64;
|
||||
let dur_fraction = 1.0 / child_slot_count as f64;
|
||||
parent.resolved.push(ResolvedEmission {
|
||||
sound: em.sound.clone(),
|
||||
params: em.params.clone(),
|
||||
parent_slot,
|
||||
offset_in_slot: offset_fraction,
|
||||
dur: dur_fraction,
|
||||
});
|
||||
}
|
||||
|
||||
// Child's resolved emissions already have fractional offsets/durs relative to their slots
|
||||
// We need to compose them: em belongs to child slot em.parent_slot, which is a fraction of child
|
||||
for em in &child.resolved {
|
||||
let child_slot_offset = em.parent_slot as f64 / child_slot_count as f64;
|
||||
let child_slot_size = 1.0 / child_slot_count as f64;
|
||||
let offset_fraction = child_slot_offset + em.offset_in_slot * child_slot_size;
|
||||
let dur_fraction = em.dur * child_slot_size;
|
||||
parent.resolved.push(ResolvedEmission {
|
||||
sound: em.sound.clone(),
|
||||
params: em.params.clone(),
|
||||
parent_slot,
|
||||
offset_in_slot: offset_fraction,
|
||||
dur: dur_fraction,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const TEMPO_SCALED_PARAMS: &[&str] = &[
|
||||
"attack",
|
||||
"decay",
|
||||
@@ -924,26 +740,23 @@ const TEMPO_SCALED_PARAMS: &[&str] = &[
|
||||
fn emit_output(
|
||||
sound: &str,
|
||||
params: &[(String, String)],
|
||||
delta: f64,
|
||||
dur: f64,
|
||||
step_duration: f64,
|
||||
nudge_secs: f64,
|
||||
outputs: &mut Vec<String>,
|
||||
) {
|
||||
let nudged_delta = delta + nudge_secs;
|
||||
let mut pairs = vec![("sound".into(), sound.to_string())];
|
||||
pairs.extend(params.iter().cloned());
|
||||
if nudged_delta > 0.0 {
|
||||
pairs.push(("delta".into(), nudged_delta.to_string()));
|
||||
if nudge_secs > 0.0 {
|
||||
pairs.push(("delta".into(), nudge_secs.to_string()));
|
||||
}
|
||||
if !pairs.iter().any(|(k, _)| k == "dur") {
|
||||
pairs.push(("dur".into(), dur.to_string()));
|
||||
pairs.push(("dur".into(), step_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 * dur).to_string();
|
||||
pairs[idx].1 = (ratio * step_duration).to_string();
|
||||
} else {
|
||||
pairs.push(("delaytime".into(), dur.to_string()));
|
||||
pairs.push(("delaytime".into(), step_duration.to_string()));
|
||||
}
|
||||
for pair in &mut pairs {
|
||||
if TEMPO_SCALED_PARAMS.contains(&pair.0.as_str()) {
|
||||
|
||||
Reference in New Issue
Block a user