Skip to main content

azul_core/
audio.rs

1//! POD types for the audio surface (SUPER_PLAN_2 ยง4 P7).
2//!
3//! Audio playback + microphone capture (rodio / cpal on the desktop;
4//! AVAudioEngine / AAudio on mobile). Capture mirrors the sensor manager (the
5//! backend pushes [`AudioFrame`]s to a process-global channel; the layout pass
6//! drains them and a callback reads them); playback queues frames to the
7//! backend. The mic permission is the existing
8//! `azul_layout::managers::permission::Capability::Microphone`.
9//!
10//! Defined in `azul-core` so the config + frame types cross the FFI without
11//! `azul-layout` (or rodio / cpal) as a dependency. For azul-meet (P8),
12//! [`AudioFrame`] is the unit captured -> sent over UDP -> played back.
13
14use azul_css::F32Vec;
15
16/// Audio stream format.
17#[repr(C)]
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub struct AudioConfig {
20    /// Samples per second per channel (e.g. 48000).
21    pub sample_rate: u32,
22    /// Channel count (1 = mono, 2 = stereo).
23    pub channels: u16,
24}
25
26impl Default for AudioConfig {
27    fn default() -> Self {
28        Self {
29            sample_rate: 48_000,
30            channels: 1,
31        }
32    }
33}
34
35impl AudioConfig {
36    /// A config with the given rate + channel count.
37    #[must_use]
38    pub const fn new(sample_rate: u32, channels: u16) -> Self {
39        Self {
40            sample_rate,
41            channels,
42        }
43    }
44}
45
46/// A chunk of audio - interleaved `f32` samples in `[-1.0, 1.0]`.
47///
48/// For stereo
49/// the layout is `L, R, L, R, ...`. This is the unit the mic backend delivers,
50/// playback consumes, and (P8) azul-meet sends over UDP.
51#[repr(C)]
52#[derive(Debug, Clone, PartialEq)]
53pub struct AudioFrame {
54    /// Samples per second per channel.
55    pub sample_rate: u32,
56    /// Channel count (1 = mono, 2 = stereo).
57    pub channels: u16,
58    /// Interleaved `f32` samples.
59    pub samples: F32Vec,
60}
61
62impl AudioFrame {
63    /// Number of sample *frames* (samples per channel) in this chunk.
64    #[must_use]
65    pub fn frame_count(&self) -> usize {
66        if self.channels == 0 {
67            0
68        } else {
69            self.samples.as_ref().len() / self.channels as usize
70        }
71    }
72}
73
74// FFI Option wrapper for accessors that may have no frame yet. `copy = false`
75// because AudioFrame holds a F32Vec (matches the convention in `json.rs`).
76impl_option!(AudioFrame, OptionAudioFrame, copy = false, [Clone, Debug]);
77
78#[cfg(test)]
79#[path = "audio_test.rs"]
80mod audio_test;