Skip to content

Sample-granular graphs

Pulp sample regions let a graph author describe a bounded scalar DSP program inside an ordinary SignalGraph. The graph remains the public composition model: a sample region is a declared set of registered scalar kernels, explicit boundary connections, and promoted parameters. The canonical executor lowers an admitted region to one prepared Processor path; callers do not instantiate a second DSP engine.

Declare a region

Create a disposable SignalGraph edit, register the built-in sample kernels, add nodes, and declare a region over those nodes. A kernel descriptor declares its stable type and version, scalar input/output counts, state size and alignment, causality, and the scalar process contract. The authoring API rejects unknown nodes, duplicate membership, unsupported edges, unresolved kernels, invalid state limits, and non-causal feedback before publication.

A one-sample recurrence uses an explicit UnitDelay node. It is the causal boundary that makes feedback legal; an ordinary graph cycle remains refused. Connect audio and promoted scalar parameters through the sample-region APIs so the resulting parameter contract can be frozen with the Processor's ordinary StateStore manifest. Do not use an ad-hoc capability bit or a block-size field to describe this scalar, per-frame ABI.

host::SignalGraph edit;
host::register_builtin_sample_region_types(edit);
// add scalar nodes, explicit UnitDelay nodes, and region connections
const auto declared = edit.declare_sample_region(std::move(region));
if (!declared.accepted) /* inspect declared.proof.message */;
const auto proof = edit.prove_sample_region(region_id);

Prepare and publish

Freeze the promoted parameter contract before exposing the Processor. Bind it to one owner-local StateStore, prepare the candidate at the host sample rate and maximum block size, then publish the edit through the normal graph transaction. The callback uses prepared storage and has no allocation or blocking admission path. A rejected edit remains unpublished and reports a structured refusal.

Runtime edits use immutable snapshots. A state cell is retained only when its region, node, kernel type/version, descriptor, and prepared configuration are an exact identity match. The old and new bindings cannot execute concurrently: SampleRegionExecutionDomain::try_admit() fails immediately on contention or when a newer generation has already been adopted. This preserves the next output for unchanged delay identities while allowing changed identities to start fresh state.

Bake and serialize

bake(graph) lowers an admitted graph to a BakedGraphProcessor. A signed .pulpbake is untrusted input: signature verification and bounded parsing happen before reconstruction, and the reconstructed graph is proved and baked again. Temporal history is runtime state and is never serialized as authored graph state. Use the installed examples/sample-region-allpass consumer to exercise graph JSON, signed bake/load, reload, and regular/irregular callback schedules against an independent scalar oracle.

Proof and portability

A useful proof has three independently inspectable parts: the declared contract, reachable execution through the canonical graph executor, and a focused positive and negative result with source, executable, artifact, and log hashes. Native, VST3, CLAP, AU, WebCLAP, and WAM projections should all consume the same Processor/graph contract. A generated catalog row or a registered test without an executed receipt does not establish reachability.

See the allpass example and the SignalGraph reference for the public vocabulary. The sample-region surface is experimental and bounded; unsupported latency, state, automation, or projection cases must remain explicit refusals until their contracts and compatibility proofs exist.

Documentation and proof authorities

This guide describes the authoring path; the inventories remain generated or source-adjacent authorities. Use the processing-model reference for the Processor-versus-SignalGraph boundary, the module reference for installed module and target names, and the capability-control reference for brokered inspection and edit operations. The inspector tables and control examples are projections of the canonical control registry, while docs/status/modules.yaml remains the module input. Do not update a generated table by hand or add a second sample-region capability registry.

For a release-facing check, run tools/check-docs.sh, python3 tools/scripts/processing_model_terms_lint.py, and the installed sample-region-allpass consumer proof. Those checks cover link/index hygiene, reserved processing-model terminology, and compilation through the installed SDK; they do not replace the runtime acceptance receipts for a packet.