Skip to content

Commit 1a6793f

Browse files
committed
[add] documentation to audio implementation.
1 parent 940240f commit 1a6793f

4 files changed

Lines changed: 113 additions & 0 deletions

File tree

crates/lambda-rs-platform/src/audio/cpal/device.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub(crate) enum AudioOutputBuffer<'buffer> {
8484
}
8585

8686
impl<'buffer> AudioOutputBuffer<'buffer> {
87+
/// Return the number of interleaved samples in the underlying buffer.
8788
#[allow(dead_code)]
8889
fn len(&self) -> usize {
8990
match self {
@@ -99,6 +100,7 @@ impl<'buffer> AudioOutputBuffer<'buffer> {
99100
}
100101
}
101102

103+
/// Return the sample format of the underlying typed buffer.
102104
fn sample_format(&self) -> AudioSampleFormat {
103105
match self {
104106
Self::F32(_) => {
@@ -125,6 +127,11 @@ pub(crate) struct InterleavedAudioOutputWriter<'buffer> {
125127
}
126128

127129
impl<'buffer> InterleavedAudioOutputWriter<'buffer> {
130+
/// Create a writer for an interleaved output buffer.
131+
///
132+
/// `channels` MUST match the channel count encoded in the output stream
133+
/// configuration. The frame count is derived from the buffer length and
134+
/// channel count.
128135
#[allow(dead_code)]
129136
pub fn new(channels: u16, buffer: AudioOutputBuffer<'buffer>) -> Self {
130137
let channels_usize = channels as usize;
@@ -141,12 +148,14 @@ impl<'buffer> InterleavedAudioOutputWriter<'buffer> {
141148
};
142149
}
143150

151+
/// Return the sample format of the current callback buffer.
144152
#[allow(dead_code)]
145153
pub fn sample_format(&self) -> AudioSampleFormat {
146154
return self.buffer.sample_format();
147155
}
148156
}
149157

158+
/// Clamp a normalized audio sample to the nominal output range `[-1.0, 1.0]`.
150159
#[allow(dead_code)]
151160
fn clamp_normalized_sample(sample: f32) -> f32 {
152161
if sample > 1.0 {
@@ -680,6 +689,12 @@ impl Default for AudioDeviceBuilder {
680689
}
681690
}
682691

692+
/// Invoke an output callback using a typed platform buffer.
693+
///
694+
/// This adapter:
695+
/// - Wraps the typed `cpal` output slice in an [`AudioOutputWriter`].
696+
/// - Clears the buffer to silence before invoking the callback.
697+
/// - Guarantees a single callback invocation per platform callback tick.
683698
fn invoke_output_callback_on_buffer<Callback>(
684699
channels: u16,
685700
buffer: AudioOutputBuffer<'_>,
@@ -694,6 +709,10 @@ fn invoke_output_callback_on_buffer<Callback>(
694709
return;
695710
}
696711

712+
/// Convert a `cpal` sample format into a stable preference ordering.
713+
///
714+
/// The current backend prefers `f32`, then `i16`, then `u16`. Any other format
715+
/// is treated as unsupported by this abstraction.
697716
fn sample_format_priority(sample_format: cpal_backend::SampleFormat) -> u8 {
698717
match sample_format {
699718
cpal_backend::SampleFormat::F32 => {
@@ -711,6 +730,17 @@ fn sample_format_priority(sample_format: cpal_backend::SampleFormat) -> u8 {
711730
}
712731
}
713732

733+
/// Select a supported output stream configuration for the default device.
734+
///
735+
/// Selection rules:
736+
/// - If `requested_channels` is set, only exact channel matches are considered.
737+
/// - If `requested_sample_rate` is set, only ranges that contain the rate are
738+
/// considered.
739+
/// - If no rate is requested, the selection targets `48_000` Hz and chooses the
740+
/// closest available rate within each range.
741+
/// - Higher-quality sample formats are preferred (`f32` > `i16` > `u16`).
742+
/// - When priorities tie, the configuration with sample rate closest to
743+
/// `48_000` Hz is preferred.
714744
fn select_output_stream_config(
715745
supported_configs: &[cpal_backend::SupportedStreamConfigRange],
716746
requested_sample_rate: Option<u32>,
@@ -844,24 +874,29 @@ mod tests {
844874

845875
use super::*;
846876

877+
/// Builder MUST reject invalid sample rates.
847878
#[test]
848879
fn build_rejects_zero_sample_rate() {
849880
let result = AudioDeviceBuilder::new().with_sample_rate(0).build();
850881
assert!(matches!(
851882
result,
852883
Err(AudioError::InvalidSampleRate { requested: 0 })
853884
));
885+
return;
854886
}
855887

888+
/// Builder MUST reject invalid channel counts.
856889
#[test]
857890
fn build_rejects_zero_channels() {
858891
let result = AudioDeviceBuilder::new().with_channels(0).build();
859892
assert!(matches!(
860893
result,
861894
Err(AudioError::InvalidChannels { requested: 0 })
862895
));
896+
return;
863897
}
864898

899+
/// Builder MUST NOT panic for typical host/device states.
865900
#[test]
866901
fn build_does_not_panic() {
867902
let result = AudioDeviceBuilder::new().build();
@@ -873,12 +908,14 @@ mod tests {
873908
return;
874909
}
875910

911+
/// Device enumeration MUST NOT panic for typical host/device states.
876912
#[test]
877913
fn enumerate_devices_does_not_panic() {
878914
let _result = enumerate_devices();
879915
return;
880916
}
881917

918+
/// Callback-based builder MUST NOT panic for typical host/device states.
882919
#[test]
883920
fn build_with_output_callback_does_not_panic() {
884921
let result = AudioDeviceBuilder::new().build_with_output_callback(
@@ -894,6 +931,7 @@ mod tests {
894931
return;
895932
}
896933

934+
/// Callback adapter MUST clear and then invoke the callback for `f32`.
897935
#[test]
898936
fn invoke_output_callback_on_buffer_clears_and_invokes_callback_f32() {
899937
let mut buffer_f32 = [1.0, -1.0, 0.5, -0.5];
@@ -923,8 +961,10 @@ mod tests {
923961

924962
assert!(callback_called);
925963
assert_eq!(buffer_f32, [0.5, 0.0, 0.0, 0.0]);
964+
return;
926965
}
927966

967+
/// Callback adapter MUST clear and then invoke the callback for `i16`.
928968
#[test]
929969
fn invoke_output_callback_on_buffer_clears_and_invokes_callback_i16() {
930970
let mut buffer_i16 = [1, -1, 200, -200];
@@ -952,8 +992,10 @@ mod tests {
952992

953993
assert!(callback_called);
954994
assert_eq!(buffer_i16, [32767, 0, 0, 0]);
995+
return;
955996
}
956997

998+
/// Callback adapter MUST clear and then invoke the callback for `u16`.
957999
#[test]
9581000
fn invoke_output_callback_on_buffer_clears_and_invokes_callback_u16() {
9591001
let mut buffer_u16 = [0, 1, 65535, 12345];
@@ -981,8 +1023,10 @@ mod tests {
9811023

9821024
assert!(callback_called);
9831025
assert_eq!(buffer_u16, [0, 32768, 32768, 32768]);
1026+
return;
9841027
}
9851028

1029+
/// Writer silence MUST match each sample format's conventions.
9861030
#[test]
9871031
fn writer_clear_sets_silence_for_all_formats() {
9881032
let mut buffer_f32 = [1.0, -1.0, 0.5, -0.5];
@@ -1008,8 +1052,10 @@ mod tests {
10081052
);
10091053
writer.clear();
10101054
assert_eq!(buffer_u16, [32768, 32768, 32768, 32768]);
1055+
return;
10111056
}
10121057

1058+
/// Writer MUST clamp normalized samples and convert to output formats.
10131059
#[test]
10141060
fn writer_set_sample_clamps_and_converts() {
10151061
let mut buffer_f32 = [0.0, 0.0, 0.0, 0.0];
@@ -1045,8 +1091,10 @@ mod tests {
10451091
assert_eq!(buffer_u16[0], 0);
10461092
assert_eq!(buffer_u16[1], 32768);
10471093
assert_eq!(buffer_u16[2], 65535);
1094+
return;
10481095
}
10491096

1097+
/// Out-of-range indices MUST be treated as no-ops.
10501098
#[test]
10511099
fn writer_set_sample_is_noop_for_out_of_range_indices() {
10521100
let mut buffer_f32 = [0.25, 0.25, 0.25, 0.25];
@@ -1059,8 +1107,10 @@ mod tests {
10591107
writer.set_sample(0, 10, 1.0);
10601108

10611109
assert_eq!(buffer_f32, [0.25, 0.25, 0.25, 0.25]);
1110+
return;
10621111
}
10631112

1113+
/// Config selection MUST prefer `f32` when available.
10641114
#[test]
10651115
fn select_output_stream_config_prefers_f32_when_available() {
10661116
let supported_configs = [
@@ -1084,8 +1134,10 @@ mod tests {
10841134
select_output_stream_config(&supported_configs, None, None).unwrap();
10851135
assert_eq!(selected.sample_format(), cpal_backend::SampleFormat::F32);
10861136
assert_eq!(selected.sample_rate(), 48_000);
1137+
return;
10871138
}
10881139

1140+
/// Config selection MUST honor exact requested channel counts.
10891141
#[test]
10901142
fn select_output_stream_config_respects_requested_channels() {
10911143
let supported_configs = [cpal_backend::SupportedStreamConfigRange::new(
@@ -1108,8 +1160,10 @@ mod tests {
11081160
requested_channels: Some(1),
11091161
})
11101162
));
1163+
return;
11111164
}
11121165

1166+
/// Config selection MUST honor requested sample rates when available.
11131167
#[test]
11141168
fn select_output_stream_config_respects_requested_sample_rate() {
11151169
let supported_configs = [cpal_backend::SupportedStreamConfigRange::new(
@@ -1134,5 +1188,6 @@ mod tests {
11341188
requested_channels: None,
11351189
})
11361190
));
1191+
return;
11371192
}
11381193
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
#![allow(clippy::needless_return)]
22

33
//! Internal audio backend abstractions used by `lambda-rs` (cpal backend).
4+
//!
5+
//! This module provides a stable, backend-agnostic surface that is implemented
6+
//! using `cpal` when the `audio-device` feature is enabled.
47
8+
/// `cpal`-backed output device discovery and stream initialization.
59
pub mod device;
610

11+
/// Re-export the backend-agnostic surface types for consumption by `lambda-rs`.
712
pub use device::{
813
enumerate_devices,
914
AudioCallbackInfo,

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
//!
55
//! This module is internal to the engine. Applications MUST NOT depend on
66
//! `lambda-rs-platform` directly.
7+
//!
8+
//! Each dependency wrapper is gated by a granular Cargo feature in this crate.
9+
//! Feature checks are scoped to the smallest possible surface so that
10+
//! `lambda-rs` can compose audio features without exposing vendor details.
711
812
#[cfg(feature = "audio-device")]
913
pub mod cpal;

0 commit comments

Comments
 (0)