Skip to main content

vst_audio_ring/
lib.rs

1#![forbid(unsafe_code)]
2
3//! A bounded, allocation-free shared-memory audio block ring.
4//!
5//! This is a Rust port of EqualizerAPO-XT `runtime/ipc/StreamRing.{h,cpp}` at
6//! commit `1432f61cc437c99fde8e8e84e10e648fe516d4ce`. Copyright 2026 115dkk.
7//! It is ported to MIT with the copyright holder's permission (ADR 0021).
8//!
9//! The wire image is little-endian. Every value is accessed atomically. The
10//! mapping is split into a prefix of [`AtomicU32`] words and a 128-byte suffix
11//! of [`AtomicU64`] words. This keeps f32 samples in `AtomicU32`, gives the
12//! counters their natural alignment, and never accesses one byte at two widths.
13//! A platform adapter must create those two non-overlapping slices and keep the
14//! mapping alive longer than the [`Region`].
15//!
16//! # Header layout
17//!
18//! | Offset | Bytes | Field |
19//! |---:|---:|---|
20//! | 0 | 4 | magic `SSRR` |
21//! | 4 | 4 | layout version |
22//! | 8 | 4 | total bytes, including the counter suffix |
23//! | 12 | 4 | lane count |
24//! | 16 | 4 | input channels |
25//! | 20 | 4 | output channels |
26//! | 24 | 4 | maximum frames |
27//! | 28 | 4 | sample rate, Hz |
28//! | 32 | 4 | latency bound, frames |
29//! | 36 | 4 | state |
30//! | 40 | 4 | fault code |
31//! | 44 | 4 | producer pid |
32//! | 48 | 4 | consumer pid |
33//! | 52 | 4 | sample format |
34//! | 56 | 4 | slots per lane |
35//! | 64 | 24 | lane geometry: first slot offset, slot bytes, slot count, per lane |
36//! | 128 | 8 | lane sequences (one u32 per lane; own cache line) |
37//! | 192 | 8 | lane completions (one u32 per lane; own cache line) |
38//! | 512 | variable | `slots` 64-byte-aligned sample slots per lane |
39//! | rounded slot end | 64 | heartbeat u64 and padding |
40//! | +64 | 64 | last error u64 and padding |
41
42use std::array;
43use std::fmt;
44use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
45use std::sync::{Arc, Condvar, Mutex};
46use std::time::{Duration, Instant};
47
48mod block;
49pub use block::{BlockError, BlockLane, Exchange};
50pub mod events;
51use events::{CONTEXT_WORDS, EVENT_WORDS, METADATA_WORDS, PACKET_WORDS};
52pub use events::{Event, EventBuffer, EventData, MAX_EVENTS, MAX_SYSEX_BYTES, Transport};
53#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
54#[repr(u32)]
55pub enum SampleFormat {
56    #[default]
57    F32 = 0,
58    F64 = 1,
59}
60impl SampleFormat {
61    pub fn words(self) -> usize {
62        if self == Self::F64 { 2 } else { 1 }
63    }
64}
65
66pub const MAGIC: u32 = u32::from_le_bytes(*b"SSRR");
67pub const LAYOUT_VERSION: u32 = 3;
68pub const HEADER_BYTES: usize = 512;
69pub const CACHE_LINE: usize = 64;
70pub const MAX_CHANNELS: u32 = 64;
71pub const MAX_FRAMES: u32 = 65_536;
72pub const MAX_SLOTS: u32 = 16;
73pub const MAX_REGION_BYTES: usize = 128 * 1024 * 1024;
74const COUNTER_BYTES: usize = 2 * CACHE_LINE;
75const HEADER_WORDS: usize = HEADER_BYTES / 4;
76const COUNTER_WORDS: usize = COUNTER_BYTES / 8;
77
78const MAGIC_I: usize = 0;
79const VERSION_I: usize = 1;
80const TOTAL_I: usize = 2;
81const LANES_I: usize = 3;
82const INPUTS_I: usize = 4;
83const OUTPUTS_I: usize = 5;
84const FRAMES_I: usize = 6;
85const RATE_I: usize = 7;
86const LATENCY_I: usize = 8;
87const STATE_I: usize = 9;
88const FAULT_I: usize = 10;
89const PRODUCER_PID_I: usize = 11;
90const CONSUMER_PID_I: usize = 12;
91const FORMAT_I: usize = 13;
92const SLOTS_I: usize = 14;
93const LANE_GEOMETRY_I: usize = 16;
94const SEQUENCE_I: usize = 32;
95const COMPLETED_I: usize = 48;
96
97#[derive(Clone, Copy, Debug, Eq, PartialEq)]
98#[repr(u32)]
99pub enum State {
100    Empty = 0,
101    Announced = 1,
102    Ready = 2,
103    Closing = 3,
104    Fault = 4,
105}
106
107impl State {
108    fn from_wire(value: u32) -> Option<Self> {
109        Some(match value {
110            0 => Self::Empty,
111            1 => Self::Announced,
112            2 => Self::Ready,
113            3 => Self::Closing,
114            4 => Self::Fault,
115            _ => return None,
116        })
117    }
118}
119
120#[derive(Clone, Copy, Debug, Eq, PartialEq)]
121#[repr(u32)]
122pub enum Fault {
123    None = 0,
124    LayoutMismatch = 1,
125    EngineFailed = 2,
126    HostShuttingDown = 3,
127}
128
129#[derive(Clone, Copy, Debug, Eq, PartialEq)]
130pub struct Geometry {
131    pub sample_format: SampleFormat,
132    pub lanes: u32,
133    pub slots: u32,
134    pub input_channels: u32,
135    pub output_channels: u32,
136    pub max_frames: u32,
137    pub sample_rate: u32,
138    pub latency_bound_frames: u32,
139}
140
141impl Geometry {
142    pub fn validate(self) -> Result<Self, GeometryError> {
143        if !(1..=2).contains(&self.lanes) {
144            return Err(GeometryError::LaneCount);
145        }
146        if !(2..=MAX_SLOTS).contains(&self.slots) {
147            return Err(GeometryError::Slots);
148        }
149        if self.input_channels > MAX_CHANNELS || self.output_channels > MAX_CHANNELS {
150            return Err(GeometryError::Channels);
151        }
152        if !(1..=MAX_FRAMES).contains(&self.max_frames) {
153            return Err(GeometryError::Frames);
154        }
155        if !(1_000..=1_000_000).contains(&self.sample_rate) {
156            return Err(GeometryError::SampleRate);
157        }
158        if layout(self).is_none() {
159            return Err(GeometryError::RegionTooLarge);
160        }
161        Ok(self)
162    }
163
164    pub fn planes(self) -> usize {
165        self.input_channels.max(self.output_channels) as usize
166    }
167
168    pub fn samples_per_slot(self) -> usize {
169        self.planes() * self.max_frames as usize
170    }
171
172    pub fn total_bytes(self) -> Result<usize, GeometryError> {
173        self.validate()?;
174        Ok(layout(self)
175            .expect("validated geometry has a layout")
176            .total_bytes)
177    }
178}
179
180#[derive(Clone, Copy, Debug, Eq, PartialEq)]
181pub enum GeometryError {
182    LaneCount,
183    Slots,
184    Channels,
185    Frames,
186    SampleRate,
187    RegionTooLarge,
188}
189
190#[derive(Clone, Copy, Debug)]
191struct Layout {
192    slot_bytes: usize,
193    first_slot_offsets: [usize; 2],
194    slots: u32,
195    prefix_bytes: usize,
196    total_bytes: usize,
197}
198
199impl Layout {
200    fn slot_offset(self, lane: usize, sequence: u32) -> usize {
201        self.first_slot_offsets[lane] + (sequence % self.slots) as usize * self.slot_bytes
202    }
203}
204
205fn round_up(value: usize, alignment: usize) -> Option<usize> {
206    value
207        .checked_add(alignment - 1)
208        .map(|v| v / alignment * alignment)
209}
210
211fn layout(geometry: Geometry) -> Option<Layout> {
212    let audio_words = geometry
213        .samples_per_slot()
214        .checked_mul(geometry.sample_format.words())?;
215    let slot_bytes = round_up(
216        audio_words.checked_add(METADATA_WORDS)?.checked_mul(4)?,
217        CACHE_LINE,
218    )?;
219    let mut offset = HEADER_BYTES;
220    let mut first_slot_offsets = [0; 2];
221    for first_slot_offset in first_slot_offsets.iter_mut().take(geometry.lanes as usize) {
222        *first_slot_offset = offset;
223        offset = offset.checked_add(slot_bytes.checked_mul(geometry.slots as usize)?)?;
224    }
225    let prefix_bytes = round_up(offset, CACHE_LINE)?;
226    let total_bytes = prefix_bytes.checked_add(COUNTER_BYTES)?;
227    (total_bytes < MAX_REGION_BYTES).then_some(Layout {
228        slot_bytes,
229        first_slot_offsets,
230        slots: geometry.slots,
231        prefix_bytes,
232        total_bytes,
233    })
234}
235
236pub trait Signal: Send + Sync {
237    fn signal(&self);
238}
239
240impl<T: Signal + ?Sized> Signal for Arc<T> {
241    fn signal(&self) {
242        T::signal(self);
243    }
244}
245
246impl<T: Signal + ?Sized> Signal for Box<T> {
247    fn signal(&self) {
248        T::signal(self);
249    }
250}
251
252pub trait Wait: Send + Sync {
253    fn wait(&self, timeout: Duration) -> Waited;
254}
255
256#[derive(Clone, Copy, Debug, Eq, PartialEq)]
257pub enum Waited {
258    Signalled,
259    TimedOut,
260    PeerGone,
261}
262
263#[derive(Debug)]
264struct EventInner {
265    generation: AtomicU64,
266    gone: AtomicBool,
267    observed: Mutex<u64>,
268    changed: Condvar,
269}
270
271/// Same-process event for tests and adapters. `signal` only performs an atomic
272/// increment and a condition-variable notification; it neither locks nor
273/// allocates.
274#[derive(Clone, Debug)]
275pub struct InProcessEvent(Arc<EventInner>);
276
277impl InProcessEvent {
278    pub fn new() -> Self {
279        Self(Arc::new(EventInner {
280            generation: AtomicU64::new(0),
281            gone: AtomicBool::new(false),
282            observed: Mutex::new(0),
283            changed: Condvar::new(),
284        }))
285    }
286
287    pub fn peer_gone(&self) {
288        self.0.gone.store(true, Ordering::Release);
289        self.0.changed.notify_all();
290    }
291}
292
293impl Default for InProcessEvent {
294    fn default() -> Self {
295        Self::new()
296    }
297}
298
299impl Signal for InProcessEvent {
300    fn signal(&self) {
301        self.0.generation.fetch_add(1, Ordering::Release);
302        self.0.changed.notify_one();
303    }
304}
305
306impl Wait for InProcessEvent {
307    fn wait(&self, timeout: Duration) -> Waited {
308        if self.0.gone.load(Ordering::Acquire) {
309            return Waited::PeerGone;
310        }
311        let mut observed = self.0.observed.lock().unwrap_or_else(|e| e.into_inner());
312        let current = self.0.generation.load(Ordering::Acquire);
313        if current != *observed {
314            *observed = current;
315            return Waited::Signalled;
316        }
317        let (mut observed, result) = self
318            .0
319            .changed
320            .wait_timeout_while(observed, timeout, |seen| {
321                !self.0.gone.load(Ordering::Acquire)
322                    && self.0.generation.load(Ordering::Acquire) == *seen
323            })
324            .unwrap_or_else(|e| e.into_inner());
325        if self.0.gone.load(Ordering::Acquire) {
326            Waited::PeerGone
327        } else {
328            let current = self.0.generation.load(Ordering::Acquire);
329            if current != *observed {
330                *observed = current;
331                Waited::Signalled
332            } else if result.timed_out() {
333                Waited::TimedOut
334            } else {
335                Waited::Signalled
336            }
337        }
338    }
339}
340
341/// Borrowed shared mapping. The u32 prefix contains the header and samples;
342/// the u64 suffix contains only heartbeat and last-error cache lines.
343#[derive(Clone, Copy, Debug)]
344pub struct Region<'a> {
345    words32: &'a [AtomicU32],
346    words64: &'a [AtomicU64],
347}
348
349impl<'a> Region<'a> {
350    pub fn new(words32: &'a [AtomicU32], words64: &'a [AtomicU64]) -> Self {
351        Self { words32, words64 }
352    }
353
354    pub fn state(self) -> Option<State> {
355        State::from_wire(self.load32(STATE_I, Ordering::Acquire))
356    }
357
358    pub fn fault_code(self) -> u32 {
359        self.load32(FAULT_I, Ordering::Acquire)
360    }
361
362    pub fn heartbeat(self) -> u64 {
363        self.words64
364            .first()
365            .map_or(0, |v| v.load(Ordering::Acquire))
366    }
367
368    pub fn last_error(self) -> u64 {
369        self.words64
370            .get(CACHE_LINE / 8)
371            .map_or(0, |v| v.load(Ordering::Acquire))
372    }
373
374    fn load32(self, index: usize, ordering: Ordering) -> u32 {
375        self.words32.get(index).map_or(0, |v| v.load(ordering))
376    }
377
378    fn store32(self, index: usize, value: u32, ordering: Ordering) {
379        if let Some(word) = self.words32.get(index) {
380            word.store(value, ordering);
381        }
382    }
383}
384
385#[derive(Debug)]
386pub struct RegionBuf {
387    words32: Box<[AtomicU32]>,
388    words64: Box<[AtomicU64]>,
389}
390
391impl RegionBuf {
392    pub fn new(geometry: Geometry) -> Result<Self, GeometryError> {
393        let geometry = geometry.validate()?;
394        let layout = layout(geometry).expect("validated geometry has a layout");
395        Ok(Self {
396            words32: (0..layout.prefix_bytes / 4)
397                .map(|_| AtomicU32::new(0))
398                .collect(),
399            words64: (0..COUNTER_WORDS).map(|_| AtomicU64::new(0)).collect(),
400        })
401    }
402
403    pub fn region(&self) -> Region<'_> {
404        Region::new(&self.words32, &self.words64)
405    }
406}
407
408/// Memory that owns a mapping and lends it as a [`Region`]: an in-process
409/// [`RegionBuf`], or a platform adapter's shared-memory view. An owner that
410/// outlives the borrow lets a [`Publisher`] travel to another thread without
411/// the [`Producer`] that announced the header.
412pub trait RegionMemory: Send + Sync {
413    fn region(&self) -> Region<'_>;
414}
415
416impl RegionMemory for RegionBuf {
417    fn region(&self) -> Region<'_> {
418        self.region()
419    }
420}
421
422impl<T: RegionMemory + ?Sized> RegionMemory for Arc<T> {
423    fn region(&self) -> Region<'_> {
424        T::region(self)
425    }
426}
427
428impl<T: RegionMemory + ?Sized> RegionMemory for Box<T> {
429    fn region(&self) -> Region<'_> {
430        T::region(self)
431    }
432}
433
434#[derive(Clone, Copy, Debug, Eq, PartialEq)]
435pub enum HeaderError {
436    TooShort,
437    Magic,
438    Version,
439    State,
440    Geometry(GeometryError),
441    TotalBytes,
442    LaneGeometry,
443}
444
445#[derive(Clone, Copy, Debug, Eq, PartialEq)]
446pub enum Publish {
447    Published,
448    Dropped,
449}
450
451/// The producer's real-time half: the announced geometry and slot layout,
452/// cached so the per-block path needs no [`Producer`] and no borrow of the
453/// mapping beyond each call. Every method takes the [`Region`] it works on and
454/// neither blocks, locks nor allocates; `work` is only signalled.
455#[derive(Clone, Copy, Debug)]
456pub struct Publisher {
457    geometry: Geometry,
458    layout: Layout,
459}
460
461impl Publisher {
462    pub fn geometry(&self) -> Geometry {
463        self.geometry
464    }
465
466    pub fn state(&self, region: Region<'_>) -> Option<State> {
467        region.state()
468    }
469
470    /// The last sequence the consumer completed on `lane` (0 outside the
471    /// geometry).
472    pub fn completed(&self, region: Region<'_>, lane: usize) -> u32 {
473        if lane >= self.geometry.lanes as usize {
474            return 0;
475        }
476        u32::from_le(region.load32(COMPLETED_I + lane, Ordering::Acquire))
477    }
478
479    /// Writes `sequence` (never 0) into its slot and publishes it, then
480    /// signals `work`. `Dropped`, touching nothing, unless the consumer is
481    /// `Ready` and has completed `sequence - slots`, whose slot this reuses.
482    /// Sequences start at 1, advance once per publish, and must not wrap; a
483    /// lane therefore carries at most `u32::MAX` sequences.
484    pub fn try_publish<F>(
485        &self,
486        region: Region<'_>,
487        lane: usize,
488        sequence: u32,
489        work: Option<&dyn Signal>,
490        write: F,
491    ) -> Publish
492    where
493        F: FnOnce(&mut SlotWriter<'_>),
494    {
495        if region.state() != Some(State::Ready)
496            || lane >= self.geometry.lanes as usize
497            || sequence == 0
498        {
499            return Publish::Dropped;
500        }
501        let completed = self.completed(region, lane);
502        if sequence > self.geometry.slots
503            && !sequence_at_least(completed, sequence - self.geometry.slots)
504        {
505            return Publish::Dropped;
506        }
507        let offset = self.layout.slot_offset(lane, sequence) / 4;
508        let Some(samples) = region.words32.get(
509            offset..offset + self.geometry.samples_per_slot() * self.geometry.sample_format.words(),
510        ) else {
511            return Publish::Dropped;
512        };
513        let meta_offset =
514            offset + self.geometry.samples_per_slot() * self.geometry.sample_format.words();
515        let metadata = &region.words32[meta_offset..meta_offset + METADATA_WORDS];
516        let mut writer = SlotWriter {
517            samples,
518            metadata,
519            format: self.geometry.sample_format,
520            frames: self.geometry.max_frames as usize,
521            planes: self.geometry.planes(),
522        };
523        writer.set_transport(Transport::default());
524        writer.set_events(&[]);
525        metadata[CONTEXT_WORDS + PACKET_WORDS].store(0, Ordering::Relaxed);
526        metadata[CONTEXT_WORDS + PACKET_WORDS + 1].store(0, Ordering::Relaxed);
527        write(&mut writer);
528        region.store32(SEQUENCE_I + lane, sequence.to_le(), Ordering::Release);
529        if let Some(signal) = work {
530            signal.signal();
531        }
532        Publish::Published
533    }
534
535    /// Lends `sequence`'s slot to `read` once the consumer has completed it.
536    pub fn try_read<F>(&self, region: Region<'_>, lane: usize, sequence: u32, read: F) -> bool
537    where
538        F: FnOnce(SlotReader<'_>),
539    {
540        if lane >= self.geometry.lanes as usize
541            || !sequence_at_least(self.completed(region, lane), sequence)
542        {
543            return false;
544        }
545        let published = u32::from_le(region.load32(SEQUENCE_I + lane, Ordering::Acquire));
546        if published.wrapping_sub(sequence) >= self.geometry.slots {
547            return false;
548        }
549        let offset = self.layout.slot_offset(lane, sequence) / 4;
550        let Some(samples) = region.words32.get(
551            offset..offset + self.geometry.samples_per_slot() * self.geometry.sample_format.words(),
552        ) else {
553            return false;
554        };
555        let meta_offset =
556            offset + self.geometry.samples_per_slot() * self.geometry.sample_format.words();
557        read(SlotReader {
558            samples,
559            metadata: &region.words32[meta_offset..meta_offset + METADATA_WORDS],
560            format: self.geometry.sample_format,
561            frames: self.geometry.max_frames as usize,
562            planes: self.geometry.planes(),
563        });
564        true
565    }
566}
567
568pub struct Producer<'a> {
569    region: Region<'a>,
570    geometry: Option<Geometry>,
571    layout: Option<Layout>,
572    work: [Option<&'a dyn Signal>; 2],
573    ready: &'a dyn Wait,
574}
575
576impl fmt::Debug for Producer<'_> {
577    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
578        formatter
579            .debug_struct("Producer")
580            .field("region", &self.region)
581            .field("geometry", &self.geometry)
582            .field("layout", &self.layout)
583            .finish_non_exhaustive()
584    }
585}
586
587impl<'a> Producer<'a> {
588    pub fn new(region: Region<'a>, work: [Option<&'a dyn Signal>; 2], ready: &'a dyn Wait) -> Self {
589        Self {
590            region,
591            geometry: None,
592            layout: None,
593            work,
594            ready,
595        }
596    }
597
598    pub fn announce(&mut self, geometry: Geometry, producer_pid: u32) -> Result<(), GeometryError> {
599        let geometry = geometry.validate()?;
600        let layout = layout(geometry).expect("validated geometry has a layout");
601        if self.region.words32.len() * 4 < layout.prefix_bytes
602            || self.region.words64.len() < COUNTER_WORDS
603        {
604            return Err(GeometryError::RegionTooLarge);
605        }
606        for word in self.region.words32.iter().take(HEADER_WORDS) {
607            word.store(0, Ordering::Relaxed);
608        }
609        for word in self.region.words64 {
610            word.store(0, Ordering::Relaxed);
611        }
612        let fields = [
613            (MAGIC_I, MAGIC),
614            (VERSION_I, LAYOUT_VERSION),
615            (TOTAL_I, layout.total_bytes as u32),
616            (LANES_I, geometry.lanes),
617            (INPUTS_I, geometry.input_channels),
618            (OUTPUTS_I, geometry.output_channels),
619            (FRAMES_I, geometry.max_frames),
620            (RATE_I, geometry.sample_rate),
621            (FORMAT_I, geometry.sample_format as u32),
622            (SLOTS_I, geometry.slots),
623            (LATENCY_I, geometry.latency_bound_frames),
624            (PRODUCER_PID_I, producer_pid),
625        ];
626        for (index, value) in fields {
627            self.region.store32(index, value.to_le(), Ordering::Relaxed);
628        }
629        for lane in 0..geometry.lanes as usize {
630            let base = LANE_GEOMETRY_I + lane * 3;
631            self.region.store32(
632                base,
633                (layout.first_slot_offsets[lane] as u32).to_le(),
634                Ordering::Relaxed,
635            );
636            self.region.store32(
637                base + 1,
638                (layout.slot_bytes as u32).to_le(),
639                Ordering::Relaxed,
640            );
641            self.region
642                .store32(base + 2, geometry.slots.to_le(), Ordering::Relaxed);
643        }
644        self.region
645            .store32(FAULT_I, Fault::None as u32, Ordering::Release);
646        self.region
647            .store32(STATE_I, State::Announced as u32, Ordering::Release);
648        self.geometry = Some(geometry);
649        self.layout = Some(layout);
650        Ok(())
651    }
652
653    pub fn wait_ready(&self, timeout: Duration) -> Waited {
654        let deadline = Instant::now() + timeout;
655        loop {
656            match self.region.state() {
657                Some(State::Ready) => return Waited::Signalled,
658                Some(State::Fault | State::Closing) | None => return Waited::PeerGone,
659                _ => {}
660            }
661            let now = Instant::now();
662            if now >= deadline {
663                return Waited::TimedOut;
664            }
665            match self.ready.wait(deadline.saturating_duration_since(now)) {
666                Waited::Signalled => {}
667                other => return other,
668            }
669        }
670    }
671
672    /// The real-time half, once [`Producer::announce`] has succeeded.
673    pub fn publisher(&self) -> Option<Publisher> {
674        Some(Publisher {
675            geometry: self.geometry?,
676            layout: self.layout?,
677        })
678    }
679
680    pub fn completed(&self, lane: usize) -> u32 {
681        self.publisher()
682            .map_or(0, |publisher| publisher.completed(self.region, lane))
683    }
684
685    pub fn try_publish<F>(&self, lane: usize, sequence: u32, write: F) -> Publish
686    where
687        F: FnOnce(&mut SlotWriter<'_>),
688    {
689        let Some(publisher) = self.publisher() else {
690            return Publish::Dropped;
691        };
692        publisher.try_publish(self.region, lane, sequence, self.work[lane], write)
693    }
694
695    pub fn try_read<F>(&self, lane: usize, sequence: u32, read: F) -> bool
696    where
697        F: FnOnce(SlotReader<'_>),
698    {
699        self.publisher()
700            .is_some_and(|publisher| publisher.try_read(self.region, lane, sequence, read))
701    }
702
703    pub fn close(&self) {
704        self.region
705            .store32(STATE_I, State::Closing as u32, Ordering::Release);
706        for signal in self.work.iter().flatten() {
707            signal.signal();
708        }
709    }
710}
711
712#[derive(Debug)]
713pub struct SlotWriter<'a> {
714    metadata: &'a [AtomicU32],
715    format: SampleFormat,
716    samples: &'a [AtomicU32],
717    frames: usize,
718    planes: usize,
719}
720
721impl SlotWriter<'_> {
722    pub fn frames(&self) -> usize {
723        self.frames
724    }
725
726    pub fn planes(&self) -> usize {
727        self.planes
728    }
729
730    pub fn write(&mut self, plane: usize, frame: usize, sample: f32) {
731        self.write_f64(plane, frame, f64::from(sample));
732    }
733
734    pub fn write_f64(&mut self, plane: usize, frame: usize, sample: f64) {
735        let index = (plane * self.frames + frame) * self.format.words();
736        if self.format == SampleFormat::F64 {
737            self.samples[index].store((sample.to_bits() as u32).to_le(), Ordering::Relaxed);
738            self.samples[index + 1]
739                .store(((sample.to_bits() >> 32) as u32).to_le(), Ordering::Relaxed);
740        } else {
741            self.samples[index].store((sample as f32).to_bits().to_le(), Ordering::Relaxed);
742        }
743    }
744
745    pub fn write_plane(&mut self, plane: usize, samples: &[f32]) -> bool {
746        if plane >= self.planes || samples.len() != self.frames {
747            return false;
748        }
749        let words = self.format.words();
750        let destination =
751            &self.samples[plane * self.frames * words..(plane + 1) * self.frames * words];
752        if self.format == SampleFormat::F64 {
753            for (to, from) in destination.as_chunks::<2>().0.iter().zip(samples) {
754                let bits = f64::from(*from).to_bits();
755                to[0].store((bits as u32).to_le(), Ordering::Relaxed);
756                to[1].store(((bits >> 32) as u32).to_le(), Ordering::Relaxed);
757            }
758        } else {
759            for (to, from) in destination.iter().zip(samples) {
760                to.store(from.to_bits().to_le(), Ordering::Relaxed);
761            }
762        }
763        true
764    }
765
766    pub fn write_plane_f64(&mut self, plane: usize, samples: &[f64]) -> bool {
767        if plane >= self.planes || samples.len() != self.frames {
768            return false;
769        }
770        let words = self.format.words();
771        let destination =
772            &self.samples[plane * self.frames * words..(plane + 1) * self.frames * words];
773        if self.format == SampleFormat::F64 {
774            for (to, from) in destination.as_chunks::<2>().0.iter().zip(samples) {
775                let bits = from.to_bits();
776                to[0].store((bits as u32).to_le(), Ordering::Relaxed);
777                to[1].store(((bits >> 32) as u32).to_le(), Ordering::Relaxed);
778            }
779        } else {
780            for (to, from) in destination.iter().zip(samples) {
781                to.store((*from as f32).to_bits().to_le(), Ordering::Relaxed);
782            }
783        }
784        true
785    }
786
787    pub fn clear_plane(&mut self, plane: usize) -> bool {
788        if plane >= self.planes {
789            return false;
790        }
791        let words = self.format.words();
792        let destination =
793            &self.samples[plane * self.frames * words..(plane + 1) * self.frames * words];
794        for word in destination {
795            word.store(0, Ordering::Relaxed);
796        }
797        true
798    }
799
800    pub fn set_transport(&mut self, transport: Transport) {
801        for (to, from) in self.metadata.iter().zip(transport.words()) {
802            to.store(from.to_le(), Ordering::Relaxed);
803        }
804    }
805    pub fn set_events(&mut self, events: &[Event]) -> bool {
806        if events.len() > MAX_EVENTS || events.iter().any(|e| !e.valid(self.frames as u32)) {
807            return false;
808        }
809        store_packet(
810            &self.metadata[CONTEXT_WORDS..CONTEXT_WORDS + PACKET_WORDS],
811            events,
812            0,
813        );
814        true
815    }
816    pub fn copy_from(&mut self, samples: &[f32]) -> bool {
817        if self.format != SampleFormat::F32 || samples.len() != self.samples.len() {
818            return false;
819        }
820        for (destination, sample) in self.samples.iter().zip(samples) {
821            destination.store(sample.to_bits(), Ordering::Relaxed);
822        }
823        true
824    }
825}
826
827#[derive(Clone, Copy, Debug)]
828pub struct SlotReader<'a> {
829    metadata: &'a [AtomicU32],
830    format: SampleFormat,
831    samples: &'a [AtomicU32],
832    frames: usize,
833    planes: usize,
834}
835
836impl SlotReader<'_> {
837    pub fn frames(self) -> usize {
838        self.frames
839    }
840
841    pub fn planes(self) -> usize {
842        self.planes
843    }
844
845    pub fn read(self, plane: usize, frame: usize) -> f32 {
846        self.read_f64(plane, frame) as f32
847    }
848
849    pub fn read_f64(self, plane: usize, frame: usize) -> f64 {
850        let index = (plane * self.frames + frame) * self.format.words();
851        let lo = u32::from_le(self.samples[index].load(Ordering::Relaxed));
852        if self.format == SampleFormat::F64 {
853            let hi = u32::from_le(self.samples[index + 1].load(Ordering::Relaxed));
854            f64::from_bits(u64::from(lo) | (u64::from(hi) << 32))
855        } else {
856            f64::from(f32::from_bits(lo))
857        }
858    }
859
860    pub fn read_plane(self, plane: usize, samples: &mut [f32]) -> bool {
861        if plane >= self.planes || samples.len() != self.frames {
862            return false;
863        }
864        let words = self.format.words();
865        let source = &self.samples[plane * self.frames * words..(plane + 1) * self.frames * words];
866        if self.format == SampleFormat::F64 {
867            for (to, from) in samples.iter_mut().zip(source.as_chunks::<2>().0.iter()) {
868                let lo = u32::from_le(from[0].load(Ordering::Relaxed));
869                let hi = u32::from_le(from[1].load(Ordering::Relaxed));
870                *to = f64::from_bits(u64::from(lo) | (u64::from(hi) << 32)) as f32;
871            }
872        } else {
873            for (to, from) in samples.iter_mut().zip(source) {
874                *to = f32::from_bits(u32::from_le(from.load(Ordering::Relaxed)));
875            }
876        }
877        true
878    }
879
880    pub fn read_plane_f64(self, plane: usize, samples: &mut [f64]) -> bool {
881        if plane >= self.planes || samples.len() != self.frames {
882            return false;
883        }
884        let words = self.format.words();
885        let source = &self.samples[plane * self.frames * words..(plane + 1) * self.frames * words];
886        if self.format == SampleFormat::F64 {
887            for (to, from) in samples.iter_mut().zip(source.as_chunks::<2>().0.iter()) {
888                let lo = u32::from_le(from[0].load(Ordering::Relaxed));
889                let hi = u32::from_le(from[1].load(Ordering::Relaxed));
890                *to = f64::from_bits(u64::from(lo) | (u64::from(hi) << 32));
891            }
892        } else {
893            for (to, from) in samples.iter_mut().zip(source) {
894                *to = f64::from(f32::from_bits(u32::from_le(from.load(Ordering::Relaxed))));
895            }
896        }
897        true
898    }
899
900    pub fn output_events(self, events: &mut EventBuffer) -> bool {
901        load_packet(
902            &self.metadata[CONTEXT_WORDS + PACKET_WORDS..],
903            self.frames as u32,
904            events,
905        )
906    }
907    pub fn copy_to(self, samples: &mut [f32]) -> bool {
908        if self.format != SampleFormat::F32 || samples.len() != self.samples.len() {
909            return false;
910        }
911        for (destination, sample) in samples.iter_mut().zip(self.samples) {
912            *destination = f32::from_bits(sample.load(Ordering::Relaxed));
913        }
914        true
915    }
916}
917
918fn store_packet(words: &[AtomicU32], events: &[Event], dropped: u32) {
919    words[0].store((events.len() as u32).to_le(), Ordering::Relaxed);
920    words[1].store(dropped.to_le(), Ordering::Relaxed);
921    for (target, event) in words[2..].as_chunks::<EVENT_WORDS>().0.iter().zip(events) {
922        for (to, from) in target.iter().zip(event.words()) {
923            to.store(from.to_le(), Ordering::Relaxed);
924        }
925    }
926}
927fn load_packet(words: &[AtomicU32], frames: u32, events: &mut EventBuffer) -> bool {
928    events.clear();
929    let count = u32::from_le(words[0].load(Ordering::Relaxed)) as usize;
930    if count > MAX_EVENTS {
931        return false;
932    }
933    events.dropped = u32::from_le(words[1].load(Ordering::Relaxed));
934    for source in words[2..].as_chunks::<EVENT_WORDS>().0.iter().take(count) {
935        let mut values = [0; EVENT_WORDS];
936        for (to, from) in values.iter_mut().zip(source) {
937            *to = u32::from_le(from.load(Ordering::Relaxed));
938        }
939        let Some(event) = Event::from_words(&values, frames) else {
940            events.clear();
941            return false;
942        };
943        events.push(event);
944    }
945    true
946}
947
948pub struct Consumer<'a> {
949    region: Region<'a>,
950    geometry: Option<Geometry>,
951    layout: Option<Layout>,
952    ready: &'a dyn Signal,
953    done: [Option<&'a dyn Signal>; 2],
954}
955
956impl fmt::Debug for Consumer<'_> {
957    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
958        formatter
959            .debug_struct("Consumer")
960            .field("region", &self.region)
961            .field("geometry", &self.geometry)
962            .field("layout", &self.layout)
963            .finish_non_exhaustive()
964    }
965}
966
967impl<'a> Consumer<'a> {
968    pub fn new(
969        region: Region<'a>,
970        ready: &'a dyn Signal,
971        done: [Option<&'a dyn Signal>; 2],
972    ) -> Self {
973        Self {
974            region,
975            geometry: None,
976            layout: None,
977            ready,
978            done,
979        }
980    }
981
982    pub fn validate_and_ready(&mut self, consumer_pid: u32) -> Result<Geometry, HeaderError> {
983        let geometry = validate_header(self.region)?;
984        let layout = layout(geometry).expect("validated geometry has a layout");
985        self.geometry = Some(geometry);
986        self.layout = Some(layout);
987        self.region
988            .store32(CONSUMER_PID_I, consumer_pid.to_le(), Ordering::Relaxed);
989        self.region
990            .store32(FAULT_I, Fault::None as u32, Ordering::Relaxed);
991        self.region
992            .store32(STATE_I, State::Ready as u32, Ordering::Release);
993        self.ready.signal();
994        Ok(geometry)
995    }
996
997    pub fn fault(&self, code: u32) {
998        self.region.store32(FAULT_I, code, Ordering::Relaxed);
999        self.region
1000            .store32(STATE_I, State::Fault as u32, Ordering::Release);
1001        self.ready.signal();
1002        for signal in self.done.iter().flatten() {
1003            signal.signal();
1004        }
1005    }
1006
1007    pub fn next_published(&self, lane: usize) -> Option<u32> {
1008        let geometry = self.geometry?;
1009        if lane >= geometry.lanes as usize || self.region.state() != Some(State::Ready) {
1010            return None;
1011        }
1012        let published = u32::from_le(self.region.load32(SEQUENCE_I + lane, Ordering::Acquire));
1013        let completed = u32::from_le(self.region.load32(COMPLETED_I + lane, Ordering::Acquire));
1014        let next = completed.wrapping_add(1);
1015        (published != completed && sequence_at_least(published, next)).then_some(next)
1016    }
1017
1018    /// Copies one slot to `buffer`, lets the caller process ordinary f32s, and
1019    /// copies it back. No plugin ever receives shared-memory atomics.
1020    pub fn slot_mut<F>(&self, lane: usize, sequence: u32, buffer: &mut [f32], process: F) -> bool
1021    where
1022        F: FnOnce(&mut [f32]),
1023    {
1024        let Some((geometry, layout)) = self.geometry.zip(self.layout) else {
1025            return false;
1026        };
1027        if geometry.sample_format != SampleFormat::F32
1028            || lane >= geometry.lanes as usize
1029            || buffer.len() != geometry.samples_per_slot()
1030        {
1031            return false;
1032        }
1033        let offset = layout.slot_offset(lane, sequence) / 4;
1034        let samples = &self.region.words32[offset..offset + buffer.len()];
1035        for (destination, sample) in buffer.iter_mut().zip(samples) {
1036            *destination = f32::from_bits(sample.load(Ordering::Relaxed));
1037        }
1038        process(buffer);
1039        for (destination, sample) in samples.iter().zip(buffer) {
1040            destination.store(sample.to_bits(), Ordering::Relaxed);
1041        }
1042        true
1043    }
1044
1045    pub fn slot_mut_f64<F>(
1046        &self,
1047        lane: usize,
1048        sequence: u32,
1049        buffer: &mut [f64],
1050        process: F,
1051    ) -> bool
1052    where
1053        F: FnOnce(&mut [f64]),
1054    {
1055        let Some((geometry, layout)) = self.geometry.zip(self.layout) else {
1056            return false;
1057        };
1058        if geometry.sample_format != SampleFormat::F64
1059            || lane >= geometry.lanes as usize
1060            || buffer.len() != geometry.samples_per_slot()
1061        {
1062            return false;
1063        }
1064        let offset = layout.slot_offset(lane, sequence) / 4;
1065        let samples = &self.region.words32[offset..offset + buffer.len() * 2];
1066        for (to, from) in buffer.iter_mut().zip(samples.as_chunks::<2>().0.iter()) {
1067            *to = f64::from_bits(
1068                u64::from(u32::from_le(from[0].load(Ordering::Relaxed)))
1069                    | (u64::from(u32::from_le(from[1].load(Ordering::Relaxed))) << 32),
1070            );
1071        }
1072        process(buffer);
1073        for (to, from) in samples.as_chunks::<2>().0.iter().zip(buffer) {
1074            to[0].store((from.to_bits() as u32).to_le(), Ordering::Relaxed);
1075            to[1].store(((from.to_bits() >> 32) as u32).to_le(), Ordering::Relaxed);
1076        }
1077        true
1078    }
1079    fn metadata(&self, lane: usize, sequence: u32) -> Option<&[AtomicU32]> {
1080        let geometry = self.geometry?;
1081        let layout = self.layout?;
1082        if lane >= geometry.lanes as usize {
1083            return None;
1084        }
1085        let offset = layout.slot_offset(lane, sequence) / 4
1086            + geometry.samples_per_slot() * geometry.sample_format.words();
1087        self.region.words32.get(offset..offset + METADATA_WORDS)
1088    }
1089    pub fn input_packet(
1090        &self,
1091        lane: usize,
1092        sequence: u32,
1093        context: &mut Transport,
1094        events: &mut EventBuffer,
1095    ) -> bool {
1096        let Some(words) = self.metadata(lane, sequence) else {
1097            return false;
1098        };
1099        let mut raw = [0; CONTEXT_WORDS];
1100        for (to, from) in raw.iter_mut().zip(words) {
1101            *to = u32::from_le(from.load(Ordering::Relaxed));
1102        }
1103        let Some(value) = Transport::from_words(&raw) else {
1104            return false;
1105        };
1106        *context = value;
1107        load_packet(
1108            &words[CONTEXT_WORDS..CONTEXT_WORDS + PACKET_WORDS],
1109            self.geometry.expect("validated").max_frames,
1110            events,
1111        )
1112    }
1113    pub fn output_packet(&self, lane: usize, sequence: u32, events: &EventBuffer) -> bool {
1114        let Some(words) = self.metadata(lane, sequence) else {
1115            return false;
1116        };
1117        if events
1118            .as_slice()
1119            .iter()
1120            .any(|e| !e.valid(self.geometry.expect("validated").max_frames))
1121        {
1122            return false;
1123        }
1124        store_packet(
1125            &words[CONTEXT_WORDS + PACKET_WORDS..],
1126            events.as_slice(),
1127            events.dropped,
1128        );
1129        true
1130    }
1131
1132    pub fn complete(&self, lane: usize, sequence: u32) {
1133        self.region
1134            .store32(COMPLETED_I + lane, sequence.to_le(), Ordering::Release);
1135        if let Some(signal) = self.done[lane] {
1136            signal.signal();
1137        }
1138    }
1139
1140    pub fn beat(&self) -> u64 {
1141        self.region
1142            .words64
1143            .first()
1144            .map_or(0, |v| v.fetch_add(1, Ordering::Release).wrapping_add(1))
1145    }
1146
1147    pub fn set_last_error(&self, error: u64) {
1148        if let Some(word) = self.region.words64.get(CACHE_LINE / 8) {
1149            word.store(error, Ordering::Release);
1150        }
1151    }
1152}
1153
1154fn validate_header(region: Region<'_>) -> Result<Geometry, HeaderError> {
1155    if region.words32.len() < HEADER_WORDS || region.words64.len() < COUNTER_WORDS {
1156        return Err(HeaderError::TooShort);
1157    }
1158    if u32::from_le(region.load32(MAGIC_I, Ordering::Relaxed)) != MAGIC {
1159        return Err(HeaderError::Magic);
1160    }
1161    if u32::from_le(region.load32(VERSION_I, Ordering::Relaxed)) != LAYOUT_VERSION {
1162        return Err(HeaderError::Version);
1163    }
1164    if region.state() != Some(State::Announced) {
1165        return Err(HeaderError::State);
1166    }
1167    let geometry = Geometry {
1168        sample_format: match u32::from_le(region.load32(FORMAT_I, Ordering::Relaxed)) {
1169            0 => SampleFormat::F32,
1170            1 => SampleFormat::F64,
1171            _ => return Err(HeaderError::Version),
1172        },
1173        lanes: u32::from_le(region.load32(LANES_I, Ordering::Relaxed)),
1174        slots: u32::from_le(region.load32(SLOTS_I, Ordering::Relaxed)),
1175        input_channels: u32::from_le(region.load32(INPUTS_I, Ordering::Relaxed)),
1176        output_channels: u32::from_le(region.load32(OUTPUTS_I, Ordering::Relaxed)),
1177        max_frames: u32::from_le(region.load32(FRAMES_I, Ordering::Relaxed)),
1178        sample_rate: u32::from_le(region.load32(RATE_I, Ordering::Relaxed)),
1179        latency_bound_frames: u32::from_le(region.load32(LATENCY_I, Ordering::Relaxed)),
1180    }
1181    .validate()
1182    .map_err(HeaderError::Geometry)?;
1183    let expected = layout(geometry).expect("validated geometry has a layout");
1184    if u32::from_le(region.load32(TOTAL_I, Ordering::Relaxed)) as usize != expected.total_bytes
1185        || region.words32.len() * 4 < expected.prefix_bytes
1186    {
1187        return Err(HeaderError::TotalBytes);
1188    }
1189    for lane in 0..geometry.lanes as usize {
1190        let base = LANE_GEOMETRY_I + lane * 3;
1191        let actual = [
1192            u32::from_le(region.load32(base, Ordering::Relaxed)) as usize,
1193            u32::from_le(region.load32(base + 1, Ordering::Relaxed)) as usize,
1194            u32::from_le(region.load32(base + 2, Ordering::Relaxed)) as usize,
1195        ];
1196        if actual
1197            != [
1198                expected.first_slot_offsets[lane],
1199                expected.slot_bytes,
1200                expected.slots as usize,
1201            ]
1202        {
1203            return Err(HeaderError::LaneGeometry);
1204        }
1205    }
1206    Ok(geometry)
1207}
1208
1209fn sequence_at_least(value: u32, reference: u32) -> bool {
1210    value.wrapping_sub(reference) as i32 >= 0
1211}
1212
1213#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1214struct PipelineEntry {
1215    sequence: u32,
1216    app_block: u64,
1217    generation: u64,
1218    valid: bool,
1219}
1220
1221/// Metadata enforcing a fixed-depth pipeline.
1222///
1223/// An application that publishes block `k` and then reads must use
1224/// `depth <= slots - 1`; read-then-publish order allows `depth <= slots`.
1225#[derive(Debug)]
1226pub struct Pipeline {
1227    entries: [PipelineEntry; MAX_SLOTS as usize],
1228    depth: u32,
1229}
1230
1231impl Default for Pipeline {
1232    fn default() -> Self {
1233        Self::with_depth(1)
1234    }
1235}
1236
1237impl Pipeline {
1238    pub fn new() -> Self {
1239        Self::default()
1240    }
1241
1242    pub fn with_depth(depth: u32) -> Self {
1243        assert!((1..=MAX_SLOTS).contains(&depth));
1244        Self {
1245            entries: array::from_fn(|_| PipelineEntry::default()),
1246            depth,
1247        }
1248    }
1249
1250    pub fn depth(&self) -> u32 {
1251        self.depth
1252    }
1253
1254    pub fn set_depth(&mut self, depth: u32) {
1255        assert!((1..=MAX_SLOTS).contains(&depth));
1256        self.depth = depth;
1257        self.clear();
1258    }
1259
1260    pub fn published(&mut self, sequence: u32, app_block: u64, generation: u64) {
1261        self.entries[sequence as usize % MAX_SLOTS as usize] = PipelineEntry {
1262            sequence,
1263            app_block,
1264            generation,
1265            valid: true,
1266        };
1267    }
1268
1269    /// Returns the sequence for `app_block - depth` only when that exact block
1270    /// was published, completed, and belongs to the current generation.
1271    pub fn output_sequence(
1272        &mut self,
1273        app_block: u64,
1274        generation: u64,
1275        completed: u32,
1276    ) -> Option<u32> {
1277        let wanted = app_block.checked_sub(u64::from(self.depth))?;
1278        let found = self.entries.iter_mut().find(|entry| {
1279            entry.valid && entry.app_block == wanted && entry.generation == generation
1280        })?;
1281        if sequence_at_least(completed, found.sequence) {
1282            found.valid = false;
1283            Some(found.sequence)
1284        } else {
1285            // Once its output instant passes, a late result must never be used.
1286            found.valid = false;
1287            None
1288        }
1289    }
1290
1291    pub fn clear(&mut self) {
1292        self.entries = array::from_fn(|_| PipelineEntry::default());
1293    }
1294}