1use std::sync::atomic::{AtomicUsize, Ordering};
2use std::sync::{Arc, Mutex};
3use std::time::{Duration, Instant};
4
5use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
6
7use crate::channel::{ChannelId, Ducking};
8use crate::clip::Clip;
9use crate::decode::{Decode, DecodeSpec, spec_at_output};
10use crate::heard::Heard;
11use crate::interp::Resample;
12use crate::mix::{Level, Mixer, VoiceId, lock};
13use crate::music::MusicTrack;
14use crate::pcm::PcmSender;
15use crate::sound::{SoundId, SoundSpec};
16use crate::synth::Wave;
17
18pub struct Output {
19 stream: Option<cpal::Stream>,
20 mixer: Arc<Mutex<Mixer>>,
21 heard: Arc<Heard>,
22 decodes: Mutex<Vec<Decode>>,
23 rate: u32,
24 channels: u16,
25}
26
27impl Output {
28 pub fn open() -> Result<Output, String> {
29 let host = cpal::default_host();
30 let device = host
31 .default_output_device()
32 .ok_or_else(|| "no output device".to_string())?;
33 let supported = device
34 .default_output_config()
35 .map_err(|e| format!("output device: {e}"))?;
36 let format = supported.sample_format();
37 let config = supported.config();
38 let rate = config.sample_rate;
39 let channels = config.channels;
40 if channels == 0 || rate == 0 {
41 return Err("output device has no channels".into());
42 }
43 let mixer = Arc::new(Mutex::new(Mixer::new(rate, channels)));
44 let heard = Arc::new(Heard::new(rate));
45 let stream = match format {
46 cpal::SampleFormat::F32 => {
47 let shared = Arc::clone(&mixer);
48 let clock = Arc::clone(&heard);
49 device
50 .build_output_stream(
51 config,
52 move |data: &mut [f32], info: &cpal::OutputCallbackInfo| {
53 fill(data, info, &shared, &clock, Mixer::render_into)
54 },
55 |_| {},
56 None,
57 )
58 .map_err(|e| format!("output stream: {e}"))?
59 }
60 cpal::SampleFormat::I16 => {
61 let shared = Arc::clone(&mixer);
62 let clock = Arc::clone(&heard);
63 device
64 .build_output_stream(
65 config,
66 move |data: &mut [i16], info: &cpal::OutputCallbackInfo| {
67 fill(data, info, &shared, &clock, Mixer::render_i16)
68 },
69 |_| {},
70 None,
71 )
72 .map_err(|e| format!("output stream: {e}"))?
73 }
74 other => return Err(format!("output format {other} is not supported")),
75 };
76 stream.play().map_err(|e| format!("output stream: {e}"))?;
77 Ok(Output {
78 stream: Some(stream),
79 mixer,
80 heard,
81 decodes: Mutex::new(Vec::new()),
82 rate,
83 channels,
84 })
85 }
86
87 pub fn rate(&self) -> u32 {
88 self.rate
89 }
90
91 pub fn channels(&self) -> u16 {
92 self.channels
93 }
94
95 pub fn play(&self, clip: &Clip, level: Level) -> VoiceId {
96 lock(&self.mixer).play(clip, level)
97 }
98
99 pub fn set_volume(&self, id: VoiceId, volume: f32) {
100 lock(&self.mixer).set_volume(id, volume);
101 }
102
103 pub fn set_pan(&self, id: VoiceId, pan: f32) {
104 lock(&self.mixer).set_pan(id, pan);
105 }
106
107 pub fn play_on(&self, channel: ChannelId, clip: &Clip, level: Level) -> VoiceId {
108 lock(&self.mixer).play_on(channel, clip, level)
109 }
110
111 pub fn stop(&self, id: VoiceId, fade_seconds: f32) {
112 lock(&self.mixer).stop(id, fade_seconds);
113 }
114
115 pub fn set_master_volume(&self, volume: f32) {
116 lock(&self.mixer).set_master_volume(volume);
117 }
118
119 pub fn set_master_muted(&self, muted: bool) {
120 lock(&self.mixer).set_master_muted(muted);
121 }
122
123 pub fn set_channel_volume(&self, channel: ChannelId, volume: f32) {
124 lock(&self.mixer).set_channel_volume(channel, volume);
125 }
126
127 pub fn set_channel_muted(&self, channel: ChannelId, muted: bool) {
128 lock(&self.mixer).set_channel_muted(channel, muted);
129 }
130
131 pub fn set_ducking(&self, ducking: Option<Ducking>) {
132 lock(&self.mixer).set_ducking(ducking);
133 }
134
135 pub fn set_pitch(&self, id: VoiceId, ratio: f32) {
136 lock(&self.mixer).set_pitch(id, ratio);
137 }
138
139 pub fn glide_pitch(&self, id: VoiceId, ratio: f32, seconds: f32) {
140 lock(&self.mixer).glide_pitch(id, ratio, seconds);
141 }
142
143 pub fn set_gain(&self, id: VoiceId, gain: f32) {
144 lock(&self.mixer).set_gain(id, gain);
145 }
146
147 pub fn glide_gain(&self, id: VoiceId, gain: f32, seconds: f32) {
148 lock(&self.mixer).glide_gain(id, gain, seconds);
149 }
150
151 pub fn set_channel_pitch(&self, channel: ChannelId, ratio: f32) {
152 lock(&self.mixer).set_channel_pitch(channel, ratio);
153 }
154
155 pub fn glide_channel_pitch(&self, channel: ChannelId, ratio: f32, seconds: f32) {
156 lock(&self.mixer).glide_channel_pitch(channel, ratio, seconds);
157 }
158
159 pub fn set_channel_lowpass(&self, channel: ChannelId, hz: Option<f32>) {
160 lock(&self.mixer).set_channel_lowpass(channel, hz);
161 }
162
163 pub fn set_channel_highpass(&self, channel: ChannelId, hz: Option<f32>) {
164 lock(&self.mixer).set_channel_highpass(channel, hz);
165 }
166
167 pub fn set_limiter(&self, threshold: Option<f32>) {
168 lock(&self.mixer).set_limiter(threshold);
169 }
170
171 pub fn set_seed(&self, seed: u64) {
172 lock(&self.mixer).set_seed(seed);
173 }
174
175 pub fn sound(&self, clip: &Clip, spec: SoundSpec) -> SoundId {
176 lock(&self.mixer).sound(clip, spec)
177 }
178
179 pub fn trigger(&self, sound: SoundId) -> Option<VoiceId> {
180 lock(&self.mixer).trigger(sound)
181 }
182
183 pub fn tone(&self, channel: ChannelId, wave: Wave, hz: f32, volume: f32) -> VoiceId {
184 lock(&self.mixer).tone(channel, wave, hz, volume)
185 }
186
187 pub fn tone_in(
188 &self,
189 offset: u64,
190 channel: ChannelId,
191 wave: Wave,
192 hz: f32,
193 volume: f32,
194 ) -> VoiceId {
195 lock(&self.mixer).tone_in(offset, channel, wave, hz, volume)
196 }
197
198 pub fn tone_sound(
199 &self,
200 wave: Wave,
201 hz: f32,
202 seconds: Option<f32>,
203 spec: SoundSpec,
204 ) -> SoundId {
205 lock(&self.mixer).tone_sound(wave, hz, seconds, spec)
206 }
207
208 pub fn trigger_in(&self, offset: u64, sound: SoundId) -> Option<VoiceId> {
209 lock(&self.mixer).trigger_in(offset, sound)
210 }
211
212 pub fn trigger_with(&self, sound: SoundId, volume: f32) -> Option<VoiceId> {
213 lock(&self.mixer).trigger_with(sound, volume)
214 }
215
216 pub fn trigger_in_with(&self, offset: u64, sound: SoundId, volume: f32) -> Option<VoiceId> {
217 lock(&self.mixer).trigger_in_with(offset, sound, volume)
218 }
219
220 pub fn set_resample(&self, id: VoiceId, resample: Resample) {
221 lock(&self.mixer).set_resample(id, resample);
222 }
223
224 pub fn set_tone_frequency(&self, id: VoiceId, hz: f32) {
225 lock(&self.mixer).set_tone_frequency(id, hz);
226 }
227
228 pub fn glide_tone_frequency(&self, id: VoiceId, hz: f32, seconds: f32) {
229 lock(&self.mixer).glide_tone_frequency(id, hz, seconds);
230 }
231
232 pub fn pcm_stream(
233 &self,
234 channel: ChannelId,
235 channels: u16,
236 capacity_seconds: f32,
237 level: Level,
238 ) -> (VoiceId, PcmSender) {
239 lock(&self.mixer).pcm_stream(channel, channels, capacity_seconds, level)
240 }
241
242 pub fn play_music(&self, track: &MusicTrack, fade_seconds: f32, level: Level) -> VoiceId {
243 lock(&self.mixer).play_music(track, fade_seconds, level)
244 }
245
246 pub fn stop_music(&self, fade_seconds: f32) {
247 lock(&self.mixer).stop_music(fade_seconds);
248 }
249
250 pub fn music_heard(&self) -> Option<f64> {
251 self.heard.seconds(Instant::now())
252 }
253
254 #[cfg(feature = "vorbis")]
255 pub fn stream_vorbis(
256 &self,
257 channel: ChannelId,
258 source: &crate::vorbis::VorbisSource,
259 looping: Option<(u64, u64)>,
260 level: Level,
261 ) -> Result<VoiceId, String> {
262 lock(&self.mixer).stream_vorbis(channel, source, looping, level)
263 }
264
265 #[cfg(feature = "vorbis")]
266 pub fn play_music_vorbis(
267 &self,
268 source: &crate::vorbis::VorbisSource,
269 looping: Option<(u64, u64)>,
270 fade_seconds: f32,
271 level: Level,
272 ) -> Result<VoiceId, String> {
273 lock(&self.mixer).play_music_vorbis(source, looping, fade_seconds, level)
274 }
275
276 pub fn stream(&self, spec: &DecodeSpec, level: Level) -> Result<VoiceId, String> {
277 let spec = spec_at_output(spec, self.rate, self.channels);
278 let decode = Decode::start(&spec)?;
279 let channels = decode.channels();
280 let queue = decode.queue();
281 let id = lock(&self.mixer).stream(queue, channels, level);
282 lock(&self.decodes).push(decode);
283 Ok(id)
284 }
285}
286
287impl Drop for Output {
288 fn drop(&mut self) {
289 self.stream.take();
290 lock(&self.decodes).clear();
291 }
292}
293
294pub fn chime(wav: &'static [u8]) {
295 spawn_chime(wav, false);
296}
297
298pub fn chime_reversed(wav: &'static [u8]) {
299 spawn_chime(wav, true);
300}
301
302fn spawn_chime(wav: &'static [u8], reversed: bool) {
303 std::thread::spawn(move || {
304 let Some(mut clip) = Clip::from_wav(wav) else {
305 return;
306 };
307 if reversed {
308 clip.reverse();
309 }
310 let _ = play_chime(clip);
311 });
312}
313
314fn play_chime(clip: Clip) -> Result<(), String> {
315 let host = cpal::default_host();
316 let device = host
317 .default_output_device()
318 .ok_or_else(|| "no output device".to_string())?;
319 let default = device
320 .default_output_config()
321 .map_err(|e| format!("output device: {e}"))?;
322 let rate = default.sample_rate();
323 let channels = default.channels();
324 let config = cpal::StreamConfig {
325 channels,
326 sample_rate: rate,
327 buffer_size: cpal::BufferSize::Default,
328 };
329 let buffer = Arc::new(clip.at_rate(rate, channels));
330 let head = Arc::new(AtomicUsize::new(0));
331 let total = buffer.len();
332 let feed_buffer = Arc::clone(&buffer);
333 let feed_head = Arc::clone(&head);
334 let stream = device
335 .build_output_stream(
336 config,
337 move |out: &mut [f32], _| {
338 let start = feed_head.load(Ordering::Relaxed);
339 for (i, slot) in out.iter_mut().enumerate() {
340 *slot = feed_buffer.get(start + i).copied().unwrap_or(0.0);
341 }
342 feed_head.store(start.saturating_add(out.len()), Ordering::Relaxed);
343 },
344 |_| {},
345 None,
346 )
347 .map_err(|e| format!("output stream: {e}"))?;
348 stream.play().map_err(|e| format!("output stream: {e}"))?;
349 let deadline = Instant::now() + Duration::from_secs_f32(clip.seconds() + 0.35);
350 while head.load(Ordering::Relaxed) < total && Instant::now() < deadline {
351 std::thread::sleep(Duration::from_millis(15));
352 }
353 drop(stream);
354 Ok(())
355}
356
357fn fill<T>(
358 data: &mut [T],
359 info: &cpal::OutputCallbackInfo,
360 mixer: &Mutex<Mixer>,
361 heard: &Heard,
362 render: fn(&mut Mixer, &mut [T]),
363) {
364 let now = Instant::now();
365 let stamp = info.timestamp();
366 let latency = stamp.playback.duration_since(stamp.callback);
367 let mut mixer = lock(mixer);
368 let place = mixer.music_place();
369 render(&mut mixer, data);
370 let frames = data.len() / usize::from(mixer.channels().max(1));
371 let block = Duration::from_secs_f64(frames as f64 / f64::from(mixer.rate().max(1)));
372 drop(mixer);
373 heard.publish(place, now + latency, block);
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379
380 #[test]
381 #[ignore = "needs a sound device"]
382 fn the_default_output_plays_a_clip() {
383 let clip = Clip {
384 rate: 48_000,
385 channels: 1,
386 samples: vec![0.2; 4_800],
387 };
388 let output = Output::open().expect("output device");
389 output.play(&clip, Level::default());
390 std::thread::sleep(Duration::from_millis(150));
391 assert!(output.rate() > 0 && output.channels() > 0);
392 }
393
394 #[test]
395 #[ignore = "needs a sound device"]
396 fn a_chime_plays_on_the_default_output() {
397 use std::sync::OnceLock;
398 static WAV: OnceLock<Vec<u8>> = OnceLock::new();
399 let wav = WAV.get_or_init(|| {
400 Clip {
401 rate: 48_000,
402 channels: 1,
403 samples: vec![0.2; 2_400],
404 }
405 .to_wav()
406 });
407 chime(wav);
408 chime_reversed(wav);
409 std::thread::sleep(Duration::from_millis(400));
410 }
411}