phonic

phonic is a cross-platform audio playback and DSP library for Rust. It provides a flexible, low-latency audio engine and DSP tools for desktop and web-based music application development.
Originally developed for the AFEC-Explorer app, phonic was created to provide precise playback position monitoring - a feature lacking in other Rust audio libraries at the time. It is now also used in the experimental algorithmic sequencer pattrns as the default sample playback engine.
[!NOTE]
phonic has not yet reached a stable version, so expect breaking changes.
Features
-
Cross-Platform Audio Playback:
- Play audio on Windows, macOS, and Linux via cpal.
- WebAssembly support for in-browser audio via emscripten.
- WAV file output for rendering computed audio to a file instead of playing it back.
-
Flexible Audio Source Handling:
- Play, seek, stop, and mix preloaded (buffered) or streamed (on-the-fly decoded) audio files.
- Support for most common audio formats through Symphonia.
- Seamless loop playback using loop points from WAV and FLAC files.
- Automatic resampling and channel mapping via a fast custom resampler and Rubato.
-
Advanced Playback Control:
- Sample-precise scheduling of playback events for accurate sequencing.
- Real-time monitoring of playback position and status for GUI integration.
- Dynamic control over volume, panning, and playback speed via
Sync + Send playback handles.
-
Custom Synthesis and DSPs:
- Play preloaded sample files via a basic sampler.
- Play custom-built synthesizers or one-shot synth sounds using the optional fundsp feature.
- Apply custom-built or use built-in DSP effects: gain, filter, eq, reverb, chorus, compressor, limiter, distortion.
- Build simple or complex DSP graphs by routing audio through optional sub-mixers.
- DSP effects are automatically bypassed to save CPU cycles when they receive no audible input.
-
Performance Monitoring:
- Measure the CPU load of the main mixer and individual audio sources in real-time.
- Access average and peak load metrics to identify performance bottlenecks.
Documentation
Rust docs for the last published versions are available at https://docs.rs/phonic
Examples
See /examples directory for more examples.
File Playback with Monitoring
Play, seek, and stop audio files on the default audio output device.
Monitor playback status of playing files.
use std::{time::Duration, sync::mpsc::sync_channel};
use phonic::{
DefaultOutputDevice, Player, PlaybackStatusEvent, Error,
FilePlaybackOptions, SynthPlaybackOptions
};
fn main() -> Result<(), Error> {
let (playback_status_sender, playback_status_receiver) = sync_channel(32);
let mut player = Player::new(DefaultOutputDevice::open()?, playback_status_sender);
let small_file = player.play_file(
"PATH_TO/some_small_file.wav",
FilePlaybackOptions::default())?;
let long_file = player.play_file(
"PATH_TO/some_long_file.mp3",
FilePlaybackOptions::default()
.streamed()
.volume_db(-6.0)
.speed(0.5)
.repeat(2),
)?;
std::thread::spawn(move || {
while let Ok(event) = playback_status_receiver.recv() {
match event {
PlaybackStatusEvent::Position { id, path, context: _, position } => {
println!("Playback pos of source #{id} '{path}': {pos}",
pos = position.as_secs_f32()
);
}
PlaybackStatusEvent::Stopped { id, path, context: _, exhausted, } => {
if exhausted {
println!("Playback of #{id} '{path}' finished");
} else {
println!("Playback of #{id} '{path}' was stopped");
}
}
}
}
});
long_file.seek(Duration::from_secs(5), None)?;
let now = player.output_sample_frame_position();
let samples_per_second = player.output_sample_rate() as u64;
if long_file.is_playing() {
long_file.set_volume(0.3, now + samples_per_second)?; long_file.stop(now + 2 * samples_per_second)?; }
player.stop_all_sources()?;
let _boom = player.play_file("PATH_TO/boom.wav", FilePlaybackOptions::default())?;
Ok(())
}
File Playback with Generators, DSP Effects in a Mixer Graph
Create DSP graphs by routing sources through different mixers and effects.
use std::time::Duration;
use phonic::{
DefaultOutputDevice, Player, Error, FilePlaybackOptions,
effects::{ChorusEffect, ReverbEffect}, ParameterValueUpdate, FourCC,
generators::Sampler, GeneratorPlaybackOptions,
};
fn main() -> Result<(), Error> {
let mut player = Player::new(DefaultOutputDevice::open()?, None);
let reverb = player.add_effect(ReverbEffect::with_parameters(0.6, 0.8), None)?;
let chorus_mixer = player.add_mixer(None)?;
let chorus = player.add_effect(ChorusEffect::default(), chorus_mixer.id())?;
reverb.set_parameter(ReverbEffect::ROOM_SIZE.value_update(0.9), None)?;
chorus.set_parameter((FourCC(*b"rate"), ParameterValueUpdate::Normalized(0.5)), None)?;
let some_file = player.play_file(
"PATH_TO/some_file.wav",
FilePlaybackOptions::default(),
)?;
let another_file = player.play_file(
"PATH_TO/another_file.wav",
FilePlaybackOptions::default().target_mixer(chorus_mixer.id()),
)?;
let generator = player.play_generator(
Sampler::from_file(
"path/to/some_sample.wav",
None,
GeneratorPlaybackOptions::default().target_mixer(sub_mixer.id())
player.output_channel_count(),
player.output_sample_rate(),
)?,
None
)?;
generator.note_on(60, Some(1.0f32), None, None)?;
another_file.stop(None)?;
generator.stop(None)?;
while some_file.is_playing() || another_file.is_playing() || generator.is_playing() {
std::thread::sleep(Duration::from_millis(500));
}
Ok(())
}
Contributing
Patches are welcome! Please fork the latest git repository and create a feature or bugfix branch.
License
phonic is distributed under the terms of the GNU Affero General Public License V3.