rg3d_sound/device/
mod.rs

1// WASM does not use some portions of the code, so compiler will complain about this,
2// here we just suppress the warning.
3#![allow(dead_code)]
4
5//! Device module.
6//!
7//! # Overview
8//!
9//! Device is an abstraction over output device which provides unified way of communication with
10//! output device.
11
12#[cfg(target_os = "windows")]
13mod dsound;
14
15#[cfg(target_os = "linux")]
16mod alsa;
17
18#[cfg(target_os = "macos")]
19mod coreaudio;
20
21// The dummy target works on all platforms
22#[cfg(not(any(
23    target_os = "windows",
24    target_os = "linux",
25    target_os = "macos",
26    target_arch = "wasm32"
27)))]
28mod dummy;
29
30#[cfg(target_arch = "wasm32")]
31mod web;
32
33#[repr(C)]
34#[derive(Debug, Copy, Clone, Default)]
35pub struct NativeSample {
36    pub left: i16,
37    pub right: i16,
38}
39
40pub type FeedCallback = dyn FnMut(&mut [(f32, f32)]) + Send;
41
42pub struct MixContext<'a> {
43    mix_buffer: &'a mut [(f32, f32)],
44    out_data: &'a mut [NativeSample],
45    callback: &'a mut FeedCallback,
46}
47
48trait Device {
49    fn get_mix_context(&mut self) -> Option<MixContext>;
50
51    fn run(&mut self);
52
53    fn mix(&mut self) {
54        if let Some(context) = self.get_mix_context() {
55            // Clear mixer buffer.
56            for (left, right) in context.mix_buffer.iter_mut() {
57                *left = 0.0;
58                *right = 0.0;
59            }
60
61            // Fill it.
62            (context.callback)(context.mix_buffer);
63
64            // Convert to i16 - device expects samples in this format.
65            assert_eq!(context.mix_buffer.len(), context.out_data.len());
66            for ((left, right), ref mut out_sample) in
67                context.mix_buffer.iter().zip(context.out_data)
68            {
69                fn sample_to_i16(sample: f32) -> i16 {
70                    const SCALE: f32 = i16::MAX as f32;
71                    let clamped = if sample > 1.0 {
72                        1.0
73                    } else if sample < -1.0 {
74                        -1.0
75                    } else {
76                        sample
77                    };
78                    (clamped * SCALE) as i16
79                }
80
81                out_sample.left = sample_to_i16(*left);
82                out_sample.right = sample_to_i16(*right);
83            }
84        }
85    }
86}
87
88/// Transfer ownership of device to separate mixer thread. It will
89/// call the callback with a specified rate to get data to send to a physical device.
90pub(in crate) fn run_device<F: FnMut(&mut [(f32, f32)]) + Send + 'static>(
91    #[allow(unused_variables)] buffer_len_bytes: u32,
92    #[allow(unused_variables)] callback: F,
93) {
94    #[cfg(not(target_arch = "wasm32"))]
95    {
96        std::thread::spawn(move || {
97            #[cfg(target_os = "windows")]
98            let mut device = dsound::DirectSoundDevice::new(buffer_len_bytes, callback).unwrap();
99            #[cfg(target_os = "linux")]
100            let mut device = alsa::AlsaSoundDevice::new(buffer_len_bytes, callback).unwrap();
101            #[cfg(target_os = "macos")]
102            let mut device =
103                coreaudio::CoreaudioSoundDevice::new(buffer_len_bytes, callback).unwrap();
104            #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
105            let mut device = dummy::DummySoundDevice::new(buffer_len_bytes, callback).unwrap();
106            device.run()
107        });
108    }
109
110    #[cfg(target_arch = "wasm32")]
111    {
112        let mut device = web::WebAudioDevice::new(buffer_len_bytes, callback);
113        device.run();
114        std::mem::forget(device);
115    }
116}