Skip to main content

qualia_core_db/audio/
acoustic_plane.rs

1//! U3 AcousticPlane — SPSC sonic token ring + parametric uniform for AudioWorklet.
2//!
3//! Shares U1 `Tensor10D` SOA read-only; no dedicated VRAM partition until bake sidecars land.
4
5use core::cell::UnsafeCell;
6use std::sync::atomic::{AtomicU32, Ordering};
7
8use bytemuck::{Pod, Zeroable};
9
10use crate::audio::audio_spectral_sheet::{preview_bins_from_tensor, SPECTRAL_PREVIEW_BINS};
11use crate::audio::dsp_kernel::configure_voice_from_tensor;
12use crate::audio::hrtf::{binaural_from_position, room_damp_from_manifold};
13use crate::gpu_context::{ComputeUniverse, OperationalMode};
14use crate::render::acoustic::{
15    phenomenal_acoustic_params, phenomenal_fm_index, phenomenal_voice_frequency_hz,
16};
17use crate::sonic_token::SonicToken;
18use crate::tensor::Tensor10D;
19
20pub const SONIC_RING_CAP: usize = 128;
21
22/// Worklet uniform — mirrors `AcousticParams` with fixed preview bins.
23#[repr(C)]
24#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
25pub struct AcousticUniform {
26    pub alpha: f32,
27    pub mu: f32,
28    pub position: [f32; 3],
29    pub track_v: f32,
30    pub manifold_w: f32,
31    pub epistemic_q: f32,
32    pub fm_index: f32,
33    pub frequency_hz: f32,
34    pub enabled: u32,
35    pub gain_l: f32,
36    pub gain_r: f32,
37    pub itd_seconds: f32,
38    pub azimuth_rad: f32,
39    pub elevation_rad: f32,
40    pub room_damp: f32,
41    pub stft_frame: f32,
42    pub preview_bins: [f32; SPECTRAL_PREVIEW_BINS],
43}
44
45impl Default for AcousticUniform {
46    fn default() -> Self {
47        Self {
48            alpha: 0.0,
49            mu: 0.0,
50            position: [0.0; 3],
51            track_v: 0.0,
52            manifold_w: 0.0,
53            epistemic_q: 0.0,
54            fm_index: 0.0,
55            frequency_hz: 440.0,
56            enabled: 0,
57            gain_l: 0.707,
58            gain_r: 0.707,
59            itd_seconds: 0.0,
60            azimuth_rad: 0.0,
61            elevation_rad: 0.0,
62            room_damp: 1.0,
63            stft_frame: 0.0,
64            preview_bins: [0.0; SPECTRAL_PREVIEW_BINS],
65        }
66    }
67}
68
69#[inline]
70pub fn apply_binaural_to_uniform(u: &mut AcousticUniform, listener_yaw: f32) {
71    let g = binaural_from_position(u.position, listener_yaw);
72    u.gain_l = g.gain_l;
73    u.gain_r = g.gain_r;
74    u.itd_seconds = g.itd_seconds;
75    u.azimuth_rad = g.azimuth_rad;
76    u.elevation_rad = g.elevation_rad;
77    u.room_damp = room_damp_from_manifold(u.manifold_w);
78}
79
80/// Scalar params extracted from a tensor node (no heap).
81#[derive(Debug, Clone, Copy, PartialEq)]
82pub struct AcousticParams {
83    pub alpha: f32,
84    pub mu: f32,
85    pub position: [f32; 3],
86    pub track_v: f32,
87    pub manifold_w: f32,
88    pub epistemic_q: f32,
89    pub preview_bins: [f32; SPECTRAL_PREVIEW_BINS],
90}
91
92impl AcousticParams {
93    #[inline]
94    pub fn from_tensor(t: &Tensor10D) -> Self {
95        Self {
96            alpha: t.alpha,
97            mu: t.mu,
98            position: [t.x, t.y, t.z],
99            track_v: t.v,
100            manifold_w: t.w,
101            epistemic_q: t.q,
102            preview_bins: preview_bins_from_tensor(t),
103        }
104    }
105
106    #[inline]
107    pub fn to_uniform(self, enabled: bool) -> AcousticUniform {
108        let mut voice = crate::audio::dsp_kernel::ParametricVoiceState::default();
109        configure_voice_from_tensor(
110            &mut voice,
111            self.epistemic_q,
112            self.mu,
113            self.alpha,
114            &self.preview_bins,
115        );
116        let mut u = AcousticUniform {
117            alpha: self.alpha,
118            mu: self.mu,
119            position: self.position,
120            track_v: self.track_v,
121            manifold_w: self.manifold_w,
122            epistemic_q: self.epistemic_q,
123            fm_index: voice.fm_index,
124            frequency_hz: voice.frequency_hz,
125            enabled: u32::from(enabled),
126            preview_bins: self.preview_bins,
127            ..AcousticUniform::default()
128        };
129        u.enabled = u32::from(enabled);
130        u
131    }
132
133    /// Phenomenal uniform — σ oracle frequency (P-F2) aligned with `portal_spectral`.
134    #[inline]
135    pub fn to_phenomenal_uniform(
136        self,
137        enabled: bool,
138        t: &Tensor10D,
139        listener_yaw: f32,
140    ) -> AcousticUniform {
141        let mut u = self.to_uniform(enabled);
142        u.frequency_hz = phenomenal_voice_frequency_hz(t);
143        u.fm_index = phenomenal_fm_index(t);
144        u.stft_frame = (t.t * 32.0).fract() * 32.0;
145        apply_binaural_to_uniform(&mut u, listener_yaw);
146        u
147    }
148}
149
150#[inline]
151pub fn acoustic_params_from_tensor(t: &Tensor10D) -> AcousticParams {
152    phenomenal_acoustic_params(t)
153}
154
155/// True when U3 synthesis is allowed under operational mode (muted in Reserve).
156#[inline]
157pub fn acoustic_enabled_for_mode(mode: OperationalMode) -> bool {
158    !matches!(mode, OperationalMode::Reserve)
159}
160
161/// U3 effective mode — shares U1 ledger; follows viewport pressure in Reserve.
162#[inline]
163pub fn acoustic_effective_mode(global: OperationalMode) -> OperationalMode {
164    crate::gpu_context::universe_orchestrator()
165        .effective_mode(ComputeUniverse::AcousticPlane, global)
166}
167
168/// Fixed-capacity SPSC ring of packed `SonicToken` (`u64`).
169pub struct SonicTokenRing {
170    slots: UnsafeCell<[u64; SONIC_RING_CAP]>,
171    write_seq: AtomicU32,
172    read_seq: AtomicU32,
173}
174
175// SAFETY: SPSC — one producer (U0/U1/portal), one consumer (worklet poll).
176unsafe impl Sync for SonicTokenRing {}
177
178impl SonicTokenRing {
179    pub const fn new() -> Self {
180        Self {
181            slots: UnsafeCell::new([0u64; SONIC_RING_CAP]),
182            write_seq: AtomicU32::new(0),
183            read_seq: AtomicU32::new(0),
184        }
185    }
186
187    #[inline]
188    pub fn len(&self) -> usize {
189        let w = self.write_seq.load(Ordering::Acquire);
190        let r = self.read_seq.load(Ordering::Acquire);
191        (w.wrapping_sub(r)) as usize
192    }
193
194    #[inline]
195    pub fn is_empty(&self) -> bool {
196        self.len() == 0
197    }
198
199    pub fn try_push(&self, token: SonicToken) -> bool {
200        let w = self.write_seq.load(Ordering::Relaxed);
201        let r = self.read_seq.load(Ordering::Acquire);
202        if w.wrapping_sub(r) >= SONIC_RING_CAP as u32 {
203            return false;
204        }
205        let slot = (w % SONIC_RING_CAP as u32) as usize;
206        unsafe {
207            (*self.slots.get())[slot] = token.raw;
208        }
209        self.write_seq.store(w.wrapping_add(1), Ordering::Release);
210        true
211    }
212
213    pub fn try_pop(&self) -> Option<SonicToken> {
214        let r = self.read_seq.load(Ordering::Relaxed);
215        let w = self.write_seq.load(Ordering::Acquire);
216        if r == w {
217            return None;
218        }
219        let slot = (r % SONIC_RING_CAP as u32) as usize;
220        let raw = unsafe { (*self.slots.get())[slot] };
221        self.read_seq.store(r.wrapping_add(1), Ordering::Release);
222        Some(SonicToken { raw })
223    }
224}
225
226static SONIC_TOKEN_RING: SonicTokenRing = SonicTokenRing::new();
227
228#[inline]
229pub fn sonic_token_ring() -> &'static SonicTokenRing {
230    &SONIC_TOKEN_RING
231}
232
233#[inline]
234pub fn push_sonic_token(token: SonicToken) -> bool {
235    sonic_token_ring().try_push(token)
236}
237
238#[inline]
239pub fn pop_sonic_token() -> Option<SonicToken> {
240    sonic_token_ring().try_pop()
241}
242
243/// Drain up to `out.len()` tokens; returns count written.
244pub fn drain_sonic_tokens(out: &mut [u64]) -> usize {
245    let mut n = 0usize;
246    while n < out.len() {
247        match pop_sonic_token() {
248            Some(t) => {
249                out[n] = t.raw;
250                n += 1;
251            }
252            None => break,
253        }
254    }
255    n
256}
257
258/// Emit parametric pulse + optional NoteOn from a tensor node (U1 → U3).
259pub fn sonify_tensor_node(tensor_index: u32, t: &Tensor10D, note_on: bool) -> usize {
260    let mut pushed = 0usize;
261    let pitch = SonicToken::pitch_from_tensor(t.w, t.q, t.sigma);
262    let velocity = (t.alpha.clamp(0.0, 1.0) * 127.0) as u8;
263    if push_sonic_token(SonicToken::parametric_pulse(tensor_index, velocity)) {
264        pushed += 1;
265    }
266    if note_on {
267        if push_sonic_token(SonicToken::note_on(tensor_index, pitch, velocity, 0)) {
268            pushed += 1;
269        }
270    }
271    pushed
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277    use crate::sonic_token::{SonicEventType, SONIC_MAGIC};
278
279    fn fresh_ring() -> SonicTokenRing {
280        SonicTokenRing::new()
281    }
282
283    #[test]
284    fn sonic_ring_push_pop() {
285        let ring = fresh_ring();
286        let t = SonicToken::note_on(1, 60, 100, 0);
287        assert!(ring.try_push(t));
288        let popped = ring.try_pop().expect("token");
289        assert_eq!(popped.tensor_index(), 1);
290        assert_eq!(popped.note(), 60);
291    }
292
293    #[test]
294    fn sonic_ring_full_returns_false() {
295        let ring = fresh_ring();
296        for i in 0..SONIC_RING_CAP {
297            assert!(
298                ring.try_push(SonicToken::note_on(i as u32, 60, 100, 0)),
299                "push {i}"
300            );
301        }
302        assert!(!ring.try_push(SonicToken::note_on(999, 60, 100, 0)));
303    }
304
305    #[test]
306    fn drain_respects_capacity() {
307        let ring = fresh_ring();
308        for i in 0..4 {
309            ring.try_push(SonicToken::note_on(i, 60, 100, 0));
310        }
311        let mut local = [0u64; 2];
312        let mut n = 0usize;
313        while n < local.len() {
314            if let Some(t) = ring.try_pop() {
315                local[n] = t.raw;
316                n += 1;
317            } else {
318                break;
319            }
320        }
321        assert_eq!(n, 2);
322        assert_eq!(ring.len(), 2);
323    }
324
325    #[test]
326    fn acoustic_uniform_size_stable() {
327        assert_eq!(std::mem::size_of::<AcousticUniform>() % 4, 0);
328        let t = Tensor10D::default();
329        let u = AcousticParams::from_tensor(&t).to_uniform(true);
330        assert_eq!(u.enabled, 1);
331    }
332
333    #[test]
334    fn acoustic_disabled_in_reserve() {
335        assert!(!acoustic_enabled_for_mode(OperationalMode::Reserve));
336        assert!(acoustic_enabled_for_mode(OperationalMode::Full));
337    }
338
339    #[test]
340    fn sonify_pushes_tokens() {
341        let ring = fresh_ring();
342        let t = Tensor10D::new(0.5, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.8, 0.1, 2.5);
343        // sonify uses global ring — test logic via local push pattern
344        let pitch = SonicToken::pitch_from_tensor(t.w, t.q, t.sigma);
345        assert!(ring.try_push(SonicToken::parametric_pulse(7, 100)));
346        assert!(ring.try_push(SonicToken::note_on(7, pitch, 100, 0)));
347        assert_eq!(ring.len(), 2);
348        let first = ring.try_pop().unwrap();
349        assert_eq!(first.event_type(), SonicEventType::Parametric);
350        let second = ring.try_pop().unwrap();
351        assert_eq!(second.event_type(), SonicEventType::NoteOn);
352        assert_eq!(second.flags() & 0xff, SONIC_MAGIC as u16);
353    }
354}