nandi/jolt-nativepublic Fork 0
fa4ecdfed6a830e6099d5fab9be24ef72ea1923b
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

feedback.rs · 176 lines · 5.7 KBRust Blame HistoryRaw
Lift freeq's AV media plane out of sleek 90f8b89 nandi 19d ago1//! Feeds back the input stream directly into the output stream.
2//!
3//! Assumes that the input and output devices can use the same stream configuration and that they
4//! support the f32 sample format.
5//!
6//! Uses a delay of `LATENCY_MS` milliseconds in case the default input and output streams are not
7//! precisely synchronised.
8
9use clap::Parser;
10use cpal::{
11 traits::{DeviceTrait, HostTrait, StreamTrait},
12 HostUnavailable,
13};
14use ringbuf::{
15 traits::{Consumer, Producer, Split},
16 HeapRb,
17};
18
19#[derive(Parser, Debug)]
20#[command(version, about = "CPAL feedback example", long_about = None)]
21struct Opt {
22 /// The input audio device to use
23 #[arg(short, long, value_name = "IN")]
24 input_device: Option<String>,
25
26 /// The output audio device to use
27 #[arg(short, long, value_name = "OUT")]
28 output_device: Option<String>,
29
30 /// Specify the delay between input and output
31 #[arg(short, long, value_name = "DELAY_MS", default_value_t = 150.0)]
32 latency: f32,
33
34 /// Use the JACK host. Requires `--features jack`.
35 #[arg(long, default_value_t = false)]
36 jack: bool,
37
38 /// Use the PulseAudio host. Requires `--features pulseaudio`.
39 #[arg(long, default_value_t = false)]
40 pulseaudio: bool,
41}
42
43fn main() -> anyhow::Result<()> {
44 let opt = Opt::parse();
45
46 // Jack/PulseAudio support must be enabled at compile time, and is
47 // only available on some platforms.
48 #[allow(unused_mut, unused_assignments)]
49 let mut jack_host_id = Err(HostUnavailable);
50 #[allow(unused_mut, unused_assignments)]
51 let mut pulseaudio_host_id = Err(HostUnavailable);
52
53 #[cfg(any(
54 target_os = "linux",
55 target_os = "dragonfly",
56 target_os = "freebsd",
57 target_os = "netbsd"
58 ))]
59 {
60 #[cfg(feature = "jack")]
61 {
62 jack_host_id = Ok(cpal::HostId::Jack);
63 }
64
65 #[cfg(feature = "pulseaudio")]
66 {
67 pulseaudio_host_id = Ok(cpal::HostId::PulseAudio);
68 }
69 }
70
71 // Manually check for flags. Can be passed through cargo with -- e.g.
72 // cargo run --release --example beep --features jack -- --jack
73 let host = if opt.jack {
74 jack_host_id
75 .and_then(cpal::host_from_id)
76 .expect("make sure `--features jack` is specified, and the platform is supported")
77 } else if opt.pulseaudio {
78 pulseaudio_host_id
79 .and_then(cpal::host_from_id)
80 .expect("make sure `--features pulseaudio` is specified, and the platform is supported")
81 } else {
82 cpal::default_host()
83 };
84
85 // Find devices.
86 let input_device = if let Some(device) = opt.input_device {
87 let id = &device.parse().expect("failed to parse input device id");
88 host.device_by_id(id)
89 } else {
90 host.default_input_device()
91 }
92 .expect("failed to find input device");
93
94 let output_device = if let Some(device) = opt.output_device {
95 let id = &device.parse().expect("failed to parse output device id");
96 host.device_by_id(id)
97 } else {
98 host.default_output_device()
99 }
100 .expect("failed to find output device");
101
102 println!("Using input device: \"{}\"", input_device.id()?);
103 println!("Using output device: \"{}\"", output_device.id()?);
104
105 // We'll try and use the same configuration between streams to keep it simple.
106 let config: cpal::StreamConfig = input_device.default_input_config()?.into();
107
108 // Create a delay in case the input and output devices aren't synced.
109 let latency_frames = (opt.latency / 1_000.0) * config.sample_rate as f32;
110 let latency_samples = latency_frames as usize * config.channels as usize;
111
112 // The buffer to share samples
113 let ring = HeapRb::<f32>::new(latency_samples * 2);
114 let (mut producer, mut consumer) = ring.split();
115
116 // Fill the samples with 0.0 equal to the length of the delay.
117 for _ in 0..latency_samples {
118 // The ring buffer has twice as much space as necessary to add latency here,
119 // so this should never fail
120 producer.try_push(0.0).unwrap();
121 }
122
123 let input_data_fn = move |data: &[f32], _: &cpal::InputCallbackInfo| {
124 let mut output_fell_behind = false;
125 for &sample in data {
126 if producer.try_push(sample).is_err() {
127 output_fell_behind = true;
128 }
129 }
130 if output_fell_behind {
131 eprintln!("output stream fell behind: try increasing latency");
132 }
133 };
134
135 let output_data_fn = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
136 let mut input_fell_behind = false;
137 for sample in data {
138 *sample = match consumer.try_pop() {
139 Some(s) => s,
140 None => {
141 input_fell_behind = true;
142 0.0
143 }
144 };
145 }
146 if input_fell_behind {
147 eprintln!("input stream fell behind: try increasing latency");
148 }
149 };
150
151 // Build streams.
152 println!("Attempting to build both streams with f32 samples and `{config:?}`.");
153 let input_stream = input_device.build_input_stream(config, input_data_fn, err_fn, None)?;
154 let output_stream = output_device.build_output_stream(config, output_data_fn, err_fn, None)?;
155 println!("Successfully built streams.");
156
157 // Play the streams.
158 println!(
159 "Starting the input and output streams with `{}` milliseconds of latency.",
160 opt.latency
161 );
162 input_stream.play()?;
163 output_stream.play()?;
164
165 // Run for 10 seconds before closing.
166 println!("Playing for 10 seconds... ");
167 std::thread::sleep(std::time::Duration::from_secs(10));
168 drop(input_stream);
169 drop(output_stream);
170 println!("Done!");
171 Ok(())
172}
173
174fn err_fn(err: cpal::StreamError) {
175 eprintln!("an error occurred on stream: {err}");
176}