Skip to main content

qualia_core_db/audio/
acoustic_sab.rs

1//! SharedArrayBuffer layout for zero-copy U3 uniform + sonic token mirror (Phase 7.4).
2//!
3//! Layout: `[header 32][uniform 352][tokens 128][float mirror 328]` = 840 B (padded to 1024).
4
5use bytemuck::{Pod, Zeroable};
6
7use super::acoustic_plane::AcousticUniform;
8use crate::sonic_token::SonicToken;
9
10pub const ACOUSTIC_SAB_MAGIC: u32 = 0x5133_4153; // "Q3AS"
11pub const ACOUSTIC_SAB_VERSION: u16 = 1;
12pub const ACOUSTIC_SAB_HEADER_BYTES: usize = 32;
13pub const ACOUSTIC_SAB_UNIFORM_OFFSET: usize = 32;
14pub const ACOUSTIC_SAB_UNIFORM_PADDED: usize = 352;
15pub const ACOUSTIC_SAB_TOKEN_OFFSET: usize =
16    ACOUSTIC_SAB_UNIFORM_OFFSET + ACOUSTIC_SAB_UNIFORM_PADDED;
17pub const ACOUSTIC_SAB_TOKEN_BYTES: usize = 128;
18pub const ACOUSTIC_SAB_FLOAT_MIRROR_OFFSET: usize =
19    ACOUSTIC_SAB_TOKEN_OFFSET + ACOUSTIC_SAB_TOKEN_BYTES;
20pub const ACOUSTIC_SAB_FLOAT_MIRROR_BYTES: usize = 328;
21pub const ACOUSTIC_SAB_BYTES: usize = 1024;
22
23/// SAB header — worklet polls `uniform_seq` / `token_seq` (Release/Acquire).
24#[repr(C)]
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
26pub struct AcousticSabHeader {
27    pub magic: u32,
28    pub version: u16,
29    pub uniform_seq: u16,
30    pub token_write: u32,
31    pub token_read: u32,
32    pub stft_frame: u32,
33    pub sample_rate: u32,
34    pub _pad: u32,
35}
36
37impl AcousticSabHeader {
38    #[inline]
39    pub const fn new() -> Self {
40        Self {
41            magic: ACOUSTIC_SAB_MAGIC,
42            version: ACOUSTIC_SAB_VERSION,
43            uniform_seq: 0,
44            token_write: 0,
45            token_read: 0,
46            stft_frame: 0,
47            sample_rate: 48_000,
48            _pad: 0,
49        }
50    }
51
52    #[inline]
53    pub fn parse(bytes: &[u8]) -> Option<Self> {
54        let hlen = std::mem::size_of::<Self>();
55        if bytes.len() < hlen {
56            return None;
57        }
58        let h = bytemuck::pod_read_unaligned::<AcousticSabHeader>(&bytes[..hlen]);
59        (h.magic == ACOUSTIC_SAB_MAGIC && h.version == ACOUSTIC_SAB_VERSION).then_some(h)
60    }
61}
62
63#[inline]
64pub fn init_acoustic_sab(out: &mut [u8]) -> bool {
65    if out.len() < ACOUSTIC_SAB_BYTES {
66        return false;
67    }
68    let header = AcousticSabHeader::new();
69    out[..ACOUSTIC_SAB_HEADER_BYTES].fill(0);
70    let hlen = std::mem::size_of::<AcousticSabHeader>();
71    out[..hlen].copy_from_slice(bytemuck::bytes_of(&header));
72    out[ACOUSTIC_SAB_UNIFORM_OFFSET..].fill(0);
73    true
74}
75
76/// Write pod uniform + optional f32 mirror for worklet `Float32Array` view.
77#[inline]
78pub fn write_uniform_to_sab(out: &mut [u8], uniform: &AcousticUniform) -> bool {
79    write_uniform_to_sab_with_mirror(out, uniform, None)
80}
81
82#[inline]
83pub fn write_uniform_to_sab_with_mirror(
84    out: &mut [u8],
85    uniform: &AcousticUniform,
86    float_mirror: Option<&[f32]>,
87) -> bool {
88    if out.len() < ACOUSTIC_SAB_FLOAT_MIRROR_OFFSET + ACOUSTIC_SAB_FLOAT_MIRROR_BYTES {
89        return false;
90    }
91    let bytes = bytemuck::bytes_of(uniform);
92    let end = ACOUSTIC_SAB_UNIFORM_OFFSET + bytes.len();
93    if end > ACOUSTIC_SAB_TOKEN_OFFSET {
94        return false;
95    }
96    out[ACOUSTIC_SAB_UNIFORM_OFFSET..end].copy_from_slice(bytes);
97    if let Some(mirror) = float_mirror {
98        let n = mirror.len().min(ACOUSTIC_SAB_FLOAT_MIRROR_BYTES / 4);
99        out[ACOUSTIC_SAB_FLOAT_MIRROR_OFFSET..ACOUSTIC_SAB_FLOAT_MIRROR_OFFSET + n * 4]
100            .copy_from_slice(bytemuck::cast_slice(&mirror[..n]));
101    }
102    let header = AcousticSabHeader::parse(out).unwrap_or(AcousticSabHeader::new());
103    let mut h = header;
104    h.uniform_seq = h.uniform_seq.wrapping_add(1);
105    let hlen = std::mem::size_of::<AcousticSabHeader>();
106    out[..hlen].copy_from_slice(bytemuck::bytes_of(&h));
107    true
108}
109
110#[inline]
111fn write_sab_header(out: &mut [u8], h: &AcousticSabHeader) {
112    let hlen = std::mem::size_of::<AcousticSabHeader>();
113    out[..hlen].copy_from_slice(bytemuck::bytes_of(h));
114}
115
116#[inline]
117pub fn read_uniform_from_sab(bytes: &[u8]) -> Option<AcousticUniform> {
118    if bytes.len() < ACOUSTIC_SAB_UNIFORM_OFFSET + std::mem::size_of::<AcousticUniform>() {
119        return None;
120    }
121    let u_size = std::mem::size_of::<AcousticUniform>();
122    Some(bytemuck::pod_read_unaligned::<AcousticUniform>(
123        &bytes[ACOUSTIC_SAB_UNIFORM_OFFSET..ACOUSTIC_SAB_UNIFORM_OFFSET + u_size],
124    ))
125}
126
127/// Push one token into SAB ring (16 slots × 8 B).
128#[inline]
129pub fn push_token_to_sab(out: &mut [u8], token: SonicToken) -> bool {
130    if out.len() < ACOUSTIC_SAB_BYTES {
131        return false;
132    }
133    let mut h = AcousticSabHeader::parse(out).unwrap_or(AcousticSabHeader::new());
134    let w = h.token_write;
135    let r = h.token_read;
136    if w.wrapping_sub(r) >= 16 {
137        return false;
138    }
139    let slot = (w % 16) as usize;
140    let off = ACOUSTIC_SAB_TOKEN_OFFSET + slot * 8;
141    out[off..off + 8].copy_from_slice(bytemuck::bytes_of(&token.raw));
142    h.token_write = w.wrapping_add(1);
143    write_sab_header(out, &h);
144    true
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn sab_header_pod_fits_slot() {
153        let hlen = std::mem::size_of::<AcousticSabHeader>();
154        assert!(hlen <= ACOUSTIC_SAB_HEADER_BYTES);
155        assert_eq!(hlen, 28);
156    }
157
158    #[test]
159    fn sab_roundtrip_uniform() {
160        let mut sab = [0u8; ACOUSTIC_SAB_BYTES];
161        assert!(init_acoustic_sab(&mut sab));
162        let u = AcousticUniform::default();
163        assert!(write_uniform_to_sab(&mut sab, &u));
164        let h = AcousticSabHeader::parse(&sab).unwrap();
165        assert_eq!(h.uniform_seq, 1);
166        let read = read_uniform_from_sab(&sab).unwrap();
167        assert_eq!(read.frequency_hz, u.frequency_hz);
168    }
169
170    #[test]
171    fn sab_token_push() {
172        let mut sab = [0u8; ACOUSTIC_SAB_BYTES];
173        init_acoustic_sab(&mut sab);
174        let t = SonicToken::note_on(1, 60, 100, 0);
175        assert!(push_token_to_sab(&mut sab, t));
176        let h = AcousticSabHeader::parse(&sab).unwrap();
177        assert_eq!(h.token_write, 1);
178    }
179}