Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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