Skip to content

fix(host): BakedGraphProcessor survives in-place buffers + resets node state on prepare#6408

Merged
shipyard-local[bot] merged 1 commit into
mainfrom
fix/baked-graph-inplace-and-reset
Jul 20, 2026
Merged

fix(host): BakedGraphProcessor survives in-place buffers + resets node state on prepare#6408
shipyard-local[bot] merged 1 commit into
mainfrom
fix/baked-graph-inplace-and-reset

Conversation

@shipyard-local

@shipyard-local shipyard-local Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Two correctness fixes for baked-graph plugins, both found via a downstream
consumer but affecting any Pulp plugin built on BakedGraphProcessor:

  1. In-place (aliased) host buffers no longer produce silence. Hosts like
    Logic (AUv2, some AUv3) hand process() input and output views over the
    SAME memory. process_routed() zeroes the main output bus before its
    AudioInput gather reads the input bus, so the zero destroyed the aliased
    input and the plugin emitted total silence. process() now detects any
    input-channel/output-channel overlap and reads the input from a scratch
    copy sized in prepare() — the audio thread does only pointer compares and
    copy_n, no allocation. Handles mono, multi-channel, and differing in/out
    channel counts.

  2. prepare() re-inits stateful Custom-node DSP state. bake() captured only
    the opaque process callback, so a re-prepare rebuilt the routing snapshot
    and buffer pool but never re-ran the custom instance's prepare/reset —
    stale state (e.g. a delay line's contents) recirculated into the fresh
    stream, violating the header's documented starts-from-zero contract.
    bake() now also captures per-node CustomNodeLifecycle closures (the
    type's prepare/reset bound to the instance) and prepare() runs them:
    prepare at the host's REAL rate/block (load_baked previously left
    instances prepared at its nominal 48k/512), then reset. Mirrors
    SignalGraph::prepare()'s own instance step.

Both fixes ship with fail-without-fix-verified tests in
test_baked_graph_processor_parity.cpp: aliased mono/stereo blocks must be
the processed signal (not silence, and allocation-free under the RT probe),
and a silent block after re-prepare must carry no residue from a stateful
delay node.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_015tUppDEybTgFDxS5BGKBWV


🔎 Provenance

Agent claude
Machine m5
Tab Analyze Pulp for AI plugin builder capabilities
Directory ~/Code/pulp-baked-fix
Session 14ef2f39-9113-4e61-bd8b-8faab97cab00

Resume

claude --resume 14ef2f39-9113-4e61-bd8b-8faab97cab00

Jump to this tab

cmux surface focus BD0A704A-23E0-4F9B-88CC-25BD1ACDAFEA

Relaunch (any agent)

cmux surface resume get --surface BD0A704A-23E0-4F9B-88CC-25BD1ACDAFEA

Restore URLhttps://claude.ai/code/session_015tUppDEybTgFDxS5BGKBWV

stamped 2026-07-20 20:33 UTC

…e state on prepare

Two correctness fixes for baked-graph plugins, both found via a downstream
consumer but affecting any Pulp plugin built on BakedGraphProcessor:

1. In-place (aliased) host buffers no longer produce silence. Hosts like
   Logic (AUv2, some AUv3) hand process() input and output views over the
   SAME memory. process_routed() zeroes the main output bus before its
   AudioInput gather reads the input bus, so the zero destroyed the aliased
   input and the plugin emitted total silence. process() now detects any
   input-channel/output-channel overlap and reads the input from a scratch
   copy sized in prepare() — the audio thread does only pointer compares and
   copy_n, no allocation. Handles mono, multi-channel, and differing in/out
   channel counts.

2. prepare() re-inits stateful Custom-node DSP state. bake() captured only
   the opaque process callback, so a re-prepare rebuilt the routing snapshot
   and buffer pool but never re-ran the custom instance's prepare/reset —
   stale state (e.g. a delay line's contents) recirculated into the fresh
   stream, violating the header's documented starts-from-zero contract.
   bake() now also captures per-node CustomNodeLifecycle closures (the
   type's prepare/reset bound to the instance) and prepare() runs them:
   prepare at the host's REAL rate/block (load_baked previously left
   instances prepared at its nominal 48k/512), then reset. Mirrors
   SignalGraph::prepare()'s own instance step.

Both fixes ship with fail-without-fix-verified tests in
test_baked_graph_processor_parity.cpp: aliased mono/stereo blocks must be
the processed signal (not silence, and allocation-free under the RT probe),
and a silent block after re-prepare must carry no residue from a stateful
delay node.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tUppDEybTgFDxS5BGKBWV
@shipyard-local
shipyard-local Bot enabled auto-merge (squash) July 20, 2026 20:44
@shipyard-local
shipyard-local Bot merged commit d769efd into main Jul 20, 2026
22 of 27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant