Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
.DS_Store
recorded.wav
rls*.log
rusty-tags.*
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ nix = "0.15.0"
libc = "0.2.65"
jack = { version = "0.6.5", optional = true }

[target.'cfg(target_os = "openbsd")'.dependencies]
sndio-sys = "0.0.*"
libc = "0.2.65"

[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
coreaudio-rs = { version = "0.9.1", default-features = false, features = ["audio_unit", "core_audio"] }
core-foundation-sys = "0.6.2" # For linking to CoreFoundation.framework and handling device name `CFString`s.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Currently supported hosts include:
- macOS (via CoreAudio)
- iOS (via CoreAudio)
- Android (via Oboe)
- OpenBSD (via sndio)
- Emscripten

Note that on Linux, the ALSA development files are required. These are provided
Expand Down
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ pub enum PauseStreamError {
}

/// Errors that might occur while a stream is running.
#[derive(Debug, Error)]
#[derive(Debug, Error, Clone)]
pub enum StreamError {
/// The device no longer exists. This can happen if the device is disconnected while the
/// program is running.
Expand Down
2 changes: 2 additions & 0 deletions src/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub(crate) mod jack;
pub(crate) mod null;
#[cfg(target_os = "android")]
pub(crate) mod oboe;
#[cfg(target_os = "openbsd")]
pub(crate) mod sndio;
#[cfg(windows)]
pub(crate) mod wasapi;
#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
Expand Down
100 changes: 100 additions & 0 deletions src/host/sndio/adapters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use crate::{Data, InputCallbackInfo, OutputCallbackInfo, Sample, SampleFormat};

/// When given an input data callback that expects samples in the specified sample format, return
/// an input data callback that expects samples in the I16 sample format. The `buffer_size` is in
/// samples.
pub(super) fn input_adapter_callback<D>(
mut original_data_callback: D,
buffer_size: usize,
sample_format: SampleFormat,
) -> Box<dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
{
if sample_format == SampleFormat::I16 {
// no-op
return Box::new(original_data_callback);
}

// Make the backing buffer for the Data used in the closure.
let mut buf: Vec<u8> = vec![0].repeat(buffer_size * sample_format.sample_size());

Box::new(move |data: &Data, info: &InputCallbackInfo| {
// Note: we construct adapted_data here instead of in the parent function because buf needs
// to be owned by the closure.
let mut adapted_data =
unsafe { Data::from_parts(buf.as_mut_ptr() as *mut _, buffer_size, sample_format) };
let data_slice: &[i16] = data.as_slice().unwrap(); // unwrap OK because data is always i16
match sample_format {
SampleFormat::F32 => {
let adapted_slice: &mut [f32] = adapted_data.as_slice_mut().unwrap(); // unwrap OK because of the match
assert_eq!(data_slice.len(), adapted_slice.len());
for (i, adapted_ref) in adapted_slice.iter_mut().enumerate() {
*adapted_ref = data_slice[i].to_f32();
}
}
SampleFormat::U16 => {
let adapted_slice: &mut [u16] = adapted_data.as_slice_mut().unwrap(); // unwrap OK because of the match
assert_eq!(data_slice.len(), adapted_slice.len());
for (i, adapted_ref) in adapted_slice.iter_mut().enumerate() {
*adapted_ref = data_slice[i].to_u16();
}
}
SampleFormat::I16 => {
unreachable!("i16 should've already been handled above");
}
}
original_data_callback(&adapted_data, info);
})
}

/// When given an output data callback that expects a place to write samples in the specified
/// sample format, return an output data callback that expects a place to write samples in the I16
/// sample format. The `buffer_size` is in samples.
pub(super) fn output_adapter_callback<D>(
mut original_data_callback: D,
buffer_size: usize,
sample_format: SampleFormat,
) -> Box<dyn FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
{
if sample_format == SampleFormat::I16 {
// no-op
return Box::new(original_data_callback);
}

// Make the backing buffer for the Data used in the closure.
let mut buf: Vec<u8> = vec![0].repeat(buffer_size * sample_format.sample_size());

Box::new(move |data: &mut Data, info: &OutputCallbackInfo| {
// Note: we construct adapted_data here instead of in the parent function because buf needs
// to be owned by the closure.
let mut adapted_data =
unsafe { Data::from_parts(buf.as_mut_ptr() as *mut _, buffer_size, sample_format) };

// Populate buf / adapted_data.
original_data_callback(&mut adapted_data, info);

let data_slice: &mut [i16] = data.as_slice_mut().unwrap(); // unwrap OK because data is always i16
match sample_format {
SampleFormat::F32 => {
let adapted_slice: &[f32] = adapted_data.as_slice().unwrap(); // unwrap OK because of the match
assert_eq!(data_slice.len(), adapted_slice.len());
for (i, data_ref) in data_slice.iter_mut().enumerate() {
*data_ref = adapted_slice[i].to_i16();
}
}
SampleFormat::U16 => {
let adapted_slice: &[u16] = adapted_data.as_slice().unwrap(); // unwrap OK because of the match
assert_eq!(data_slice.len(), adapted_slice.len());
for (i, data_ref) in data_slice.iter_mut().enumerate() {
*data_ref = adapted_slice[i].to_i16();
}
}
SampleFormat::I16 => {
unreachable!("i16 should've already been handled above");
}
}
})
}
14 changes: 14 additions & 0 deletions src/host/sndio/endian.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(super) enum Endian {
BE,
LE,
}

pub(super) fn get_endianness() -> Endian {
let n: i16 = 1;
match n.to_ne_bytes()[0] {
1 => Endian::LE,
0 => Endian::BE,
_ => unreachable!("unexpected value in byte"),
}
}
Loading