Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
rt_safety_contract.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4#include <cstddef>
5#include <cstdint>
6#include <span>
7#include <string_view>
8
9namespace pulp::audio {
10
11enum class RtSafetyClass : std::uint8_t {
15 // Bounded reporting/counter operations that may run on the realtime thread,
16 // but are not signal-processing or mutable sampler-control operations.
21};
22
24 std::string_view component;
25 std::string_view operation;
28 bool may_allocate = false;
29 bool may_lock = false;
30 bool may_block = false;
31 bool requires_prepare = false;
32 std::string_view owner_boundary;
33 std::string_view notes;
34};
35
36[[nodiscard]] constexpr std::string_view rt_safety_class_name(
37 RtSafetyClass safety_class) noexcept {
38 switch (safety_class) {
40 return "audio-callback-safe";
42 return "audio-callback-safe-after-prepare";
44 return "audio-callback-safe-with-immutable-inputs";
46 return "realtime-telemetry-only";
48 return "control-thread-only";
50 return "background-thread-only";
52 return "offline-only";
53 }
54 return "unknown";
55}
56
58 RtSafetyClass safety_class) noexcept {
59 switch (safety_class) {
64 return true;
68 return false;
69 }
70 return false;
71}
72
73[[nodiscard]] constexpr bool rt_safety_class_is_audio_dsp_safe(
74 RtSafetyClass safety_class) noexcept {
75 switch (safety_class) {
79 return true;
84 return false;
85 }
86 return false;
87}
88
89namespace detail {
90
92 std::string_view component,
93 std::string_view operation,
94 RtSafetyClass safety_class,
95 bool audio_callback_allowed,
96 bool may_allocate,
97 bool may_lock,
98 bool may_block,
99 bool requires_prepare,
100 std::string_view owner_boundary,
101 std::string_view notes) noexcept {
102 return RtSafetyContract{component,
103 operation,
104 safety_class,
105 audio_callback_allowed,
106 may_allocate,
107 may_lock,
108 may_block,
109 requires_prepare,
110 owner_boundary,
111 notes};
112}
113
114} // namespace detail
115
116inline constexpr std::array kSamplerLooperRtSafetyContracts{
118 "PlanarAudioRingBuffer",
119 "read_write",
121 true,
122 false,
123 false,
124 false,
125 true,
126 "one source producer and one host consumer",
127 "Prepared fixed storage; short reads zero-fill and short writes drop excess frames."),
129 "AudioStreamHandoff",
130 "push_pull",
132 true,
133 false,
134 false,
135 false,
136 true,
137 "one generated-audio producer and one host consumer",
138 "Prepared resampler handoff; underruns are zero-filled."),
140 "RollingAudioCaptureBuffer",
141 "append_snapshot_hold",
143 true,
144 false,
145 false,
146 false,
147 true,
148 "single capture owner",
149 "Append/snapshot/hold are owner-thread operations; held appends are discarded and counted."),
151 "RollingAudioCaptureBuffer",
152 "materialize",
154 false,
155 false,
156 false,
157 false,
158 false,
159 "capture owner or paused external materializer",
160 "Materialization copies held or quiescent storage and must not race live append."),
162 "RealtimeSampleRecorder",
163 "enqueue_pop_process",
165 true,
166 false,
167 false,
168 false,
169 true,
170 "one command producer, one event consumer, one audio processor",
171 "Prepared queues/storage; commands are block-offset sorted inside process()."),
173 "RealtimeSampleRecorder",
174 "materialize_snapshot",
176 false,
177 false,
178 false,
179 false,
180 false,
181 "paused capture owner or active hold owner",
182 "Copies rolling-capture data into recorder storage outside the callback."),
184 "RealtimeSampleRecorder",
185 "release_reset_consume",
187 false,
188 false,
189 false,
190 false,
191 false,
192 "offline owner",
193 "Do not call concurrently with process(); use queued reset for live control."),
195 "SampleSlotBank",
196 "publish_from_buffer",
198 false,
199 false,
200 false,
201 false,
202 true,
203 "single serialized publication owner",
204 "Prepared fixed slots are mutated off callback and exposed through generation-safe views."),
206 "SampleSlotBank",
207 "read_published_view",
209 true,
210 false,
211 false,
212 false,
213 true,
214 "audio reader with externally retained slot generations",
215 "Audio code reads PublishedSampleView snapshots and generation-aware channel pointers only."),
217 "PublishedSampleStore",
218 "load_publish",
220 false,
221 true,
222 true,
223 true,
224 true,
225 "controller, importer, or background publication owner",
226 "Load/publish may copy, allocate, block, and take the store mutex."),
228 "PublishedSampleView",
229 "read",
231 true,
232 false,
233 false,
234 false,
235 false,
236 "generation-pinned audio reader",
237 "Borrowed channel pointers remain valid only while the published generation is retained."),
239 "SampleZoneMap",
240 "configure",
242 false,
243 true,
244 false,
245 false,
246 false,
247 "mapping owner",
248 "Copies zone metadata and publishes whole-map snapshots for realtime readers."),
250 "SampleZoneMap",
251 "select",
253 true,
254 false,
255 false,
256 false,
257 false,
258 "audio reader over immutable map snapshot",
259 "Selection owns no sample storage and performs bounded metadata lookup."),
261 "SamplePool",
262 "resolve",
264 true,
265 false,
266 false,
267 false,
268 false,
269 "audio reader over immutable pool snapshot",
270 "Borrowed PublishedSampleStore instances must outlive realtime readers."),
272 "InstrumentRuntime",
273 "trigger",
275 true,
276 false,
277 false,
278 false,
279 false,
280 "audio trigger path with immutable zone map and sample pool",
281 "Resolves pool-backed zones and playback rate without allocating voices."),
283 "InstrumentVoiceAllocator",
284 "trigger_release_finish",
286 true,
287 false,
288 false,
289 false,
290 true,
291 "audio-owned prepared voice slots",
292 "Mutates only the prepared voice array; publish telemetry through separate queues."),
294 "AhdsrEnvelope",
295 "note_render",
297 true,
298 false,
299 false,
300 false,
301 true,
302 "per-voice envelope owner",
303 "Prepared envelope state renders gains into caller-owned buffers."),
305 "VoiceModulationBuffer",
306 "begin_add_read",
308 true,
309 false,
310 false,
311 false,
312 true,
313 "per-voice or per-block modulation owner",
314 "Prepared lane/value storage is reused for each block."),
316 "SampleVoiceRenderer",
317 "render",
319 true,
320 false,
321 false,
322 false,
323 false,
324 "voice render owner with borrowed sample view and caller-owned scratch",
325 "Renders scalar one-shot or region playback into caller-owned output buffers."),
327 "VoiceSumMixer",
328 "mix",
330 true,
331 false,
332 false,
333 false,
334 false,
335 "caller-owned voice scratch and output buffers",
336 "Static mixer over non-overlapping source and destination buffers."),
338 "SampleStreamWindow",
339 "read_frames",
341 true,
342 false,
343 false,
344 false,
345 true,
346 "audio reader with generation-gated page lifetimes",
347 "Reads resident prepared pages only; never loads, fills, evicts, or performs file I/O."),
349 "LoopReader",
350 "read_validated",
352 true,
353 false,
354 false,
355 false,
356 false,
357 "renderer that already validated source region for the block",
358 "Unchecked fast path for hot loops; public read() remains the guard rail."),
360 "LoopRenderer",
361 "render",
363 true,
364 false,
365 false,
366 false,
367 false,
368 "voice or loop render owner with immutable source buffer",
369 "Stateful overwrite renderer for caller-owned scratch/output buffers."),
371 "SampleAssetView",
372 "read",
374 true,
375 false,
376 false,
377 false,
378 false,
379 "audio reader over an immutable asset publication",
380 "Metadata, preload pointer, and stream identity reads do not mutate the asset."),
382 "PreparedSampleInterpolation",
383 "footprint_evaluate",
385 true,
386 false,
387 false,
388 false,
389 false,
390 "renderer over prepared interpolation state and caller-owned taps",
391 "Footprint resolution and evaluation are bounded arithmetic over fixed spans."),
393 "LoopPlaybackCursor",
394 "frame_read_plan_advance",
396 true,
397 false,
398 false,
399 false,
400 true,
401 "audio-owned cursor with an immutable validated loop region",
402 "Read planning and cursor advancement mutate only bounded per-voice state."),
404 "SampleStreamRequestInbox",
405 "try_push_pop",
407 true,
408 false,
409 false,
410 false,
411 true,
412 "one demand producer and one scheduler consumer",
413 "Fixed-capacity SPSC demand publication drops overflow instead of allocating."),
415 "SampleStreamCommandInbox",
416 "demand_cancel",
418 true,
419 false,
420 false,
421 false,
422 true,
423 "one audio command producer and one cache-service consumer",
424 "Demand and cancellation commands carry fixed-size tokens through SPSC storage."),
426 "SampleStreamService",
427 "begin_complete_audio_page_read",
429 true,
430 false,
431 false,
432 false,
433 true,
434 "audio reader bracketing immutable page-view access",
435 "Epoch entry and completion protect cache-page reclamation without locks."),
437 "SampleStreamVoiceReader",
438 "plan_render",
440 true,
441 false,
442 false,
443 false,
444 true,
445 "audio-owned forward voice reader over prepared asset and page views",
446 "Narrow one-shot planning and rendering use fixed demand and tap storage."),
448 "SampleStreamLoopVoiceReader",
449 "plan_render",
451 true,
452 false,
453 false,
454 false,
455 true,
456 "audio-owned loop voice reader over prepared asset and page views",
457 "Loop-aware planning and rendering use fixed demand, page, cursor, and tap storage."),
459 "SampleStarvationEnvelope",
460 "next_gain",
462 true,
463 false,
464 false,
465 false,
466 true,
467 "per-voice starvation state owner",
468 "Gain transitions are bounded arithmetic over prepared fade lengths."),
470 "SampleAsset",
471 "prepare_release",
473 false,
474 true,
475 true,
476 true,
477 false,
478 "serialized asset owner after realtime readers quiesce",
479 "Preparation copies or adopts preload storage and publishes immutable metadata."),
481 "SampleStreamService",
482 "prepare_mutate_cache",
484 false,
485 true,
486 true,
487 true,
488 false,
489 "single cache-service owner",
490 "Preparation, fill completion, eviction, cancellation, and reclamation mutate shared cache state."),
492 "SampleStreamDecodePool",
493 "decode_work",
495 false,
496 false,
497 true,
498 true,
499 true,
500 "registered source worker selected by the decode pool",
501 "Workers may wait, call decoder bindings, and publish completion records."),
503 "SampleStreamAsyncService",
504 "owner_work",
506 false,
507 true,
508 true,
509 true,
510 true,
511 "single async-service owner coordinating cache and decode workers",
512 "Demand draining, reservations, completion handling, and retirement are off callback."),
514 "SampleMemoryGovernorHandle",
515 "reserve_stats",
517 false,
518 true,
519 true,
520 true,
521 false,
522 "sampler memory-budget owner",
523 "Reservations and statistics lock shared accounting and may create lease ownership."),
525 "SampleMemoryLease",
526 "reset_release",
528 false,
529 false,
530 true,
531 true,
532 false,
533 "lease owner outside realtime processing",
534 "Lease reset and destruction return bytes through locked shared accounting."),
536 "LoopPointAnalyzer",
537 "analyze",
539 false,
540 true,
541 false,
542 false,
543 false,
544 "editor, importer, or analysis job",
545 "May inspect long windows and run candidate search outside Processor::process()."),
547 "OnsetDetector",
548 "detect",
550 false,
551 true,
552 false,
553 false,
554 false,
555 "editor, importer, or analysis job",
556 "Produces onset markers for slice analysis outside package-specific APIs."),
558 "SlicePointAnalyzer",
559 "analyze",
561 false,
562 true,
563 false,
564 false,
565 false,
566 "editor, importer, or analysis job",
567 "Combines transient/beat/manual/imported markers into slice maps off callback."),
569 "TimePitchProcessor",
570 "prepare_process_release",
572 false,
573 true,
574 true,
575 true,
576 true,
577 "offline render, bounce, or analysis owner",
578 "Package-backed processors are optional and may initialize package/platform internals."),
580 "SampleAssetImporter",
581 "import",
583 false,
584 true,
585 true,
586 true,
587 false,
588 "importer or platform adapter",
589 "May do file I/O, codec work, locks, and decoded-audio allocation."),
591 "SampleAssetExporter",
592 "export",
594 false,
595 true,
596 true,
597 true,
598 false,
599 "exporter or platform adapter",
600 "May do file I/O, codec work, locks, and encoded-audio allocation."),
602 "SampleSlotMaterializer",
603 "publish_completed_recording",
605 false,
606 false,
607 false,
608 false,
609 false,
610 "publication owner with recorder and slot bank quiesced as required",
611 "Moves completed recorder data into prepared slots outside the callback."),
613 "WaveformOverview",
614 "build_and_cache",
616 false,
617 true,
618 true,
619 true,
620 false,
621 "waveform analysis/cache owner",
622 "Builds peaks, serializes cache payloads, and may read files, lock, or allocate."),
623};
624
625[[nodiscard]] constexpr std::span<const RtSafetyContract>
629
631 std::string_view component,
632 std::string_view operation) noexcept {
633 for (const auto& contract : kSamplerLooperRtSafetyContracts) {
634 if (contract.component == component && contract.operation == operation) {
635 return &contract;
636 }
637 }
638 return nullptr;
639}
640
641// Core realtime-runtime callback-boundary contracts. Companion to the
642// sampler/looper table above, extending the codified RT-safety registry beyond
643// the sampler to the lock-free primitives, the per-block automation queue, the
644// host graph walk, RealtimeResourceSlot's read side, the load-telemetry counter,
645// the denormal-mode guard, and the Processor DSP entry point. These labels are
646// DESCRIPTIVE — the actual
647// enforcement is the no-alloc/no-lock abort-trap in
648// test/native_components/rt_intercept_test_support.cpp plus the per-primitive
649// no-alloc tests; the same well-formedness invariants the sampler table is
650// drift-checked against (see test_sampler_rt_safety_contract.cpp) apply here
651// and are asserted in test_core_runtime_rt_safety_contract.cpp.
652inline constexpr std::array kCoreRuntimeRtSafetyContracts{
654 "SeqLock",
655 "read",
657 true,
658 false,
659 false,
660 false,
661 false,
662 "one writer thread; any number of reader threads",
663 "Lock-free coherent snapshot via acquire/release seq counter; readers "
664 "bounded-spin-retry on a concurrent write, never block on a lock."),
666 "SeqLock",
667 "write",
669 true,
670 false,
671 false,
672 false,
673 false,
674 "single writer thread",
675 "Relaxed atomic word/tail-byte payload copy bracketed by an odd/even "
676 "seq increment; cost scales with sizeof(T), so keep T small for "
677 "audio-thread writers."),
679 "TripleBuffer",
680 "read_write",
682 true,
683 false,
684 false,
685 false,
686 false,
687 "one writer thread and one reader thread",
688 "Lock-free latest-value publication via a dirty-bit CAS; the writer "
689 "copies whole T into the back buffer, so bound sizeof(T)."),
691 "SpscQueue",
692 "try_push_pop",
694 true,
695 false,
696 false,
697 false,
698 false,
699 "one producer thread and one consumer thread",
700 "Wraps choc SingleReaderSingleWriterFIFO; fixed capacity, overflow is "
701 "counted and the push fails rather than allocating."),
703 "RealtimeResourceSlot",
704 "get",
706 true,
707 false,
708 false,
709 false,
710 false,
711 "one non-RT publisher/reclaimer; any number of audio readers",
712 "Acquire-loads a raw prepared-resource pointer. The pointer is not "
713 "reader-pinned; the non-RT owner reclaims retired resources only after "
714 "a block-boundary grace point."),
716 "ParameterEventQueue",
717 "push_sort_clear",
719 true,
720 false,
721 false,
722 false,
723 false,
724 "per-block automation owner on the audio thread",
725 "Fixed-capacity (kCapacity) std::array storage; push/sort/clear never "
726 "allocate, overflow is dropped and counted. sort() is a stable "
727 "insertion sort tuned for near-sorted host automation."),
729 "AudioProcessLoadMeasurer",
730 "begin_end",
732 true,
733 false,
734 false,
735 false,
736 false,
737 "one measuring thread; any number of polling threads",
738 "steady_clock delta plus relaxed-atomic latest-value stores; emits "
739 "telemetry only, performs no signal processing or control mutation."),
741 "ScopedFlushDenormals",
742 "scope",
744 true,
745 false,
746 false,
747 false,
748 false,
749 "audio callback boundary owner",
750 "Sets the CPU flush-to-zero FP mode (MXCSR FTZ / FPCR.FZ) on entry and "
751 "restores the caller's mode on exit; no allocation, lock, or syscall."),
753 "SignalGraph",
754 "process",
756 true,
757 false,
758 false,
759 false,
760 true,
761 "audio thread over a prepared, published CompiledGraph snapshot",
762 "Loads the live snapshot via an atomic raw pointer under a reader-count "
763 "guard (deliberately not atomic<shared_ptr>), then walks ordered_runtime "
764 "single-threaded; topology mutation happens off the callback."),
766 "SignalGraph",
767 "inject_midi",
769 true,
770 false,
771 false,
772 false,
773 true,
774 "exactly one writer per MidiInput node; audio-thread snapshot consumer",
775 "Publishes a latest-wins, one-shot fixed-capacity mailbox batch. A "
776 "preserved node ID shares the mailbox across a gap-free graph swap; "
777 "concurrent writers are unsupported."),
779 "Processor",
780 "process",
782 true,
783 false,
784 false,
785 false,
786 true,
787 "audio thread; format adapter sets RT sidecars before the call",
788 "The plugin DSP entry point: no allocation, lock, I/O, or exceptions. "
789 "Adapters wrap it in ScopedNoAlloc; prepare() must run first."),
791 "GraphRuntimeExecutor",
792 "process_routed",
794 true,
795 false,
796 false,
797 false,
798 true,
799 "audio thread over a prepared snapshot + pre-sized GraphRuntimeBufferPool",
800 "Routes inter-node audio through caller-allocated scratch slots: "
801 "gather/scatter, the one-block feedback capture, and the per-connection "
802 "delay-compensation rings are bounded fills and copies, no allocation or "
803 "lock. The snapshot's buffer assignment and the pool (slots + delay "
804 "rings) are built off the callback; pool.fits() must hold for the block."),
805};
806
807[[nodiscard]] constexpr std::span<const RtSafetyContract>
811
813 std::string_view component,
814 std::string_view operation) noexcept {
815 for (const auto& contract : kCoreRuntimeRtSafetyContracts) {
816 if (contract.component == component && contract.operation == operation) {
817 return &contract;
818 }
819 }
820 return nullptr;
821}
822
823} // namespace pulp::audio
constexpr RtSafetyContract make_rt_safety_contract(std::string_view component, std::string_view operation, RtSafetyClass safety_class, bool audio_callback_allowed, bool may_allocate, bool may_lock, bool may_block, bool requires_prepare, std::string_view owner_boundary, std::string_view notes) noexcept
Definition rt_safety_contract.hpp:91
Definition analyzer_provider.hpp:12
constexpr std::array kCoreRuntimeRtSafetyContracts
Definition rt_safety_contract.hpp:652
constexpr bool rt_safety_class_may_be_called_from_realtime_thread(RtSafetyClass safety_class) noexcept
Definition rt_safety_contract.hpp:57
constexpr const RtSafetyContract * find_sampler_looper_rt_safety_contract(std::string_view component, std::string_view operation) noexcept
Definition rt_safety_contract.hpp:630
constexpr std::span< const RtSafetyContract > core_runtime_rt_safety_contracts() noexcept
Definition rt_safety_contract.hpp:808
constexpr std::array kSamplerLooperRtSafetyContracts
Definition rt_safety_contract.hpp:116
RtSafetyClass
Definition rt_safety_contract.hpp:11
@ ControlThreadOnly
Definition rt_safety_contract.hpp:18
@ BackgroundThreadOnly
Definition rt_safety_contract.hpp:19
@ AudioCallbackSafeAfterPrepare
Definition rt_safety_contract.hpp:13
@ AudioCallbackSafe
Definition rt_safety_contract.hpp:12
@ OfflineOnly
Definition rt_safety_contract.hpp:20
@ RealtimeTelemetryOnly
Definition rt_safety_contract.hpp:17
@ AudioCallbackSafeWithImmutableInputs
Definition rt_safety_contract.hpp:14
constexpr std::string_view rt_safety_class_name(RtSafetyClass safety_class) noexcept
Definition rt_safety_contract.hpp:36
constexpr std::span< const RtSafetyContract > sampler_looper_rt_safety_contracts() noexcept
Definition rt_safety_contract.hpp:626
constexpr const RtSafetyContract * find_core_runtime_rt_safety_contract(std::string_view component, std::string_view operation) noexcept
Definition rt_safety_contract.hpp:812
constexpr bool rt_safety_class_is_audio_dsp_safe(RtSafetyClass safety_class) noexcept
Definition rt_safety_contract.hpp:73
@ OfflineOnly
Definition analyzer_provider.hpp:52
Definition rt_safety_contract.hpp:23
bool may_lock
Definition rt_safety_contract.hpp:29
bool requires_prepare
Definition rt_safety_contract.hpp:31
bool may_block
Definition rt_safety_contract.hpp:30
std::string_view operation
Definition rt_safety_contract.hpp:25
bool audio_callback_allowed
Definition rt_safety_contract.hpp:27
bool may_allocate
Definition rt_safety_contract.hpp:28
RtSafetyClass safety_class
Definition rt_safety_contract.hpp:26
std::string_view owner_boundary
Definition rt_safety_contract.hpp:32
std::string_view component
Definition rt_safety_contract.hpp:24
std::string_view notes
Definition rt_safety_contract.hpp:33