use sim_kernel::{Error, Result, Symbol};
use sim_lib_stream_audio::PcmSpec;
use sim_lib_stream_core::StreamMedia;
use sim_lib_stream_host::HostDirection;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AsioTiming {
sample_rate_hz: u32,
buffer_frames: usize,
input_latency_frames: u32,
output_latency_frames: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AsioDriver {
id: Symbol,
name: String,
timing: AsioTiming,
audio_inputs: usize,
audio_outputs: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AsioPort {
id: Symbol,
driver: Symbol,
name: String,
media: StreamMedia,
direction: HostDirection,
index: usize,
}
impl AsioTiming {
pub fn new(
sample_rate_hz: u32,
buffer_frames: usize,
input_latency_frames: u32,
output_latency_frames: u32,
) -> Result<Self> {
if sample_rate_hz == 0 {
return Err(Error::Eval(
"ASIO sample rate must be greater than zero".to_owned(),
));
}
if buffer_frames == 0 {
return Err(Error::Eval(
"ASIO buffer size must be greater than zero".to_owned(),
));
}
Ok(Self {
sample_rate_hz,
buffer_frames,
input_latency_frames,
output_latency_frames,
})
}
pub fn pro_audio_default() -> Self {
Self::new(48_000, 128, 128, 128).expect("valid ASIO timing")
}
pub fn sample_rate_hz(self) -> u32 {
self.sample_rate_hz
}
pub fn buffer_frames(self) -> usize {
self.buffer_frames
}
pub fn input_latency_frames(self) -> u32 {
self.input_latency_frames
}
pub fn output_latency_frames(self) -> u32 {
self.output_latency_frames
}
}
impl AsioDriver {
pub fn new(
name: impl Into<String>,
timing: AsioTiming,
audio_inputs: usize,
audio_outputs: usize,
) -> Result<Self> {
let name = name.into();
if name.is_empty() {
return Err(Error::Eval("ASIO driver name must not be empty".to_owned()));
}
if audio_inputs == 0 && audio_outputs == 0 {
return Err(Error::Eval(
"ASIO driver must expose at least one audio channel".to_owned(),
));
}
Ok(Self {
id: Symbol::new(format!("asio/{name}/driver")),
name,
timing,
audio_inputs,
audio_outputs,
})
}
pub fn sim_default() -> Result<Self> {
Self::new("SIM-ASIO", AsioTiming::pro_audio_default(), 2, 2)
}
pub fn id(&self) -> &Symbol {
&self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn timing(&self) -> AsioTiming {
self.timing
}
pub fn audio_inputs(&self) -> usize {
self.audio_inputs
}
pub fn audio_outputs(&self) -> usize {
self.audio_outputs
}
pub fn direction(&self) -> HostDirection {
match (self.audio_inputs > 0, self.audio_outputs > 0) {
(true, true) => HostDirection::Duplex,
(true, false) => HostDirection::Input,
(false, true) => HostDirection::Output,
(false, false) => HostDirection::Duplex,
}
}
pub fn is_compatible_with(&self, requested: HostDirection) -> bool {
self.direction() == requested || self.direction() == HostDirection::Duplex
}
pub fn output_spec(&self) -> Result<PcmSpec> {
PcmSpec::f32(self.audio_outputs.max(1), self.timing.sample_rate_hz)
}
pub fn ports(&self) -> Vec<AsioPort> {
let inputs = (0..self.audio_inputs).map(|index| {
AsioPort::new(
Symbol::new(format!("{}/input_{index}", self.id)),
self.id.clone(),
format!("input_{index}"),
HostDirection::Input,
index,
)
});
let outputs = (0..self.audio_outputs).map(|index| {
AsioPort::new(
Symbol::new(format!("{}/output_{index}", self.id)),
self.id.clone(),
format!("output_{index}"),
HostDirection::Output,
index,
)
});
inputs.chain(outputs).collect()
}
}
impl AsioPort {
pub fn new(
id: Symbol,
driver: Symbol,
name: String,
direction: HostDirection,
index: usize,
) -> Self {
Self {
id,
driver,
name,
media: StreamMedia::Pcm,
direction,
index,
}
}
pub fn id(&self) -> &Symbol {
&self.id
}
pub fn driver(&self) -> &Symbol {
&self.driver
}
pub fn name(&self) -> &str {
&self.name
}
pub fn media(&self) -> StreamMedia {
self.media
}
pub fn direction(&self) -> HostDirection {
self.direction
}
pub fn index(&self) -> usize {
self.index
}
}
pub fn asio_sdk_build_requirements() -> Vec<&'static str> {
vec![
"Windows target",
"Steinberg ASIO SDK headers supplied outside this repository",
"vendor driver import library or COM registration",
"SIM stream-asio feature enabled explicitly",
]
}