Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/lambda-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ with-wgpu-gl=["with-wgpu", "lambda-rs-platform/wgpu-with-gl"]
# ---------------------------------- AUDIO ------------------------------------

# Umbrella features
audio = ["audio-output-device", "audio-sound-buffer"]
audio = ["audio-output-device", "audio-sound-buffer", "audio-playback"]

# Granular feature flags
audio-output-device = ["lambda-rs-platform/audio-device"]
audio-sound-buffer-wav = ["lambda-rs-platform/audio-decode-wav"]
audio-sound-buffer-vorbis = ["lambda-rs-platform/audio-decode-vorbis"]
audio-playback = ["audio-output-device", "audio-sound-buffer"]

# Umbrella feature
audio-sound-buffer = [
Expand Down
63 changes: 57 additions & 6 deletions crates/lambda-rs/src/audio/buffer.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
#![allow(clippy::needless_return)]

use std::path::Path;
use std::{
path::Path,
sync::Arc,
};

use crate::audio::AudioError;

/// Fully decoded, in-memory audio samples suitable for future mixing and
/// playback.
#[derive(Clone, Debug, PartialEq)]
pub struct SoundBuffer {
samples: Vec<f32>,
samples: Arc<[f32]>,
sample_rate: u32,
channels: u16,
}
Expand Down Expand Up @@ -139,12 +142,60 @@ impl SoundBuffer {
}

return Ok(Self {
samples: decoded.samples,
samples: decoded.samples.into(),
sample_rate: decoded.sample_rate,
channels: decoded.channels,
});
}

/// Construct a `SoundBuffer` from interleaved samples for unit tests.
///
/// # Arguments
/// - `samples`: Interleaved samples, `frames * channels` in length.
/// - `sample_rate`: Sample rate in Hz.
/// - `channels`: Interleaved channel count.
///
/// # Returns
/// A validated `SoundBuffer` constructed from the provided samples.
///
/// # Errors
/// Returns [`AudioError::InvalidData`] when the metadata is invalid or when
/// the sample vector length is not a multiple of `channels`.
#[cfg(test)]
pub(crate) fn from_interleaved_samples_for_test(
samples: Vec<f32>,
sample_rate: u32,
channels: u16,
) -> Result<Self, AudioError> {
if sample_rate == 0 {
return Err(AudioError::InvalidData {
details: "test sound buffer sample rate was 0".to_string(),
});
}

if channels == 0 {
return Err(AudioError::InvalidData {
details: "test sound buffer channel count was 0".to_string(),
});
}

if !samples.len().is_multiple_of(channels as usize) {
return Err(AudioError::InvalidData {
details: format!(
"test sound buffer sample length was not divisible by channels (samples={}, channels={})",
samples.len(),
channels
),
});
}

return Ok(Self {
samples: samples.into(),
sample_rate,
channels,
});
}

/// Return the sample rate in Hz.
///
/// # Returns
Expand All @@ -166,7 +217,7 @@ impl SoundBuffer {
/// # Returns
/// A slice of interleaved samples.
pub fn samples(&self) -> &[f32] {
return self.samples.as_slice();
return self.samples.as_ref();
}

/// Return the number of frames in this buffer.
Expand Down Expand Up @@ -233,7 +284,7 @@ mod tests {
#[test]
fn duration_seconds_computes_expected_value() {
let buffer = SoundBuffer {
samples: vec![0.0; 48000],
samples: vec![0.0; 48000].into(),
sample_rate: 48000,
channels: 1,
};
Expand All @@ -246,7 +297,7 @@ mod tests {
#[test]
fn frames_returns_zero_when_channels_is_zero() {
let buffer = SoundBuffer {
samples: vec![0.0, 0.0],
samples: vec![0.0, 0.0].into(),
sample_rate: 48_000,
channels: 0,
};
Expand Down
5 changes: 5 additions & 0 deletions crates/lambda-rs/src/audio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ pub mod devices;

#[cfg(feature = "audio-output-device")]
pub use devices::output::*;

#[cfg(feature = "audio-playback")]
mod playback;
#[cfg(feature = "audio-playback")]
pub use playback::*;
Loading
Loading