diff --git a/Cargo.toml b/Cargo.toml index aca94e4..b59c19d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "examples/shadow_demo", "examples/ibl_demo", "crates/voltex_physics", + "crates/voltex_audio", ] [workspace.dependencies] @@ -25,6 +26,7 @@ voltex_renderer = { path = "crates/voltex_renderer" } voltex_ecs = { path = "crates/voltex_ecs" } voltex_asset = { path = "crates/voltex_asset" } voltex_physics = { path = "crates/voltex_physics" } +voltex_audio = { path = "crates/voltex_audio" } wgpu = "28.0" winit = "0.30" bytemuck = { version = "1", features = ["derive"] } diff --git a/crates/voltex_audio/Cargo.toml b/crates/voltex_audio/Cargo.toml new file mode 100644 index 0000000..e1ac68f --- /dev/null +++ b/crates/voltex_audio/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "voltex_audio" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/crates/voltex_audio/src/audio_clip.rs b/crates/voltex_audio/src/audio_clip.rs new file mode 100644 index 0000000..b5dddd4 --- /dev/null +++ b/crates/voltex_audio/src/audio_clip.rs @@ -0,0 +1,55 @@ +/// A loaded audio clip stored as normalized f32 PCM samples. +#[derive(Debug)] +pub struct AudioClip { + /// Interleaved PCM samples in the range [-1.0, 1.0]. + pub samples: Vec, + /// Number of samples per second (e.g. 44100). + pub sample_rate: u32, + /// Number of audio channels (1 = mono, 2 = stereo). + pub channels: u16, +} + +impl AudioClip { + /// Create a new AudioClip from raw f32 samples. + pub fn new(samples: Vec, sample_rate: u32, channels: u16) -> Self { + Self { + samples, + sample_rate, + channels, + } + } + + /// Total number of multi-channel frames. + /// A frame contains one sample per channel. + pub fn frame_count(&self) -> usize { + self.samples.len() / self.channels as usize + } + + /// Duration in seconds. + pub fn duration(&self) -> f32 { + self.frame_count() as f32 / self.sample_rate as f32 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mono_clip() { + // 44100 Hz, 1 channel, 1 second of silence + let samples = vec![0.0f32; 44100]; + let clip = AudioClip::new(samples, 44100, 1); + assert_eq!(clip.frame_count(), 44100); + assert!((clip.duration() - 1.0).abs() < 1e-6); + } + + #[test] + fn stereo_clip() { + // 44100 Hz, 2 channels, 0.5 seconds + let samples = vec![0.0f32; 44100]; // 44100 interleaved samples = 22050 frames + let clip = AudioClip::new(samples, 44100, 2); + assert_eq!(clip.frame_count(), 22050); + assert!((clip.duration() - 0.5).abs() < 1e-6); + } +} diff --git a/crates/voltex_audio/src/lib.rs b/crates/voltex_audio/src/lib.rs new file mode 100644 index 0000000..91abd33 --- /dev/null +++ b/crates/voltex_audio/src/lib.rs @@ -0,0 +1,7 @@ +pub mod audio_clip; +pub mod wav; +pub mod mixing; + +pub use audio_clip::AudioClip; +pub use wav::{parse_wav, generate_wav_bytes}; +pub use mixing::{PlayingSound, mix_sounds};