Observe one SignalGraph audio edge¶
SignalGraph::attach_audio_edge_probe observes one named ordinary audio edge
on the serial executor. It records source-side peak and RMS before downstream
mixing or delay compensation. A source is measured immediately after execution,
before another node can reuse its scratch. This is a diagnostic observation;
it neither changes audio nor inserts a graph node.
auto source = graph.node(gain)->out("audio");
auto destination = graph.node(output)->in("audio");
// Stop processing before prepare, attach, detach, or replacing an attachment.
graph.prepare(48000, 128);
auto status = graph.attach_audio_edge_probe(source, destination);
if (status != pulp::host::SignalGraph::EdgeProbeStatus::Attached)
return; // Handle the explicit refusal in the application's control thread.
const auto identity = graph.audio_edge_probe_identity();
// Resume processing. One control-thread consumer can drain while audio runs.
pulp::graph::GraphRuntimeProbeSample sample;
while (graph.read_audio_edge_probe(sample)) {
// sample.sequence, timestamp_samples, edge_index, frames, peak, rms
}
const auto lost_blocks = graph.audio_edge_probe_dropped();
// Stop processing before graph.detach_audio_edge_probe().
The ring holds 256 records. Full rings drop the newest record and increment
audio_edge_probe_dropped(); processing never waits for the reader. Sequence
numbers count delivered records. Sample timestamps count frames from attachment,
including blocks lost to overflow, and are independent of host transport time.
Variable block sizes advance by their actual frame count. A failed node produces
no record for that block, but its admitted block still advances the probe clock.
Zero-size, oversized and unprepared calls produce no record and do not advance
it. Non-finite source samples remain non-finite in RMS; the probe is not an audio
quality verdict.
audio_edge_probe_identity() binds records to this graph's authoring receipt,
connection identity, and endpoint IDs/ports. The record's dense edge_index is
local to this attachment and must never be reused to identify a later topology.
Disconnecting and reconnecting the same endpoints gives a new connection identity.
Read identity and records on the same control thread; do not race them with
control-thread topology edits. Reads serialize with graph mutations, while the
producer only uses bounded arithmetic and lock-free ring atomics.
Attach/detach require all graph processing, including retained execution-snapshot
rendering, to be stopped. They allocate/free only on the control thread. A failed
attach leaves the existing attachment untouched. A successful replacement starts
with an empty ring, zero loss count, sequence zero and timestamp zero. Reprepare
or a newly published topology retires the attachment. Ordinary topology mutation
unpublishes the graph, so reads then return empty and status is Unprepared.
A transport reset or execution-mode change detaches at the next admitted block;
old queued records remain readable with their original identity. Reattach after
the discontinuity to start a new observation.
Unsupported requests return UnsupportedKind for MIDI/event, control/parameter,
feedback and sidechain edges. Parallel, legacy and anticipative execution return
UnsupportedExecution. Missing edges, invalid named handles, unprepared graphs
and an active swap edit have separate statuses. Sample-region internal edges
that have no serial-plan edge are unsupported. No measurement work runs without
an attached probe; an unattached serial node checks only a null pointer.
The focused [edge-probe] tests use public graph APIs to check source-versus-output
identity, audio equivalence, timestamp/overflow accounting, reset/topology behavior,
unsupported requests, concurrent reading and callback allocation absence. These
are macOS functional and allocation checks, not the roadmap's paced deadline or
cross-platform realtime qualification. CLI/MCP probes, control/event capture,
latency/PDC maps and broad plan introspection remain separate work.
Observe a saved graph offline¶
pulp graph probe graph.pulpgraph --input input.wav \
--source-node source --source-port audio \
--destination-node downstream --destination-port audio \
--block-frames 64 --json
Node names must be unique. The command prepares the graph, attaches the named
edge, processes the supplied audio, and drains after each block. It emits JSON
only, including without --json. It does not write the graph or audio files.
code is Attached on success; attachment refusals retain the C++ enum spelling
(UnknownPort, MissingEdge, UnsupportedKind, UnsupportedExecution,
Unprepared, Busy, Detached). Other input failures have descriptive codes
such as invalid_block_frames or input_limit. A refusal exits 2.
Limits are a 1 MiB graph, 128 nodes, 512 connections, 1024 ports, built-in input/output/gain/MIDI nodes, and an audio input with 1–8 channels, 8000–192000 Hz, 1–480000 frames, and at most 4096 blocks. Block size is 1–4096 frames, default 64. Plugin/custom nodes are refused. Nonfinite input or measured peak/RMS is refused before publishing a sample report; JSON never substitutes zero for these values. The command reports source-side observations, not downstream output quality.
The report includes records (sequence, timestamp_samples, edge_index,
frames, peak, rms), identity, dropped, input frames and sample_rate.
The identity's graph lifetime, generation and connection counters are decimal
strings to preserve 64-bit precision. Graph lifetime is process-local; do not
use it as a persisted semantic fingerprint or compare it across CLI processes.
The installed MCP pulp_graph operation probe accepts path, input,
source_node, source_port, destination_node, destination_port, and optional
integer block_frames. It returns the same CLI JSON as text and marks refusals
with isError: true. It runs its matched sibling CLI with a 30-second deadline
and a 2 MiB output cap, including outside a source checkout.
The examples/graph-edge-probe standalone consumer links only installed
Pulp::host. It checks a source gain of 0.5 before a downstream gain of 0.25,
unchanged rendered audio, clocks, identity, loss and refusals. The installed
consumer test also invokes actual CLI/MCP binaries against its saved fixture.
This is bounded offline functional proof, not paced realtime qualification,
control/event capture, or a complete latency/PDC map.
Optional introspection sections¶
The same read-only probe command can include deterministic control-thread
introspection sections:
pulp graph probe graph.pulpgraph --input input.wav \
--source-node source --source-port audio \
--destination-node downstream --destination-port audio \
--plan --latency --node-loads --json
--plan adds the authored node and connection view, --latency adds the
prepared total, per-node input/output latency, and per-connection PDC delays,
and --node-loads opts into per-node callback timing and reports the bounded
latest-value snapshots. The timing option is intentionally opt-in: an
unattached or ordinary graph does not read per-node clocks, preserving the
zero-cost audio path. The installed MCP pulp_graph operation mirrors these
three booleans and invokes the matching sibling CLI, so its JSON sections and
refusals stay identical to the CLI surface.
Control and event taps remain deferred until a prepared producer/consumer contract exists. Requests for those lanes must fail closed rather than being represented as audio measurements or fabricated zero values.