175 lines
5.6 KiB
Rust
175 lines
5.6 KiB
Rust
//! Feeds back the input stream directly into the output stream.
|
|
//!
|
|
//! Assumes that the input and output devices can use the same stream configuration and that they
|
|
//! support the f32 sample format.
|
|
//!
|
|
//! Uses a delay of `LATENCY_MS` milliseconds in case the default input and output streams are not
|
|
//! precisely synchronised.
|
|
|
|
use clap::Parser;
|
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
|
use ringbuf::HeapRb;
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(version, about = "CPAL feedback example", long_about = None)]
|
|
struct Opt {
|
|
/// The input audio device to use
|
|
#[arg(short, long, value_name = "IN", default_value_t = String::from("default"))]
|
|
input_device: String,
|
|
|
|
/// The output audio device to use
|
|
#[arg(short, long, value_name = "OUT", default_value_t = String::from("default"))]
|
|
output_device: String,
|
|
|
|
/// Specify the delay between input and output
|
|
#[arg(short, long, value_name = "DELAY_MS", default_value_t = 150.0)]
|
|
latency: f32,
|
|
|
|
/// Use the JACK host
|
|
#[cfg(all(
|
|
any(
|
|
target_os = "linux",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd"
|
|
),
|
|
feature = "jack"
|
|
))]
|
|
#[arg(short, long)]
|
|
#[allow(dead_code)]
|
|
jack: bool,
|
|
}
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
let opt = Opt::parse();
|
|
|
|
// Conditionally compile with jack if the feature is specified.
|
|
#[cfg(all(
|
|
any(
|
|
target_os = "linux",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd"
|
|
),
|
|
feature = "jack"
|
|
))]
|
|
// Manually check for flags. Can be passed through cargo with -- e.g.
|
|
// cargo run --release --example beep --features jack -- --jack
|
|
let host = if opt.jack {
|
|
cpal::host_from_id(cpal::available_hosts()
|
|
.into_iter()
|
|
.find(|id| *id == cpal::HostId::Jack)
|
|
.expect(
|
|
"make sure --features jack is specified. only works on OSes where jack is available",
|
|
)).expect("jack host unavailable")
|
|
} else {
|
|
cpal::default_host()
|
|
};
|
|
|
|
#[cfg(any(
|
|
not(any(
|
|
target_os = "linux",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd"
|
|
)),
|
|
not(feature = "jack")
|
|
))]
|
|
let host = cpal::default_host();
|
|
|
|
// Find devices.
|
|
let input_device = if opt.input_device == "default" {
|
|
host.default_input_device()
|
|
} else {
|
|
host.input_devices()?
|
|
.find(|x| x.name().map(|y| y == opt.input_device).unwrap_or(false))
|
|
}
|
|
.expect("failed to find input device");
|
|
|
|
let output_device = if opt.output_device == "default" {
|
|
host.default_output_device()
|
|
} else {
|
|
host.output_devices()?
|
|
.find(|x| x.name().map(|y| y == opt.output_device).unwrap_or(false))
|
|
}
|
|
.expect("failed to find output device");
|
|
|
|
println!("Using input device: \"{}\"", input_device.name()?);
|
|
println!("Using output device: \"{}\"", output_device.name()?);
|
|
|
|
// We'll try and use the same configuration between streams to keep it simple.
|
|
let config: cpal::StreamConfig = input_device.default_input_config()?.into();
|
|
|
|
// Create a delay in case the input and output devices aren't synced.
|
|
let latency_frames = (opt.latency / 1_000.0) * config.sample_rate.0 as f32;
|
|
let latency_samples = latency_frames as usize * config.channels as usize;
|
|
|
|
// The buffer to share samples
|
|
let ring = HeapRb::<f32>::new(latency_samples * 2);
|
|
let (mut producer, mut consumer) = ring.split();
|
|
|
|
// Fill the samples with 0.0 equal to the length of the delay.
|
|
for _ in 0..latency_samples {
|
|
// The ring buffer has twice as much space as necessary to add latency here,
|
|
// so this should never fail
|
|
producer.push(0.0).unwrap();
|
|
}
|
|
|
|
let input_data_fn = move |data: &[f32], _: &cpal::InputCallbackInfo| {
|
|
let mut output_fell_behind = false;
|
|
for &sample in data {
|
|
if producer.push(sample).is_err() {
|
|
output_fell_behind = true;
|
|
}
|
|
}
|
|
if output_fell_behind {
|
|
eprintln!("output stream fell behind: try increasing latency");
|
|
}
|
|
};
|
|
|
|
let output_data_fn = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
|
let mut input_fell_behind = false;
|
|
for sample in data {
|
|
*sample = match consumer.pop() {
|
|
Some(s) => s,
|
|
None => {
|
|
input_fell_behind = true;
|
|
0.0
|
|
}
|
|
};
|
|
}
|
|
if input_fell_behind {
|
|
eprintln!("input stream fell behind: try increasing latency");
|
|
}
|
|
};
|
|
|
|
// Build streams.
|
|
println!(
|
|
"Attempting to build both streams with f32 samples and `{:?}`.",
|
|
config
|
|
);
|
|
let input_stream = input_device.build_input_stream(&config, input_data_fn, err_fn, None)?;
|
|
let output_stream = output_device.build_output_stream(&config, output_data_fn, err_fn, None)?;
|
|
println!("Successfully built streams.");
|
|
|
|
// Play the streams.
|
|
println!(
|
|
"Starting the input and output streams with `{}` milliseconds of latency.",
|
|
opt.latency
|
|
);
|
|
input_stream.play()?;
|
|
output_stream.play()?;
|
|
|
|
// Run for 3 seconds before closing.
|
|
println!("Playing for 3 seconds... ");
|
|
std::thread::sleep(std::time::Duration::from_secs(3));
|
|
drop(input_stream);
|
|
drop(output_stream);
|
|
println!("Done!");
|
|
Ok(())
|
|
}
|
|
|
|
fn err_fn(err: cpal::StreamError) {
|
|
eprintln!("an error occurred on stream: {}", err);
|
|
}
|