⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/audio/
client.rs

1//! Client API for audio streams
2//!
3//! Provides the user-facing interface for creating, controlling, and writing
4//! to audio streams. Each stream is backed by a `SharedAudioBuffer` and a
5//! corresponding mixer channel.
6
7#![allow(dead_code)]
8
9use alloc::{collections::BTreeMap, string::String};
10use core::sync::atomic::{AtomicU32, Ordering};
11
12use crate::{
13    audio::{buffer::SharedAudioBuffer, AudioConfig},
14    error::KernelError,
15};
16
17/// Default ring buffer capacity in frames
18const DEFAULT_BUFFER_FRAMES: u32 = 4096;
19
20// ============================================================================
21// Audio Stream Types
22// ============================================================================
23
24/// Unique identifier for an audio stream
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub struct AudioStreamId(pub u32);
27
28impl AudioStreamId {
29    /// Get the raw numeric ID
30    pub fn as_u32(&self) -> u32 {
31        self.0
32    }
33}
34
35/// State of an audio stream
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum StreamState {
38    /// Stream is created but not playing
39    Stopped,
40    /// Stream is actively playing audio
41    Playing,
42    /// Stream is temporarily paused
43    Paused,
44}
45
46/// An active audio stream with buffer and mixer channel association
47pub struct AudioStream {
48    /// Unique stream identifier
49    pub id: AudioStreamId,
50    /// Audio configuration for this stream
51    pub config: AudioConfig,
52    /// Ring buffer for sample data
53    pub buffer: SharedAudioBuffer,
54    /// Associated mixer channel ID
55    pub mixer_channel_id: u16,
56    /// Current stream state
57    pub state: StreamState,
58    /// Human-readable stream name
59    pub name: String,
60}
61
62// ============================================================================
63// Audio Client Manager
64// ============================================================================
65
66/// Manages all active audio streams
67pub struct AudioClient {
68    /// Map of stream ID -> AudioStream
69    streams: BTreeMap<u32, AudioStream>,
70    /// Next stream ID counter
71    next_id: AtomicU32,
72}
73
74impl AudioClient {
75    /// Create a new audio client manager
76    fn new() -> Self {
77        Self {
78            streams: BTreeMap::new(),
79            next_id: AtomicU32::new(1),
80        }
81    }
82
83    /// Create a new audio stream
84    ///
85    /// Allocates a ring buffer and registers a mixer channel for the stream.
86    /// The stream starts in the `Stopped` state.
87    pub fn create_stream(
88        &mut self,
89        name: &str,
90        config: AudioConfig,
91    ) -> Result<AudioStreamId, KernelError> {
92        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
93        let stream_id = AudioStreamId(id);
94
95        // Create ring buffer for this stream
96        let buffer_frames = if config.buffer_frames > 0 {
97            config.buffer_frames
98        } else {
99            DEFAULT_BUFFER_FRAMES
100        };
101        let buffer = SharedAudioBuffer::new(buffer_frames, config);
102
103        // Register a mixer channel for this stream
104        let mixer_channel_id = crate::audio::mixer::with_mixer(|mixer| mixer.add_channel(name))?;
105
106        let stream = AudioStream {
107            id: stream_id,
108            config,
109            buffer,
110            mixer_channel_id,
111            state: StreamState::Stopped,
112            name: String::from(name),
113        };
114
115        self.streams.insert(id, stream);
116        println!("[AUDIO] Created stream '{}' (id={})", name, id);
117
118        Ok(stream_id)
119    }
120
121    /// Destroy an audio stream, freeing its buffer and mixer channel
122    pub fn destroy_stream(&mut self, id: AudioStreamId) -> Result<(), KernelError> {
123        let stream = self.streams.remove(&id.0).ok_or(KernelError::NotFound {
124            resource: "audio stream",
125            id: id.0 as u64,
126        })?;
127
128        // Remove the mixer channel
129        let _ = crate::audio::mixer::with_mixer(|mixer| {
130            mixer.remove_channel(stream.mixer_channel_id);
131        });
132
133        println!("[AUDIO] Destroyed stream '{}' (id={})", stream.name, id.0);
134        Ok(())
135    }
136
137    /// Write i16 samples to a stream's buffer
138    ///
139    /// Returns the number of samples actually written (may be less if the
140    /// buffer is full).
141    pub fn write_samples(
142        &mut self,
143        id: AudioStreamId,
144        samples: &[i16],
145    ) -> Result<usize, KernelError> {
146        let stream = self.streams.get_mut(&id.0).ok_or(KernelError::NotFound {
147            resource: "audio stream",
148            id: id.0 as u64,
149        })?;
150
151        let written = stream.buffer.write_samples(samples);
152
153        // Also feed samples to the mixer channel so they appear in mixed output
154        let channel_id = stream.mixer_channel_id;
155        let _ = crate::audio::mixer::with_mixer(|mixer| {
156            mixer.write_channel_samples(channel_id, samples);
157        });
158
159        Ok(written)
160    }
161
162    /// Start playing a stream
163    pub fn play(&mut self, id: AudioStreamId) -> Result<(), KernelError> {
164        let stream = self.streams.get_mut(&id.0).ok_or(KernelError::NotFound {
165            resource: "audio stream",
166            id: id.0 as u64,
167        })?;
168
169        stream.state = StreamState::Playing;
170        println!("[AUDIO] Stream '{}' (id={}) -> Playing", stream.name, id.0);
171        Ok(())
172    }
173
174    /// Pause a playing stream
175    pub fn pause(&mut self, id: AudioStreamId) -> Result<(), KernelError> {
176        let stream = self.streams.get_mut(&id.0).ok_or(KernelError::NotFound {
177            resource: "audio stream",
178            id: id.0 as u64,
179        })?;
180
181        if stream.state != StreamState::Playing {
182            return Err(KernelError::InvalidState {
183                expected: "Playing",
184                actual: "not Playing",
185            });
186        }
187
188        stream.state = StreamState::Paused;
189        println!("[AUDIO] Stream '{}' (id={}) -> Paused", stream.name, id.0);
190        Ok(())
191    }
192
193    /// Stop a stream (resets to beginning)
194    pub fn stop(&mut self, id: AudioStreamId) -> Result<(), KernelError> {
195        let stream = self.streams.get_mut(&id.0).ok_or(KernelError::NotFound {
196            resource: "audio stream",
197            id: id.0 as u64,
198        })?;
199
200        stream.state = StreamState::Stopped;
201        println!("[AUDIO] Stream '{}' (id={}) -> Stopped", stream.name, id.0);
202        Ok(())
203    }
204
205    /// Set the volume for a stream (0..65535 maps to 0.0..1.0)
206    pub fn set_volume(&self, id: AudioStreamId, volume: u16) -> Result<(), KernelError> {
207        let stream = self.streams.get(&id.0).ok_or(KernelError::NotFound {
208            resource: "audio stream",
209            id: id.0 as u64,
210        })?;
211
212        let channel_id = stream.mixer_channel_id;
213        crate::audio::mixer::with_mixer(|mixer| {
214            mixer.set_volume(channel_id, volume);
215        })?;
216
217        Ok(())
218    }
219
220    /// Get the state of a stream
221    pub fn get_state(&self, id: AudioStreamId) -> Result<StreamState, KernelError> {
222        let stream = self.streams.get(&id.0).ok_or(KernelError::NotFound {
223            resource: "audio stream",
224            id: id.0 as u64,
225        })?;
226        Ok(stream.state)
227    }
228
229    /// Close and remove an audio stream
230    pub fn close_stream(&mut self, id: AudioStreamId) -> Result<(), KernelError> {
231        self.streams.remove(&id.0).ok_or(KernelError::NotFound {
232            resource: "audio stream",
233            id: id.0 as u64,
234        })?;
235        println!("[AUDIO] Closed stream {}", id.0);
236        Ok(())
237    }
238
239    /// Get the number of active streams
240    pub fn stream_count(&self) -> usize {
241        self.streams.len()
242    }
243
244    /// Get the sample rate of the client's default config
245    pub fn default_sample_rate(&self) -> u32 {
246        48000
247    }
248
249    /// Get the default channel count
250    pub fn default_channels(&self) -> u8 {
251        2
252    }
253}
254
255// ============================================================================
256// Global Client State
257// ============================================================================
258
259static CLIENT: spin::Mutex<Option<AudioClient>> = spin::Mutex::new(None);
260
261/// Initialize the global audio client manager
262pub fn init() {
263    let mut client = CLIENT.lock();
264    *client = Some(AudioClient::new());
265    println!("[AUDIO] Client manager initialized");
266}
267
268/// Access the global audio client through a closure
269pub fn with_client<R, F: FnOnce(&mut AudioClient) -> R>(f: F) -> Result<R, KernelError> {
270    let mut guard = CLIENT.lock();
271    match guard.as_mut() {
272        Some(client) => Ok(f(client)),
273        None => Err(KernelError::NotInitialized {
274            subsystem: "audio client",
275        }),
276    }
277}
278
279// ============================================================================
280// Tests
281// ============================================================================
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286    use crate::audio::SampleFormat;
287
288    fn test_config() -> AudioConfig {
289        AudioConfig {
290            sample_rate: 48000,
291            channels: 2,
292            format: SampleFormat::S16Le,
293            buffer_frames: 1024,
294        }
295    }
296
297    #[test]
298    fn test_audio_stream_id() {
299        let id = AudioStreamId(42);
300        assert_eq!(id.as_u32(), 42);
301    }
302
303    #[test]
304    fn test_stream_state() {
305        assert_eq!(StreamState::Stopped, StreamState::Stopped);
306        assert_ne!(StreamState::Playing, StreamState::Paused);
307    }
308}