Skip to main content

ringkernel_audio_fft/
messages.rs

1//! Message types for audio FFT processing and K2K communication.
2//!
3//! This module defines all message types used for communication between
4//! the host and GPU bin actors, as well as between neighboring bin actors.
5
6use bytemuck::{Pod, Zeroable};
7use ringkernel_core::prelude::*;
8use rkyv::{Archive, Deserialize, Serialize};
9
10/// Message type IDs for audio FFT messages.
11pub mod message_types {
12    /// Audio frame input message.
13    pub const AUDIO_FRAME: u64 = 0x4155_4449_4F00_0001; // "AUDIO" + 1
14    /// Frequency bin data message.
15    pub const FREQUENCY_BIN: u64 = 0x4155_4449_4F00_0002;
16    /// Neighbor data exchange message.
17    pub const NEIGHBOR_DATA: u64 = 0x4155_4449_4F00_0003;
18    /// Separated bin output message.
19    pub const SEPARATED_BIN: u64 = 0x4155_4449_4F00_0004;
20    /// Processing complete signal.
21    pub const FRAME_COMPLETE: u64 = 0x4155_4449_4F00_0005;
22    /// Bin actor control message.
23    pub const BIN_CONTROL: u64 = 0x4155_4449_4F00_0006;
24}
25
26/// A frame of audio samples for FFT processing.
27#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
28pub struct AudioFrame {
29    /// Unique frame identifier.
30    pub frame_id: u64,
31    /// Sample rate in Hz.
32    pub sample_rate: u32,
33    /// Number of channels (1 = mono, 2 = stereo).
34    pub channels: u8,
35    /// Audio samples (interleaved if stereo).
36    pub samples: Vec<f32>,
37    /// Timestamp in samples from start.
38    pub timestamp_samples: u64,
39}
40
41impl AudioFrame {
42    /// Create a new audio frame.
43    pub fn new(
44        frame_id: u64,
45        sample_rate: u32,
46        channels: u8,
47        samples: Vec<f32>,
48        timestamp_samples: u64,
49    ) -> Self {
50        Self {
51            frame_id,
52            sample_rate,
53            channels,
54            samples,
55            timestamp_samples,
56        }
57    }
58
59    /// Get the duration of this frame in seconds.
60    pub fn duration_secs(&self) -> f64 {
61        let sample_count = self.samples.len() / self.channels as usize;
62        sample_count as f64 / self.sample_rate as f64
63    }
64
65    /// Get samples for a specific channel.
66    pub fn channel_samples(&self, channel: usize) -> Vec<f32> {
67        if channel >= self.channels as usize {
68            return Vec::new();
69        }
70        self.samples
71            .iter()
72            .skip(channel)
73            .step_by(self.channels as usize)
74            .copied()
75            .collect()
76    }
77}
78
79impl RingMessage for AudioFrame {
80    fn message_type() -> u64 {
81        message_types::AUDIO_FRAME
82    }
83
84    fn message_id(&self) -> MessageId {
85        MessageId::new(self.frame_id)
86    }
87
88    fn serialize(&self) -> Vec<u8> {
89        rkyv::to_bytes::<_, 256>(self)
90            .map(|v| v.to_vec())
91            .unwrap_or_default()
92    }
93
94    fn deserialize(bytes: &[u8]) -> ringkernel_core::error::Result<Self> {
95        // SAFETY: We trust the serialized data from our own FFT processing
96        let archived = unsafe { rkyv::archived_root::<Self>(bytes) };
97        Ok(archived
98            .deserialize(&mut rkyv::Infallible)
99            .expect("rkyv::Infallible deserialization cannot fail"))
100    }
101}
102
103/// Complex number representation for FFT bins.
104#[derive(Debug, Clone, Copy, Default, Pod, Zeroable, Archive, Serialize, Deserialize)]
105#[repr(C)]
106pub struct Complex {
107    /// Real component.
108    pub re: f32,
109    /// Imaginary component.
110    pub im: f32,
111}
112
113impl Complex {
114    /// Create a new complex number.
115    pub const fn new(re: f32, im: f32) -> Self {
116        Self { re, im }
117    }
118
119    /// Create from polar coordinates.
120    pub fn from_polar(magnitude: f32, phase: f32) -> Self {
121        Self {
122            re: magnitude * phase.cos(),
123            im: magnitude * phase.sin(),
124        }
125    }
126
127    /// Get magnitude.
128    pub fn magnitude(&self) -> f32 {
129        (self.re * self.re + self.im * self.im).sqrt()
130    }
131
132    /// Get phase in radians.
133    pub fn phase(&self) -> f32 {
134        self.im.atan2(self.re)
135    }
136
137    /// Get magnitude squared (more efficient than magnitude).
138    pub fn magnitude_squared(&self) -> f32 {
139        self.re * self.re + self.im * self.im
140    }
141
142    /// Multiply by another complex number.
143    pub fn mul(&self, other: &Self) -> Self {
144        Self {
145            re: self.re * other.re - self.im * other.im,
146            im: self.re * other.im + self.im * other.re,
147        }
148    }
149
150    /// Complex conjugate.
151    pub fn conj(&self) -> Self {
152        Self {
153            re: self.re,
154            im: -self.im,
155        }
156    }
157
158    /// Add another complex number.
159    pub fn add(&self, other: &Self) -> Self {
160        Self {
161            re: self.re + other.re,
162            im: self.im + other.im,
163        }
164    }
165
166    /// Scale by a real number.
167    pub fn scale(&self, s: f32) -> Self {
168        Self {
169            re: self.re * s,
170            im: self.im * s,
171        }
172    }
173}
174
175/// Data for a single frequency bin.
176#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
177pub struct FrequencyBin {
178    /// Frame this bin belongs to.
179    pub frame_id: u64,
180    /// Bin index (0 = DC, fft_size/2 = Nyquist).
181    pub bin_index: u32,
182    /// Total number of bins.
183    pub total_bins: u32,
184    /// Complex FFT value.
185    pub value: Complex,
186    /// Frequency in Hz for this bin.
187    pub frequency_hz: f32,
188    /// Previous frame's value (for temporal analysis).
189    pub prev_value: Option<Complex>,
190}
191
192impl FrequencyBin {
193    /// Create a new frequency bin.
194    pub fn new(
195        frame_id: u64,
196        bin_index: u32,
197        total_bins: u32,
198        value: Complex,
199        frequency_hz: f32,
200    ) -> Self {
201        Self {
202            frame_id,
203            bin_index,
204            total_bins,
205            value,
206            frequency_hz,
207            prev_value: None,
208        }
209    }
210
211    /// Set the previous frame's value.
212    pub fn with_prev_value(mut self, prev: Complex) -> Self {
213        self.prev_value = Some(prev);
214        self
215    }
216
217    /// Get magnitude in dB.
218    pub fn magnitude_db(&self) -> f32 {
219        let mag = self.value.magnitude();
220        if mag > 1e-10 {
221            20.0 * mag.log10()
222        } else {
223            -200.0 // Floor
224        }
225    }
226}
227
228impl RingMessage for FrequencyBin {
229    fn message_type() -> u64 {
230        message_types::FREQUENCY_BIN
231    }
232
233    fn message_id(&self) -> MessageId {
234        MessageId::new(self.frame_id * 10000 + self.bin_index as u64)
235    }
236
237    fn serialize(&self) -> Vec<u8> {
238        rkyv::to_bytes::<_, 128>(self)
239            .map(|v| v.to_vec())
240            .unwrap_or_default()
241    }
242
243    fn deserialize(bytes: &[u8]) -> ringkernel_core::error::Result<Self> {
244        // SAFETY: We trust the serialized data from our own FFT processing
245        let archived = unsafe { rkyv::archived_root::<Self>(bytes) };
246        Ok(archived
247            .deserialize(&mut rkyv::Infallible)
248            .expect("rkyv::Infallible deserialization cannot fail"))
249    }
250}
251
252/// Neighbor data exchanged via K2K messaging.
253#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
254pub struct NeighborData {
255    /// Source bin index.
256    pub source_bin: u32,
257    /// Frame ID.
258    pub frame_id: u64,
259    /// Complex value.
260    pub value: Complex,
261    /// Magnitude.
262    pub magnitude: f32,
263    /// Phase.
264    pub phase: f32,
265    /// Temporal derivative (phase change from previous frame).
266    pub phase_derivative: f32,
267    /// Spectral flux (magnitude change).
268    pub spectral_flux: f32,
269}
270
271impl NeighborData {
272    /// Create neighbor data from a frequency bin.
273    pub fn from_bin(bin: &FrequencyBin, phase_derivative: f32, spectral_flux: f32) -> Self {
274        Self {
275            source_bin: bin.bin_index,
276            frame_id: bin.frame_id,
277            value: bin.value,
278            magnitude: bin.value.magnitude(),
279            phase: bin.value.phase(),
280            phase_derivative,
281            spectral_flux,
282        }
283    }
284}
285
286impl RingMessage for NeighborData {
287    fn message_type() -> u64 {
288        message_types::NEIGHBOR_DATA
289    }
290
291    fn message_id(&self) -> MessageId {
292        MessageId::new(self.frame_id * 10000 + self.source_bin as u64)
293    }
294
295    fn serialize(&self) -> Vec<u8> {
296        rkyv::to_bytes::<_, 64>(self)
297            .map(|v| v.to_vec())
298            .unwrap_or_default()
299    }
300
301    fn deserialize(bytes: &[u8]) -> ringkernel_core::error::Result<Self> {
302        // SAFETY: We trust the serialized data from our own FFT processing
303        let archived = unsafe { rkyv::archived_root::<Self>(bytes) };
304        Ok(archived
305            .deserialize(&mut rkyv::Infallible)
306            .expect("rkyv::Infallible deserialization cannot fail"))
307    }
308}
309
310/// Separated frequency bin with direct and ambience components.
311#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
312pub struct SeparatedBin {
313    /// Frame ID.
314    pub frame_id: u64,
315    /// Bin index.
316    pub bin_index: u32,
317    /// Direct signal component.
318    pub direct: Complex,
319    /// Ambience component.
320    pub ambience: Complex,
321    /// Coherence score (0.0 = pure ambience, 1.0 = pure direct).
322    pub coherence: f32,
323    /// Transient score (0.0 = steady, 1.0 = transient).
324    pub transient: f32,
325}
326
327impl SeparatedBin {
328    /// Create a new separated bin.
329    pub fn new(
330        frame_id: u64,
331        bin_index: u32,
332        direct: Complex,
333        ambience: Complex,
334        coherence: f32,
335        transient: f32,
336    ) -> Self {
337        Self {
338            frame_id,
339            bin_index,
340            direct,
341            ambience,
342            coherence,
343            transient,
344        }
345    }
346
347    /// Get the original combined value.
348    pub fn combined(&self) -> Complex {
349        self.direct.add(&self.ambience)
350    }
351
352    /// Get direct signal magnitude ratio.
353    pub fn direct_ratio(&self) -> f32 {
354        let total = self.direct.magnitude() + self.ambience.magnitude();
355        if total > 1e-10 {
356            self.direct.magnitude() / total
357        } else {
358            0.5
359        }
360    }
361}
362
363impl RingMessage for SeparatedBin {
364    fn message_type() -> u64 {
365        message_types::SEPARATED_BIN
366    }
367
368    fn message_id(&self) -> MessageId {
369        MessageId::new(self.frame_id * 10000 + self.bin_index as u64)
370    }
371
372    fn serialize(&self) -> Vec<u8> {
373        rkyv::to_bytes::<_, 128>(self)
374            .map(|v| v.to_vec())
375            .unwrap_or_default()
376    }
377
378    fn deserialize(bytes: &[u8]) -> ringkernel_core::error::Result<Self> {
379        // SAFETY: We trust the serialized data from our own FFT processing
380        let archived = unsafe { rkyv::archived_root::<Self>(bytes) };
381        Ok(archived
382            .deserialize(&mut rkyv::Infallible)
383            .expect("rkyv::Infallible deserialization cannot fail"))
384    }
385}
386
387/// Control message for bin actors.
388#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
389pub enum BinControl {
390    /// Reset state for new audio stream.
391    Reset,
392    /// Update separation parameters.
393    UpdateParams {
394        /// Coherence threshold.
395        coherence_threshold: f32,
396        /// Transient sensitivity.
397        transient_sensitivity: f32,
398        /// Temporal smoothing factor.
399        temporal_smoothing: f32,
400    },
401    /// Request current state.
402    GetState,
403    /// Shutdown the actor.
404    Shutdown,
405}
406
407impl RingMessage for BinControl {
408    fn message_type() -> u64 {
409        message_types::BIN_CONTROL
410    }
411
412    fn message_id(&self) -> MessageId {
413        MessageId::generate()
414    }
415
416    fn serialize(&self) -> Vec<u8> {
417        rkyv::to_bytes::<_, 64>(self)
418            .map(|v| v.to_vec())
419            .unwrap_or_default()
420    }
421
422    fn deserialize(bytes: &[u8]) -> ringkernel_core::error::Result<Self> {
423        // SAFETY: We trust the serialized data from our own FFT processing
424        let archived = unsafe { rkyv::archived_root::<Self>(bytes) };
425        Ok(archived
426            .deserialize(&mut rkyv::Infallible)
427            .expect("rkyv::Infallible deserialization cannot fail"))
428    }
429}
430
431/// Frame processing complete notification.
432#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
433pub struct FrameComplete {
434    /// Frame ID.
435    pub frame_id: u64,
436    /// Processing time in microseconds.
437    pub processing_time_us: u64,
438    /// Number of bins processed.
439    pub bins_processed: u32,
440}
441
442impl RingMessage for FrameComplete {
443    fn message_type() -> u64 {
444        message_types::FRAME_COMPLETE
445    }
446
447    fn message_id(&self) -> MessageId {
448        MessageId::new(self.frame_id)
449    }
450
451    fn serialize(&self) -> Vec<u8> {
452        rkyv::to_bytes::<_, 32>(self)
453            .map(|v| v.to_vec())
454            .unwrap_or_default()
455    }
456
457    fn deserialize(bytes: &[u8]) -> ringkernel_core::error::Result<Self> {
458        // SAFETY: We trust the serialized data from our own FFT processing
459        let archived = unsafe { rkyv::archived_root::<Self>(bytes) };
460        Ok(archived
461            .deserialize(&mut rkyv::Infallible)
462            .expect("rkyv::Infallible deserialization cannot fail"))
463    }
464}
465
466#[cfg(test)]
467mod tests {
468    use super::*;
469
470    #[test]
471    fn test_complex_operations() {
472        let c1 = Complex::new(3.0, 4.0);
473        assert!((c1.magnitude() - 5.0).abs() < 1e-6);
474
475        let c2 = Complex::from_polar(5.0, 0.927295); // approx atan2(4, 3)
476        assert!((c2.re - 3.0).abs() < 0.01);
477        assert!((c2.im - 4.0).abs() < 0.01);
478    }
479
480    #[test]
481    fn test_audio_frame_serialization() {
482        use ringkernel_core::RingMessage;
483        let frame = AudioFrame::new(1, 44100, 2, vec![0.0, 0.1, 0.2, 0.3], 0);
484        let bytes = RingMessage::serialize(&frame);
485        let restored = <AudioFrame as RingMessage>::deserialize(&bytes).unwrap();
486        assert_eq!(restored.frame_id, 1);
487        assert_eq!(restored.sample_rate, 44100);
488        assert_eq!(restored.samples.len(), 4);
489    }
490
491    #[test]
492    fn test_frequency_bin_db() {
493        let bin = FrequencyBin::new(0, 10, 1024, Complex::new(1.0, 0.0), 440.0);
494        assert!((bin.magnitude_db() - 0.0).abs() < 0.1); // 1.0 magnitude = 0 dB
495
496        let quiet_bin = FrequencyBin::new(0, 10, 1024, Complex::new(0.1, 0.0), 440.0);
497        assert!((quiet_bin.magnitude_db() - (-20.0)).abs() < 0.1); // 0.1 magnitude = -20 dB
498    }
499}