-
Notifications
You must be signed in to change notification settings - Fork 515
Expand file tree
/
Copy pathfeedback.rs
More file actions
167 lines (143 loc) · 5.62 KB
/
Copy pathfeedback.rs
File metadata and controls
167 lines (143 loc) · 5.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! 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},
Error, ErrorKind, HostId, InputCallbackInfo, OutputCallbackInfo, Sample, StreamConfig,
};
use ringbuf::{
traits::{Consumer, Producer, Split},
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")]
input_device: Option<String>,
/// The output audio device to use
#[arg(short, long, value_name = "OUT")]
output_device: Option<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. Requires `--features jack`.
#[arg(long, default_value_t = false)]
jack: bool,
/// Use the PulseAudio host. Requires `--features pulseaudio`.
#[arg(long, default_value_t = false)]
pulseaudio: bool,
}
fn main() -> anyhow::Result<()> {
let opt = Opt::parse();
// JACK/PulseAudio support must be enabled at compile time, and is
// only available on some platforms.
#[allow(unused_mut, unused_assignments)]
let mut jack_host_id: Result<HostId, Error> = Err(ErrorKind::HostUnavailable.into());
#[allow(unused_mut, unused_assignments)]
let mut pulseaudio_host_id: Result<HostId, Error> = Err(ErrorKind::HostUnavailable.into());
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
))]
{
#[cfg(feature = "jack")]
{
jack_host_id = Ok(HostId::Jack);
}
#[cfg(feature = "pulseaudio")]
{
pulseaudio_host_id = Ok(HostId::PulseAudio);
}
}
// 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 {
jack_host_id
.and_then(cpal::host_from_id)
.expect("make sure `--features jack` is specified, and the platform is supported")
} else if opt.pulseaudio {
pulseaudio_host_id
.and_then(cpal::host_from_id)
.expect("make sure `--features pulseaudio` is specified, and the platform is supported")
} else {
cpal::default_host()
};
// Find devices.
let input_device = if let Some(device) = opt.input_device {
let id = &device.parse().expect("failed to parse input device id");
host.device_by_id(id)
} else {
host.default_input_device()
}
.expect("failed to find input device");
let output_device = if let Some(device) = opt.output_device {
let id = &device.parse().expect("failed to parse output device id");
host.device_by_id(id)
} else {
host.default_output_device()
}
.expect("failed to find output device");
println!("Using input device: \"{}\"", input_device.id()?);
println!("Using output device: \"{}\"", output_device.id()?);
// We'll try and use the same configuration between streams to keep it simple.
let config: 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 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();
// Pre-fill with silence 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.try_push(f32::EQUILIBRIUM).unwrap();
}
let input_data_fn = move |data: &[f32], _: &InputCallbackInfo| {
if producer.push_slice(data) < data.len() {
eprintln!("output stream fell behind: try increasing latency");
}
};
let output_data_fn = move |data: &mut [f32], _: &OutputCallbackInfo| {
let read = consumer.pop_slice(data);
if read < data.len() {
data[read..].fill(f32::EQUILIBRIUM);
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 10 seconds before closing.
println!("Playing for 10 seconds... ");
std::thread::sleep(std::time::Duration::from_secs(10));
drop(input_stream);
drop(output_stream);
println!("Done!");
Ok(())
}
fn err_fn(err: Error) {
match err.kind() {
ErrorKind::DeviceChanged | ErrorKind::Xrun | ErrorKind::RealtimeDenied => {
eprintln!("{err}")
}
_ => eprintln!("Stream error: {err}"),
}
}