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;