-
Notifications
You must be signed in to change notification settings - Fork 0
Audio
Two paths: offline (render to PCM/WAV bytes; the default audio story) and live (drive a system output device through cpal; shipped as an example).
Pure Rust via rustysynth. The
caller supplies a SoundFont (SF2) — this crate does not bundle one,
because usable SoundFonts are 6+ MB and ship best as separate downloads.
verovio = { version = "0.1", features = ["audio"] }let sf2 = std::fs::read("TimGM6mb.sf2")?;
let wav = tk.render_to_wav(&sf2, 44_100)?;
std::fs::write("out.wav", wav)?;
# Ok::<(), Box<dyn std::error::Error>>(())| Name | Size | License |
|---|---|---|
| TimGM6mb | ~6 MB | GPL-2 |
| GeneralUser GS | ~30 MB | custom (free) |
| FluidR3 GM2-2 | ~140 MB | MIT |
| ChoriumRevA | ~26 MB | custom (free) |
verovio::audio::render_pcm(midi_bytes, sf2_bytes, sample_rate) -> Result<Pcm>
verovio::audio::render_wav(midi_bytes, sf2_bytes, sample_rate) -> Result<Vec<u8>>
verovio::audio::pcm_to_wav(&Pcm) -> Vec<u8>
// Toolkit convenience:
tk.render_to_pcm(&sf2, sample_rate)?;
tk.render_to_wav(&sf2, sample_rate)?;
tk.render_to_wav_with_policy(&sf2, sample_rate, &midi_policy)?;Pcm carries sample_rate, left, right and offers duration_secs
and interleaved for hand-off to APIs that expect interleaved PCM.
WAV output is 16-bit signed PCM stereo RIFF — universal player
compatibility. Samples outside [-1.0, 1.0] are clipped.
Audio rendering touches no Verovio C++ state. The synth runs entirely in Rust on the SMF bytes Verovio already produced. Safe to spawn N parallel renders.
Working end-to-end demo using cpal for the OS audio device:
cargo run --release --features live-audio \
--example live_playback -- path/to/font.sf2The example renders a built-in PAE demo, opens the default output
device, and drives a MidiFileSequencer from inside cpal's audio
callback. ~120 LoC; copy-paste starting point for full players.
cpal pulls in alsa-sys on Linux, which needs alsa-lib from the
system. To keep cargo test portable on bare environments (CI, NixOS),
cpal is gated behind the live-audio feature (which also turns on
audio).
nix-shell -p alsa-lib pkg-config
cargo run --release --features live-audio \
--example live_playback -- soundfont.sf2The shipped example is intentionally minimal — no transport, no seeking, no UI sync. For production:
-
Lock-free command channel —
crossbeam-channelSPSC for play / pause / seek instructions from UI thread to audio thread. -
Atomic playhead —
AtomicU64of milliseconds; the audio thread writes, the UI thread reads to sync highlight overlays. -
Avoid
Mutexin the audio callback — the example uses one forSend-passing only. Production should keep the sequencer owned exclusively by the audio thread and never lock. - Error recovery on device disconnect — cpal surfaces this via the error callback; reopen the stream on a known good device.
The expectation is that consumers (e.g., xpart) build their own
player; this crate provides the synth, the SMF, and the timemap.