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