Skip to main content

oss_audio_midi/
lib.rs

1use nix::libc;
2use std::{
3    fs::File,
4    mem,
5    os::{fd::AsRawFd, unix::fs::OpenOptionsExt},
6};
7
8// Format
9pub const AFMT_QUERY: u32 = 0x00000000;
10pub const AFMT_MU_LAW: u32 = 0x00000001;
11pub const AFMT_A_LAW: u32 = 0x00000002;
12pub const AFMT_IMA_ADPCM: u32 = 0x00000004;
13pub const AFMT_U8: u32 = 0x00000008;
14pub const AFMT_S16_LE: u32 = 0x00000010;
15pub const AFMT_S16_BE: u32 = 0x00000020;
16pub const AFMT_S8: u32 = 0x00000040;
17pub const AFMT_U16_LE: u32 = 0x00000080;
18pub const AFMT_U16_BE: u32 = 0x00000100;
19pub const AFMT_MPEG: u32 = 0x00000200;
20pub const AFMT_AC3: u32 = 0x00000400;
21pub const AFMT_S32_LE: u32 = 0x00001000;
22pub const AFMT_S32_BE: u32 = 0x00002000;
23pub const AFMT_U32_LE: u32 = 0x00004000;
24pub const AFMT_U32_BE: u32 = 0x00008000;
25pub const AFMT_S24_LE: u32 = 0x00010000;
26pub const AFMT_S24_BE: u32 = 0x00020000;
27pub const AFMT_U24_LE: u32 = 0x00040000;
28pub const AFMT_U24_BE: u32 = 0x00080000;
29pub const AFMT_STEREO: u32 = 0x10000000;
30pub const AFMT_WEIRD: u32 = 0x20000000;
31pub const AFMT_FULLDUPLEX: u32 = 0x80000000;
32
33// Triggers
34pub const PCM_ENABLE_INPUT: u32 = 0x00000001;
35pub const PCM_ENABLE_OUTPUT: u32 = 0x00000002;
36
37#[derive(Debug)]
38pub struct Config {
39    pub dsp: File,
40    pub channels: i32,
41    pub rate: i32,
42    pub bytes: i32,
43    pub format: u32,
44    pub samples: i32,
45    pub chsamples: i32,
46}
47
48impl Config {
49    pub fn new(path: &str, rate: i32, bits: i32, mmap: bool) -> Config {
50        let mut binding = File::options();
51        let options = binding.write(true);
52        if mmap {
53            options.custom_flags(libc::O_RDONLY | libc::O_EXCL | libc::O_NONBLOCK);
54        }
55        let mut c = Config {
56            dsp: options.open(path).unwrap(),
57            channels: 0,
58            rate,
59            bytes: 0,
60            format: AFMT_S32_LE,
61            samples: 0,
62            chsamples: 0,
63        };
64        if bits == 32 {
65            c.format = AFMT_S32_LE;
66        } else if bits == 16 {
67            c.format = AFMT_S16_LE;
68        } else if bits == 8 {
69            c.format = AFMT_S8;
70        } else {
71            panic!("No format with {} bits", bits);
72        }
73        let mut audio_info = AudioInfo::new();
74        let mut buffer_info = BufferInfo::new();
75        unsafe {
76            let fd = c.dsp.as_raw_fd();
77
78            // Get info about hardware
79            if mmap {
80                oss_set_cooked(fd, 0).expect("Failed to disable cooked mode");
81                oss_set_trigger(fd, 0).expect("Failed to set trigger");
82            }
83            oss_audio_info(fd, &mut audio_info).expect("Failed to get info on device");
84
85            // Set number of channels, sample format and rate
86            oss_set_format(fd, &mut c.format).expect("Failed to set format");
87            oss_channels(fd, &mut audio_info.max_channels)
88                .expect("Failed to set number of channels");
89            oss_set_speed(fd, &mut c.rate).expect("Failed to set sample rate");
90
91            // When it's all set and good to go, gather buffer size info
92            oss_buffer_info(fd, &mut buffer_info).expect("Failed to get info on buffer size");
93            oss_caps(fd, &mut audio_info.caps).expect("Failed to get capabilities of the device");
94        }
95        c.channels = audio_info.max_channels;
96        c.bytes = buffer_info.bytes;
97        c.samples = c.bytes / mem::size_of::<i32>() as i32;
98        c.chsamples = c.samples / c.channels;
99        c
100    }
101}
102
103#[repr(C)]
104struct AudioInfo {
105    pub dev: libc::c_int,
106    pub name: [libc::c_char; 64],
107    pub busy: libc::c_int,
108    pub pid: libc::c_int,
109    pub caps: libc::c_int,
110    pub iformats: libc::c_int,
111    pub oformats: libc::c_int,
112    pub magic: libc::c_int,
113    pub cmd: [libc::c_char; 64],
114    pub card_number: libc::c_int,
115    pub port_number: libc::c_int,
116    pub mixer_dev: libc::c_int,
117    pub legacy_device: libc::c_int,
118    pub enabled: libc::c_int,
119    pub flags: libc::c_int,
120    pub min_rate: libc::c_int,
121    pub max_rate: libc::c_int,
122    pub min_channels: libc::c_int,
123    pub max_channels: libc::c_int,
124    pub binding: libc::c_int,
125    pub rate_source: libc::c_int,
126    pub handle: [libc::c_char; 32],
127    pub nrates: libc::c_uint,
128    pub rates: [libc::c_uint; 20],
129    pub song_name: [libc::c_char; 64],
130    pub label: [libc::c_char; 16],
131    pub latency: libc::c_int,
132    pub devnode: [libc::c_char; 32],
133    pub next_play_engine: libc::c_int,
134    pub next_rec_engine: libc::c_int,
135    pub filler: [libc::c_int; 184],
136}
137
138impl AudioInfo {
139    pub fn new() -> AudioInfo {
140        AudioInfo {
141            dev: 0,
142            name: [0; 64],
143            busy: 0,
144            pid: 0,
145            caps: 0,
146            iformats: 0,
147            oformats: 0,
148            magic: 0,
149            cmd: [0; 64],
150            card_number: 0,
151            port_number: 0,
152            mixer_dev: 0,
153            legacy_device: 0,
154            enabled: 0,
155            flags: 0,
156            min_rate: 0,
157            max_rate: 0,
158            min_channels: 0,
159            max_channels: 0,
160            binding: 0,
161            rate_source: 0,
162            handle: [0; 32],
163            nrates: 0,
164            rates: [0; 20],
165            song_name: [0; 64],
166            label: [0; 16],
167            latency: 0,
168            devnode: [0; 32],
169            next_play_engine: 0,
170            next_rec_engine: 0,
171            filler: [0; 184],
172        }
173    }
174}
175
176#[repr(C)]
177struct BufferInfo {
178    pub fragments: libc::c_int,
179    pub fragstotal: libc::c_int,
180    pub fragsize: libc::c_int,
181    pub bytes: libc::c_int,
182}
183
184impl BufferInfo {
185    pub fn new() -> BufferInfo {
186        BufferInfo {
187            fragments: 0,
188            fragstotal: 0,
189            fragsize: 0,
190            bytes: 0,
191        }
192    }
193}
194
195const SNDCTL_DSP_MAGIC: u8 = b'P';
196const SNDCTL_DSP_SPEED: u8 = 2;
197const SNDCTL_DSP_SETFMT: u8 = 5;
198const SNDCTL_DSP_CHANNELS: u8 = 6;
199const SNDCTL_DSP_GETOSPACE: u8 = 12;
200const SNDCTL_DSP_GETCAPS: u8 = 15;
201const SNDCTL_DSP_SETTRIGGER: u8 = 16;
202const SNDCTL_DSP_COOKEDMODE: u8 = 30;
203nix::ioctl_readwrite!(oss_channels, SNDCTL_DSP_MAGIC, SNDCTL_DSP_CHANNELS, i32);
204nix::ioctl_read!(
205    oss_buffer_info,
206    SNDCTL_DSP_MAGIC,
207    SNDCTL_DSP_GETOSPACE,
208    BufferInfo
209);
210nix::ioctl_read!(oss_caps, SNDCTL_DSP_MAGIC, SNDCTL_DSP_GETCAPS, i32);
211nix::ioctl_readwrite!(oss_set_format, SNDCTL_DSP_MAGIC, SNDCTL_DSP_SETFMT, u32);
212nix::ioctl_readwrite!(oss_set_speed, SNDCTL_DSP_MAGIC, SNDCTL_DSP_SPEED, i32);
213nix::ioctl_write_int!(oss_set_cooked, SNDCTL_DSP_MAGIC, SNDCTL_DSP_COOKEDMODE);
214nix::ioctl_write_int!(oss_set_trigger, SNDCTL_DSP_MAGIC, SNDCTL_DSP_SETTRIGGER);
215
216const SNDCTL_INFO_MAGIC: u8 = b'X';
217const SNDCTL_ENGINEINFO: u8 = 12;
218nix::ioctl_readwrite!(
219    oss_audio_info,
220    SNDCTL_INFO_MAGIC,
221    SNDCTL_ENGINEINFO,
222    AudioInfo
223);
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use std::io::Write;
229    use std::slice::from_raw_parts;
230    use wavers::Wav;
231
232    #[test]
233    fn it_works() {
234        let mut oss = Config::new("/dev/dsp", 48000, 32, false);
235        let mut wav: Wav<i32> = Wav::from_path("./stereo32.wav").unwrap();
236        let nchannels: usize = wav.n_channels().into();
237        let mut out: Vec<i32> = vec![];
238        let mut iter = wav.frames();
239
240        'outer: loop {
241            let mut i = 0;
242            while i < oss.chsamples {
243                match iter.next() {
244                    Some(ref samples) => {
245                        for ch in 0..nchannels {
246                            out.push(samples[ch]);
247                            i += 1;
248                        }
249                    }
250                    None => break 'outer,
251                }
252            }
253            let bytes = unsafe { from_raw_parts(out.as_ptr() as *const u8, out.len() * 4) };
254            let _ret = oss.dsp.write(bytes);
255            out.clear();
256        }
257        // assert_eq!(1, 2, "Fake error");
258    }
259
260    #[test]
261    #[ignore]
262    fn mmap_mode() {
263        let _oss = Config::new("/dev/dsp4", 48000, 32, true);
264        // assert_eq!(1, 2, "Fake error");
265    }
266}