1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/*
    Copyright (C) 2020-2022  Rafal Michalski

    This file is part of SPECTRUSTY, a Rust library for building emulators.

    For the full copyright notice, see the lib.rs file.
*/
//! Audio device streaming implementation for [cpal](https://crates.io/crates/cpal).
//!
//! This module implements [carousel][crate::carousel] using the **cpal** audio layer.
//!
//! Requires "cpal" feature to be enabled.
use core::convert::TryInto;
use core::time::Duration;

#[allow(unused_imports)]
use log::{error, warn, info, debug, trace};

use cpal::{
    Stream,
    PlayStreamError, PauseStreamError, DefaultStreamConfigError, BuildStreamError,
    traits::{DeviceTrait, HostTrait, StreamTrait}
};

pub use cpal::SampleFormat;

use spectrusty_core::audio::AudioSample;
use crate::carousel::*;
pub use super::{AudioHandleError, AudioHandleErrorKind};

/// The struct for producing and controlling the audio playback.
///
/// It embeds the interconnected pair of [carousel][crate::carousel]'s [AudioFrameProducer] with the
/// [AudioFrameConsumer] directly exposing the `producer` to the user. The consumer lives in the
/// **cpal** audio thread and is responsible for filling up the audio output buffer with sample data
/// sent from the `producer`.
///
/// The `T` parameter should be one of the [sample primitives][cpal::Sample].
pub struct AudioHandle<T: cpal::Sample + AudioSample> {
    /// The audio sample frequency of the output stream.
    pub sample_rate: u32,
    /// The number of audio channels in the output stream.
    pub channels: u8,
    /// The audio sample producer, interconnected with an audio consumer living in the audio thread.
    pub producer: AudioFrameProducer<T>,
    stream: Stream
}

/// The enum for producing and controlling the audio playback regardless of the sample format used.
pub enum AudioHandleAnyFormat {
    I16(AudioHandle<i16>),
    U16(AudioHandle<u16>),
    F32(AudioHandle<f32>),
}

impl AudioHandleAnyFormat {
    /// Returns the sample format of the current variant.
    pub fn sample_format(&self) -> SampleFormat {
        use AudioHandleAnyFormat::*;
        match self {
            I16(..) => SampleFormat::I16,
            U16(..) => SampleFormat::U16,
            F32(..) => SampleFormat::F32,
        }
    }
    /// Returns the audio sample frequency of the output stream.
    pub fn sample_rate(&self) -> u32 {
        use AudioHandleAnyFormat::*;
        match self {
            I16(audio) => audio.sample_rate,
            U16(audio) => audio.sample_rate,
            F32(audio) => audio.sample_rate,
        }
    }
    /// Returns the number of audio channels in the output stream.
    pub fn channels(&self) -> u8 {
        use AudioHandleAnyFormat::*;
        match self {
            I16(audio) => audio.channels,
            U16(audio) => audio.channels,
            F32(audio) => audio.channels,
        }
    }
    /// Starts playback of the audio device.
    pub fn play(&self) -> Result<(), AudioHandleError> {
        use AudioHandleAnyFormat::*;
        match self {
            I16(audio) => audio.play(),
            U16(audio) => audio.play(),
            F32(audio) => audio.play(),
        }
    }
    /// Pauses playback of the audio device.
    pub fn pause(&self) -> Result<(), AudioHandleError> {
        use AudioHandleAnyFormat::*;
        match self {
            I16(audio) => audio.pause(),
            U16(audio) => audio.pause(),
            F32(audio) => audio.pause(),
        }
    }
    /// Closes audio playback and frees underlying resources.
    pub fn close(self) {}
    /// Calls the underlying [AudioFrameProducer::send_frame].
    pub fn send_frame(&mut self) -> AudioFrameResult<()> {
        use AudioHandleAnyFormat::*;
        match self {
            I16(audio) => audio.producer.send_frame(),
            U16(audio) => audio.producer.send_frame(),
            F32(audio) => audio.producer.send_frame(),
        }
    }
    /// Creates an instance of the [AudioHandleAnyFormat] from the provided **cpal** `host` with
    /// the default output device and the default audio parameters.
    ///
    /// * `frame_duration_nanos` is the duration in nanoseconds of the standard emulation frame.
    /// * `latency` is the audio latency passed to the [create_carousel].
    pub fn create(
            host: &cpal::Host,
            frame_duration_nanos: u32,
            latency: usize
        ) -> Result<Self, AudioHandleError>
    {
        let device = host.default_output_device()
                     .ok_or_else(|| ("no default output device".to_string(),
                                    AudioHandleErrorKind::AudioSubsystem))?;
        Self::create_with_device(&device, frame_duration_nanos, latency)
    }
    /// Creates an instance of the [AudioHandleAnyFormat] from the provided **cpal** `device`
    /// with the default audio parameters.
    ///
    /// * `frame_duration_nanos` is the duration in nanoseconds of the standard emulation frame.
    /// * `latency` is the audio latency passed to the [create_carousel].
    pub fn create_with_device(
            device: &cpal::Device,
            frame_duration_nanos: u32,
            latency: usize
        ) -> Result<Self, AudioHandleError>
    {
        let config = device.default_output_config()?.config();
        Self::create_with_device_and_config(
            device,
            &config,
            frame_duration_nanos,
            latency,
        )
    }
    /// Creates an instance of the [AudioHandleAnyFormat] from the provided **cpal** `device`
    /// with the desired audio parameters and sample format.
    ///
    /// * `config` specifies the desired audio parameters.
    /// * `frame_duration_nanos` is the duration in nanoseconds of the standard emulation frame.
    /// * `latency` is the audio latency passed to the [create_carousel].
    pub fn create_with_device_and_config(
            device: &cpal::Device,
            config: &cpal::StreamConfig,
            frame_duration_nanos: u32,
            latency: usize,
        ) -> Result<Self, AudioHandleError>
    {
        Ok(match device.default_output_config()?.sample_format() {
            SampleFormat::I16 => AudioHandleAnyFormat::I16(
                AudioHandle::<i16>::create_with_device_and_config(device, config, frame_duration_nanos, latency)?
            ),
            SampleFormat::U16 => AudioHandleAnyFormat::U16(
                AudioHandle::<u16>::create_with_device_and_config(device, config, frame_duration_nanos, latency)?
            ),
            SampleFormat::F32 => AudioHandleAnyFormat::F32(
                AudioHandle::<f32>::create_with_device_and_config(device, config, frame_duration_nanos, latency)?
            )
        })
    }
}

impl<T: cpal::Sample + AudioSample> AudioHandle<T> {
    /// Starts playback of the audio device.
    pub fn play(&self) -> Result<(), AudioHandleError> {
        self.stream.play().map_err(From::from)
    }
    /// Pauses playback of the audio device.
    pub fn pause(&self) -> Result<(), AudioHandleError> {
        self.stream.pause().map_err(From::from)
    }
    /// Closes audio playback and frees underlying resources.
    pub fn close(self) {}
    /// Creates an instance of the [AudioHandle] from the provided **cpal** `device` with the
    /// desired audio parameters.
    ///
    /// * `config` specifies the desired audio parameters.
    /// * `frame_duration_nanos` is the duration in nanoseconds of the standard emulation frame.
    /// * `latency` is the audio latency passed to the [create_carousel].
    pub fn create_with_device_and_config(
            device: &cpal::Device,
            config: &cpal::StreamConfig,
            frame_duration_nanos: u32,
            latency: usize,
        ) -> Result<Self, AudioHandleError>
    {
        let channels: u8 = config.channels.try_into()
                           .map_err(|_| (format!("number of channels: {} exceed the maximum value of 255", config.channels),
                                         AudioHandleErrorKind::InvalidArguments))?;
        let sample_rate = config.sample_rate.0;

        let frame_duration_secs = Duration::from_nanos(frame_duration_nanos.into()).as_secs_f64();
        let audio_frame_samples = (sample_rate as f64 * frame_duration_secs).ceil() as usize;
        debug!("audio specs: {:?}", config);
        debug!("audio frame samples: {} latency: {}", audio_frame_samples, latency);
        let (producer, mut consumer) = create_carousel::<T>(latency, audio_frame_samples, channels);

        let data_fn = move |out: &mut [T], _: &_| match consumer.fill_buffer(out, false) {
            Ok(unfilled) => {
                if !unfilled.is_empty() {
                    for t in unfilled {
                        *t = T::silence()
                    }
                    debug!("missing buffer");
                }
            }
            Err(_) => {
                error!("fatal: producer terminated");
            }
        };

        let err_fn = |err| error!("an error occurred on stream: {}", err);

        let stream = device.build_output_stream(config, data_fn, err_fn)?;

        Ok(AudioHandle {
            sample_rate,
            channels,
            producer,
            stream
        })
    }
}

impl From<PlayStreamError> for AudioHandleError {
    fn from(e: PlayStreamError) -> Self {
        let kind = match e {
            PlayStreamError::DeviceNotAvailable => AudioHandleErrorKind::AudioSubsystem,
            _ => AudioHandleErrorKind::AudioStream
        };
        (e.to_string(), kind).into()
    }
}

impl From<PauseStreamError> for AudioHandleError {
    fn from(e: PauseStreamError) -> Self {
        let kind = match e {
            PauseStreamError::DeviceNotAvailable => AudioHandleErrorKind::AudioSubsystem,
            _ => AudioHandleErrorKind::AudioStream
        };
        (e.to_string(), kind).into()
    }
}

impl From<DefaultStreamConfigError> for AudioHandleError {
    fn from(e: DefaultStreamConfigError) -> Self {
        let kind = match e {
            DefaultStreamConfigError::StreamTypeNotSupported => AudioHandleErrorKind::InvalidArguments,
            _ => AudioHandleErrorKind::AudioSubsystem
        };
        (e.to_string(), kind).into()
    }
}

impl From<BuildStreamError> for AudioHandleError {
    fn from(e: BuildStreamError) -> Self {
        let kind = match e {
            BuildStreamError::DeviceNotAvailable => AudioHandleErrorKind::AudioSubsystem,
            BuildStreamError::StreamConfigNotSupported|
            BuildStreamError::InvalidArgument => AudioHandleErrorKind::InvalidArguments,
            _ => AudioHandleErrorKind::AudioStream
        };
        (e.to_string(), kind).into()
    }
}