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