Skip to content

audiograph: Graph.carry, so a rebuilt patch takes over a running one's state - #348

Merged
bdbarnett merged 2 commits into
mainfrom
graph/carry
Oct 30, 2026
Merged

bdbarnett merged 2 commits into
mainfrom
graph/carry

Conversation

@bdbarnett

Copy link
Copy Markdown
Contributor

Graph.carry(node, src_graph, src_node) hands a node of a running graph to a graph built beside it, so a patch can change while it plays: build the new graph, carry each node the two share, and run the new one from the next block. Voices, envelopes, oscillator phases, filter memory, delay lines and modal banks continue where they were.

The destination keeps its own wiring, parameters and wow table. A parameter that differs from the source's, or a change the source had queued but not rendered, lands on the next block as a set() would. A delay handed an equal copy of the wow table playing plays on without a dip; a different table takes over with the dip table() gives. A table the source lent is held for as long as the destination might read it.

It raises ValueError for a node of another kind, arg, state size or sample rate, or a bad node, and TypeError for a source that isn't a graph. Neither graph may be playing from the pump.

Proof

  • tests/parity/graph_carry_probe.py builds one patch with every node kind three times: a reference that renders 140 blocks, a source that renders 48, and a destination that carries the source's nodes. From block 48 on, every node of the destination renders what the reference renders, byte for byte, while the source keeps rendering other input and is then freed and its heap churned. Three delays cover the wow cases: one shared table, an equal copy, and a new table at the swap. A destination that carries nothing differs on every stateful kind.
  • verify_graph.py compares that render across CPython and MicroPython (CI) and CircuitPython (local), with a planted offbeat fault (the carry taken a block late) that must fail.
  • test-cpython builds the extension with AUDIODSP_GRAPH_CARRY_FAULT=1 (the delay line left on the source's) and =2 (a pending change dropped); the probe must fail on each.
  • Unit tests for each refusal and for a borrowed table outliving its source.

…over its nodes' state

carry(node, src_graph, src_node) copies a node's running state and config
from another graph: voices, envelopes, phases, filter memory and delay lines
continue across a patch change. The destination keeps its own wiring,
parameters and wow table; a parameter that differs, or a change the source
had queued, lands on the next block. It refuses a node of another kind, arg,
state size or sample rate.

graph_carry_probe.py builds every node kind three times and proves the
carried graph renders what the reference renders from the swap block on,
byte for byte; verify_graph.py compares it across interpreters. CI builds the
CPython extension with each planted C fault (the delay line left on the
source's, a pending change dropped) and the probe must fail.
@bdbarnett
bdbarnett merged commit 7aad48f into main Oct 30, 2026
31 checks passed
@bdbarnett
bdbarnett deleted the graph/carry branch October 30, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant