Skip to main content

pfx_sound/
live.rs

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}