phonic

phonic is a cross-platform audio playback and DSP library for Rust. It provides a flexible, low-latency audio engine for playing back audio files, synthesizing sounds, applying real-time effects, and building complex audio processing graphs:
- Plays audio on Windows, macOS, Linux via CPAL, on the web via WebAssembly and
Emscripten, or offline to WAV files.
- Decodes most common audio formats via Symphonia, wth playback preloaded from RAM or streamed on-the-fly.
- Processes mixer graphs concurrently with custom or built-in DSP effects (gain, panning, filter, 5-band EQ, delay, reverb, chorus, compressor/limiter, gate, distortion) and sample-accurate event scheduling.
- Allows creating custom synths via the optional FunDSP integration.
- Includes a basic polyphonic sampler with AHDSR envelopes, granular synthesis, and glide/portamento.
Send + Sync playback handles allow monitoring and controlling components from any thread.
Originally developed for the afec-explorer app, phonic is now used in the experimental algorithmic sequencer pattrns as example playback engine and related projects.
Docs
Rust docs for the last published versions are available at https://docs.rs/phonic
[!NOTE]
phonic has not yet reached a stable version, so expect breaking changes.
Examples
See /examples directory for more examples.
File Playback with Monitoring
Play audio files on the default audio output device. Monitor playback status of 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()?, Some(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",
GeneratorPlaybackOptions::default().target_mixer(chorus_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(())
}
Compiling for WebAssembly
The play-emscripten Example shows how to compile for WebAssembly with Emscripten and includes an example webpage with custom FunDSP synths, DSP effects and sequenced sample file playback.
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.