Skip to main content

qualia_core_db/net/
sonic_token.rs

1//! 64-bit Sonic Token — symbolic audio events (U0/U1 → U3 AcousticPlane).
2//!
3//! Hot path: `Pod` packed `u64`; no heap. MIDI export is a cold-path serializer.
4
5use bytemuck::{Pod, Zeroable};
6
7pub const SONIC_MAGIC: u8 = 0x53; // 'S'
8
9/// Packed sonic event (8 bytes).
10#[repr(C)]
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
12pub struct SonicToken {
13    pub raw: u64,
14}
15
16#[repr(u8)]
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum SonicEventType {
19    NoteOn = 0,
20    NoteOff = 1,
21    ControlChange = 2,
22    Parametric = 3,
23}
24
25impl SonicToken {
26    #[inline]
27    pub const fn empty() -> Self {
28        Self { raw: 0 }
29    }
30
31    #[inline]
32    pub fn pack(
33        delta_time: u8,
34        event_type: SonicEventType,
35        channel: u8,
36        note: u8,
37        velocity: u8,
38        tensor_index: u32,
39        flags: u16,
40    ) -> Self {
41        let et = event_type as u64;
42        let raw = (delta_time as u64)
43            | ((et & 0x0f) << 8)
44            | ((channel as u64 & 0x0f) << 12)
45            | ((note as u64) << 16)
46            | ((velocity as u64) << 24)
47            | ((tensor_index as u64 & 0xffff) << 32)
48            | ((flags as u64 & 0xffff) << 48);
49        Self { raw }
50    }
51
52    #[inline]
53    pub fn delta_time(self) -> u8 {
54        self.raw as u8
55    }
56
57    #[inline]
58    pub fn event_type(self) -> SonicEventType {
59        match (self.raw >> 8) & 0x0f {
60            1 => SonicEventType::NoteOff,
61            2 => SonicEventType::ControlChange,
62            3 => SonicEventType::Parametric,
63            _ => SonicEventType::NoteOn,
64        }
65    }
66
67    #[inline]
68    pub fn channel(self) -> u8 {
69        ((self.raw >> 12) & 0x0f) as u8
70    }
71
72    #[inline]
73    pub fn note(self) -> u8 {
74        ((self.raw >> 16) & 0xff) as u8
75    }
76
77    #[inline]
78    pub fn velocity(self) -> u8 {
79        ((self.raw >> 24) & 0xff) as u8
80    }
81
82    #[inline]
83    pub fn tensor_index(self) -> u32 {
84        ((self.raw >> 32) & 0xffff) as u32
85    }
86
87    #[inline]
88    pub fn flags(self) -> u16 {
89        (self.raw >> 48) as u16
90    }
91
92    /// Map manifold `w` + epistemic `q` to a MIDI note (0–127).
93    #[inline]
94    pub fn pitch_from_tensor(w: f32, q: f32, sigma: f32) -> u8 {
95        let base = 48.0 + w * 12.0 + sigma.fract() * 24.0;
96        let jitter = q * 6.0;
97        (base + jitter).clamp(0.0, 127.0) as u8
98    }
99
100    /// Build NoteOn from a tensor node index and pitch/velocity.
101    #[inline]
102    pub fn note_on(tensor_index: u32, note: u8, velocity: u8, channel: u8) -> Self {
103        Self::pack(
104            0,
105            SonicEventType::NoteOn,
106            channel,
107            note,
108            velocity,
109            tensor_index,
110            SONIC_MAGIC as u16,
111        )
112    }
113
114    #[inline]
115    pub fn parametric_pulse(tensor_index: u32, velocity: u8) -> Self {
116        Self::pack(
117            0,
118            SonicEventType::Parametric,
119            0,
120            0,
121            velocity,
122            tensor_index,
123            SONIC_MAGIC as u16,
124        )
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn pack_roundtrip() {
134        let t = SonicToken::pack(3, SonicEventType::NoteOn, 2, 60, 100, 42, 0x0053);
135        assert_eq!(t.delta_time(), 3);
136        assert_eq!(t.event_type(), SonicEventType::NoteOn);
137        assert_eq!(t.channel(), 2);
138        assert_eq!(t.note(), 60);
139        assert_eq!(t.velocity(), 100);
140        assert_eq!(t.tensor_index(), 42);
141        assert_eq!(t.flags(), 0x0053);
142        assert_eq!(std::mem::size_of::<SonicToken>(), 8);
143    }
144
145    #[test]
146    fn pitch_from_tensor_in_range() {
147        let n = SonicToken::pitch_from_tensor(2.0, 0.5, 3.7);
148        assert!(n <= 127);
149    }
150}