1pub trait AudioOut: Send {
9 fn configure(&mut self, rate: u32, channels: u32);
11 fn push_le_f32(&mut self, bytes: &[u8]);
13}
14
15pub struct Resampler {
17 dst_rate: u32,
18 src_rate: u32,
19 channels: u32,
20 t: f64,
21 prev: [f32; 2],
22}
23
24impl Resampler {
25 pub fn new(dst_rate: u32) -> Resampler {
26 Resampler { dst_rate, src_rate: 44100, channels: 2, t: 0.0, prev: [0.0; 2] }
27 }
28
29 pub fn configure(&mut self, rate: u32, channels: u32) {
30 self.src_rate = rate;
31 self.channels = channels;
32 }
33
34 pub fn process(&mut self, bytes: &[u8], out: &mut Vec<f32>) {
36 let step = self.src_rate as f64 / self.dst_rate as f64;
37 let ch = self.channels as usize;
38 out.reserve(((bytes.len() / 4 / ch) as f64 / step) as usize * 2 + 4);
39 for frame in bytes.chunks_exact(4 * ch) {
40 let s = |i: usize| f32::from_le_bytes([frame[i * 4], frame[i * 4 + 1], frame[i * 4 + 2], frame[i * 4 + 3]]);
41 let cur = if ch == 1 { [s(0), s(0)] } else { [s(0), s(1)] };
42 while self.t < 1.0 {
43 let t = self.t as f32;
44 out.push(self.prev[0] + (cur[0] - self.prev[0]) * t);
45 out.push(self.prev[1] + (cur[1] - self.prev[1]) * t);
46 self.t += step;
47 }
48 self.t -= 1.0;
49 self.prev = cur;
50 }
51 }
52}
53
54#[cfg(feature = "window")]
55pub use sink::{AudioSink, AudioStream};
56
57#[cfg(feature = "window")]
58mod sink {
59 use std::sync::Arc;
60 use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
61
62 use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
63 use cpal::{FromSample, SizedSample};
64
65 use super::{AudioOut, Resampler};
66
67 const TARGET_LATENCY_S: f32 = 0.06;
69 const MAX_LATENCY_S: f32 = 0.20;
71
72 struct Ring {
75 slots: Box<[AtomicU32]>,
76 head: AtomicUsize,
78 tail: AtomicUsize,
79 trim: AtomicBool,
82 }
83
84 impl Ring {
85 fn new(capacity: usize) -> Ring {
86 Ring {
87 slots: (0..capacity).map(|_| AtomicU32::new(0)).collect(),
88 head: AtomicUsize::new(0),
89 tail: AtomicUsize::new(0),
90 trim: AtomicBool::new(false),
91 }
92 }
93
94 fn len(&self) -> usize {
95 self.head.load(Ordering::Acquire) - self.tail.load(Ordering::Acquire)
96 }
97
98 fn push(&self, samples: &[f32]) {
100 let head = self.head.load(Ordering::Relaxed);
101 let free = self.slots.len() - (head - self.tail.load(Ordering::Acquire));
102 let n = samples.len().min(free) & !1;
103 for (i, s) in samples[..n].iter().enumerate() {
104 self.slots[(head + i) % self.slots.len()].store(s.to_bits(), Ordering::Relaxed);
105 }
106 self.head.store(head + n, Ordering::Release);
107 }
108
109 fn pop(&self) -> Option<(f32, f32)> {
111 let tail = self.tail.load(Ordering::Relaxed);
112 if self.head.load(Ordering::Acquire) - tail < 2 {
113 return None;
114 }
115 let get = |i: usize| f32::from_bits(self.slots[i % self.slots.len()].load(Ordering::Relaxed));
116 let frame = (get(tail), get(tail + 1));
117 self.tail.store(tail + 2, Ordering::Release);
118 Some(frame)
119 }
120
121 fn skip_to(&self, keep: usize) {
123 let tail = self.tail.load(Ordering::Relaxed);
124 let len = self.head.load(Ordering::Acquire) - tail;
125 if len > keep {
126 self.tail.store(tail + ((len - keep) & !1), Ordering::Release);
127 }
128 }
129 }
130
131 pub struct AudioSink {
132 ring: Arc<Ring>,
133 device_rate: u32,
134 resampler: Resampler,
135 scratch: Vec<f32>,
136 max: usize,
137 }
138
139 pub struct AudioStream {
143 _stream: cpal::Stream,
144 }
145
146 fn build<T: SizedSample + FromSample<f32>>(
147 device: &cpal::Device,
148 config: &cpal::StreamConfig,
149 ring: Arc<Ring>,
150 target: usize,
151 ) -> Result<cpal::Stream, cpal::Error> {
152 let channels = config.channels as usize;
153 let mut primed = false;
154 device.build_output_stream::<T, _, _>(
155 *config,
156 move |out: &mut [T], _| {
157 if ring.trim.swap(false, Ordering::AcqRel) {
158 ring.skip_to(target);
159 }
160 if !primed && ring.len() >= target {
161 primed = true;
162 }
163 for frame in out.chunks_mut(channels) {
164 let (l, r) = match ring.pop().filter(|_| primed) {
165 Some(s) => s,
166 None => {
167 primed = false; (0.0, 0.0)
169 }
170 };
171 match frame.len() {
172 1 => frame[0] = T::from_sample(0.5 * (l + r)),
173 _ => {
174 frame[0] = T::from_sample(l);
175 frame[1] = T::from_sample(r);
176 for s in &mut frame[2..] {
177 *s = T::from_sample(0.0);
178 }
179 }
180 }
181 }
182 },
183 |e| eprintln!("[audio] stream error: {e}"),
184 None,
185 )
186 }
187
188 impl AudioSink {
189 pub fn open() -> Result<(AudioSink, AudioStream), String> {
190 let host = cpal::default_host();
191 let device = host.default_output_device().ok_or("no audio output device")?;
192 let supported = device.default_output_config().map_err(|e| e.to_string())?;
193 let format = supported.sample_format();
194 let config = supported.config();
195 let device_rate = config.sample_rate;
196 let target = (device_rate as f32 * TARGET_LATENCY_S) as usize * 2;
197 let max = (device_rate as f32 * MAX_LATENCY_S) as usize * 2;
198 let ring = Arc::new(Ring::new(max * 2));
199 let r = ring.clone();
200 use cpal::SampleFormat as F;
201 let stream = match format {
202 F::F32 => build::<f32>(&device, &config, r, target),
203 F::F64 => build::<f64>(&device, &config, r, target),
204 F::I16 => build::<i16>(&device, &config, r, target),
205 F::U16 => build::<u16>(&device, &config, r, target),
206 F::I32 => build::<i32>(&device, &config, r, target),
207 F::U32 => build::<u32>(&device, &config, r, target),
208 F::I8 => build::<i8>(&device, &config, r, target),
209 F::U8 => build::<u8>(&device, &config, r, target),
210 other => return Err(format!("unsupported device sample format {other:?}")),
211 }
212 .map_err(|e| e.to_string())?;
213 stream.play().map_err(|e| e.to_string())?;
214 let sink = AudioSink { ring, device_rate, resampler: Resampler::new(device_rate), scratch: Vec::new(), max };
215 Ok((sink, AudioStream { _stream: stream }))
216 }
217
218 pub fn device_rate(&self) -> u32 {
219 self.device_rate
220 }
221 }
222
223 impl AudioOut for AudioSink {
224 fn configure(&mut self, rate: u32, channels: u32) {
225 self.resampler.configure(rate, channels);
226 }
227
228 fn push_le_f32(&mut self, bytes: &[u8]) {
229 self.scratch.clear();
230 self.resampler.process(bytes, &mut self.scratch);
231 self.ring.push(&self.scratch);
232 if self.ring.len() > self.max {
233 self.ring.trim.store(true, Ordering::Release);
234 }
235 }
236 }
237}