mod dummy_impl;
#[cfg(target_os = "macos")]
mod coreaudio_impl;
#[cfg(target_os = "windows")]
mod wasapi_impl;
#[cfg(target_os = "linux")]
mod alsa_impl;
use std::fmt;
use std::sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
};
use crate::buffer::Buffer;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Api {
#[default]
Unspecified,
CoreAudio,
Alsa,
Pulse,
Oss,
Jack,
Wasapi,
Asio,
DirectSound,
Dummy,
}
impl fmt::Display for Api {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Api::Unspecified => "Unspecified",
Api::CoreAudio => "CoreAudio",
Api::Alsa => "ALSA",
Api::Pulse => "PulseAudio",
Api::Oss => "OSS",
Api::Jack => "Jack",
Api::Wasapi => "WASAPI",
Api::Asio => "ASIO",
Api::DirectSound => "DirectSound",
Api::Dummy => "Dummy",
};
write!(f, "{}", name)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SampleFormat {
Int8,
Int16,
Int24,
Int32,
Float32,
#[default]
Float64,
}
impl SampleFormat {
pub fn byte_size(&self) -> usize {
match self {
SampleFormat::Int8 => 1,
SampleFormat::Int16 => 2,
SampleFormat::Int24 => 3,
SampleFormat::Int32 => 4,
SampleFormat::Float32 => 4,
SampleFormat::Float64 => 8,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct StreamFlags {
pub noninterleaved: bool,
pub minimize_latency: bool,
pub hog_device: bool,
pub schedule_realtime: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct StreamStatus {
pub input_overflow: bool,
pub output_underflow: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StreamParameters {
pub device_id: usize,
pub num_channels: usize,
pub first_channel: usize,
}
impl Default for StreamParameters {
fn default() -> Self {
Self {
device_id: 0,
num_channels: 2,
first_channel: 0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct StreamOptions {
pub flags: StreamFlags,
pub number_of_buffers: usize,
pub stream_name: String,
pub priority: i32,
}
#[derive(Debug, Clone)]
pub struct DeviceInfo {
pub id: usize,
pub name: String,
pub output_channels: usize,
pub input_channels: usize,
pub duplex_channels: usize,
pub is_default_output: bool,
pub is_default_input: bool,
pub sample_rates: Vec<usize>,
pub preferred_sample_rate: usize,
pub native_formats: Vec<SampleFormat>,
}
impl Default for DeviceInfo {
fn default() -> Self {
Self {
id: 0,
name: String::new(),
output_channels: 0,
input_channels: 0,
duplex_channels: 0,
is_default_output: false,
is_default_input: false,
sample_rates: vec![44100, 48000, 96000],
preferred_sample_rate: 44100,
native_formats: vec![SampleFormat::Float32],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MKAudioError {
Warning(String),
DebugWarning(String),
Unspecified(String),
NoDevicesFound,
InvalidDevice(String),
DeviceDisconnect(String),
MemoryError(String),
InvalidParameter(String),
InvalidUse(String),
DriverError(String),
SystemError(String),
ThreadError(String),
}
impl std::fmt::Display for MKAudioError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MKAudioError::Warning(s) => write!(f, "Warning: {}", s),
MKAudioError::DebugWarning(s) => write!(f, "Debug: {}", s),
MKAudioError::Unspecified(s) => write!(f, "Error: {}", s),
MKAudioError::NoDevicesFound => write!(f, "No audio devices found"),
MKAudioError::InvalidDevice(s) => write!(f, "Invalid device: {}", s),
MKAudioError::DeviceDisconnect(s) => write!(f, "Device disconnected: {}", s),
MKAudioError::MemoryError(s) => write!(f, "Memory error: {}", s),
MKAudioError::InvalidParameter(s) => write!(f, "Invalid parameter: {}", s),
MKAudioError::InvalidUse(s) => write!(f, "Invalid use: {}", s),
MKAudioError::DriverError(s) => write!(f, "Driver error: {}", s),
MKAudioError::SystemError(s) => write!(f, "System error: {}", s),
MKAudioError::ThreadError(s) => write!(f, "Thread error: {}", s),
}
}
}
impl std::error::Error for MKAudioError {}
pub type MKAudioResult<T> = Result<T, MKAudioError>;
pub type AudioCallback = Box<dyn FnMut(&mut [f32], &[f32], usize, f32, StreamStatus) -> i32 + Send>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum StreamState {
Closed,
Stopped,
Running,
}
pub(crate) trait Backend: Send {
fn device_ids(&self) -> Vec<usize>;
fn device_info(&self, device_id: usize) -> MKAudioResult<DeviceInfo>;
fn default_output_device(&self) -> usize;
fn default_input_device(&self) -> usize;
#[allow(clippy::too_many_arguments)]
fn open_stream(
&mut self,
output_params: Option<&StreamParameters>,
input_params: Option<&StreamParameters>,
sample_rate: usize,
buffer_frames: usize,
callback: AudioCallback,
options: &StreamOptions,
) -> MKAudioResult<usize>;
fn start(&mut self) -> MKAudioResult<()>;
fn stop(&mut self) -> MKAudioResult<()>;
fn close(&mut self);
fn is_running(&self) -> bool;
fn stream_time(&self) -> f32;
fn latency_samples(&self) -> usize;
}
fn make_backend(api: Api) -> Box<dyn Backend> {
match api {
#[cfg(target_os = "macos")]
Api::CoreAudio => Box::new(coreaudio_impl::CoreAudioBackend::new()),
#[cfg(target_os = "windows")]
Api::Wasapi => Box::new(wasapi_impl::WasapiBackend::new()),
#[cfg(target_os = "linux")]
Api::Alsa | Api::Pulse => Box::new(alsa_impl::AlsaBackend::new()),
_ => Box::new(dummy_impl::DummyBackend::new()),
}
}
pub struct Realtime {
api: Api,
backend: Box<dyn Backend>,
show_warnings: bool,
}
impl Realtime {
pub fn new(api: Option<Api>) -> MKAudioResult<Self> {
let selected_api = api.unwrap_or_else(Self::detect_api);
Ok(Self {
api: selected_api,
backend: make_backend(selected_api),
show_warnings: true,
})
}
pub fn get_current_api(&self) -> Api {
self.api
}
pub fn get_compiled_apis() -> Vec<Api> {
let mut apis = vec![Api::Dummy];
#[cfg(target_os = "macos")]
apis.push(Api::CoreAudio);
#[cfg(target_os = "windows")]
apis.push(Api::Wasapi);
#[cfg(target_os = "linux")]
apis.push(Api::Alsa);
apis
}
fn detect_api() -> Api {
#[cfg(target_os = "macos")]
return Api::CoreAudio;
#[cfg(target_os = "windows")]
return Api::Wasapi;
#[cfg(target_os = "linux")]
return Api::Alsa;
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
return Api::Dummy;
}
pub fn get_device_count(&self) -> usize {
self.backend.device_ids().len()
}
pub fn get_device_ids(&self) -> Vec<usize> {
self.backend.device_ids()
}
pub fn get_device_names(&self) -> Vec<String> {
self.backend
.device_ids()
.iter()
.filter_map(|&id| self.backend.device_info(id).ok())
.map(|info| info.name)
.collect()
}
pub fn get_device_info(&self, device_id: usize) -> MKAudioResult<DeviceInfo> {
self.backend.device_info(device_id)
}
pub fn get_default_output_device(&self) -> usize {
self.backend.default_output_device()
}
pub fn get_default_input_device(&self) -> usize {
self.backend.default_input_device()
}
pub fn open_stream(
&mut self,
output_params: Option<&StreamParameters>,
input_params: Option<&StreamParameters>,
sample_rate: usize,
buffer_frames: usize,
callback: AudioCallback,
options: Option<StreamOptions>,
) -> MKAudioResult<usize> {
if output_params.is_none() && input_params.is_none() {
return Err(MKAudioError::InvalidParameter(
"At least one of output or input parameters must be specified".into(),
));
}
let options = options.unwrap_or_default();
self.backend.open_stream(
output_params,
input_params,
sample_rate,
buffer_frames,
callback,
&options,
)
}
pub fn close_stream(&mut self) {
self.backend.close();
}
pub fn start_stream(&mut self) -> MKAudioResult<()> {
self.backend.start()
}
pub fn stop_stream(&mut self) -> MKAudioResult<()> {
self.backend.stop()
}
pub fn abort_stream(&mut self) -> MKAudioResult<()> {
self.backend.stop()
}
pub fn is_stream_running(&self) -> bool {
self.backend.is_running()
}
pub fn get_stream_time(&self) -> f32 {
self.backend.stream_time()
}
pub fn get_stream_latency(&self) -> usize {
self.backend.latency_samples()
}
pub fn show_warnings(&mut self, show: bool) {
self.show_warnings = show;
}
}
impl Drop for Realtime {
fn drop(&mut self) {
self.backend.close();
}
}
pub fn deinterleave(interleaved: &[f32], channels: usize, frames: usize) -> Vec<Buffer<f32>> {
let mut buffers = Vec::with_capacity(channels);
for ch in 0..channels {
let mut buffer = Buffer::new(frames);
for frame in 0..frames {
buffer[frame] = interleaved[frame * channels + ch];
}
buffers.push(buffer);
}
buffers
}
pub fn interleave(buffers: &[Buffer<f32>], interleaved: &mut [f32], frames: usize) {
let channels = buffers.len();
for (ch, buffer) in buffers.iter().enumerate() {
for frame in 0..frames {
interleaved[frame * channels + ch] = buffer[frame];
}
}
}
pub fn stereo_callback<F>(mut processor: F) -> AudioCallback
where
F: FnMut(&[f32], &[f32], &mut [f32], &mut [f32], usize) + Send + 'static,
{
Box::new(move |output, input, frames, _time, _status| {
let mut left_in = vec![0.0; frames];
let mut right_in = vec![0.0; frames];
for i in 0..frames {
if input.len() >= (i + 1) * 2 {
left_in[i] = input[i * 2];
right_in[i] = input[i * 2 + 1];
}
}
let mut left_out = vec![0.0; frames];
let mut right_out = vec![0.0; frames];
processor(&left_in, &right_in, &mut left_out, &mut right_out, frames);
for i in 0..frames {
if output.len() >= (i + 1) * 2 {
output[i * 2] = left_out[i];
output[i * 2 + 1] = right_out[i];
}
}
0
})
}
pub(crate) fn invoke_callback(
callback: &Arc<Mutex<Option<AudioCallback>>>,
running: &Arc<AtomicBool>,
output: &mut [f32],
input: &[f32],
frames: usize,
stream_time: f32,
status: StreamStatus,
) {
let mut guard = callback.lock().unwrap();
if let Some(cb) = guard.as_mut() {
let result = cb(output, input, frames, stream_time, status);
if result != 0 {
running.store(false, Ordering::SeqCst);
}
} else {
output.fill(0.0);
}
}