Expand description
§rodio_tap
rodio_tap taps rodio::Source audio while still passing the source through to playback.
Use it when you want to analyze, visualize, meter, or record playback data in real time.
§What it provides
TapReader+TapAdapter: low-level packet ring-buffer access.FrameReader: synchronous high-level reader that yields frame batches.AsyncFrameReader(featureasync): async high-level reader for Tokio runtimes.Visualizer(featurevisualizer): callback-driven FFT bins + peak/rms per channel.
§Installation
In your Cargo.toml:
[dependencies]
rodio_tap = "0.2.0"Enable the visualizer module:
[dependencies]
rodio_tap = { version = "0.2.0", features = ["visualizer"] }Enable async support if tokio support is needed:
[dependencies]
rodio_tap = { version = "0.2.0", features = ["async"] }§Quick start
use std::sync::Arc;
use std::thread;
use rodio::Decoder;
use rodio_tap::{FrameReader, TapReader};
fn run<S>(rodio_source: S)
where
S: rodio::Source + Send + 'static,
S::Item: cpal::Sample + Send + 'static,
f32: cpal::FromSample<S::Item>,
{
// Create a stereo (2-channel) tap reader from any rodio source.
let (tap_reader, tap_adapter) = TapReader::<2>::new(rodio_source);
// Send `tap_adapter` into your rodio playback pipeline.
let _ = tap_adapter;
let tap_for_reader = Arc::clone(&tap_reader);
thread::spawn(move || {
// Create a stereo (2 channel) frame reader
let mut reader = FrameReader::<2>::new(move || Some(Arc::clone(&tap_for_reader)));
reader.run(|batch, channels, sample_rate_hz| {
let frames = batch.len();
println!("{} frames @ {} Hz", frames, sample_rate_hz);
for frame in batch {
let _ = frame;
// Process one interleaved frame.
}
});
});
}§Multiple tracks:
use std::sync::Arc;
use rodio::queue;
use rodio_tap::TapReader;
// One queue source for all tracks.
let (queue_in, queue_out) = queue::queue(false);
// One persistent tap around the queue output.
let (tap_reader, tap_adapter) = TapReader::<2>::new(queue_out);
let _ = (tap_reader, tap_adapter);
// Append each decoder to queue_in.append(decoder).§Async reader
With the async feature enabled:
use std::sync::Arc;
use rodio_tap::AsyncFrameReader;
async fn run_reader(tap: Arc<rodio_tap::TapReader<2>>) {
let mut reader = AsyncFrameReader::<2>::new(move || Some(Arc::clone(&tap)));
reader
.run(|batch, channels, sample_rate_hz| {
let frames = batch.len();
println!("{} frames @ {} Hz", frames, sample_rate_hz);
})
.await;
}§Visualizer
Visualizer (feature visualizer) provides an abstract for building music visualizers.
use rodio::source::SineWave;
use rodio::{DeviceSinkBuilder, Player, Source};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use rodio_tap::{Visualizer, VisualizerConfig, TapReader, Transform};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Build a simple test tone and loop it forever.
let tone = SineWave::new(440.0).amplify(0.2).repeat_infinite();
// Tap the source before sending it to playback.
let (tap_reader, tap_adapter) = TapReader::<2>::new(tone);
// Play audio through rodio.
let mut sink = DeviceSinkBuilder::open_default_sink()?;
sink.log_on_drop(false);
let player = Player::connect_new(sink.mixer());
player.append(tap_adapter);
player.play();
// Visualizer callback runs forever, so run it on a worker thread.
let tap_for_visualizer = Arc::clone(&tap_reader);
thread::spawn(move || {
let config = VisualizerConfig {
period: Duration::from_millis(33), // ~30 FPS updates
transform: Transform::FourierLog(28), // default transform
..Default::default()
};
let bins = config.frequency_bins(); // stable hz ranges for each bin
// Run visualizer with the frame reader
// You will get a list of channels on your callback, and each channel will have been frequency magnitures
// Use `run_with_frame_reader_async()` for use with tokio
Visualizer::<2>::run_with_frame_reader(
move || Some(Arc::clone(&tap_for_visualizer)),
config,
move |channels, sample_rate_hz| {
if let Some(ch0) = channels.first() {
// Print only the first few bins for demo purposes.
for (i, magnitude) in ch0.bins.iter().copied().take(5).enumerate() {
let range = &bins[i];
println!(
"[{} Hz] {:>6.0}..{:>6.0} Hz => {:.4}",
sample_rate_hz, range.hz_lo, range.hz_hi, magnitude
);
}
println!("---");
}
},
);
});
// Keep main alive while audio + visualizer run.
thread::sleep(Duration::from_secs(1));
Ok(())
}§Real-Time Use
rodio_tap is suitable for real-time monitoring and low-latency audio
pipeline paths when configured for low latency. In release mode, typical
overhead is very small (often around ~100 ns).
Suggested low-latency FrameReaderConfig starting point:
frames_per_batch: Some(64)(equivalent to 128 sample buffer size in stereo)time_per_batch: None(use fixed frame batches)sleep_bias: 0.5(wake earlier to avoid late batch delivery)min_sleep: Duration::from_micros(5)(tiny cooperative sleep)- Run in
--releasemode for realistic performance numbers
This profile is generally appropriate for real-time use cases such as ASIO, CoreAudio, WASAPI, and JACK style pipelines.
§Examples
§wav_visualizer_full
Terminal FFT visualizer with explicit pipeline wiring and rendering logic.
cargo run --example wav_visualizer_full -- examples/example.wav§wav_visualizer_simple (feature: visualizer)
Higher-level visualizer API example with less boilerplate.
cargo run --example wav_visualizer_simple --features visualizer -- examples/example.wav§wav_recorder
Queues one or more WAV files, captures frames with FrameReader, and writes a
single output WAV file.
cargo run --example wav_recorder -- examples/example.wav examples/sweep.wav§wav_low_latency
Low-latency timing monitor for callback interval and overhead measurement.
cargo run --release --example wav_low_latency -- --window=5 --loop examples/example.wav§License
Licensed under either of:
- Apache License, Version 2.0
- MIT license
at your option.
Structs§
- Frame
Format - Runtime audio format metadata for tapped packets.
- Frame
Reader - High-level reader for tapped frame batches.
- Frame
Reader Config - Configuration shared by
FrameReaderand [AsyncFrameReader]. - OnFirst
Sample - TapAdapter
- Adapts a
rodio::Sourceand taps packets into a lock-free ring buffer. - TapReader
- Read side for packets produced by
TapAdapter.
Enums§
- TapPacket
- Packet emitted by the low-level tap ring. Generic C over the maximum number of channels supported.