Skip to content

Commit e85f790

Browse files
committed
[add] audio-playback feature and initial SoundInstance & AudioContext implementations.
1 parent 6ae5a92 commit e85f790

4 files changed

Lines changed: 237 additions & 2 deletions

File tree

crates/lambda-rs/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ with-wgpu-gl=["with-wgpu", "lambda-rs-platform/wgpu-with-gl"]
3535
# ---------------------------------- AUDIO ------------------------------------
3636

3737
# Umbrella features
38-
audio = ["audio-output-device", "audio-sound-buffer"]
38+
audio = ["audio-output-device", "audio-sound-buffer", "audio-playback"]
3939

4040
# Granular feature flags
4141
audio-output-device = ["lambda-rs-platform/audio-device"]
4242
audio-sound-buffer-wav = ["lambda-rs-platform/audio-decode-wav"]
4343
audio-sound-buffer-vorbis = ["lambda-rs-platform/audio-decode-vorbis"]
44+
audio-playback = ["audio-output-device", "audio-sound-buffer"]
4445

4546
# Umbrella feature
4647
audio-sound-buffer = [

crates/lambda-rs/src/audio/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,8 @@ pub mod devices;
2727

2828
#[cfg(feature = "audio-output-device")]
2929
pub use devices::output::*;
30+
31+
#[cfg(feature = "audio-playback")]
32+
mod playback;
33+
#[cfg(feature = "audio-playback")]
34+
pub use playback::*;
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
#![allow(clippy::needless_return)]
2+
3+
//! Single-sound playback and transport controls.
4+
//!
5+
//! This module provides a minimal, backend-agnostic playback facade that
6+
//! supports one active `SoundBuffer` at a time.
7+
8+
use crate::audio::{
9+
AudioError,
10+
SoundBuffer,
11+
};
12+
13+
/// A queryable playback state for a `SoundInstance`.
14+
///
15+
/// This state is observable from the application thread and is intended to
16+
/// provide basic transport visibility.
17+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18+
pub enum PlaybackState {
19+
/// The sound is currently playing.
20+
Playing,
21+
/// The sound is currently paused.
22+
Paused,
23+
/// The sound is stopped and positioned at the start.
24+
Stopped,
25+
}
26+
27+
/// A lightweight handle controlling the active sound playback slot.
28+
///
29+
/// This type is a placeholder API surface used while sound playback is under
30+
/// active development. It is expected to become fully functional in a
31+
/// subsequent change set.
32+
pub struct SoundInstance {
33+
state: PlaybackState,
34+
looping: bool,
35+
}
36+
37+
impl SoundInstance {
38+
/// Begin playback, or resume if paused.
39+
///
40+
/// # Returns
41+
/// `()` after updating the requested transport state.
42+
pub fn play(&mut self) {
43+
self.state = PlaybackState::Playing;
44+
return;
45+
}
46+
47+
/// Pause playback, preserving playback position.
48+
///
49+
/// # Returns
50+
/// `()` after updating the requested transport state.
51+
pub fn pause(&mut self) {
52+
self.state = PlaybackState::Paused;
53+
return;
54+
}
55+
56+
/// Stop playback and reset position to the start of the buffer.
57+
///
58+
/// # Returns
59+
/// `()` after updating the requested transport state.
60+
pub fn stop(&mut self) {
61+
self.state = PlaybackState::Stopped;
62+
return;
63+
}
64+
65+
/// Enable or disable looping playback.
66+
///
67+
/// # Arguments
68+
/// - `looping`: Whether the sound should loop on completion.
69+
///
70+
/// # Returns
71+
/// `()` after updating the looping flag.
72+
pub fn set_looping(&mut self, looping: bool) {
73+
self.looping = looping;
74+
return;
75+
}
76+
77+
/// Query the current state of this instance.
78+
///
79+
/// # Returns
80+
/// The current transport state.
81+
pub fn state(&self) -> PlaybackState {
82+
return self.state;
83+
}
84+
85+
/// Convenience query for `state() == PlaybackState::Playing`.
86+
///
87+
/// # Returns
88+
/// `true` if the instance state is `Playing`.
89+
pub fn is_playing(&self) -> bool {
90+
return self.state == PlaybackState::Playing;
91+
}
92+
93+
/// Convenience query for `state() == PlaybackState::Paused`.
94+
///
95+
/// # Returns
96+
/// `true` if the instance state is `Paused`.
97+
pub fn is_paused(&self) -> bool {
98+
return self.state == PlaybackState::Paused;
99+
}
100+
101+
/// Convenience query for `state() == PlaybackState::Stopped`.
102+
///
103+
/// # Returns
104+
/// `true` if the instance state is `Stopped`.
105+
pub fn is_stopped(&self) -> bool {
106+
return self.state == PlaybackState::Stopped;
107+
}
108+
}
109+
110+
/// A playback context owning an output device and one active playback slot.
111+
///
112+
/// This type is a placeholder API surface used while sound playback is under
113+
/// active development. It is expected to become fully functional in a
114+
/// subsequent change set.
115+
pub struct AudioContext {
116+
_requested_sample_rate: Option<u32>,
117+
_requested_channels: Option<u16>,
118+
_label: Option<String>,
119+
}
120+
121+
/// Builder for creating an `AudioContext`.
122+
#[derive(Debug, Clone)]
123+
pub struct AudioContextBuilder {
124+
sample_rate: Option<u32>,
125+
channels: Option<u16>,
126+
label: Option<String>,
127+
}
128+
129+
impl AudioContextBuilder {
130+
/// Create a builder with engine defaults.
131+
///
132+
/// # Returns
133+
/// A builder with no explicit configuration requests.
134+
pub fn new() -> Self {
135+
return Self {
136+
sample_rate: None,
137+
channels: None,
138+
label: None,
139+
};
140+
}
141+
142+
/// Request an output sample rate.
143+
///
144+
/// # Arguments
145+
/// - `rate`: Requested output sample rate in frames per second.
146+
///
147+
/// # Returns
148+
/// The updated builder.
149+
pub fn with_sample_rate(mut self, rate: u32) -> Self {
150+
self.sample_rate = Some(rate);
151+
return self;
152+
}
153+
154+
/// Request an output channel count.
155+
///
156+
/// # Arguments
157+
/// - `channels`: Requested interleaved output channel count.
158+
///
159+
/// # Returns
160+
/// The updated builder.
161+
pub fn with_channels(mut self, channels: u16) -> Self {
162+
self.channels = Some(channels);
163+
return self;
164+
}
165+
166+
/// Attach a label for diagnostics.
167+
///
168+
/// # Arguments
169+
/// - `label`: A human-readable label used for diagnostics.
170+
///
171+
/// # Returns
172+
/// The updated builder.
173+
pub fn with_label(mut self, label: &str) -> Self {
174+
self.label = Some(label.to_string());
175+
return self;
176+
}
177+
178+
/// Build an `AudioContext` using the requested configuration.
179+
///
180+
/// # Returns
181+
/// An initialized audio context handle.
182+
///
183+
/// # Errors
184+
/// Returns an error until sound playback is integrated with an audio output
185+
/// device callback.
186+
pub fn build(self) -> Result<AudioContext, AudioError> {
187+
return Err(AudioError::InvalidData {
188+
details: format!(
189+
"audio context playback is not implemented in this build (requested_sample_rate={:?}, requested_channels={:?}, label={:?})",
190+
self.sample_rate,
191+
self.channels,
192+
self.label
193+
),
194+
});
195+
}
196+
}
197+
198+
impl Default for AudioContextBuilder {
199+
fn default() -> Self {
200+
return Self::new();
201+
}
202+
}
203+
204+
impl AudioContext {
205+
/// Play a decoded `SoundBuffer` through this context.
206+
///
207+
/// # Arguments
208+
/// - `buffer`: The decoded sound buffer to schedule for playback.
209+
///
210+
/// # Returns
211+
/// A lightweight `SoundInstance` handle for controlling playback.
212+
///
213+
/// # Errors
214+
/// Returns an error until the playback implementation is integrated with an
215+
/// audio output device callback.
216+
pub fn play_sound(
217+
&mut self,
218+
buffer: &SoundBuffer,
219+
) -> Result<SoundInstance, AudioError> {
220+
return Err(AudioError::InvalidData {
221+
details: format!(
222+
"sound playback is not implemented in this build (buffer_sample_rate={}, buffer_channels={})",
223+
buffer.sample_rate(),
224+
buffer.channels()
225+
),
226+
});
227+
}
228+
}

demos/audio/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ lambda-rs = { path = "../../crates/lambda-rs" }
99

1010
[features]
1111
default = ["audio"]
12-
audio = ["audio-output-device", "audio-sound-buffer"]
12+
audio = ["audio-output-device", "audio-sound-buffer", "audio-playback"]
1313
audio-output-device = ["lambda-rs/audio-output-device"]
1414
audio-sound-buffer = ["lambda-rs/audio-sound-buffer"]
1515
audio-sound-buffer-wav = ["lambda-rs/audio-sound-buffer-wav"]
1616
audio-sound-buffer-vorbis = ["lambda-rs/audio-sound-buffer-vorbis"]
17+
audio-playback = ["lambda-rs/audio-playback"]

0 commit comments

Comments
 (0)