README

dowdiness/moondsp/graph does not have a README file

#
Adsr

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
ArithSym

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
AudioBuffer

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
Biquad

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
BiquadMode

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
DelayLine

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
DelaySym

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
DspContext

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
DspSym

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
EnvStage

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
FilterSym

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
Noise

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
Oscillator

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
Pan

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
StereoDelaySym

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
StereoFilterSym

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
StereoSym

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
Waveform

Re-export dsp/ types and traits so graph source and test files can use them unqualified. pub using is needed because blackbox tests (_test.mbt) only see the package's public API.

#
GraphControllable

pub trait GraphControllable {
fn apply_control(Self, GraphControl) -> Result[Unit, GraphControlError]
fn apply_controls(Self, Array[GraphControl]) -> Result[Unit, GraphControlError]
}

Capability trait for runtime control of compiled DSP graphs.

Both mono and stereo compiled graphs, their hot-swap wrappers, and their topology controllers share the same control interface. This trait captures that shared interface so consumer code can be written generically over any controllable graph.

The process method is intentionally excluded because mono and stereo graphs have different output arity (one buffer vs. two).

Visibility: pub, not pub(open). The trait is intended for the six canonical wrapper types defined in this package; external types should compose with these wrappers rather than implement the trait directly. If a downstream use case for an external impl emerges, the contract to honour is: (a) apply_control returns the same GraphControlError variants the canonical wrappers do, (b) apply_controls is transactional — validate the whole batch first, then apply, leaving the graph unchanged on the first error.

WHY 12 identical impl blocks below: MoonBit has no blanket impls or default trait methods. Each of the 6 wrapper types (CompiledDsp, CompiledStereoDsp, CompiledDspHotSwap, CompiledStereoDspHotSwap, CompiledDspTopologyController, CompiledStereoDspTopologyController) must declare both methods individually, all delegating to .0.apply_control_impl() / .0.apply_controls_impl(). This is irreducible boilerplate under the current type system.

#
GraphDebuggable

pub(open) trait GraphDebuggable {
fn enable_debug_validation(Self) -> Unit
fn disable_debug_validation(Self) -> Unit
fn last_validation_errors(Self) -> Array[GraphValidationError]
}

Capability trait for debug validation of compiled DSP graphs.

Both mono and stereo compiled graphs support enabling debug validation mode, which checks node inputs and state before each process call.

#
NodeEditable

pub(open) trait NodeEditable : NodeSpanning + NodeFoldable {
fn can_insert_after(Self) -> Bool
fn can_delete(Self) -> Bool
}

Capability trait for topology editing queries.

#
NodeFoldable

pub(open) trait NodeFoldable : NodeSpanning {
fn is_foldable(Self) -> Bool
fn fold_value(Self, Double, Double) -> Double
}

Capability trait for compile-time constant folding.

#
NodeSpanning

pub(open) trait NodeSpanning {
fn signal_shape(Self) -> Int
}

Capability trait for signal shape queries.

#
NodeStateful

pub(open) trait NodeStateful : NodeSpanning {
fn is_stateful(Self) -> Bool
}

Capability trait for stateful node identification.

#
GraphTemplateDocError

pub(all) suberror GraphTemplateDocError {
LengthMismatch(Int, Int)
DuplicateNodeId(String)
MissingNodeId(String)
RetiredNodeId(String)
Topology(GraphTopologyEditError)
} derive(
Debug
)

#
CompiledDsp

type CompiledDsp

Executable buffer-based DSP graph compiled from DspNodes.

#
CompiledDsp::apply_control

fn CompiledDsp::apply_control(self : CompiledDsp, control : GraphControl) -> Result[Unit, GraphControlError]

Apply one runtime control message, returning a specific rejection reason.

#
CompiledDsp::apply_controls

fn CompiledDsp::apply_controls(self : CompiledDsp, controls : Array[GraphControl]) -> Result[Unit, GraphControlError]

Apply a runtime control batch transactionally.

#
CompiledDsp::compile

Compile a declarative mono graph into an executable buffer graph.

Accepts a CompiledTemplate (the runtime exchange boundary per ADR-0010) and returns None if the template's topology is rejected (e.g., no reachable Output, invalid feedback cycle, missing node inputs). Produce the input via CompiledTemplate::analyze(nodes) or GraphBuilder::analyze.

#
CompiledDsp::gate_off

fn CompiledDsp::gate_off(self : CompiledDsp, node_index : Int) -> Result[Unit, GraphControlError]

Trigger gate_off() on an ADSR node using its original authoring index.

#
CompiledDsp::gate_on

fn CompiledDsp::gate_on(self : CompiledDsp, node_index : Int) -> Result[Unit, GraphControlError]

Trigger gate_on() on an ADSR node using its original authoring index.

#
CompiledDsp::has_feedback_edges

fn CompiledDsp::has_feedback_edges(self : CompiledDsp) -> Bool

True when the compiled graph contains feedback (back) edges that require per-sample processing with self-registers.

#
CompiledDsp::is_voice_finished

fn CompiledDsp::is_voice_finished(self : CompiledDsp) -> Bool

Returns true when a voice using this compiled graph can be safely reclaimed.

Two-stage check:
  1. All ADSR nodes must be in Idle stage (envelope has finished)
  2. The last output buffer must be silent (all samples below threshold)

WHY two stages: ADSR-only detection would cut voices with downstream delay or feedback tails that are still audible. Energy-only detection would keep voices alive during sustain (where output is non-zero but expected).

#
CompiledDsp::last_sanitized_count

fn CompiledDsp::last_sanitized_count(self : CompiledDsp) -> Int

Number of non-finite samples replaced with 0.0 during the most recent process() call. Returns 0 if output was clean.

#
CompiledDsp::process

Run the compiled graph for one buffer and write the final output into output.

#
CompiledDsp::set_param

fn CompiledDsp::set_param(self : CompiledDsp, node_index : Int, slot : GraphParamSlot, value : Double) -> Result[Unit, GraphControlError]

Update a runtime parameter on a compiled node using its original authoring index.

#
CompiledDsp::validate_controls

fn CompiledDsp::validate_controls(self : CompiledDsp, controls : Array[GraphControl]) -> Result[Unit, GraphControlError]

Validate a runtime control batch without mutating the compiled graph.

#
CompiledDspHotSwap

type CompiledDspHotSwap

Block-boundary mono graph hot-swap wrapper for CompiledDsp.

This first Phase 2 slice supports swapping between already-compiled mono graphs with an optional equal-power crossfade. It does not migrate internal node state between graphs.

#
CompiledDspHotSwap::apply_control

fn CompiledDspHotSwap::apply_control(self : CompiledDspHotSwap, control : GraphControl) -> Result[Unit, GraphControlError]

Apply one runtime control message through a mono hot-swap wrapper.

#
CompiledDspHotSwap::apply_controls

fn CompiledDspHotSwap::apply_controls(self : CompiledDspHotSwap, controls : Array[GraphControl]) -> Result[Unit, GraphControlError]

Apply a runtime control batch transactionally through a mono hot-swap wrapper.

#
CompiledDspHotSwap::from_graph

fn CompiledDspHotSwap::from_graph(active : CompiledDsp, crossfade_samples? : Int) -> CompiledDspHotSwap

Create a hot-swap wrapper around an active mono compiled graph.

#
CompiledDspHotSwap::gate_off

fn CompiledDspHotSwap::gate_off(self : CompiledDspHotSwap, node_index : Int) -> Result[Unit, GraphControlError]

Trigger runtime gate-off through a mono hot-swap wrapper.

#
CompiledDspHotSwap::gate_on

fn CompiledDspHotSwap::gate_on(self : CompiledDspHotSwap, node_index : Int) -> Result[Unit, GraphControlError]

Trigger runtime gate-on through a mono hot-swap wrapper.

#
CompiledDspHotSwap::process

Process one block, crossfading between the active and pending graphs when a swap is in flight.

#
CompiledDspHotSwap::queue_swap

fn CompiledDspHotSwap::queue_swap(self : CompiledDspHotSwap, next : CompiledDsp) -> Result[Unit, HotSwapQueueError]

Queue a replacement graph for the next process(...) call.

Returns an error when the replacement graph is incompatible with the active graph's compile-time sample rate or block capacity.

#
CompiledDspHotSwap::set_param

fn CompiledDspHotSwap::set_param(self : CompiledDspHotSwap, node_index : Int, slot : GraphParamSlot, value : Double) -> Result[Unit, GraphControlError]

Update a runtime parameter through a mono hot-swap wrapper.

#
CompiledDspTopologyController

type CompiledDspTopologyController

Mono topology-edit wrapper that recompiles authoring nodes and stages the replacement through HotSwapGraph.

This first slice keeps edits narrow and deterministic: only ReplaceNode and RewireInput frames are supported, plus narrow unary InsertNode and DeleteNode frames on the mono path. Only one staged topology replacement may be pending at a time.

#
CompiledDspTopologyController::apply_control

Apply one runtime control message through a mono topology controller.

#
CompiledDspTopologyController::apply_controls

Apply a runtime control batch transactionally through a mono topology controller.

#
CompiledDspTopologyController::from_nodes

Build a topology-edit wrapper from authoring-order mono nodes.

#
CompiledDspTopologyController::gate_off

fn CompiledDspTopologyController::gate_off(self : CompiledDspTopologyController, node_index : Int) -> Result[Unit, GraphControlError]

Trigger runtime gate-off through a mono topology controller.

#
CompiledDspTopologyController::gate_on

fn CompiledDspTopologyController::gate_on(self : CompiledDspTopologyController, node_index : Int) -> Result[Unit, GraphControlError]

Trigger runtime gate-on through a mono topology controller.

#
CompiledDspTopologyController::process

Process one block through the active or crossfading mono graph.

#
CompiledDspTopologyController::queue_topology_edit

Queue one topology edit for the next hot-swap recompilation.

#
CompiledDspTopologyController::queue_topology_edits

Queue an ordered topology-edit batch.

The batch is transactional: invalid node indices or a recompilation failure reject the whole edit set and leave the current graph unchanged.

#
CompiledDspTopologyController::set_param

fn CompiledDspTopologyController::set_param(self : CompiledDspTopologyController, node_index : Int, slot : GraphParamSlot, value : Double) -> Result[Unit, GraphControlError]

Update a runtime parameter through a mono topology controller.

#
CompiledStereoDsp

type CompiledStereoDsp

Executable terminal-stereo graph compiled from DspNodes.

#
CompiledStereoDsp::apply_control

fn CompiledStereoDsp::apply_control(self : CompiledStereoDsp, control : GraphControl) -> Result[Unit, GraphControlError]

Apply one runtime control message, returning a specific rejection reason.

#
CompiledStereoDsp::apply_controls

fn CompiledStereoDsp::apply_controls(self : CompiledStereoDsp, controls : Array[GraphControl]) -> Result[Unit, GraphControlError]

Apply a runtime control batch transactionally.

#
CompiledStereoDsp::compile

Compile a declarative terminal-stereo graph into an executable stereo graph.

Accepts a CompiledTemplate (the runtime exchange boundary per ADR-0010) and returns None if the template's topology is rejected — including the stereo-specific requirement of a single reachable StereoOutput.

#
CompiledStereoDsp::gate_off

fn CompiledStereoDsp::gate_off(self : CompiledStereoDsp, node_index : Int) -> Result[Unit, GraphControlError]

Trigger gate_off() on a stereo-graph ADSR node using its original authoring index.

#
CompiledStereoDsp::gate_on

fn CompiledStereoDsp::gate_on(self : CompiledStereoDsp, node_index : Int) -> Result[Unit, GraphControlError]

Trigger gate_on() on a stereo-graph ADSR node using its original authoring index.

#
CompiledStereoDsp::has_feedback_edges

fn CompiledStereoDsp::has_feedback_edges(self : CompiledStereoDsp) -> Bool

#
CompiledStereoDsp::last_sanitized_count

fn CompiledStereoDsp::last_sanitized_count(self : CompiledStereoDsp) -> Int

#
CompiledStereoDsp::process

Run the compiled stereo graph for one buffer and write the final output into explicit left and right buffers.

#
CompiledStereoDsp::set_param

fn CompiledStereoDsp::set_param(self : CompiledStereoDsp, node_index : Int, slot : GraphParamSlot, value : Double) -> Result[Unit, GraphControlError]

Update a runtime parameter on a compiled stereo node using its original authoring index.

#
CompiledStereoDsp::validate_controls

fn CompiledStereoDsp::validate_controls(self : CompiledStereoDsp, controls : Array[GraphControl]) -> Result[Unit, GraphControlError]

Validate a runtime control batch without mutating the compiled graph.

#
CompiledStereoDspHotSwap

type CompiledStereoDspHotSwap

Block-boundary stereo graph hot-swap wrapper for CompiledStereoDsp.

This first stereo parity slice supports swapping between already-compiled terminal-stereo graphs with an optional equal-power crossfade. It does not migrate internal node state between graphs.

#
CompiledStereoDspHotSwap::apply_control

fn CompiledStereoDspHotSwap::apply_control(self : CompiledStereoDspHotSwap, control : GraphControl) -> Result[Unit, GraphControlError]

Apply one runtime control message through a stereo hot-swap wrapper.

#
CompiledStereoDspHotSwap::apply_controls

fn CompiledStereoDspHotSwap::apply_controls(self : CompiledStereoDspHotSwap, controls : Array[GraphControl]) -> Result[Unit, GraphControlError]

Apply a runtime control batch transactionally through a stereo hot-swap wrapper.

#
CompiledStereoDspHotSwap::from_graph

fn CompiledStereoDspHotSwap::from_graph(active : CompiledStereoDsp, crossfade_samples? : Int) -> CompiledStereoDspHotSwap

Create a hot-swap wrapper around an active stereo compiled graph.

#
CompiledStereoDspHotSwap::gate_off

fn CompiledStereoDspHotSwap::gate_off(self : CompiledStereoDspHotSwap, node_index : Int) -> Result[Unit, GraphControlError]

Trigger runtime gate-off through a stereo hot-swap wrapper.

#
CompiledStereoDspHotSwap::gate_on

fn CompiledStereoDspHotSwap::gate_on(self : CompiledStereoDspHotSwap, node_index : Int) -> Result[Unit, GraphControlError]

Trigger runtime gate-on through a stereo hot-swap wrapper.

#
CompiledStereoDspHotSwap::process

Process one block, crossfading between the active and pending stereo graphs when a swap is in flight.

#
CompiledStereoDspHotSwap::queue_swap

Queue a replacement stereo graph for the next process(...) call.

Returns an error when the replacement graph is incompatible with the active graph's compile-time sample rate or block capacity.

#
CompiledStereoDspHotSwap::set_param

fn CompiledStereoDspHotSwap::set_param(self : CompiledStereoDspHotSwap, node_index : Int, slot : GraphParamSlot, value : Double) -> Result[Unit, GraphControlError]

Update a runtime parameter through a stereo hot-swap wrapper.

#
CompiledStereoDspTopologyController

type CompiledStereoDspTopologyController

Terminal-stereo topology-edit wrapper that recompiles authoring nodes and stages the replacement through HotSwapGraph.

Supports the same edit kinds as the mono controller (all six variants of GraphTopologyEdit). Only one staged topology replacement may be pending at a time.

#
CompiledStereoDspTopologyController::apply_control

Apply one runtime control message through a stereo topology controller.

#
CompiledStereoDspTopologyController::apply_controls

Apply a runtime control batch transactionally through a stereo topology controller.

#
CompiledStereoDspTopologyController::from_nodes

Build a topology-edit wrapper from authoring-order terminal-stereo nodes.

#
CompiledStereoDspTopologyController::gate_off

fn CompiledStereoDspTopologyController::gate_off(self : CompiledStereoDspTopologyController, node_index : Int) -> Result[Unit, GraphControlError]

Trigger runtime gate-off through a stereo topology controller.

#
CompiledStereoDspTopologyController::gate_on

Trigger runtime gate-on through a stereo topology controller.

#
CompiledStereoDspTopologyController::process

Process one block through the active or crossfading stereo graph.

#
CompiledStereoDspTopologyController::queue_topology_edit

Queue one topology edit for the next stereo hot-swap recompilation.

#
CompiledStereoDspTopologyController::queue_topology_edits

Queue an ordered stereo topology-edit batch.

The batch is transactional: invalid node indices or a recompilation failure reject the whole edit set and leave the current graph unchanged.

#
CompiledStereoDspTopologyController::set_param

fn CompiledStereoDspTopologyController::set_param(self : CompiledStereoDspTopologyController, node_index : Int, slot : GraphParamSlot, value : Double) -> Result[Unit, GraphControlError]

Update a runtime parameter through a stereo topology controller.

#
CompiledTemplate

pub struct CompiledTemplate {
// private fields
}

Topology artifact: template snapshot paired with its optimize_graph result. Holds the minimum needed to answer post-optimization topology questions ("is this node live?", "how many ADSRs were eliminated?") without allocating runtime buffers or per-voice state.

WHY a separate artifact from CompiledDsp: CompiledDsp conflates topology with runtime state. Binding validation and pool-level orphan gates only need topology — making that a lightweight standalone type avoids paying for buffers we never use, and keeps the API mono/stereo-agnostic (the same CompiledTemplate shape applies to any graph variant).

#
CompiledTemplate::adsr_authoring_indices

fn CompiledTemplate::adsr_authoring_indices(self : CompiledTemplate) -> FixedArray[Int]

Authoring indices of ADSR nodes that survived optimize_graph, in authoring order. Used by voice/ to gate the surviving ADSRs on note_on / note_off — they call CompiledDsp::gate_on/gate_off which take authoring indices and remap through index_map internally.

WHY authoring indices, not runtime: CompiledDsp::gate_on/gate_off expect the original authoring index; returning runtime indices would cause voice/ to double-map and target wrong nodes.

WHY a separate accessor instead of exposing length/node_at: keeps the accessor surface minimal (principle 7). Specific validation patterns get their own public method; generic introspection waits for a concrete consumer.

#
CompiledTemplate::analyze

fn CompiledTemplate::analyze(template : Array[DspNode]) -> CompiledTemplate

Snapshot the template and run optimize_graph once.

WHY defensive copy: the caller's template array is mutable from outside. Without a snapshot, any post-analyze mutation of the source array would corrupt the self.template field we rely on for every subsequent query. optimize_graph itself does not mutate its input, so the copy is only guarding against caller-side mutation.

WHY no DspContext: optimize_graph is a pure function of Array[DspNode]; sample rate and block size do not affect which nodes survive dead-code elimination. Omitting the context makes analyze infallible and cheap.

#
CompiledTemplate::orphan_adsr_count

fn CompiledTemplate::orphan_adsr_count(self : CompiledTemplate) -> Int

Count Adsr nodes in the template snapshot whose compiled index is < 0 (eliminated by optimize_graph as dead code). On a CompiledDsp, gate_on for an orphan ADSR returns Err(OrphanNode) — VoicePool::new and set_template reject templates where this count is > 0 so the per-note gate loop can .unwrap() instead of branching on every voice trigger.

Parameterless because the template snapshot is owned by self; callers do not need to remember which template was compiled.

#
ControlBinding

pub struct ControlBinding {
key : String
node_index : Int
slot : GraphParamSlot
} derive(Eq,
Debug
)
#alias(new)
fn ControlBinding::ControlBinding(key~ : String, node_index~ : Int, slot~ : GraphParamSlot) -> ControlBinding

#
ControlBindingBuilder

pub struct ControlBindingBuilder {
// private fields
} derive(
Debug
)
#alias(new)
fn ControlBindingBuilder::ControlBindingBuilder() -> ControlBindingBuilder

#
ControlBindingBuilder::bind

fn ControlBindingBuilder::bind(self : ControlBindingBuilder, key~ : String, node_index~ : Int, slot~ : GraphParamSlot) -> ControlBindingBuilder

Add a binding. Mutates internal array, returns self for chaining.

#
ControlBindingBuilder::build

Validate all bindings against the compiled template and transition to the proven-valid ControlBindingMap. Per-binding checks in order: node index bounds, slot compatibility with the authoring node kind, post-optimization liveness (rejects bindings on nodes eliminated by optimize_graph), and key uniqueness. Returns the first error found.

#
ControlBindingError

pub(all) enum ControlBindingError {
InvalidNodeIndex(Int)
InvalidSlotForNode(Int, GraphParamSlot)
DuplicateKey(String)
OrphanBinding(String, Int)
} derive(Eq,
Debug
)

#
ControlBindingMap

pub struct ControlBindingMap {
// private fields
} derive(Eq,
Debug
)

Proven-valid control bindings. Validated against a specific CompiledTemplate at build time (bounds + slot compatibility + orphan detection + key uniqueness).

No public constructor — only reachable through ControlBindingBuilder::build().

WARNING: A ControlBindingMap's validity is tied to the template it was built against. After VoicePool::set_template swaps to a new template, bindings validated against the prior template remain type-level valid but may silently retarget the wrong kind of node or no-op against nodes the new template's optimize_graph eliminated. Rebuild the ControlBindingMap whenever the template changes. Structural staleness detection is tracked as a follow-up.

#
ControlBindingMap::length

fn ControlBindingMap::length(self : ControlBindingMap) -> Int

Number of validated bindings in this map.

#
ControlBindingMap::resolve_controls

fn ControlBindingMap::resolve_controls(self : ControlBindingMap, controls : Map[String, Double]) -> Array[GraphControl]

Convert pattern controls to graph controls using the validated bindings. Emits GraphControl::set_param for each bound key found in the input map, in binding insertion order. Missing keys are skipped; unrecognized keys are ignored. Values are passed through without domain validation.

#
DspNode

pub struct DspNode {
// private fields
}

Flat graph node representation for compiled DSP graphs.

Design decision: DspNode uses a flat struct with generic fields (value0-value3) rather than per-kind structs or a tagged union with payloads. This is deliberate:

  1. Flat memory layout — all nodes are the same size, enabling fixed-array storage without boxing. Critical for zero-allocation audio processing.
  2. Copy-on-update — node_with_value0/value1/delay_samples create updated copies without knowing the node kind, supporting runtime parameter changes.
  3. Serialization — uniform layout simplifies graph serialization for hot-swap and topology editing.

The cost is that field semantics depend on kind — see each constructor (e.g. DspNode::oscillator, DspNode::biquad) for the field mapping.

#
DspNode::adsr

fn DspNode::adsr(attack_ms~ : Double, decay_ms~ : Double, sustain~ : Double, release_ms~ : Double) -> DspNode

Create an ADSR envelope source node.

Use CompiledDsp::apply_control(GraphControl::gate_on(...)) and CompiledDsp::apply_control(GraphControl::gate_off(...)) to drive the envelope after compilation.

#
DspNode::biquad

fn DspNode::biquad(input~ : Int, mode~ :
BiquadMode
, cutoff_hz~ : Double, q~ : Double) -> DspNode

Create a fixed-parameter biquad node from one upstream node.

#
DspNode::clip

fn DspNode::clip(input : Int, threshold : Double) -> DspNode

Create a clip node from one upstream node.

#
DspNode::constant

fn DspNode::constant(value : Double) -> DspNode

Create a constant-value node.

#
DspNode::delay

fn DspNode::delay(input~ : Int, max_delay_samples~ : Int, delay_samples? : Int, feedback? : Double) -> DspNode

Create a fixed-delay node from one upstream node.

#
DspNode::delay_max_samples

fn DspNode::delay_max_samples(self : DspNode) -> Int

#
DspNode::delay_samples

fn DspNode::delay_samples(self : DspNode) -> Int

#
DspNode::envelope_gain

fn DspNode::envelope_gain(input~ : Int, envelope~ : Int, amount~ : Double) -> DspNode

Create a gain node with envelope modulation from two upstream nodes. The output is: input_signal * envelope_buffer * amount.

#
DspNode::filter_mode

#
DspNode::gain

fn DspNode::gain(input : Int, amount : Double) -> DspNode

Create an in-place gain node from one upstream node.

#
DspNode::input0

fn DspNode::input0(self : DspNode) -> Int

#
DspNode::input1

fn DspNode::input1(self : DspNode) -> Int

#
DspNode::kind

fn DspNode::kind(self : DspNode) -> DspNodeKind

#
DspNode::mix

fn DspNode::mix(left : Int, right : Int) -> DspNode

Create a mix node from two upstream nodes.

#
DspNode::mul

fn DspNode::mul(left : Int, right : Int) -> DspNode

Create a sample-wise multiply node from two upstream nodes.

#
DspNode::noise

fn DspNode::noise(seed : UInt) -> DspNode

Create a white-noise source node.

#
DspNode::oscillator

fn DspNode::oscillator(waveform :
Waveform
, freq : Double) -> DspNode

Create an oscillator node with a fixed frequency.

#
DspNode::oscillator_from

fn DspNode::oscillator_from(input : Int, waveform :
Waveform
) -> DspNode

Create an oscillator node that reads frequency from another node (FM mode).

#
DspNode::output

fn DspNode::output(input : Int) -> DspNode

Mark the final output node.

#
DspNode::pan

fn DspNode::pan(input : Int, position : Double) -> DspNode

Create a terminal stereo pan node from one mono upstream node.

#
DspNode::seed

fn DspNode::seed(self : DspNode) -> UInt

#
DspNode::stereo_biquad

fn DspNode::stereo_biquad(input~ : Int, mode~ :
BiquadMode
, cutoff_hz~ : Double, q~ : Double) -> DspNode

Create a stereo biquad node from one stereo upstream node.

#
DspNode::stereo_clip

fn DspNode::stereo_clip(input~ : Int, threshold~ : Double) -> DspNode

Create a stereo clip node from one stereo upstream node.

#
DspNode::stereo_delay

fn DspNode::stereo_delay(input~ : Int, max_delay_samples~ : Int, delay_samples? : Int, feedback? : Double) -> DspNode

Create a stereo delay node from one stereo upstream node.

#
DspNode::stereo_gain

fn DspNode::stereo_gain(input~ : Int, amount~ : Double) -> DspNode

Create a stereo gain node from one stereo upstream node.

#
DspNode::stereo_mixdown

fn DspNode::stereo_mixdown(input : Int) -> DspNode

Create a fixed-policy stereo-to-mono fold-down node.

#
DspNode::stereo_output

fn DspNode::stereo_output(input : Int) -> DspNode

Mark the final stereo output node.

#
DspNode::value0

fn DspNode::value0(self : DspNode) -> Double

#
DspNode::value1

fn DspNode::value1(self : DspNode) -> Double

#
DspNode::value2

fn DspNode::value2(self : DspNode) -> Double

#
DspNode::value3

fn DspNode::value3(self : DspNode) -> Double

#
DspNode::waveform

#
DspNodeKind

pub(all) enum DspNodeKind {
Constant
Oscillator
Noise
Adsr
Biquad
Delay
Gain
Mul
Mix
Clip
Output
Pan
StereoGain
StereoClip
StereoBiquad
StereoDelay
StereoMixDown
StereoOutput
} derive(Eq,
Debug
)

Minimal Phase 2 graph node kinds for compiled mono DSP graphs.

#
GraphBuilder

pub struct GraphBuilder {
// private fields
}

A tagless interpretation that builds an Array[DspNode] graph.

Each GraphBuilder value carries a mutable node array and the index of the "current" node within that array. Source nodes (constant, noise, adsr) start a fresh array, unary ops append to the existing array, and binary ops merge two arrays when they originate from different sources.

#
GraphBuilder::analyze

Produce a CompiledTemplate from the builder's current node list. Sugar over CompiledTemplate::analyze(self.nodes()) — exists because GraphBuilder is the canonical entry point and the runtime boundary is CompiledTemplate. See ADR-0010.

#
GraphBuilder::nodes

fn GraphBuilder::nodes(self : GraphBuilder) -> Array[DspNode]

Return the accumulated node array.

#
GraphControl

pub struct GraphControl {
// private fields
}

Runtime control message for a compiled graph.

#
GraphControl::gate_off

fn GraphControl::gate_off(node_index : Int) -> GraphControl

Create a runtime gate-off control targeting an original authoring index.

#
GraphControl::gate_on

fn GraphControl::gate_on(node_index : Int) -> GraphControl

Create a runtime gate-on control targeting an original authoring index.

#
GraphControl::kind

#
GraphControl::node_index

fn GraphControl::node_index(self : GraphControl) -> Int

#
GraphControl::set_param

fn GraphControl::set_param(node_index : Int, slot : GraphParamSlot, value : Double) -> GraphControl

Create a runtime numeric parameter update targeting an original authoring index.

#
GraphControl::slot

#
GraphControl::value

fn GraphControl::value(self : GraphControl) -> Double

#
GraphControlError

pub(all) enum GraphControlError {
InvalidNodeIndex(Int)
OrphanNode(Int)
InvalidGateNode(Int, DspNodeKind)
InvalidSlotForNode(Int, DspNodeKind, GraphParamSlot)
InvalidParamValue(Int, DspNodeKind, GraphParamSlot, Double)
MissingRuntimeState(Int, DspNodeKind)
} derive(
Debug
)

Failure reason for runtime graph control APIs.

#
GraphControlKind

pub(all) enum GraphControlKind {
GateOn
GateOff
SetParam
} derive(Eq)

Unified runtime control kinds for compiled graphs.

#
GraphIndexMap

pub struct GraphIndexMap {
// private fields
}

#
GraphIndexMap::bind_control

#
GraphIndexMap::insert_chain

#
GraphIndexMap::insert_node

#
GraphIndexMap::length

fn GraphIndexMap::length(self : GraphIndexMap) -> Int

#
GraphIndexMap::node_id_at

#
GraphIndexMap::node_index

#
GraphIndexMap::replace_node

#
GraphIndexMap::set_param

#
GraphParamSlot

pub(all) enum GraphParamSlot {
Value0
Value1
Value2
Value3
DelaySamples
} derive(Eq,
Debug
)

Runtime-updatable numeric graph parameter slots.

#
GraphTemplateDoc

pub struct GraphTemplateDoc {
// private fields
}

Identity-bearing authoring wrapper for graph templates.

Runtime graph compilation remains index-based. GraphTemplateDoc owns the stable authoring IDs and exposes GraphIndexMap at the boundary where controls, bindings, and topology edits are translated to existing indices.

#
GraphTemplateDoc::analyze

#
GraphTemplateDoc::compile

#
GraphTemplateDoc::compile_stereo

#
GraphTemplateDoc::contains_node

#
GraphTemplateDoc::contains_retired_node

#
GraphTemplateDoc::index_map

#
GraphTemplateDoc::length

fn GraphTemplateDoc::length(self : GraphTemplateDoc) -> Int

#
GraphTemplateDoc::node_id_at

#
GraphTemplateDoc::node_index

#
GraphTemplateDoc::nodes

#
GraphTemplateDoc::replace_node

#
GraphTopologyEdit

pub(all) enum GraphTopologyEdit {
ReplaceNode(Int, DspNode)
RewireInput(Int, GraphTopologyInputSlot, Int)
InsertNode(Int, GraphTopologyInputSlot, DspNode)
DeleteNode(Int, Int, GraphTopologyInputSlot, Int)
InsertChain(Int, GraphTopologyInputSlot, Array[DspNode])
DeleteChain(Int, Int, Int, GraphTopologyInputSlot, Int)
}

Declarative topology edit applied in authoring order before recompilation.

Each variant carries exactly the fields it needs — no sentinel values.

#
GraphTopologyEdit::delete_chain

fn GraphTopologyEdit::delete_chain(start_node : Int, end_node : Int, retarget_node : Int, input_slot : GraphTopologyInputSlot, replacement_source : Int) -> GraphTopologyEdit

Delete a contiguous chain of unary authoring-order nodes and retarget one downstream input to a replacement upstream source.

All nodes from start_node through end_node (inclusive) must be unary and each must feed exactly one downstream consumer. The retarget input of retarget_node is rerouted to replacement_source, and every node in the chain is removed with index compaction.

#
GraphTopologyEdit::delete_node

fn GraphTopologyEdit::delete_node(delete_node_index : Int, retarget_node_index : Int, retarget_input_slot : GraphTopologyInputSlot, replacement_source_index : Int) -> GraphTopologyEdit

Delete one unary authoring-order node and retarget one downstream input to a replacement upstream source.

This first slice is the inverse of append-only InsertNode: it only supports deleting unary nodes on the mono path, and the caller must provide the downstream target plus the replacement upstream source explicitly.

#
GraphTopologyEdit::insert_chain

fn GraphTopologyEdit::insert_chain(retarget_node : Int, input_slot : GraphTopologyInputSlot, chain_nodes : Array[DspNode]) -> GraphTopologyEdit

Insert a chain of unary authoring-order nodes and retarget one downstream input to the last node in the chain.

Each node in the chain is appended at the end of the authoring array. The first chain node inherits the previous upstream source of the retarget input; each subsequent chain node reads from the preceding chain node. The retarget input is then rerouted to the last chain node.

#
GraphTopologyEdit::insert_node

fn GraphTopologyEdit::insert_node(retarget_node_index : Int, retarget_input_slot : GraphTopologyInputSlot, inserted_node : DspNode) -> GraphTopologyEdit

Insert one unary authoring-order node and retarget one downstream input to the inserted node.

This first slice appends the inserted node at the end of the authoring array so existing node indices remain stable across the staged replacement. retarget_node_index and retarget_input_slot identify the downstream input that will be rerouted to the inserted node. The inserted node itself inherits that input's previous upstream source as its own input0.

#
GraphTopologyEdit::replace_node

fn GraphTopologyEdit::replace_node(node_index : Int, replacement : DspNode) -> GraphTopologyEdit

Replace one authoring-order node, including its kind and input wiring.

#
GraphTopologyEdit::rewire_input

fn GraphTopologyEdit::rewire_input(node_index : Int, input_slot : GraphTopologyInputSlot, source_index : Int) -> GraphTopologyEdit

Rewire one authoring-order node input to a different upstream source.

#
GraphTopologyEditError

pub(all) enum GraphTopologyEditError {
InvalidNodeIndex(Int)
InvalidSourceIndex(Int)
UnsupportedInputSlot(Int, GraphTopologyInputSlot, DspNodeKind)
UnsupportedInsertTemplate(DspNodeKind)
EmptyInsertChain
UnsupportedChainNode(Int, DspNodeKind)
InvalidDeleteRange(Int, Int)
ReplacementSourceInDeletedRange(Int)
DeleteNodeRequiresUnary(Int, DspNodeKind)
DeleteNodeRequiresSingleConsumer(Int)
DeleteNodeConsumerMismatch(Int, Int)
DeleteChainRequiresUnary(Int, DspNodeKind)
DeleteChainRequiresSingleConsumer(Int)
DeleteChainConsumerMismatch(Int, Int, Int)
} derive(
Debug
)

Failure reason for result-typed topology queue APIs.

#
GraphTopologyInputSlot

pub(all) enum GraphTopologyInputSlot {
Input0
Input1
} derive(Eq,
Debug
)

Input slot selector for fixed-length topology rewiring.

#
GraphTopologyQueueError

pub(all) enum GraphTopologyQueueError {
PendingSwap
InvalidEdit(Int, GraphTopologyEditError)
RecompileRejected
HotSwap(HotSwapQueueError)
} derive(
Debug
)

Failure reason for result-typed topology queue APIs.

#
GraphValidationError

pub(all) enum GraphValidationError {
InvalidInput0(Int, Int, Int)
InvalidInput1(Int, Int, Int)
InvalidStateIndex(Int, String)
} derive(Eq,
Debug
)

#
HotSwapQueueError

pub(all) enum HotSwapQueueError {
SampleRateMismatch(Double, Double)
BlockCapacityMismatch(Int, Int)
} derive(
Debug
)

Failure reason for result-typed hot-swap queue APIs.

#
exit_deliverable

The Phase 2 exit deliverable: sine(2).range(200,400).sine().lpf(800,1).out() An LFO at 2 Hz modulates the frequency of a carrier oscillator between 200-400 Hz, then filtered through a low-pass biquad at 800 Hz.

#
is_finite

fn is_finite(value : Double) -> Bool

Check whether a floating-point value is finite (not NaN, not Inf).

#
node_accepts_slot

fn node_accepts_slot(node : DspNode, slot : GraphParamSlot) -> Bool

Structural check: does this node kind accept SetParam on the given slot? Used by ControlBindingBuilder::build() to validate bindings at graph construction time. Does not check value-domain constraints.

#
range

fn[T :
ArithSym
] range(input : T, lo : Double, hi : Double) -> T

Maps [-1, 1] -> [lo, hi]. Only requires ArithSym.

#
replay

Walk a DspNode array and reconstruct the DSP graph through the tagless API, enabling round-trip testing (GraphBuilder -> nodes -> replay -> Eval). Returns None for empty arrays or invalid node references.