feat(audio): add voltex_audio crate with AudioClip type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ members = [
|
|||||||
"examples/shadow_demo",
|
"examples/shadow_demo",
|
||||||
"examples/ibl_demo",
|
"examples/ibl_demo",
|
||||||
"crates/voltex_physics",
|
"crates/voltex_physics",
|
||||||
|
"crates/voltex_audio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
@@ -25,6 +26,7 @@ voltex_renderer = { path = "crates/voltex_renderer" }
|
|||||||
voltex_ecs = { path = "crates/voltex_ecs" }
|
voltex_ecs = { path = "crates/voltex_ecs" }
|
||||||
voltex_asset = { path = "crates/voltex_asset" }
|
voltex_asset = { path = "crates/voltex_asset" }
|
||||||
voltex_physics = { path = "crates/voltex_physics" }
|
voltex_physics = { path = "crates/voltex_physics" }
|
||||||
|
voltex_audio = { path = "crates/voltex_audio" }
|
||||||
wgpu = "28.0"
|
wgpu = "28.0"
|
||||||
winit = "0.30"
|
winit = "0.30"
|
||||||
bytemuck = { version = "1", features = ["derive"] }
|
bytemuck = { version = "1", features = ["derive"] }
|
||||||
|
|||||||
6
crates/voltex_audio/Cargo.toml
Normal file
6
crates/voltex_audio/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "voltex_audio"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
55
crates/voltex_audio/src/audio_clip.rs
Normal file
55
crates/voltex_audio/src/audio_clip.rs
Normal file
@@ -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<f32>,
|
||||||
|
/// 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<f32>, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
crates/voltex_audio/src/lib.rs
Normal file
7
crates/voltex_audio/src/lib.rs
Normal file
@@ -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};
|
||||||
Reference in New Issue
Block a user