spectrusty_audio/host/
sdl2.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8//! Audio device streaming implementation for [SDL2](https://crates.io/crates/sdl2).
9//!
10//! This module implements [carousel][crate::carousel] using the **SDL2** audio layer.
11//!
12//! Requires "sdl2" feature to be enabled.
13use core::convert::TryFrom;
14use core::time::Duration;
15
16#[allow(unused_imports)]
17use log::{error, warn, info, debug, trace};
18
19use sdl2::{
20    Sdl,
21    audio::{AudioDevice, AudioCallback, AudioSpecDesired, AudioFormatNum}
22};
23
24pub use sdl2::audio::AudioStatus;
25
26use spectrusty_core::audio::AudioSample;
27use crate::carousel::*;
28pub use super::{AudioHandleError, AudioHandleErrorKind};
29
30struct AudioCb<T>(AudioFrameConsumer<T>);
31
32impl<T: AudioFormatNum + AudioSample> AudioCallback for AudioCb<T> {
33    type Channel = T;
34
35    fn callback(&mut self, out: &mut [T]) {
36        match self.0.fill_buffer(out, false) {
37            Ok(unfilled) => {
38                if !unfilled.is_empty() {
39                    for t in unfilled {
40                        *t = T::SILENCE
41                    }
42                    debug!("missing buffer");
43                }
44            }
45            Err(_) => {
46                error!("fatal: producer terminated");
47            }
48        }
49    }
50}
51
52/// The struct for producing and controlling the audio playback.
53///
54/// It embeds the interconnected pair of [carousel][crate::carousel]'s [AudioFrameProducer] with the
55/// [AudioFrameConsumer] directly exposing the `producer` to the user. The consumer lives in the
56/// **SDL2** audio thread and is responsible for filling up the audio output buffer with sample data
57/// sent from the `producer`.
58///
59/// The `T` parameter should be one of the [sample primitives][AudioSample].
60pub struct AudioHandle<T: AudioFormatNum + AudioSample> {
61    /// The audio sample frequency of the output stream.
62    pub sample_rate: u32,
63    /// The number of audio channels in the output stream.
64    pub channels: u8,
65    /// The number of samples in the output audio buffer.
66    pub samples: u16,
67    /// The audio sample producer, interconnected with an audio consumer living in the audio thread.
68    pub producer: AudioFrameProducer<T>,
69    device: AudioDevice<AudioCb<T>>,
70}
71
72const DEFAULT_SAMPLE_RATE: i32 = 44100;
73const DEFAULT_CHANNELS: u8 = 2;
74
75impl<T: AudioFormatNum + AudioSample> AudioHandle<T> {
76    /// Returns the status of the audio device playback.
77    pub fn status(&self) -> AudioStatus {
78        self.device.status()
79    }
80    /// Starts playback of the audio device.
81    pub fn play(&self) {
82        self.device.resume()
83    }
84    /// Pauses playback of the audio device.
85    pub fn pause(&self) {
86        self.device.pause()
87    }
88    /// Closes audio playback and frees underlying resources.
89    /// Returns the unwrapped frame consumer.
90    pub fn close(self) -> AudioFrameConsumer<T> {
91        self.device.close_and_get_callback().0
92    }
93    /// Creates an instance of the [AudioHandle] from the provided **SDL2** context.
94    ///
95    /// The audio parameters used by default are the sample rate of 44100, 2 channels, and the
96    /// output buffer size adjusted to the requested latency.
97    ///
98    /// * `frame_duration_nanos` is the duration in nanoseconds of the standard emulation frame.
99    /// * `latency` is the requested audio latency; the actual `latency` argument passed to the
100    ///   [create_carousel] may be larger but never smaller than the provided one.
101    pub fn create(
102                sdl_context: &Sdl,
103                frame_duration_nanos: u32,
104                latency: usize
105            ) -> Result<Self, AudioHandleError>
106    {
107        Self::create_with_specs(
108                sdl_context,
109                AudioSpecDesired { freq: None, channels: None, samples: None },
110                frame_duration_nanos,
111                latency
112        )
113    }
114    /// Creates an instance of the [AudioHandle] from the provided **SDL2** context with the
115    /// desired audio parameters.
116    ///
117    /// The audio parameters used by default are the sample rate of 44100, 2 channels, and the
118    /// output buffer size adjusted to the requested latency.
119    ///
120    /// * `frame_duration_nanos` is the duration in nanoseconds of the standard emulation frame.
121    /// * `latency` is the requested audio latency; the actual `latency` argument passed to the
122    ///   [create_carousel] may be larger but never smaller than the provided one.
123    /// * `desired_spec` can be populated with the desired audio parameters.
124    pub fn create_with_specs(
125                sdl_context: &Sdl,
126                mut desired_spec: AudioSpecDesired,
127                frame_duration_nanos: u32,
128                latency: usize
129            ) -> Result<Self, AudioHandleError>
130    {
131        let audio_subsystem = sdl_context.audio().map_err(|e| (e, AudioHandleErrorKind::AudioSubsystem))?;
132        let frame_duration_secs = Duration::from_nanos(frame_duration_nanos.into()).as_secs_f64();
133
134        if desired_spec.freq.is_none() {
135            desired_spec.freq = Some(DEFAULT_SAMPLE_RATE);
136        }
137        if desired_spec.channels.is_none() {
138            desired_spec.channels = Some(DEFAULT_CHANNELS);
139        }
140        if desired_spec.samples.is_none() {
141            let audio_frame_samples = (desired_spec.freq.unwrap() as f64 * frame_duration_secs).ceil() as usize;
142            let samples: u16 = (audio_frame_samples * latency.max(1)).checked_next_power_of_two()
143                .and_then(|samples| u16::try_from(samples).ok())
144                .unwrap_or(0x8000);
145            desired_spec.samples = Some(samples);
146        }
147
148        let mut producer: Option<AudioFrameProducer<T>> = None;
149
150        let device = audio_subsystem.open_playback(None, &desired_spec, |spec| {
151            let audio_frame_samples = (spec.freq as f64 * frame_duration_secs).ceil() as usize;
152            let min_latency = (spec.samples as usize / audio_frame_samples).max(1);
153            let latency = latency.max(min_latency);
154            debug!("audio specs: {:?}", spec);
155            debug!("audio frame samples: {} latency: {}", audio_frame_samples, latency);
156            let (prd, consumer) = create_carousel::<T>(latency, audio_frame_samples, spec.channels);
157            producer = Some(prd);
158            AudioCb(consumer)
159        }).map_err(|e| (e, AudioHandleErrorKind::AudioStream))?;
160
161        let spec = device.spec();
162
163        Ok(AudioHandle {
164            sample_rate: spec.freq as u32,
165            channels: spec.channels,
166            samples: spec.samples,
167            producer: producer.unwrap(),
168            device
169        })
170    }
171}