Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
graph_runtime_executor.hpp
Go to the documentation of this file.
1#pragma once
3
12#include <pulp/midi/buffer.hpp>
16
17#include <array>
18#include <atomic>
19#include <cstddef>
20#include <cstdint>
21#include <memory>
22#include <span>
23#include <vector>
24
25namespace pulp::audio {
27}
28
29namespace pulp::format {
30
31// One source-side audio observation, before downstream mixing/PDC and before
32// serial scratch reuse. Configure only while processing is stopped. The ring
33// has one audio-thread producer and one control-thread reader.
35 static_assert(std::atomic<std::uint64_t>::is_always_lock_free &&
36 std::atomic<bool>::is_always_lock_free);
38 std::uint32_t source_index = 0;
39 std::uint32_t source_port = 0;
40 std::uint32_t edge_index = 0;
41 std::uint64_t timestamp_samples = 0;
42};
43
44enum class AudioRateModulationDelivery : std::uint8_t {
47};
48
49enum class GraphRuntimeCommandStatus : std::uint8_t {
52};
53
64
69
71 const graph::GraphRuntimePlan* plan = nullptr;
73 std::uint32_t node_index = 0;
74 std::span<const GraphRuntimeCommandDecision> command_results;
75 // True when this context came from process_routed(): node_inputs/node_outputs
76 // are populated and a binding MUST read/write them rather than the block's
77 // buses. False on the shared-block process() path, where they are empty and
78 // a binding works the buses directly. A binding should assert the mode it
79 // expects — the shared-block path is slated to retire once callers migrate
80 // onto routing, at which point this flag and the dual mode collapse.
81 bool routed = false;
82 // Per-node routed I/O (valid only when `routed`). A routing binding reads
83 // `node_inputs` (already gathered from upstream output slots) and writes
84 // `node_outputs` (its assigned scratch slots). Channels are mono slots.
87 // Per-node MIDI (non-null only on the routing path when the call supplies a
88 // GraphRuntimeMidiScratch). `node_midi_in` holds the events gathered from
89 // this node's inbound event connections (the executor fills it before the
90 // binding runs); a binding that emits MIDI writes `node_midi_out` (clearing
91 // it first). Null when the graph carries no MIDI — a binding then falls back
92 // to its own scratch.
95 // MPE expression sidecar gathered alongside node_midi_in. Null when the
96 // graph carries no sidecar; bindings pass it to Processor::mpe_input().
98 // Per-node parameter automation events (non-null only on the routing path
99 // when the call supplies a GraphRuntimeAutomationScratch). The executor fills
100 // it from this node's inbound automation connections before the binding runs;
101 // a plugin binding passes it to PluginSlot::process. Null when the graph
102 // carries no automation — a binding then uses an empty queue.
104 // Borrowed, final plain-domain dense lanes for this node. The executor owns
105 // both descriptors and samples; they remain valid only for this callback.
106 std::span<const AudioRateModulationView> node_audio_rate_modulations;
108 std::span<graph::SpectralFrameBatchView* const> spectral_outputs;
109};
110
112 ProcessBlock& block,
113 const GraphRuntimeNodeProcessContext& context,
114 void* user_data) noexcept;
115
117 ProcessBlock& block,
118 const graph::GraphRuntimePlan& plan,
119 const graph::GraphTimedCommand& command,
120 void* user_data) noexcept;
121
123 // Block-level side effects are applied before node processing, so this
124 // handler is only invoked for block_offset == 0 commands until the executor
125 // grows block-splitting/sample-accurate command application.
127 void* user_data = nullptr;
128};
129
133 void* user_data = nullptr;
134 bool required = true;
135 // Optional per-node CPU-load measurer. When set, the executor wraps this
136 // node's per-block work in begin()/end() so routed execution attributes
137 // per-node load exactly as the legacy walk does. Null = not measured. The
138 // pointee is owned elsewhere (the host's persistent per-node measurer map)
139 // and must outlive the snapshot; the executor only calls begin()/end().
144 // The binding only reads its inputs, through the const views it is handed.
145 // When every reader of a node's output does, the snapshot lets them read that
146 // output in place (see GraphRuntimeSnapshot::InputPortSource), even when it
147 // fans out or the source keeps its output across blocks. Hosted plug-in
148 // slots and native cores, which pass inputs on as mutable, leave it false.
149 bool reads_inputs_only = false;
151 std::span<graph::SpectralFrameBatchView* const> spectral_outputs;
152};
153
160public:
161 // `parallel_safe` builds the buffer assignment without slot reuse so the
162 // snapshot can drive process_parallel (concurrent nodes never alias a
163 // recycled slot). Default false = the compact serial layout for
164 // process_routed. Both layouts produce identical output; parallel just costs
165 // more slots.
167 std::span<const GraphRuntimeNodeBinding> bindings,
168 bool parallel_safe = false);
169 void clear() noexcept;
170
171 // True if this snapshot's assignment is reuse-free (safe for process_parallel).
172 bool parallel_safe() const noexcept { return parallel_safe_; }
173
174 bool valid() const noexcept;
175 const graph::GraphRuntimePlan& plan() const noexcept { return plan_; }
176 bool contains_node(graph::NodeId node_id) const noexcept;
177 std::span<const GraphRuntimeNodeBinding> bindings() const noexcept {
178 return bindings_;
179 }
180 std::uint32_t node_count() const noexcept { return plan_.node_count(); }
181
182 // Scratch-slot layout for the routing process() path, computed off-RT in
183 // reset() from the plan. slot_count() is the number of mono slots a backing
184 // GraphRuntimeBufferPool must provide.
186 return assignment_;
187 }
188 std::uint32_t buffer_slot_count() const noexcept { return assignment_.slot_count; }
189
190 // Where one input port of a node reads from each block, resolved in reset()
191 // from the plan and the assignment so the routed walk does not rediscover
192 // it from the connection table on every block.
194 enum class Mode : std::uint8_t {
195 Zero, // no audio edge: the port's own slot, zeroed
196 InPlace, // one undelayed edge read in place from `slot`
197 Copy, // one undelayed edge copied bit-exactly from `slot`
198 Sum, // several edges, or one through a PDC ring: summed
199 };
201 std::uint32_t slot = 0;
202 };
203 // The node's input ports, in port order (empty for a node with none).
204 std::span<const InputPortSource> input_port_sources(std::uint32_t node_index) const noexcept {
205 if (node_index + 1 >= input_port_base_.size())
206 return {};
207 return std::span<const InputPortSource>(input_ports_)
208 .subspan(input_port_base_[node_index],
209 input_port_base_[node_index + 1] - input_port_base_[node_index]);
210 }
211 // True when the plan has exactly one AudioOutput node.
212 bool sole_audio_output() const noexcept {
213 return sole_audio_output_;
214 }
215 // That node's input port count, which is how many output bus channels it
216 // writes; 0 unless sole_audio_output().
217 std::uint32_t sole_audio_output_ports() const noexcept {
218 return sole_audio_output_ports_;
219 }
220 // Identifies this snapshot's contents: a new value on every successful
221 // reset(), shared by copies (which carry the same contents), 0 when empty.
222 // A GraphRuntimeBufferPool bound to it resolves against these contents.
223 std::uint64_t id() const noexcept {
224 return id_;
225 }
226
227 private:
228 bool compute_valid() const noexcept;
229 void resolve_input_ports();
230
231 graph::GraphRuntimePlan plan_;
233 std::vector<graph::NodeId> node_ids_sorted_;
234 graph::GraphRuntimeBufferAssignment assignment_;
235 bool parallel_safe_ = false;
236 // Computed once in reset() and clear(); the snapshot does not change in
237 // between. An empty snapshot is valid (it processes nothing).
238 bool valid_ = true;
239 bool sole_audio_output_ = false;
240 std::uint32_t sole_audio_output_ports_ = 0;
241 std::vector<InputPortSource> input_ports_;
242 std::vector<std::uint32_t> input_port_base_;
243 std::uint64_t id_ = 0;
244};
245
247 void* user_data = nullptr;
248 bool (*push_event)(void* user_data, const graph::GraphEvent& event) noexcept = nullptr;
249
250 bool push(const graph::GraphEvent& event) const noexcept {
251 return push_event ? push_event(user_data, event) : true;
252 }
253};
254
256 std::uint64_t blocks_processed = 0;
257 std::uint64_t nodes_processed = 0;
258 std::uint64_t commands_drained = 0;
259 std::uint64_t commands_accepted = 0;
260 std::uint64_t commands_rejected = 0;
261 std::uint64_t events_dropped = 0;
262 std::uint64_t invalid_blocks = 0;
263 std::uint64_t invalid_snapshots = 0;
264 std::uint64_t command_scratch_failures = 0;
265 std::uint64_t node_failures = 0;
266 // process_parallel() level routing: how many levels were dispatched across
267 // the worker pool vs run serially on the audio thread (single-node levels,
268 // AudioOutput-accumulation levels, or levels below the cost threshold).
269 std::uint64_t parallel_levels_dispatched = 0;
270 std::uint64_t serial_levels_run = 0;
271};
272
275 std::uint32_t nodes_processed = 0;
276 std::uint32_t commands_drained = 0;
277 std::uint32_t commands_accepted = 0;
278 std::uint32_t commands_rejected = 0;
279 std::uint32_t events_dropped = 0;
280 std::uint32_t failed_node_index = 0;
281
282 bool ok() const noexcept {
284 }
285};
286
298 public:
300 void clear_history() noexcept;
305 GraphRuntimeBufferPool& operator=(GraphRuntimeBufferPool&&) noexcept = default;
306
307 // Off-RT: (re)allocate storage for `slot_count` mono slots of `max_frames`.
308 // Zero-fills, so the first block (and any feedback edge) reads silence.
309 // Returns false on allocation failure or zero max_frames with slots.
310 bool reset(std::uint32_t slot_count, std::uint32_t max_frames);
311
312 // Off-RT: as above, plus per-connection plug-in-delay-compensation rings.
313 // `connection_delay_samples` is the snapshot assignment's per-connection
314 // delay (0 = no ring). Each delayed connection gets a contiguous ring of
315 // (delay + max_frames) floats and a persisted write position, zero-filled so
316 // the leading delay reads silence. The 2-arg reset is equivalent to passing
317 // an all-zero / empty span. Returns false on allocation failure.
318 bool reset(std::uint32_t slot_count, std::uint32_t max_frames,
319 std::span<const std::uint32_t> connection_delay_samples);
320 void clear() noexcept;
321
322 std::uint32_t slot_count() const noexcept { return slot_count_; }
323 std::uint32_t max_frames() const noexcept { return max_frames_; }
324
325 // RT-safe: mono buffer for slot `index`, or nullptr if out of range.
326 float* slot_data(std::uint32_t index) noexcept {
327 if (index >= slot_count_) return nullptr;
328 return storage_.data() + static_cast<std::size_t>(index) * max_frames_;
329 }
330 const float* slot_data(std::uint32_t index) const noexcept {
331 if (index >= slot_count_) return nullptr;
332 return storage_.data() + static_cast<std::size_t>(index) * max_frames_;
333 }
334
335 // A per-connection plug-in-delay-compensation ring: a contiguous float
336 // buffer of `size` (= delay + max_frames) with a persisted write position the
337 // executor advances by the block's frame count. `data == nullptr` when the
338 // connection needs no delay.
339 struct DelayRing {
340 float* data = nullptr;
341 std::uint32_t size = 0;
342 std::uint32_t delay = 0;
343 int* write_pos = nullptr;
344 };
345
346 // RT-safe: the delay ring for connection `index`, or an empty ring (data ==
347 // nullptr) when the connection has no delay or the pool carries no rings.
348 DelayRing delay_ring(std::uint32_t index) noexcept {
349 if (index >= ring_.size()) return {};
350 RingSlot& r = ring_[index];
351 if (!r.state) return {};
352 return DelayRing{r.state->storage.data(),
353 static_cast<std::uint32_t>(r.state->storage.size()),
354 r.delay,
355 &r.state->write_pos};
356 }
357
358 // Off-RT: make one destination ring share the source ring's mutable RT
359 // state. Used by SignalGraph's gap-free snapshot swap after it has matched
360 // connection identities and proven equal delay structure. No live samples
361 // or write position are read/copied while the audio thread may be running.
362 bool can_adopt_delay_ring_state(std::uint32_t index,
363 const GraphRuntimeBufferPool& source,
364 std::uint32_t source_index) const noexcept;
365 bool adopt_delay_ring_state(std::uint32_t index,
366 const GraphRuntimeBufferPool& source,
367 std::uint32_t source_index) noexcept;
368
369 // True when the pool can back `snapshot`'s routing for a block of `frames`:
370 // enough mono slots, a large-enough max_frames, and — if the snapshot needs
371 // PDC — a delay-ring layout matching its connection count (so a pool sized
372 // without rings falls back to the legacy walk rather than routing without
373 // delay compensation and diverging). This validates ring COUNT, not each
374 // ring's size; the invariant that the rings were sized from THIS snapshot's
375 // per-connection delays is held by construction — reset() the pool from the
376 // same buffer_assignment that built the snapshot (both travel as one
377 // CompiledGraph). Pairing a pool with a same-connection-count-but-different-
378 // delays snapshot is not a supported call sequence.
379 // Off-RT: resolve each node's channel pointers into this pool's slots for
380 // `snapshot` and prefill its routed process context, so a routed block only
381 // sets frame counts and the per-block context fields. An unbound pool, or one
382 // bound to another snapshot, takes the unbound path, which produces the same
383 // output. Returns false, leaving the pool unbound, when the pool does not fit
384 // the snapshot, a node exceeds the routed port limit, or allocation fails.
385 // reset(), clear() and copying unbind.
386 bool bind(const GraphRuntimeSnapshot& snapshot) noexcept;
387 bool bound_to(const GraphRuntimeSnapshot& snapshot) const noexcept {
388 return bound_snapshot_id_ != 0 && bound_snapshot_id_ == snapshot.id();
389 }
390
391 // One node's resolved routing state while the pool is bound.
392 struct BoundNode {
393 // node_index, routed and the channel pointer arrays are filled; the
394 // executor sets the frame counts and the per-block fields each block.
396 const float** inputs = nullptr;
397 float** outputs = nullptr;
398 // False when every input port reads its source in place, so the block
399 // has no input to copy, sum or zero.
400 bool gathers = false;
401 // Set for the block while this AudioInput node's readers read the host
402 // input bus directly, so the node has nothing to copy.
403 bool host_input_aliased = false;
404 };
405 // RT-safe: node `index`'s bound state. Valid only while bound_to() holds.
406 BoundNode& bound_node(std::uint32_t index) noexcept {
407 return bound_nodes_[index];
408 }
409
410 // RT-safe, bound pools: for one block, point the views that read the
411 // graph's single AudioInput node at the host's main input channels instead
412 // of the slots that node would copy them into, and mark the node so it
413 // copies nothing. bind() records whether the graph qualifies: every reader
414 // of the AudioInput node is an audio input port that reads it in place,
415 // without feedback, and is read-only (an AudioOutput, or a binding with
416 // reads_inputs_only); an automation lane fed by the input disqualifies it.
417 // Returns false, aliasing nothing, when the graph does not qualify, the
418 // input bus is missing or narrower than the node, or an input channel
419 // overlaps an output channel (a host processing in place). Undo with
420 // release_host_input(), which the executor calls before it returns, so the
421 // bound views never hold host pointers between blocks.
422 bool alias_host_input(const BusBuffer* input, const BusBuffer* output,
423 std::uint32_t frames) noexcept;
424 void release_host_input() noexcept;
425
426 bool fits(const GraphRuntimeSnapshot& snapshot, std::uint32_t frames) const noexcept {
427 if (slot_count_ < snapshot.buffer_slot_count() || frames > max_frames_) {
428 return false;
429 }
430 const auto& assignment = snapshot.buffer_assignment();
431 if (assignment.has_delay &&
432 ring_.size() != snapshot.plan().connections.size()) {
433 return false;
434 }
435 return true;
436 }
437
438private:
439 // Per-connection delay-ring layout (parallel to the plan's connections; empty
440 // when the pool was reset without delays). size == 0 means no ring.
441 struct DelayRingState {
442 std::vector<float> storage;
443 int write_pos = 0;
444 };
445 struct RingSlot {
446 std::uint32_t delay = 0;
447 std::shared_ptr<DelayRingState> state;
448 };
449
450 void unbind() noexcept;
451
452 std::vector<float> storage_;
453 std::uint32_t slot_count_ = 0;
454 std::uint32_t max_frames_ = 0;
455 std::vector<RingSlot> ring_;
456 // A view that can read the host input in place of the AudioInput slot.
457 struct HostInputAlias {
458 const float** view = nullptr;
459 const float* slot = nullptr;
460 std::uint32_t channel = 0;
461 };
462
463 std::vector<BoundNode> bound_nodes_;
464 std::vector<const float*> bound_inputs_;
465 std::vector<float*> bound_outputs_;
466 std::uint64_t bound_snapshot_id_ = 0;
467 std::vector<HostInputAlias> host_input_aliases_;
468 std::uint32_t host_input_node_ = 0;
469 std::uint32_t host_input_channels_ = 0;
470 bool host_input_aliasable_ = false;
471};
472
487public:
488 static constexpr std::size_t kDefaultEventCapacity = 1024;
489 static constexpr std::size_t kDefaultSysexCapacity = 128;
490 static constexpr std::size_t kDefaultSysexPayloadCapacity = 4096;
491
492 // Off-RT: (re)allocate `node_count` in/out buffer pairs, each reserved to the
493 // requested realtime capacities. Returns false on allocation failure.
494 bool reset(std::uint32_t node_count,
495 std::size_t event_capacity = kDefaultEventCapacity,
496 std::size_t sysex_capacity = kDefaultSysexCapacity,
497 std::size_t sysex_payload_capacity = kDefaultSysexPayloadCapacity);
498 bool reset(std::span<const std::size_t> event_capacities,
499 std::size_t sysex_capacity = kDefaultSysexCapacity,
500 std::size_t sysex_payload_capacity = kDefaultSysexPayloadCapacity);
501 void clear() noexcept;
502
503 std::uint32_t node_count() const noexcept { return node_count_; }
504
505 // RT-safe: this node's gathered MIDI input / produced MIDI output buffer, or
506 // nullptr if the node index is out of range.
507 midi::MidiBuffer* in(std::uint32_t node_index) noexcept {
508 return node_index < node_count_ ? &slots_[node_index]->in_buffer : nullptr;
509 }
510 midi::MidiBuffer* out(std::uint32_t node_index) noexcept {
511 return node_index < node_count_ ? &slots_[node_index]->out_buffer : nullptr;
512 }
513
514 // RT-safe: this node's gathered MPE expression sidecar, or nullptr when
515 // the node index is out of range. The sidecar follows the same event-edge
516 // topology as MIDI and is reserved during reset().
517 midi::MpeBuffer* mpe_in(std::uint32_t node_index) noexcept {
518 return node_index < node_count_ ? &slots_[node_index]->in_mpe : nullptr;
519 }
520 midi::MpeBuffer* mpe_out(std::uint32_t node_index) noexcept {
521 return node_index < node_count_ ? &slots_[node_index]->out_mpe : nullptr;
522 }
523
524 // Per-node incompleteness flags (a buffer dropped events, or carried an
525 // upstream drop). Tracked so the host egress can report the same incomplete
526 // state through extract_midi() that the legacy walk does.
527 bool in_incomplete(std::uint32_t node_index) const noexcept {
528 return node_index < node_count_ && slots_[node_index]->in_incomplete;
529 }
530 void set_in_incomplete(std::uint32_t node_index, bool v) noexcept {
531 if (node_index < node_count_) slots_[node_index]->in_incomplete = v;
532 }
533 bool out_incomplete(std::uint32_t node_index) const noexcept {
534 return node_index < node_count_ && slots_[node_index]->out_incomplete;
535 }
536 void set_out_incomplete(std::uint32_t node_index, bool v) noexcept {
537 if (node_index < node_count_) slots_[node_index]->out_incomplete = v;
538 }
539
540 bool fits(std::uint32_t node_count) const noexcept {
541 return node_count_ >= node_count;
542 }
543
544private:
545 struct Slot {
546 midi::MidiBuffer in_buffer;
547 midi::UmpBuffer in_ump;
548 midi::MidiBuffer out_buffer;
549 midi::UmpBuffer out_ump;
550 midi::MpeBuffer in_mpe;
551 midi::MpeBuffer out_mpe;
552 bool in_incomplete = false;
553 bool out_incomplete = false;
554 };
555 std::vector<std::unique_ptr<Slot>> slots_;
556 std::uint32_t node_count_ = 0;
557};
558
567public:
568 static constexpr std::uint32_t kMaxDenseLanesPerNode = 64;
569 static constexpr std::uint32_t kMaxDenseLanesPerGraph = 256;
570 static constexpr std::uint32_t kMaxDenseFrames = 16384;
571
573 std::uint32_t dense_lane_count = 0;
574 std::size_t dense_sample_bytes = 0;
575 std::size_t dense_descriptor_bytes = 0;
576 std::size_t dense_accumulator_bytes = 0;
577 std::size_t dense_node_index_bytes = 0;
578
583 };
584
585 // Per-node SPARSE (control-rate) automation accumulator. The gather dedups a
586 // node's inbound sparse-automation edges by parameter id and accumulates two
587 // control points (v0 at sample 0, vN at sample N-1) plus the parameter's
588 // clamp bounds and whether any edge mixed Add. One per distinct sparse
589 // parameter a node receives; held in per-node scratch (see sparse_accum) so
590 // an arbitrary per-node parameter count is handled without any on-stack cap,
591 // and disjoint per-node slices keep the parallel gather race-free.
592 struct SparseAccum {
593 std::uint32_t param_id = 0;
594 float v0 = 0.0f;
595 float vN = 0.0f;
596 float lo = 0.0f;
597 float hi = 0.0f;
598 bool has_add = false;
599 };
600
601 // Off-RT: allocate per-node event queues + per-connection slew state; for each
602 // node a max_frames accumulation buffer per distinct DENSE (audio-rate)
603 // automation parameter plus its transient gather flags/bounds; and a per-node
604 // SPARSE accumulator slice — all precomputed from the plan in first-seen
605 // connection order. The source-compatible two-argument overload applies
606 // legacy event-queue admission to every node; callers publishing a snapshot
607 // pass its bindings so dense consumers use the separate dense caps. Returns
608 // false on invalid limits, queue overflow, or allocation failure.
609 bool reset(const graph::GraphRuntimePlan& plan, std::uint32_t max_frames);
611 std::span<const GraphRuntimeNodeBinding> bindings, std::uint32_t max_frames);
612 void clear() noexcept;
613
614 std::uint32_t node_count() const noexcept { return node_count_; }
615 std::uint32_t connection_count() const noexcept { return connection_count_; }
616 std::uint32_t max_frames() const noexcept { return max_frames_; }
617 const PreparedStats& prepared_stats() const noexcept {
618 return prepared_stats_;
619 }
620
621 state::ParameterEventQueue* events(std::uint32_t node_index) noexcept {
622 return node_index < node_count_ ? events_[node_index].get() : nullptr;
623 }
624 // Persisted per-connection slew state (RT-mutable). last() is the
625 // previous block's post-slew value; primed() guards the first-block snap.
626 float& slew_last(std::uint32_t conn_index) noexcept { return slew_last_[conn_index]; }
627 bool slew_primed(std::uint32_t conn_index) const noexcept {
628 return slew_primed_[conn_index] != 0;
629 }
630 void set_slew_primed(std::uint32_t conn_index, bool v) noexcept {
631 slew_primed_[conn_index] = v ? 1 : 0;
632 }
633
634 // Per-node DENSE (audio-rate) parameter accumulation buffers. A node receives
635 // `dense_param_count(n)` distinct audio-rate parameters; dense_param_id(n,i)
636 // is the i-th and dense_buffer(n,i) its max_frames accumulation region.
637 std::uint32_t dense_param_count(std::uint32_t node_index) const noexcept {
638 return node_index < node_count_ ? node_dense_count_[node_index] : 0;
639 }
640 std::uint32_t dense_param_id(std::uint32_t node_index, std::uint32_t i) const noexcept {
641 return dense_params_[node_dense_first_[node_index] + i].param_id;
642 }
643 float* dense_buffer(std::uint32_t node_index, std::uint32_t i) noexcept {
644 return dense_storage_.data() + dense_params_[node_dense_first_[node_index] + i].offset;
645 }
646
647 // Per-node DENSE transient gather state — one slot per distinct audio-rate
648 // parameter the node receives (same i-indexing as dense_param_id /
649 // dense_buffer). dense_replace/dense_add are the per-param "saw a Replace /
650 // Add edge" flags; dense_lo/dense_hi the clamp bounds. dense_touched is the
651 // per-node emit list used by the gather so it only clears and emits params
652 // reached this block. These hold no state between blocks; they live in
653 // per-node scratch only so the gather needs no on-stack arrays, and disjoint
654 // per-node slices keep the parallel gather race-free.
655 std::span<float> dense_lo(std::uint32_t node_index) noexcept {
656 return dense_span(dense_lo_, node_index);
657 }
658 std::span<float> dense_hi(std::uint32_t node_index) noexcept {
659 return dense_span(dense_hi_, node_index);
660 }
661 std::span<std::uint8_t> dense_replace(std::uint32_t node_index) noexcept {
662 return dense_span(dense_replace_, node_index);
663 }
664 std::span<std::uint8_t> dense_add(std::uint32_t node_index) noexcept {
665 return dense_span(dense_add_, node_index);
666 }
667 std::span<std::uint32_t> dense_touched(std::uint32_t node_index) noexcept {
668 return dense_span(dense_touched_, node_index);
669 }
670 std::span<AudioRateModulationView> dense_views(std::uint32_t node_index) noexcept {
671 return dense_span(dense_views_, node_index);
672 }
673 std::uint32_t dense_view_count(std::uint32_t node_index) const noexcept {
674 return node_index < node_count_ ? node_dense_view_count_[node_index] : 0;
675 }
676 void set_dense_view_count(std::uint32_t node_index, std::uint32_t count) noexcept {
677 if (node_index < node_count_)
678 node_dense_view_count_[node_index] = count;
679 }
680
681 // Per-node SPARSE accumulator slice (one SparseAccum per distinct control-rate
682 // parameter the node receives). Disjoint per node => parallel-safe.
683 std::span<SparseAccum> sparse_accum(std::uint32_t node_index) noexcept {
684 if (node_index >= node_count_) return {};
685 return {sparse_accum_storage_.data() + node_sparse_first_[node_index],
686 node_sparse_count_[node_index]};
687 }
688
689 bool fits(std::uint32_t node_count, std::uint32_t connection_count,
690 std::uint32_t frames) const noexcept {
691 return node_count_ >= node_count && connection_count_ >= connection_count &&
692 frames <= max_frames_;
693 }
694
695private:
696 struct DenseParam {
697 std::uint32_t param_id = 0;
698 std::uint32_t offset = 0; // into dense_storage_ (floats)
699 };
700 // Slice a per-(node,denseparam) flat vector to this node's transient dense
701 // gather state — same first/count layout as dense_params_.
702 template<typename T>
703 std::span<T> dense_span(std::vector<T>& v, std::uint32_t node_index) noexcept {
704 if (node_index >= node_count_) return {};
705 return {v.data() + node_dense_first_[node_index], node_dense_count_[node_index]};
706 }
707 // ParameterEventQueue is large and non-copyable; hold it via unique_ptr so a
708 // reallocating vector never needs to move/copy it.
709 std::vector<std::unique_ptr<state::ParameterEventQueue>> events_; // per node
710 std::vector<float> slew_last_; // per connection
711 std::vector<std::uint8_t> slew_primed_; // per connection
712 std::vector<DenseParam> dense_params_; // flattened per-node
713 std::vector<std::uint32_t> node_dense_first_; // per node: index into dense_params_
714 std::vector<std::uint32_t> node_dense_count_; // per node
715 std::vector<float> dense_storage_; // total dense params × max_frames
716 // Per-(node,denseparam) transient dense gather state (keyed by
717 // node_dense_first_[n] + i, same as dense_params_): clamp bounds + the
718 // Replace/Add edge flags. Sized total_dense; re-zeroed per gather.
719 std::vector<float> dense_lo_;
720 std::vector<float> dense_hi_;
721 std::vector<std::uint8_t> dense_replace_;
722 std::vector<std::uint8_t> dense_add_;
723 std::vector<std::uint32_t> dense_touched_;
724 std::vector<AudioRateModulationView> dense_views_;
725 // Number of descriptors published for the current block. Kept separately
726 // from the prepared lane count because a source may be unavailable.
727 std::vector<std::uint32_t> node_dense_view_count_;
728 // Per-node SPARSE accumulators, flattened (one slice per node).
729 std::vector<SparseAccum> sparse_accum_storage_;
730 std::vector<std::uint32_t> node_sparse_first_; // per node: index into storage
731 std::vector<std::uint32_t> node_sparse_count_; // per node
732 std::uint32_t node_count_ = 0;
733 std::uint32_t connection_count_ = 0;
734 std::uint32_t max_frames_ = 0;
735 PreparedStats prepared_stats_{};
736};
737
739public:
740 static constexpr std::size_t kMaxInlineCommandCapacity = 256;
741 // Upper bound on ports per node for the routing path's on-stack channel
742 // pointer arrays. Matches GraphRuntimeLimits::max_ports_per_node.
743 static constexpr std::uint32_t kMaxRoutedPortsPerNode = 64;
744
746
749
751 ProcessBlock& block,
752 const GraphRuntimeSnapshot& snapshot,
753 std::span<const graph::GraphTimedCommand> commands = {},
754 std::span<GraphRuntimeCommandDecision> command_results = {},
755 GraphRuntimeCommandHandler command_handler = {},
756 GraphRuntimeEventSink event_sink = {}) noexcept;
757
758 template<std::size_t CommandCapacity,
759 std::size_t EventCapacity,
760 std::size_t MidiOutputCapacity>
762 ProcessBlock& block,
763 const GraphRuntimeSnapshot& snapshot,
765 GraphRuntimeCommandHandler command_handler = {}) noexcept {
766 static_assert(CommandCapacity <= kMaxInlineCommandCapacity,
767 "Use a smaller queue capacity or an explicit scratch path");
768 if (!block.validate()) return fail_invalid_block();
769 if (!snapshot.valid()) return fail_invalid_snapshot();
770
771 std::array<graph::GraphTimedCommand, CommandCapacity> commands{};
772 std::array<GraphRuntimeCommandDecision, CommandCapacity> command_results{};
773 const auto command_count = queues.drain_commands_for_block(commands, block.frame_count);
774 GraphRuntimeEventSink sink{
775 &queues,
776 [](void* user_data, const graph::GraphEvent& event) noexcept {
777 auto* typed = static_cast<
778 graph::GraphRuntimeQueues<CommandCapacity, EventCapacity, MidiOutputCapacity>*>(
779 user_data);
780 return typed->push_event_from_realtime(event);
781 },
782 };
783 return process(block, snapshot,
784 std::span<const graph::GraphTimedCommand>(
785 commands.data(), command_count),
786 std::span<GraphRuntimeCommandDecision>(
787 command_results.data(), command_count),
788 command_handler,
789 sink);
790 }
791
792 // Routing process() path: routes inter-node audio through `pool` per the
793 // snapshot's buffer_assignment(). AudioInput nodes read the block's main
794 // input bus into their output slots; AudioOutput nodes write their gathered
795 // input slots to the main output bus; all other nodes have their
796 // node_inputs gathered (summed) from upstream output slots and their
797 // node_outputs pointed at their scratch slots before the binding runs.
798 // Allocation-free: `pool` must already fit() the snapshot for this block.
799 //
800 // Feedback connections are honored as a one-block delay: gather adds each
801 // feedback edge's previous-block slot, and after the walk the source's
802 // output is captured into that slot for the next block (matching
803 // host::SignalGraph's feedback_prev). The pool's zero-init gives the first
804 // block silent feedback.
805 // When `midi` is non-null it must fit() the snapshot's node count: the
806 // executor clears each node's MIDI input, gathers its inbound event
807 // connections into it, and exposes the per-node in/out buffers to bindings
808 // via the process context. MidiInput/MidiOutput system nodes carry no audio
809 // and no binding; the caller pre-fills a MidiInput node's `out` and drains a
810 // MidiOutput node's `in` around this call. Pass null for audio-only graphs.
811 GraphRuntimeExecutorResult
814 GraphRuntimeAutomationScratch* automation = nullptr,
815 std::span<const graph::GraphTimedCommand> commands = {},
816 std::span<GraphRuntimeCommandDecision> command_results = {},
817 GraphRuntimeCommandHandler command_handler = {},
818 GraphRuntimeEventSink event_sink = {},
819 std::span<const std::uint8_t> skip_mask = {},
820 GraphRuntimeAudioEdgeProbe* audio_probe = nullptr) noexcept;
821
822 // `skip_mask` (optional, indexed by dense node index; empty = run everything):
823 // a non-zero entry means "do not run this node — its output slots are already
824 // filled by the caller". Used by anticipative rendering, where a pre-rendered
825 // sub-graph's boundary outputs are written into the skipped interior nodes'
826 // output slots before the call so the rest of the graph reads them instead of
827 // re-running the interior (which advances plugin state that the anticipation
828 // producer already owns). A masked node must NOT be a feedback endpoint (source
829 // or destination — the post-walk feedback capture would read its prefilled slot
830 // and feed stale history) nor an AudioOutput (skipping it drops its accumulate
831 // into the shared output bus, which no pool prefill can restore). The
832 // anticipation interior satisfies both — 6a excludes feedback endpoints and the
833 // interior never contains a live sink. Debug builds assert the contract.
834
835 // Levelized PARALLEL routing path: same per-node work as process_routed, but
836 // each topological level's independent nodes are dispatched across `workers`
837 // (the audio thread is participant 0), barriered between levels. Output is
838 // bit-identical to process_routed: each node owns disjoint scratch, its
839 // fan-in sum is single-threaded, and the level barrier orders producers
840 // before consumers. A level that contains an AudioOutput node (which
841 // accumulates into the shared output bus) or has a single node runs serially.
842 // `levels` must be the levelization of `snapshot`'s plan and `workers` must be
843 // started; pool/midi/automation must fit() the snapshot as for process_routed.
845 ProcessBlock& block,
846 const GraphRuntimeSnapshot& snapshot,
849 GraphRuntimeWorkerPool& workers,
850 GraphRuntimeMidiScratch* midi = nullptr,
851 GraphRuntimeAutomationScratch* automation = nullptr,
852 std::span<const graph::GraphTimedCommand> commands = {},
853 std::span<GraphRuntimeCommandDecision> command_results = {},
854 GraphRuntimeCommandHandler command_handler = {},
855 GraphRuntimeEventSink event_sink = {}) noexcept;
856
858 void reset_stats() noexcept;
859
860 // Cost threshold (off-RT setter): process_parallel dispatches a level across
861 // the worker pool only when its static work-weight x frame_count reaches this
862 // many "channel-samples"; below it the level runs serially to avoid losing the
863 // fork/join overhead on trivial work (the break-even guard — fork/join can
864 // lose at 32-64 frames). DEFAULT 0 = parallelize every eligible (width>1,
865 // no-AudioOutput) level; worker-pool dispatch is the executor's job and the
866 // real safety gate is the caller's opt-in (SignalGraph parallel routing is
867 // default-OFF). Set a positive break-even (e.g. 4096 ~= 16 stereo nodes x 128
868 // frames) to keep trivial levels serial once parallel routing is enabled on
869 // small graphs.
870 void set_parallel_min_work_units(std::uint64_t channel_samples) noexcept {
871 parallel_min_work_units_.store(channel_samples, std::memory_order_relaxed);
872 }
873 std::uint64_t parallel_min_work_units() const noexcept {
874 return parallel_min_work_units_.load(std::memory_order_relaxed);
875 }
876
877 // Whether routed execution (serial and parallel) times each node into its
878 // binding's load measurer. A node timing costs two clock reads, which is a
879 // large share of a small graph's per-node cost, so an owner that knows no one
880 // reads per-node load turns it off. Read once per block. Default on.
881 void set_node_load_timing(bool enabled) noexcept {
882 node_load_timing_.store(enabled, std::memory_order_relaxed);
883 }
884 bool node_load_timing() const noexcept {
885 return node_load_timing_.load(std::memory_order_relaxed);
886 }
887
888private:
889 GraphRuntimeExecutorResult fail_invalid_block() noexcept;
890 GraphRuntimeExecutorResult fail_invalid_snapshot() noexcept;
891 GraphRuntimeExecutorResult fail_command_scratch_too_small() noexcept;
892
893 // Shared command-drain used by both process() paths; fills command_results
894 // and updates result + stats. Returns false if command_results is too small.
895 bool drain_commands(
896 ProcessBlock& block,
897 const GraphRuntimeSnapshot& snapshot,
898 std::span<const graph::GraphTimedCommand> commands,
899 std::span<GraphRuntimeCommandDecision> command_results,
900 GraphRuntimeCommandHandler command_handler,
901 GraphRuntimeEventSink event_sink,
902 GraphRuntimeExecutorResult& result) noexcept;
903
904 std::atomic<std::uint64_t> blocks_processed_{0};
905 std::atomic<std::uint64_t> nodes_processed_{0};
906 std::atomic<std::uint64_t> commands_drained_{0};
907 std::atomic<std::uint64_t> commands_accepted_{0};
908 std::atomic<std::uint64_t> commands_rejected_{0};
909 std::atomic<std::uint64_t> events_dropped_{0};
910 std::atomic<std::uint64_t> invalid_blocks_{0};
911 std::atomic<std::uint64_t> invalid_snapshots_{0};
912 std::atomic<std::uint64_t> command_scratch_failures_{0};
913 std::atomic<std::uint64_t> node_failures_{0};
914 std::atomic<std::uint64_t> parallel_levels_dispatched_{0};
915 std::atomic<std::uint64_t> serial_levels_run_{0};
916 // 0 = no cost gate (parallelize every eligible level); see the setter.
917 std::atomic<std::uint64_t> parallel_min_work_units_{0};
918 std::atomic<bool> node_load_timing_{true};
919};
920
921} // namespace pulp::format
Measures the CPU load of an audio processing callback.
Definition load_measurer.hpp:182
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
Pre-allocated scratch backing the routing path's parameter-automation edges.
Definition graph_runtime_executor.hpp:566
std::span< SparseAccum > sparse_accum(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:683
float & slew_last(std::uint32_t conn_index) noexcept
Definition graph_runtime_executor.hpp:626
bool reset(const graph::GraphRuntimePlan &plan, std::uint32_t max_frames)
std::uint32_t dense_param_id(std::uint32_t node_index, std::uint32_t i) const noexcept
Definition graph_runtime_executor.hpp:640
std::uint32_t connection_count() const noexcept
Definition graph_runtime_executor.hpp:615
const PreparedStats & prepared_stats() const noexcept
Definition graph_runtime_executor.hpp:617
std::span< std::uint8_t > dense_replace(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:661
static constexpr std::uint32_t kMaxDenseFrames
Definition graph_runtime_executor.hpp:570
static constexpr std::uint32_t kMaxDenseLanesPerNode
Definition graph_runtime_executor.hpp:568
std::uint32_t max_frames() const noexcept
Definition graph_runtime_executor.hpp:616
state::ParameterEventQueue * events(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:621
static constexpr std::uint32_t kMaxDenseLanesPerGraph
Definition graph_runtime_executor.hpp:569
void set_dense_view_count(std::uint32_t node_index, std::uint32_t count) noexcept
Definition graph_runtime_executor.hpp:676
std::uint32_t dense_param_count(std::uint32_t node_index) const noexcept
Definition graph_runtime_executor.hpp:637
std::uint32_t node_count() const noexcept
Definition graph_runtime_executor.hpp:614
bool slew_primed(std::uint32_t conn_index) const noexcept
Definition graph_runtime_executor.hpp:627
void set_slew_primed(std::uint32_t conn_index, bool v) noexcept
Definition graph_runtime_executor.hpp:630
std::uint32_t dense_view_count(std::uint32_t node_index) const noexcept
Definition graph_runtime_executor.hpp:673
std::span< float > dense_hi(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:658
std::span< AudioRateModulationView > dense_views(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:670
bool fits(std::uint32_t node_count, std::uint32_t connection_count, std::uint32_t frames) const noexcept
Definition graph_runtime_executor.hpp:689
std::span< float > dense_lo(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:655
bool reset(const graph::GraphRuntimePlan &plan, std::span< const GraphRuntimeNodeBinding > bindings, std::uint32_t max_frames)
std::span< std::uint8_t > dense_add(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:664
float * dense_buffer(std::uint32_t node_index, std::uint32_t i) noexcept
Definition graph_runtime_executor.hpp:643
std::span< std::uint32_t > dense_touched(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:667
Pre-allocated scratch buffers backing the routing process() path.
Definition graph_runtime_executor.hpp:297
const float * slot_data(std::uint32_t index) const noexcept
Definition graph_runtime_executor.hpp:330
bool bind(const GraphRuntimeSnapshot &snapshot) noexcept
bool can_adopt_delay_ring_state(std::uint32_t index, const GraphRuntimeBufferPool &source, std::uint32_t source_index) const noexcept
DelayRing delay_ring(std::uint32_t index) noexcept
Definition graph_runtime_executor.hpp:348
bool alias_host_input(const BusBuffer *input, const BusBuffer *output, std::uint32_t frames) noexcept
void clear_history() noexcept
Render-owner reset of prepared audio/PDC storage without reallocating.
bool adopt_delay_ring_state(std::uint32_t index, const GraphRuntimeBufferPool &source, std::uint32_t source_index) noexcept
float * slot_data(std::uint32_t index) noexcept
Definition graph_runtime_executor.hpp:326
bool fits(const GraphRuntimeSnapshot &snapshot, std::uint32_t frames) const noexcept
Definition graph_runtime_executor.hpp:426
std::uint32_t max_frames() const noexcept
Definition graph_runtime_executor.hpp:323
BoundNode & bound_node(std::uint32_t index) noexcept
Definition graph_runtime_executor.hpp:406
bool reset(std::uint32_t slot_count, std::uint32_t max_frames)
bool bound_to(const GraphRuntimeSnapshot &snapshot) const noexcept
Definition graph_runtime_executor.hpp:387
std::uint32_t slot_count() const noexcept
Definition graph_runtime_executor.hpp:322
GraphRuntimeExecutorResult process_parallel(ProcessBlock &block, const GraphRuntimeSnapshot &snapshot, const graph::GraphRuntimeLevelization &levels, GraphRuntimeBufferPool &pool, GraphRuntimeWorkerPool &workers, GraphRuntimeMidiScratch *midi=nullptr, GraphRuntimeAutomationScratch *automation=nullptr, std::span< const graph::GraphTimedCommand > commands={}, std::span< GraphRuntimeCommandDecision > command_results={}, GraphRuntimeCommandHandler command_handler={}, GraphRuntimeEventSink event_sink={}) noexcept
std::uint64_t parallel_min_work_units() const noexcept
Definition graph_runtime_executor.hpp:873
GraphRuntimeExecutorResult process(ProcessBlock &block, const GraphRuntimeSnapshot &snapshot, graph::GraphRuntimeQueues< CommandCapacity, EventCapacity, MidiOutputCapacity > &queues, GraphRuntimeCommandHandler command_handler={}) noexcept
Definition graph_runtime_executor.hpp:761
void set_parallel_min_work_units(std::uint64_t channel_samples) noexcept
Definition graph_runtime_executor.hpp:870
void set_node_load_timing(bool enabled) noexcept
Definition graph_runtime_executor.hpp:881
bool node_load_timing() const noexcept
Definition graph_runtime_executor.hpp:884
static constexpr std::uint32_t kMaxRoutedPortsPerNode
Definition graph_runtime_executor.hpp:743
GraphRuntimeExecutor(const GraphRuntimeExecutor &)=delete
GraphRuntimeExecutorResult process(ProcessBlock &block, const GraphRuntimeSnapshot &snapshot, std::span< const graph::GraphTimedCommand > commands={}, std::span< GraphRuntimeCommandDecision > command_results={}, GraphRuntimeCommandHandler command_handler={}, GraphRuntimeEventSink event_sink={}) noexcept
GraphRuntimeExecutorStats stats() const noexcept
GraphRuntimeExecutorResult process_routed(ProcessBlock &block, const GraphRuntimeSnapshot &snapshot, GraphRuntimeBufferPool &pool, GraphRuntimeMidiScratch *midi=nullptr, GraphRuntimeAutomationScratch *automation=nullptr, std::span< const graph::GraphTimedCommand > commands={}, std::span< GraphRuntimeCommandDecision > command_results={}, GraphRuntimeCommandHandler command_handler={}, GraphRuntimeEventSink event_sink={}, std::span< const std::uint8_t > skip_mask={}, GraphRuntimeAudioEdgeProbe *audio_probe=nullptr) noexcept
static constexpr std::size_t kMaxInlineCommandCapacity
Definition graph_runtime_executor.hpp:740
GraphRuntimeExecutor & operator=(const GraphRuntimeExecutor &)=delete
Pre-allocated per-node MIDI buffers backing the routing path's event edges.
Definition graph_runtime_executor.hpp:486
void set_out_incomplete(std::uint32_t node_index, bool v) noexcept
Definition graph_runtime_executor.hpp:536
static constexpr std::size_t kDefaultSysexCapacity
Definition graph_runtime_executor.hpp:489
bool in_incomplete(std::uint32_t node_index) const noexcept
Definition graph_runtime_executor.hpp:527
static constexpr std::size_t kDefaultSysexPayloadCapacity
Definition graph_runtime_executor.hpp:490
bool reset(std::span< const std::size_t > event_capacities, std::size_t sysex_capacity=kDefaultSysexCapacity, std::size_t sysex_payload_capacity=kDefaultSysexPayloadCapacity)
midi::MidiBuffer * out(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:510
static constexpr std::size_t kDefaultEventCapacity
Definition graph_runtime_executor.hpp:488
void set_in_incomplete(std::uint32_t node_index, bool v) noexcept
Definition graph_runtime_executor.hpp:530
bool out_incomplete(std::uint32_t node_index) const noexcept
Definition graph_runtime_executor.hpp:533
bool reset(std::uint32_t node_count, std::size_t event_capacity=kDefaultEventCapacity, std::size_t sysex_capacity=kDefaultSysexCapacity, std::size_t sysex_payload_capacity=kDefaultSysexPayloadCapacity)
std::uint32_t node_count() const noexcept
Definition graph_runtime_executor.hpp:503
midi::MidiBuffer * in(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:507
bool fits(std::uint32_t node_count) const noexcept
Definition graph_runtime_executor.hpp:540
midi::MpeBuffer * mpe_out(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:520
midi::MpeBuffer * mpe_in(std::uint32_t node_index) noexcept
Definition graph_runtime_executor.hpp:517
Control-thread-built graph snapshot for GraphRuntimeExecutor.
Definition graph_runtime_executor.hpp:159
const graph::GraphRuntimeBufferAssignment & buffer_assignment() const noexcept
Definition graph_runtime_executor.hpp:185
std::uint32_t node_count() const noexcept
Definition graph_runtime_executor.hpp:180
bool parallel_safe() const noexcept
Definition graph_runtime_executor.hpp:172
std::span< const GraphRuntimeNodeBinding > bindings() const noexcept
Definition graph_runtime_executor.hpp:177
std::span< const InputPortSource > input_port_sources(std::uint32_t node_index) const noexcept
Definition graph_runtime_executor.hpp:204
std::uint32_t sole_audio_output_ports() const noexcept
Definition graph_runtime_executor.hpp:217
std::uint64_t id() const noexcept
Definition graph_runtime_executor.hpp:223
bool contains_node(graph::NodeId node_id) const noexcept
bool reset(graph::GraphRuntimePlan plan, std::span< const GraphRuntimeNodeBinding > bindings, bool parallel_safe=false)
bool sole_audio_output() const noexcept
Definition graph_runtime_executor.hpp:212
const graph::GraphRuntimePlan & plan() const noexcept
Definition graph_runtime_executor.hpp:175
std::uint32_t buffer_slot_count() const noexcept
Definition graph_runtime_executor.hpp:188
Persistent fork-join worker pool for the levelized parallel graph executor.
Definition graph_runtime_worker_pool.hpp:32
Definition graph_runtime_probe.hpp:20
Fixed-capacity graph command/event queues for realtime graph runtimes.
Definition graph_runtime_queue.hpp:111
std::uint32_t drain_commands_for_block(std::span< GraphTimedCommand > out, std::uint32_t block_frames) noexcept
Definition graph_runtime_queue.hpp:135
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
A time-ordered list of per-note expression events for one block.
Definition mpe_buffer.hpp:51
A time-ordered list of UMP events for one audio block.
Definition ump_buffer.hpp:41
Definition parameter_event_queue.hpp:82
Sample-accurate MPE expression event stream.
Definition analyzer_provider.hpp:12
What the product grants a device: the quota table indexed by capability tier and live thermal state.
Definition aax_editor.hpp:31
@ vector
a block of samples for a scope-style display
Definition editor_host.hpp:237
GraphRuntimeCommandStatus
Definition graph_runtime_executor.hpp:49
@ Accepted
Definition graph_runtime_executor.hpp:50
@ Rejected
Definition graph_runtime_executor.hpp:51
AudioRateModulationDelivery
Definition graph_runtime_executor.hpp:44
@ LegacyParameterEvents
Definition graph_runtime_executor.hpp:45
@ DenseViews
Definition graph_runtime_executor.hpp:46
GraphRuntimeCommandStatus(*)( ProcessBlock &block, const graph::GraphRuntimePlan &plan, const graph::GraphTimedCommand &command, void *user_data) noexcept GraphRuntimeCommandApplyFn
Definition graph_runtime_executor.hpp:116
bool(*)( ProcessBlock &block, const GraphRuntimeNodeProcessContext &context, void *user_data) noexcept GraphRuntimeNodeProcessFn
Definition graph_runtime_executor.hpp:111
GraphRuntimeExecutorErrorCode
Definition graph_runtime_executor.hpp:54
@ BufferPoolTooSmall
Definition graph_runtime_executor.hpp:61
@ MissingRequiredProcessor
Definition graph_runtime_executor.hpp:59
@ InvalidProcessBlock
Definition graph_runtime_executor.hpp:56
@ CommandScratchTooSmall
Definition graph_runtime_executor.hpp:58
@ None
Definition graph_runtime_executor.hpp:55
@ NodePortLimitExceeded
Definition graph_runtime_executor.hpp:62
@ NodeProcessorFailed
Definition graph_runtime_executor.hpp:60
@ InvalidSnapshot
Definition graph_runtime_executor.hpp:57
@ None
Definition ara.hpp:21
Definition graph_runtime_buffer_assignment.hpp:8
std::uint32_t NodeId
Definition graph_types.hpp:7
Definition custom_node_events.hpp:26
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
STL namespace.
Definition process_block.hpp:117
Definition graph_runtime_executor.hpp:34
std::uint32_t source_port
Definition graph_runtime_executor.hpp:39
std::uint32_t source_index
Definition graph_runtime_executor.hpp:38
graph::GraphRuntimeProbe samples
Definition graph_runtime_executor.hpp:37
std::uint32_t edge_index
Definition graph_runtime_executor.hpp:40
std::uint64_t timestamp_samples
Definition graph_runtime_executor.hpp:41
Definition graph_runtime_executor.hpp:572
std::uint32_t dense_lane_count
Definition graph_runtime_executor.hpp:573
std::size_t dense_node_index_bytes
Definition graph_runtime_executor.hpp:577
std::size_t dense_descriptor_bytes
Definition graph_runtime_executor.hpp:575
std::size_t dense_sample_bytes
Definition graph_runtime_executor.hpp:574
std::size_t dense_accumulator_bytes
Definition graph_runtime_executor.hpp:576
std::size_t total_dense_bytes() const noexcept
Definition graph_runtime_executor.hpp:579
Definition graph_runtime_executor.hpp:592
std::uint32_t param_id
Definition graph_runtime_executor.hpp:593
float hi
Definition graph_runtime_executor.hpp:597
float vN
Definition graph_runtime_executor.hpp:595
float lo
Definition graph_runtime_executor.hpp:596
bool has_add
Definition graph_runtime_executor.hpp:598
float v0
Definition graph_runtime_executor.hpp:594
Definition graph_runtime_executor.hpp:392
const float ** inputs
Definition graph_runtime_executor.hpp:396
bool gathers
Definition graph_runtime_executor.hpp:400
float ** outputs
Definition graph_runtime_executor.hpp:397
bool host_input_aliased
Definition graph_runtime_executor.hpp:403
GraphRuntimeNodeProcessContext context
Definition graph_runtime_executor.hpp:395
Definition graph_runtime_executor.hpp:339
float * data
Definition graph_runtime_executor.hpp:340
int * write_pos
Definition graph_runtime_executor.hpp:343
std::uint32_t size
Definition graph_runtime_executor.hpp:341
std::uint32_t delay
Definition graph_runtime_executor.hpp:342
Definition graph_runtime_executor.hpp:65
graph::GraphTimedCommand command
Definition graph_runtime_executor.hpp:66
GraphRuntimeCommandStatus status
Definition graph_runtime_executor.hpp:67
Definition graph_runtime_executor.hpp:122
void * user_data
Definition graph_runtime_executor.hpp:127
GraphRuntimeCommandApplyFn apply
Definition graph_runtime_executor.hpp:126
Definition graph_runtime_executor.hpp:246
bool(* push_event)(void *user_data, const graph::GraphEvent &event) noexcept
Definition graph_runtime_executor.hpp:248
void * user_data
Definition graph_runtime_executor.hpp:247
bool push(const graph::GraphEvent &event) const noexcept
Definition graph_runtime_executor.hpp:250
Definition graph_runtime_executor.hpp:273
std::uint32_t nodes_processed
Definition graph_runtime_executor.hpp:275
std::uint32_t commands_accepted
Definition graph_runtime_executor.hpp:277
GraphRuntimeExecutorErrorCode error
Definition graph_runtime_executor.hpp:274
std::uint32_t failed_node_index
Definition graph_runtime_executor.hpp:280
std::uint32_t commands_drained
Definition graph_runtime_executor.hpp:276
std::uint32_t events_dropped
Definition graph_runtime_executor.hpp:279
bool ok() const noexcept
Definition graph_runtime_executor.hpp:282
std::uint32_t commands_rejected
Definition graph_runtime_executor.hpp:278
Definition graph_runtime_executor.hpp:255
std::uint64_t commands_accepted
Definition graph_runtime_executor.hpp:259
std::uint64_t invalid_snapshots
Definition graph_runtime_executor.hpp:263
std::uint64_t events_dropped
Definition graph_runtime_executor.hpp:261
std::uint64_t commands_drained
Definition graph_runtime_executor.hpp:258
std::uint64_t blocks_processed
Definition graph_runtime_executor.hpp:256
std::uint64_t parallel_levels_dispatched
Definition graph_runtime_executor.hpp:269
std::uint64_t commands_rejected
Definition graph_runtime_executor.hpp:260
std::uint64_t nodes_processed
Definition graph_runtime_executor.hpp:257
std::uint64_t node_failures
Definition graph_runtime_executor.hpp:265
std::uint64_t serial_levels_run
Definition graph_runtime_executor.hpp:270
std::uint64_t command_scratch_failures
Definition graph_runtime_executor.hpp:264
std::uint64_t invalid_blocks
Definition graph_runtime_executor.hpp:262
Definition graph_runtime_executor.hpp:130
graph::NodeId node_id
Definition graph_runtime_executor.hpp:131
AudioRateModulationDelivery audio_rate_modulation_delivery
Definition graph_runtime_executor.hpp:141
bool required
Definition graph_runtime_executor.hpp:134
bool preserve_parameter_events
Definition graph_runtime_executor.hpp:143
bool reads_inputs_only
Definition graph_runtime_executor.hpp:149
GraphRuntimeNodeProcessFn process
Definition graph_runtime_executor.hpp:132
audio::AudioProcessLoadMeasurer * load
Definition graph_runtime_executor.hpp:140
const graph::SpectralFrameBatchView * spectral_input
Definition graph_runtime_executor.hpp:150
std::span< graph::SpectralFrameBatchView *const > spectral_outputs
Definition graph_runtime_executor.hpp:151
void * user_data
Definition graph_runtime_executor.hpp:133
Definition graph_runtime_executor.hpp:70
bool routed
Definition graph_runtime_executor.hpp:81
const graph::GraphRuntimePlan * plan
Definition graph_runtime_executor.hpp:71
const graph::GraphRuntimeNodePlan * node
Definition graph_runtime_executor.hpp:72
std::span< const AudioRateModulationView > node_audio_rate_modulations
Definition graph_runtime_executor.hpp:106
state::ParameterEventQueue * node_param_events
Definition graph_runtime_executor.hpp:103
midi::MidiBuffer * node_midi_in
Definition graph_runtime_executor.hpp:93
midi::MidiBuffer * node_midi_out
Definition graph_runtime_executor.hpp:94
midi::MpeBuffer * node_mpe_in
Definition graph_runtime_executor.hpp:97
audio::BufferView< const float > node_inputs
Definition graph_runtime_executor.hpp:85
std::span< graph::SpectralFrameBatchView *const > spectral_outputs
Definition graph_runtime_executor.hpp:108
const graph::SpectralFrameBatchView * spectral_input
Definition graph_runtime_executor.hpp:107
std::uint32_t node_index
Definition graph_runtime_executor.hpp:73
std::span< const GraphRuntimeCommandDecision > command_results
Definition graph_runtime_executor.hpp:74
audio::BufferView< float > node_outputs
Definition graph_runtime_executor.hpp:86
Definition graph_runtime_executor.hpp:193
Mode mode
Definition graph_runtime_executor.hpp:200
std::uint32_t slot
Definition graph_runtime_executor.hpp:201
Mode
Definition graph_runtime_executor.hpp:194
@ InPlace
Definition graph_runtime_executor.hpp:196
@ Copy
Definition graph_runtime_executor.hpp:197
@ Sum
Definition graph_runtime_executor.hpp:198
@ Zero
Definition graph_runtime_executor.hpp:195
Additive block-scoped process contract.
Definition process_block.hpp:517
Definition graph_runtime_queue.hpp:68
Definition graph_runtime_buffer_assignment.hpp:42
Definition graph_runtime_levelization.hpp:25
Definition graph_runtime_plan.hpp:113
Definition graph_runtime_plan.hpp:169
Definition graph_runtime_queue.hpp:50
Snapshot-owned payload and descriptors; callback borrows never own storage.
Definition spectral_frame.hpp:228
Sample-accurate MIDI 2.0 UMP packet stream.