| Crates.io | petalsonic |
| lib.rs | petalsonic |
| version | 0.2.1 |
| created_at | 2025-10-18 13:02:54.864108+00 |
| updated_at | 2025-11-19 18:01:16.06955+00 |
| description | Real-time safe spatial audio library for Rust using Steam Audio for 3D spatialization |
| homepage | |
| repository | https://github.com/tr-nc/petalsonic |
| max_upload_size | |
| id | 1889189 |
| size | 205,856 |
A real-time safe spatial audio library for Rust that uses Steam Audio for 3D spatialization.
Add this to your Cargo.toml:
[dependencies]
petalsonic = "0.1"
use petalsonic::*;
use std::sync::Arc;
fn main() -> Result<(), PetalSonicError> {
// Create a world configuration
let config = PetalSonicWorldDesc::default();
// Create the audio world (runs on main thread)
let world = PetalSonicWorld::new(config.clone())?;
// Create and start the audio engine (spawns audio thread)
let mut engine = PetalSonicEngine::new(config, &world)?;
engine.start()?;
// Load audio data from file
let audio_data = audio_data::PetalSonicAudioData::from_path("path/to/audio.wav")?;
// Register audio with spatial configuration
let source_id = world.register_audio(
audio_data,
SourceConfig::spatial(Vec3::new(5.0, 0.0, 0.0), 1.0) // Position at (5, 0, 0) with volume 1.0
)?;
// Play the audio once
world.play(source_id, playback::LoopMode::Once)?;
// Update listener position (typically in your game loop)
world.set_listener_pose(Pose::from_position(Vec3::new(0.0, 0.0, 0.0)));
// Poll for events
for event in engine.poll_events() {
match event {
PetalSonicEvent::SourceCompleted { source_id } => {
println!("Audio completed: {:?}", source_id);
}
PetalSonicEvent::SourceLooped { source_id, loop_count } => {
println!("Audio looped: {:?} (iteration {})", source_id, loop_count);
}
_ => {}
}
}
Ok(())
}
use petalsonic::*;
// Load background music
let music = audio_data::PetalSonicAudioData::from_path("music.mp3")?;
// Register as non-spatial (no 3D effects, just plays normally)
let music_id = world.register_audio(
music,
SourceConfig::non_spatial()
)?;
// Play on infinite loop
world.play(music_id, playback::LoopMode::Infinite)?;
use petalsonic::audio_data::*;
// Force mono conversion for spatial audio sources
let options = LoadOptions::new()
.convert_to_mono(ConvertToMono::ForceMono);
let audio = PetalSonicAudioData::from_path_with_options(
"sound_effect.wav",
&options
)?;
PetalSonic uses a three-layer threading model to ensure real-time safety:
┌──────────────────────────────────────────────────────────────┐
│ Main Thread (World) │
│ - register_audio(audio_data, SourceConfig) │
│ - set_listener_pose(pose) │
│ - play(), pause(), stop() │
│ - poll_events() │
└──────────────────────────────────────────────────────────────┘
↓ Commands via channel
┌──────────────────────────────────────────────────────────────┐
│ Render Thread (generates samples at world rate) │
│ - Process playback commands │
│ - Spatialize audio sources via Steam Audio │
│ - Mix sources together │
│ - Push frames to ring buffer │
└──────────────────────────────────────────────────────────────┘
↓ Lock-free ring buffer
┌──────────────────────────────────────────────────────────────┐
│ Audio Callback (device rate) │
│ - Consume from ring buffer (real-time safe) │
│ - Output to audio device via CPAL │
└──────────────────────────────────────────────────────────────┘
PetalSonicWorld: Main API for managing audio sources and playback (main thread)PetalSonicEngine: Audio processing engine (dedicated thread)SourceId: Type-safe handle for audio sourcesSourceConfig: Configuration for spatial vs. non-spatial sourcesPetalSonicAudioData: Container for loaded and decoded audio dataPetalSonicWorldDesc: World configuration (sample rate, channels, buffer size, etc.)LoadOptions: Options for audio loading (mono conversion, etc.)LoopMode: Once or InfinitePlayState: Playing, Paused, or StoppedPlaybackInfo: Detailed playback position and timingPetalSonicEvent: Events emitted by the engine
SourceCompleted, SourceLooped, SourceStarted, SourceStoppedBufferUnderrun, BufferOverrunEngineError, SpatializationErrorPose: Position + rotation for listener and sourcesVec3: 3D vector (from glam crate)Quat: Quaternion rotation (from glam crate)use petalsonic::*;
let config = PetalSonicWorldDesc {
sample_rate: 48000, // Audio sample rate (Hz)
block_size: 512, // Render block size (frames)
channels: 2, // Output channels (stereo)
max_sources: 64, // Maximum simultaneous sources
hrtf_path: None, // Optional custom HRTF data path
hrtf_gain: 0.0, // HRTF gain compensation (dB)
distance_scaler: 10.0, // 1 world unit = 10 meters in spatial simulation
};
The audio callback thread is completely real-time safe:
block_size: Smaller = lower latency, higher CPU usage (typical: 256-1024)// Get timing information for performance profiling
for event in engine.poll_timing_events() {
println!("Mixing: {}μs, Spatial: {}μs, Total: {}μs",
event.mixing_time_us,
event.spatial_time_us,
event.total_time_us
);
}
Implement AudioDataLoader for custom file formats:
use petalsonic::audio_data::*;
struct MyLoader;
impl AudioDataLoader for MyLoader {
fn load(&self, path: &str, options: &LoadOptions) -> Result<Arc<PetalSonicAudioData>> {
// Your custom loading logic
todo!()
}
}
See the petalsonic-demo crate for complete examples:
# Run the demo application
cargo run --package petalsonic-demo
PetalSonic uses:
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.