This commit is contained in:
2026-01-27 12:00:34 +01:00
parent 61daa9d79d
commit 40c509e295
17 changed files with 277 additions and 833 deletions

View File

@@ -95,7 +95,6 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
let mut ops = Vec::new();
let mut i = 0;
let mut list_depth: usize = 0;
let mut pipe_parity = false;
while i < tokens.len() {
match &tokens[i] {
@@ -131,13 +130,6 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result<Vec<Op>, String> {
ops.push(Op::Branch(else_ops.len()));
ops.extend(else_ops);
}
} else if word == "|" {
if pipe_parity {
ops.push(Op::InternalCycleEnd);
} else {
ops.push(Op::ListStart);
}
pipe_parity = !pipe_parity;
} else if is_list_start(word) {
ops.push(Op::ListStart);
list_depth += 1;

View File

@@ -6,5 +6,5 @@ mod vm;
mod words;
pub use types::{Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables};
pub use vm::{EmissionCounter, Forth};
pub use vm::Forth;
pub use words::{Word, WordCompile, WORDS};

View File

@@ -84,7 +84,6 @@ pub enum Op {
Loop,
Degree(&'static [i64]),
Oct,
InternalCycleEnd,
DivStart,
DivEnd,
StackStart,

View File

@@ -48,7 +48,6 @@ pub enum Value {
Str(String, Option<SourceSpan>),
Marker,
Quotation(Vec<Op>, Option<SourceSpan>),
Alternator(Vec<Value>),
}
impl PartialEq for Value {
@@ -59,7 +58,6 @@ impl PartialEq for Value {
(Value::Str(a, _), Value::Str(b, _)) => a == b,
(Value::Marker, Value::Marker) => true,
(Value::Quotation(a, _), Value::Quotation(b, _)) => a == b,
(Value::Alternator(a), Value::Alternator(b)) => a == b,
_ => false,
}
}
@@ -96,7 +94,6 @@ impl Value {
Value::Str(s, _) => !s.is_empty(),
Value::Marker => false,
Value::Quotation(..) => true,
Value::Alternator(items) => !items.is_empty(),
}
}
@@ -111,14 +108,13 @@ impl Value {
Value::Str(s, _) => s.clone(),
Value::Marker => String::new(),
Value::Quotation(..) => String::new(),
Value::Alternator(_) => String::new(),
}
}
pub(super) fn span(&self) -> Option<SourceSpan> {
match self {
Value::Int(_, s) | Value::Float(_, s) | Value::Str(_, s) => *s,
Value::Marker | Value::Quotation(..) | Value::Alternator(_) => None,
Value::Int(_, s) | Value::Float(_, s) | Value::Str(_, s) | Value::Quotation(_, s) => *s,
Value::Marker => None,
}
}
}

View File

@@ -5,17 +5,14 @@ use super::compiler::compile_script;
use super::ops::Op;
use super::types::{
CmdRegister, Dictionary, ExecutionTrace, PendingEmission, ResolvedEmission, Rng, ScopeContext,
SourceSpan, Stack, StepContext, Value, Variables,
Stack, StepContext, Value, Variables,
};
pub type EmissionCounter = std::sync::Arc<std::sync::Mutex<usize>>;
pub struct Forth {
stack: Stack,
vars: Variables,
dict: Dictionary,
rng: Rng,
emission_count: EmissionCounter,
}
impl Forth {
@@ -25,22 +22,6 @@ impl Forth {
vars,
dict,
rng,
emission_count: std::sync::Arc::new(std::sync::Mutex::new(0)),
}
}
pub fn new_with_counter(
vars: Variables,
dict: Dictionary,
rng: Rng,
emission_count: EmissionCounter,
) -> Self {
Self {
stack: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
vars,
dict,
rng,
emission_count,
}
}
@@ -92,7 +73,6 @@ impl Forth {
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();
let mut emission_count = self.emission_count.lock().unwrap();
self.execute_ops(
ops,
@@ -102,7 +82,6 @@ impl Forth {
&mut scope_stack,
&mut cmd,
trace,
&mut emission_count,
)?;
// Resolve root scope at end of script
@@ -124,11 +103,122 @@ impl Forth {
scope_stack: &mut Vec<ScopeContext>,
cmd: &mut CmdRegister,
trace: Option<&mut ExecutionTrace>,
emission_count: &mut usize,
) -> 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 mut trace_opt = trace_cell.borrow_mut().take();
self.execute_ops(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
)?;
*trace_cell.borrow_mut() = trace_opt;
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 {
return Err("stack underflow".into());
}
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)
};
// 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();
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, scope_stack, 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 (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,
});
Ok(Some(sound_val))
};
while pc < ops.len() {
match &ops[pc] {
Op::PushInt(n, span) => stack.push(Value::Int(*n, *span)),
@@ -304,41 +394,13 @@ impl Forth {
}
Op::Emit => {
let (sound_val, params) = cmd.snapshot().ok_or("no sound set")?;
// Resolve alternators using emission count, tracking selected spans
let (resolved_sound, sound_span) =
resolve_value_with_span(&sound_val, *emission_count);
if let Some(span) = sound_span {
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
trace.selected_spans.push(span);
if let Some(sound_val) = emit_once(cmd, scope_stack)? {
if let Some(span) = sound_val.span() {
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
trace.selected_spans.push(span);
}
}
}
let sound = resolved_sound.as_str()?.to_string();
let resolved_params: Vec<(String, String)> = params
.iter()
.map(|(k, v)| {
let (resolved, param_span) =
resolve_value_with_span(v, *emission_count);
if let Some(span) = param_span {
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
trace.selected_spans.push(span);
}
}
(k.clone(), resolved.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,
});
*emission_count += 1;
}
Op::Silence => {
@@ -406,84 +468,16 @@ impl Forth {
*self.rng.lock().unwrap() = StdRng::seed_from_u64(s as u64);
}
Op::Cycle => {
Op::Cycle | Op::PCycle => {
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(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*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(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*trace_cell.borrow_mut() = trace_opt;
} else {
stack.push(selected);
}
let idx = match &ops[pc] {
Op::Cycle => ctx.runs,
_ => ctx.iter,
} % count;
drain_select_run(count, idx, stack, outputs, scope_stack, cmd)?;
}
Op::Choose => {
@@ -491,98 +485,20 @@ impl Forth {
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(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*trace_cell.borrow_mut() = trace_opt;
} else {
stack.push(selected);
}
drain_select_run(count, idx, stack, outputs, scope_stack, cmd)?;
}
Op::ChanceExec => {
let prob = stack.pop().ok_or("stack underflow")?.as_float()?;
Op::ChanceExec | Op::ProbExec => {
let threshold = 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(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*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(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*trace_cell.borrow_mut() = trace_opt;
}
_ => return Err("expected quotation".into()),
}
let limit = match &ops[pc] {
Op::ChanceExec => threshold,
_ => threshold / 100.0,
};
if val < limit {
run_quotation(quot, stack, outputs, scope_stack, cmd)?;
}
}
@@ -604,61 +520,15 @@ impl Forth {
stack.push(Value::Quotation(quote_ops.clone(), *body_span));
}
Op::When => {
Op::When | 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(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*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(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*trace_cell.borrow_mut() = trace_opt;
}
_ => return Err("expected quotation".into()),
}
let should_run = match &ops[pc] {
Op::When => cond.is_truthy(),
_ => !cond.is_truthy(),
};
if should_run {
run_quotation(quot, stack, outputs, scope_stack, cmd)?;
}
}
@@ -667,28 +537,7 @@ impl Forth {
let false_quot = stack.pop().ok_or("stack underflow")?;
let true_quot = stack.pop().ok_or("stack underflow")?;
let quot = if cond.is_truthy() { true_quot } else { false_quot };
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(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*trace_cell.borrow_mut() = trace_opt;
}
_ => return Err("expected quotation".into()),
}
run_quotation(quot, stack, outputs, scope_stack, cmd)?;
}
Op::Pick => {
@@ -709,31 +558,9 @@ impl Forth {
"pick index {} out of range (have {} quotations)",
idx,
quots.len()
)
.into());
}
match &quots[idx] {
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(
quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*trace_cell.borrow_mut() = trace_opt;
}
_ => unreachable!(),
));
}
run_quotation(quots.swap_remove(idx), stack, outputs, scope_stack, cmd)?;
}
Op::Mtof => {
@@ -821,88 +648,16 @@ impl Forth {
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(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*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(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*trace_cell.borrow_mut() = trace_opt;
} else {
stack.push(selected);
}
Op::ListEndCycle | Op::ListEndPCycle => {
let idx_source = match &ops[pc] {
Op::ListEndCycle => ctx.runs,
_ => ctx.iter,
};
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, scope_stack, cmd)?;
}
Op::Adsr => {
@@ -926,29 +681,9 @@ impl Forth {
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(
&quot_ops,
ctx,
stack,
outputs,
scope_stack,
cmd,
trace_opt.as_deref_mut(),
emission_count,
)?;
*trace_cell.borrow_mut() = trace_opt;
}
_ => return Err("expected quotation".into()),
}
run_quotation(quot, stack, outputs, scope_stack, cmd)?;
}
Op::Ramp => {
let curve = stack.pop().ok_or("stack underflow")?.as_float()?;
let freq = stack.pop().ok_or("stack underflow")?.as_float()?;
@@ -976,21 +711,6 @@ impl Forth {
stack.push(Value::Float(val, None));
}
Op::InternalCycleEnd => {
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 internal cycle".into());
}
values.reverse();
stack.push(Value::Alternator(values));
}
Op::DivStart => {
let parent = scope_stack.last().ok_or("scope stack underflow")?;
let mut new_scope = ScopeContext::new(parent.start, parent.duration);
@@ -1005,10 +725,8 @@ impl Forth {
let child = scope_stack.pop().unwrap();
if child.stacked {
// Stack doesn't claim a slot - resolve directly to outputs
resolve_scope(&child, outputs);
} else {
// Div claims a slot in the parent
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);
@@ -1029,42 +747,10 @@ impl Forth {
return Err("emit count must be >= 0".into());
}
for _ in 0..n {
let (sound_val, params) = cmd.snapshot().ok_or("no sound set")?;
let (resolved_sound, sound_span) =
resolve_value_with_span(&sound_val, *emission_count);
if let Some(span) = sound_span {
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
trace.selected_spans.push(span);
}
}
let sound = resolved_sound.as_str()?.to_string();
let resolved_params: Vec<(String, String)> = params
.iter()
.map(|(k, v)| {
let (resolved, param_span) =
resolve_value_with_span(v, *emission_count);
if let Some(span) = param_span {
if let Some(trace) = trace_cell.borrow_mut().as_mut() {
trace.selected_spans.push(span);
}
}
(k.clone(), resolved.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,
});
*emission_count += 1;
emit_once(cmd, scope_stack)?;
}
}
}
pc += 1;
}
@@ -1073,20 +759,6 @@ impl Forth {
}
}
fn resolve_value_with_span(val: &Value, emission_count: usize) -> (Value, Option<SourceSpan>) {
match val {
Value::Alternator(items) if !items.is_empty() => {
let idx = emission_count % items.len();
let selected = &items[idx];
let (resolved, inner_span) = resolve_value_with_span(selected, emission_count);
// Prefer inner span (for nested alternators), fall back to selected's span
let span = inner_span.or_else(|| selected.span());
(resolved, span)
}
other => (other.clone(), other.span()),
}
}
fn resolve_scope(scope: &ScopeContext, outputs: &mut Vec<String>) {
let slot_dur = if scope.slot_count == 0 {
scope.duration * scope.weight
@@ -1094,26 +766,41 @@ fn resolve_scope(scope: &ScopeContext, outputs: &mut Vec<String>) {
scope.duration * scope.weight / scope.slot_count as f64
};
// Collect all emissions with their deltas for sorting
let mut emissions: Vec<(f64, String, Vec<(String, String)>, f64)> = Vec::new();
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((delta, em.sound.clone(), em.params.clone(), slot_dur));
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((delta, em.sound.clone(), em.params.clone(), dur));
emissions.push(Emission {
delta,
sound: em.sound.clone(),
params: em.params.clone(),
dur,
});
}
// Sort by delta to ensure temporal ordering
emissions.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
emissions.sort_by(|a, b| a.delta.partial_cmp(&b.delta).unwrap_or(std::cmp::Ordering::Equal));
for (delta, sound, params, dur) in emissions {
emit_output(&sound, &params, delta, dur, outputs);
for em in emissions {
emit_output(&em.sound, &em.params, em.delta, em.dur, outputs);
}
}
@@ -1202,64 +889,23 @@ fn float_to_value(result: f64) -> Value {
fn lift_unary<F>(val: Value, f: F) -> Result<Value, String>
where
F: Fn(f64) -> f64 + Copy,
F: Fn(f64) -> f64,
{
match val {
Value::Alternator(items) => {
let mapped: Result<Vec<Value>, String> =
items.into_iter().map(|v| lift_unary(v, f)).collect();
Ok(Value::Alternator(mapped?))
}
other => Ok(float_to_value(f(other.as_float()?))),
}
Ok(float_to_value(f(val.as_float()?)))
}
fn lift_unary_int<F>(val: Value, f: F) -> Result<Value, String>
where
F: Fn(i64) -> i64 + Copy,
F: Fn(i64) -> i64,
{
match val {
Value::Alternator(items) => {
let mapped: Result<Vec<Value>, String> =
items.into_iter().map(|v| lift_unary_int(v, f)).collect();
Ok(Value::Alternator(mapped?))
}
other => Ok(Value::Int(f(other.as_int()?), None)),
}
Ok(Value::Int(f(val.as_int()?), None))
}
fn lift_binary<F>(a: Value, b: Value, f: F) -> Result<Value, String>
where
F: Fn(f64, f64) -> f64 + Copy,
F: Fn(f64, f64) -> f64,
{
match (a, b) {
(Value::Alternator(a_items), Value::Alternator(b_items)) => {
let len = a_items.len().max(b_items.len());
let mapped: Result<Vec<Value>, String> = (0..len)
.map(|i| {
let ai = a_items[i % a_items.len()].clone();
let bi = b_items[i % b_items.len()].clone();
lift_binary(ai, bi, f)
})
.collect();
Ok(Value::Alternator(mapped?))
}
(Value::Alternator(items), scalar) => {
let mapped: Result<Vec<Value>, String> = items
.into_iter()
.map(|v| lift_binary(v, scalar.clone(), f))
.collect();
Ok(Value::Alternator(mapped?))
}
(scalar, Value::Alternator(items)) => {
let mapped: Result<Vec<Value>, String> = items
.into_iter()
.map(|v| lift_binary(scalar.clone(), v, f))
.collect();
Ok(Value::Alternator(mapped?))
}
(a, b) => Ok(float_to_value(f(a.as_float()?, b.as_float()?))),
}
Ok(float_to_value(f(a.as_float()?, b.as_float()?)))
}
fn binary_op<F>(stack: &mut Vec<Value>, f: F) -> Result<(), String>
@@ -1274,45 +920,12 @@ where
fn cmp_op<F>(stack: &mut Vec<Value>, f: F) -> Result<(), String>
where
F: Fn(f64, f64) -> bool + Copy,
F: Fn(f64, f64) -> bool,
{
fn lift_cmp<F>(a: Value, b: Value, f: F) -> Result<Value, String>
where
F: Fn(f64, f64) -> bool + Copy,
{
match (a, b) {
(Value::Alternator(a_items), Value::Alternator(b_items)) => {
let len = a_items.len().max(b_items.len());
let mapped: Result<Vec<Value>, String> = (0..len)
.map(|i| {
let ai = a_items[i % a_items.len()].clone();
let bi = b_items[i % b_items.len()].clone();
lift_cmp(ai, bi, f)
})
.collect();
Ok(Value::Alternator(mapped?))
}
(Value::Alternator(items), scalar) => {
let mapped: Result<Vec<Value>, String> = items
.into_iter()
.map(|v| lift_cmp(v, scalar.clone(), f))
.collect();
Ok(Value::Alternator(mapped?))
}
(scalar, Value::Alternator(items)) => {
let mapped: Result<Vec<Value>, String> = items
.into_iter()
.map(|v| lift_cmp(scalar.clone(), v, f))
.collect();
Ok(Value::Alternator(mapped?))
}
(a, b) => Ok(Value::Int(if f(a.as_float()?, b.as_float()?) { 1 } else { 0 }, None)),
}
}
let b = stack.pop().ok_or("stack underflow")?;
let a = stack.pop().ok_or("stack underflow")?;
stack.push(lift_cmp(a, b, f)?);
let result = if f(a.as_float()?, b.as_float()?) { 1 } else { 0 };
stack.push(Value::Int(result, None));
Ok(())
}

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8"
ratatui = "0.29"
regex = "1"
tui-textarea = { version = "0.7", features = ["search"] }

View File

@@ -6,6 +6,7 @@ mod modal;
mod nav_minimap;
mod sample_browser;
mod scope;
mod sparkles;
mod spectrum;
mod text_input;
mod vu_meter;
@@ -18,6 +19,7 @@ pub use modal::ModalFrame;
pub use nav_minimap::{NavMinimap, NavTile};
pub use sample_browser::{SampleBrowser, TreeLine, TreeLineKind};
pub use scope::{Orientation, Scope};
pub use sparkles::Sparkles;
pub use spectrum::Spectrum;
pub use text_input::TextInputModal;
pub use vu_meter::VuMeter;

View File

@@ -0,0 +1,65 @@
use rand::Rng;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::widgets::Widget;
const CHARS: &[char] = &['·', '✦', '✧', '°', '•', '+', '⋆', '*'];
const COLORS: &[(u8, u8, u8)] = &[
(200, 220, 255),
(255, 200, 150),
(150, 255, 200),
(255, 150, 200),
(200, 150, 255),
];
struct Sparkle {
x: u16,
y: u16,
char_idx: usize,
life: u8,
}
#[derive(Default)]
pub struct Sparkles {
sparkles: Vec<Sparkle>,
}
impl Sparkles {
pub fn tick(&mut self, area: Rect) {
let mut rng = rand::thread_rng();
for _ in 0..3 {
if rng.gen_bool(0.6) {
self.sparkles.push(Sparkle {
x: rng.gen_range(0..area.width),
y: rng.gen_range(0..area.height),
char_idx: rng.gen_range(0..CHARS.len()),
life: rng.gen_range(15..40),
});
}
}
self.sparkles
.iter_mut()
.for_each(|s| s.life = s.life.saturating_sub(1));
self.sparkles.retain(|s| s.life > 0);
}
}
impl Widget for &Sparkles {
fn render(self, area: Rect, buf: &mut Buffer) {
for sparkle in &self.sparkles {
let color = COLORS[sparkle.char_idx % COLORS.len()];
let intensity = (sparkle.life as f32 / 30.0).min(1.0);
let r = (color.0 as f32 * intensity) as u8;
let g = (color.1 as f32 * intensity) as u8;
let b = (color.2 as f32 * intensity) as u8;
if sparkle.x < area.width && sparkle.y < area.height {
let x = area.x + sparkle.x;
let y = area.y + sparkle.y;
let ch = CHARS[sparkle.char_idx];
buf[(x, y)].set_char(ch).set_style(Style::new().fg(Color::Rgb(r, g, b)));
}
}
}
}