use std::fmt;
use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}};
use crate::buffer::Buffer;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Api
{
Unspecified,
CoreAudio,
Alsa,
Pulse,
Oss,
Jack,
Wasapi,
Asio,
DirectSound,
Dummy,
}
impl Default for Api
{
fn default() -> Self { Self::Unspecified }
}
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)]
pub enum SampleFormat
{
Int8,
Int16,
Int24,
Int32,
Float32,
Float64,
}
impl Default for SampleFormat
{
fn default() -> Self { Self::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 [f64], &[f64], usize, f64, StreamStatus) -> i32 + Send>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StreamState
{
Closed,
Stopped,
Running,
}
struct StreamData
{
output_params : Option<StreamParameters>,
input_params : Option<StreamParameters>,
sample_rate : usize,
buffer_frames : usize,
options : StreamOptions,
state : StreamState,
stream_time : f64,
output_buffer : Vec<f64>,
input_buffer : Vec<f64>,
}
impl Default for StreamData
{
fn default() -> Self
{
Self
{
output_params: None,
input_params: None,
sample_rate: 44100,
buffer_frames: 256,
options: StreamOptions::default(),
state: StreamState::Closed,
stream_time: 0.0,
output_buffer: Vec::new(),
input_buffer: Vec::new(),
}
}
}
pub struct Realtime
{
api : Api,
stream : Arc<Mutex<StreamData>>,
callback : Arc<Mutex<Option<AudioCallback>>>,
running : Arc<AtomicBool>,
thread_handle : Option<std::thread::JoinHandle<()>>,
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,
stream: Arc::new(Mutex::new(StreamData::default())),
callback: Arc::new(Mutex::new(None)),
running: Arc::new(AtomicBool::new(false)),
thread_handle: None,
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);
apis.push(Api::DirectSound);
}
#[cfg(target_os = "linux")]
{
apis.push(Api::Alsa);
apis.push(Api::Pulse);
}
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.get_device_ids().len()
}
pub fn get_device_ids(&self) -> Vec<usize>
{
match self.api
{
Api::Dummy => vec![0],
_ => vec![0, 1], }
}
pub fn get_device_names(&self) -> Vec<String>
{
self.get_device_ids()
.iter()
.filter_map(|&id| self.get_device_info(id).ok())
.map(|info| info.name)
.collect()
}
pub fn get_device_info(&self, device_id : usize) -> MKAudioResult<DeviceInfo>
{
match self.api
{
Api::Dummy =>
{
if device_id == 0
{
Ok(DeviceInfo
{
id: 0,
name: String::from("Dummy Audio Device"),
output_channels: 2,
input_channels: 2,
duplex_channels: 2,
is_default_output: true,
is_default_input: true,
sample_rates: vec![44100, 48000, 96000],
preferred_sample_rate: 44100,
native_formats: vec![SampleFormat::Float32, SampleFormat::Float64],
})
}
else
{
Err(MKAudioError::InvalidDevice(format!("Device {} not found", device_id)))
}
}
_ =>
{
Ok(DeviceInfo
{
id: device_id,
name: format!("Audio Device {}", device_id),
output_channels: if device_id == 0 { 2 } else { 0 },
input_channels: if device_id == 1 { 2 } else { 0 },
duplex_channels: 0,
is_default_output: device_id == 0,
is_default_input: device_id == 1,
sample_rates: vec![44100, 48000, 96000],
preferred_sample_rate: 48000,
native_formats: vec![SampleFormat::Float32],
})
}
}
}
pub fn get_default_output_device(&self) -> usize
{
self.get_device_ids()
.iter()
.find(|&&id|
{
self.get_device_info(id)
.map(|info| info.is_default_output)
.unwrap_or(false)
})
.copied()
.unwrap_or(0)
}
pub fn get_default_input_device(&self) -> usize
{
self.get_device_ids()
.iter()
.find(|&&id|
{
self.get_device_info(id)
.map(|info| info.is_default_input)
.unwrap_or(false)
})
.copied()
.unwrap_or(0)
}
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 mut stream = self.stream.lock().unwrap();
if stream.state != StreamState::Closed
{
return Err(MKAudioError::InvalidUse("Stream is already open".into()));
}
let output_channels = output_params.map(|p| p.num_channels).unwrap_or(0);
let input_channels = input_params.map(|p| p.num_channels).unwrap_or(0);
stream.output_params = output_params.cloned();
stream.input_params = input_params.cloned();
stream.sample_rate = sample_rate;
stream.buffer_frames = buffer_frames;
stream.options = options.unwrap_or_default();
stream.state = StreamState::Stopped;
stream.stream_time = 0.0;
stream.output_buffer = vec![0.0; buffer_frames * output_channels];
stream.input_buffer = vec![0.0; buffer_frames * input_channels];
*self.callback.lock().unwrap() = Some(callback);
Ok(buffer_frames)
}
pub fn close_stream(&mut self)
{
if self.is_stream_running()
{
let _ = self.stop_stream();
}
let mut stream = self.stream.lock().unwrap();
stream.state = StreamState::Closed;
stream.output_buffer.clear();
stream.input_buffer.clear();
*self.callback.lock().unwrap() = None;
}
pub fn start_stream(&mut self) -> MKAudioResult<()>
{
{
let mut stream = self.stream.lock().unwrap();
if stream.state == StreamState::Closed
{
return Err(MKAudioError::InvalidUse("Stream is not open".into()));
}
if stream.state == StreamState::Running
{
return Err(MKAudioError::InvalidUse("Stream is already running".into()));
}
stream.state = StreamState::Running;
}
self.running.store(true, Ordering::SeqCst);
let stream_clone = self.stream.clone();
let callback_clone = self.callback.clone();
let running_clone = self.running.clone();
let api = self.api;
self.thread_handle = Some(std::thread::spawn(move ||
{
Self::audio_thread(api, stream_clone, callback_clone, running_clone);
}));
Ok(())
}
pub fn stop_stream(&mut self) -> MKAudioResult<()>
{
{
let stream = self.stream.lock().unwrap();
if stream.state != StreamState::Running
{
return Err(MKAudioError::InvalidUse("Stream is not running".into()));
}
}
self.running.store(false, Ordering::SeqCst);
if let Some(handle) = self.thread_handle.take()
{
let _ = handle.join();
}
let mut stream = self.stream.lock().unwrap();
stream.state = StreamState::Stopped;
Ok(())
}
pub fn abort_stream(&mut self) -> MKAudioResult<()>
{
self.stop_stream()
}
pub fn is_stream_open(&self) -> bool
{
let stream = self.stream.lock().unwrap();
stream.state != StreamState::Closed
}
pub fn is_stream_running(&self) -> bool
{
let stream = self.stream.lock().unwrap();
stream.state == StreamState::Running
}
pub fn get_stream_time(&self) -> f64
{
let stream = self.stream.lock().unwrap();
stream.stream_time
}
pub fn set_stream_time(&mut self, time : f64)
{
let mut stream = self.stream.lock().unwrap();
stream.stream_time = time;
}
pub fn get_stream_latency(&self) -> usize
{
let stream = self.stream.lock().unwrap();
stream.buffer_frames * stream.options.number_of_buffers.max(2)
}
pub fn get_stream_sample_rate(&self) -> usize
{
let stream = self.stream.lock().unwrap();
stream.sample_rate
}
pub fn show_warnings(&mut self, show : bool)
{
self.show_warnings = show;
}
fn audio_thread(
api : Api,
stream : Arc<Mutex<StreamData>>,
callback : Arc<Mutex<Option<AudioCallback>>>,
running : Arc<AtomicBool>,
)
{
let (sample_rate, buffer_frames) =
{
let s = stream.lock().unwrap();
(s.sample_rate, s.buffer_frames)
};
let frame_duration = std::time::Duration::from_secs_f64(
buffer_frames as f64 / sample_rate as f64
);
while running.load(Ordering::SeqCst)
{
let status = StreamStatus::default();
let (stream_time, mut output_buffer, input_buffer) =
{
let mut s = stream.lock().unwrap();
if api == Api::Dummy
{
s.input_buffer.fill(0.0);
}
(s.stream_time, s.output_buffer.clone(), s.input_buffer.clone())
};
let result =
{
let mut cb_guard = callback.lock().unwrap();
if let Some(ref mut cb) = *cb_guard
{
cb(
&mut output_buffer,
&input_buffer,
buffer_frames,
stream_time,
status,
)
}
else
{
0
}
};
{
let mut s = stream.lock().unwrap();
s.output_buffer.copy_from_slice(&output_buffer);
s.stream_time += buffer_frames as f64 / sample_rate as f64;
}
match result
{
1 | 2 =>
{
running.store(false, Ordering::SeqCst);
break;
}
_ => {}
}
if api == Api::Dummy
{
std::thread::sleep(frame_duration);
}
}
{
let mut s = stream.lock().unwrap();
s.state = StreamState::Stopped;
}
}
}
impl Drop for Realtime
{
fn drop(&mut self)
{
if self.is_stream_open()
{
self.close_stream();
}
}
}
pub fn deinterleave(interleaved : &[f64], channels : usize, frames : usize) -> Vec<Buffer<f64>>
{
let mut buffers = Vec::with_capacity(channels);
for ch in 0..channels
{
let buffer = Buffer::new(frames);
{
let mut guard = buffer.write();
for frame in 0..frames
{
guard[frame] = interleaved[frame * channels + ch];
}
}
buffers.push(buffer);
}
buffers
}
pub fn interleave(buffers : &[Buffer<f64>], interleaved : &mut [f64], frames : usize)
{
let channels = buffers.len();
for (ch, buffer) in buffers.iter().enumerate()
{
let guard = buffer.read();
for frame in 0..frames
{
interleaved[frame * channels + ch] = guard[frame];
}
}
}
pub fn stereo_callback<F>(mut processor : F) -> AudioCallback
where
F : FnMut(&[f64], &[f64], &mut [f64], &mut [f64], 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
})
}