use std::{
fmt,
hash::{Hash, Hasher},
time::Duration,
};
use super::{stream::Stream, JACK_SAMPLE_FORMAT};
pub use crate::iter::{SupportedInputConfigs, SupportedOutputConfigs};
use crate::{
traits::DeviceTrait, BufferSize, ChannelCount, Data, DeviceDescription,
DeviceDescriptionBuilder, DeviceDirection, DeviceId, Error, ErrorKind, InputCallbackInfo,
OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, SupportedBufferSize,
SupportedStreamConfig, SupportedStreamConfigRange,
};
const DEFAULT_NUM_CHANNELS: ChannelCount = 2;
#[derive(Clone, Debug)]
pub struct Device {
name: String,
sample_rate: SampleRate,
buffer_size: SupportedBufferSize,
max_channels: ChannelCount,
direction: DeviceDirection,
start_server_automatically: bool,
connect_ports_automatically: bool,
}
impl Device {
fn new_device(
name: String,
connect_ports_automatically: bool,
start_server_automatically: bool,
direction: DeviceDirection,
) -> Result<Self, Error> {
let client_options = super::get_client_options(start_server_automatically);
let client = super::get_client(&name, client_options)?;
let port_pattern = match direction {
DeviceDirection::Input => "system:capture_.*",
DeviceDirection::Output => "system:playback_.*",
_ => {
return Err(Error::with_message(
ErrorKind::UnsupportedOperation,
format!("JACK does not support {direction:?} direction"),
))
}
};
let max_channels = client
.ports(Some(port_pattern), None, jack::PortFlags::empty())
.len()
.try_into()
.unwrap_or(DEFAULT_NUM_CHANNELS)
.max(DEFAULT_NUM_CHANNELS);
Ok(Self {
name: client.name().to_owned(),
sample_rate: client.sample_rate(),
buffer_size: SupportedBufferSize::Range {
min: client.buffer_size(),
max: client.buffer_size(),
},
max_channels,
direction,
start_server_automatically,
connect_ports_automatically,
})
}
fn id(&self) -> Result<DeviceId, Error> {
Ok(DeviceId::new(crate::platform::HostId::Jack, &self.name))
}
pub fn default_output_device(
name: &str,
connect_ports_automatically: bool,
start_server_automatically: bool,
) -> Result<Self, Error> {
let output_client_name = format!("{}_out", name);
Device::new_device(
output_client_name,
connect_ports_automatically,
start_server_automatically,
DeviceDirection::Output,
)
}
pub fn default_input_device(
name: &str,
connect_ports_automatically: bool,
start_server_automatically: bool,
) -> Result<Self, Error> {
let input_client_name = format!("{}_in", name);
Device::new_device(
input_client_name,
connect_ports_automatically,
start_server_automatically,
DeviceDirection::Input,
)
}
pub fn default_config(&self) -> Result<SupportedStreamConfig, Error> {
let channels = DEFAULT_NUM_CHANNELS;
let sample_rate = self.sample_rate;
let buffer_size = self.buffer_size;
let sample_format = JACK_SAMPLE_FORMAT;
Ok(SupportedStreamConfig {
channels,
sample_rate,
buffer_size,
sample_format,
})
}
pub fn supported_configs(&self) -> Vec<SupportedStreamConfigRange> {
let f = match self.default_config() {
Err(_) => return vec![],
Ok(f) => f,
};
(1..=self.max_channels)
.map(|channels| SupportedStreamConfigRange {
channels,
min_sample_rate: f.sample_rate,
max_sample_rate: f.sample_rate,
buffer_size: f.buffer_size,
sample_format: f.sample_format,
})
.collect()
}
pub fn is_input(&self) -> bool {
matches!(self.direction, DeviceDirection::Input)
}
pub fn is_output(&self) -> bool {
matches!(self.direction, DeviceDirection::Output)
}
}
impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type Stream = Stream;
fn description(&self) -> Result<DeviceDescription, Error> {
Ok(DeviceDescriptionBuilder::new(self.name.clone())
.direction(self.direction)
.build())
}
fn id(&self) -> Result<DeviceId, Error> {
Device::id(self)
}
fn supported_input_configs(&self) -> Result<Self::SupportedInputConfigs, Error> {
Ok(self.supported_configs().into_iter())
}
fn supported_output_configs(&self) -> Result<Self::SupportedOutputConfigs, Error> {
Ok(self.supported_configs().into_iter())
}
fn default_input_config(&self) -> Result<SupportedStreamConfig, Error> {
self.default_config()
}
fn default_output_config(&self) -> Result<SupportedStreamConfig, Error> {
self.default_config()
}
fn build_input_stream_raw<D, E>(
&self,
conf: StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, Error>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
if self.is_output() {
return Err(Error::with_message(
ErrorKind::UnsupportedOperation,
"Device does not support input",
));
}
crate::validate_stream_config(&conf)?;
if sample_format != JACK_SAMPLE_FORMAT {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!("Sample format {sample_format} is not supported; required format is {JACK_SAMPLE_FORMAT}"),
));
}
let name = self.name.clone();
let start_server_automatically = self.start_server_automatically;
let connect_ports_automatically = self.connect_ports_automatically;
let build = move || -> Result<Stream, Error> {
let client_options = super::get_client_options(start_server_automatically);
let client = super::get_client(&name, client_options)?;
if conf.sample_rate != client.sample_rate() {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Sample rate {} Hz does not match the server rate {} Hz",
conf.sample_rate,
client.sample_rate()
),
));
}
if let BufferSize::Fixed(size) = conf.buffer_size {
if size != client.buffer_size() {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Buffer size {size} does not match the server buffer size {}",
client.buffer_size()
),
));
}
}
let mut stream =
Stream::new_input(client, conf.channels, data_callback, error_callback)?;
if connect_ports_automatically {
stream.connect_to_system_inputs()?;
}
Ok(stream)
};
if let Some(dur) = timeout {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
tx.send(build()).ok();
});
match rx.recv_timeout(dur) {
Ok(result) => result,
Err(_) => Err(Error::with_message(
ErrorKind::DeviceNotAvailable,
"timed out waiting for JACK server",
)),
}
} else {
build()
}
}
fn build_output_stream_raw<D, E>(
&self,
conf: StreamConfig,
sample_format: SampleFormat,
data_callback: D,
error_callback: E,
timeout: Option<Duration>,
) -> Result<Self::Stream, Error>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
if self.is_input() {
return Err(Error::with_message(
ErrorKind::UnsupportedOperation,
"Device does not support output",
));
}
crate::validate_stream_config(&conf)?;
if sample_format != JACK_SAMPLE_FORMAT {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!("Sample format {sample_format} is not supported; required format is {JACK_SAMPLE_FORMAT}"),
));
}
let name = self.name.clone();
let start_server_automatically = self.start_server_automatically;
let connect_ports_automatically = self.connect_ports_automatically;
let build = move || -> Result<Stream, Error> {
let client_options = super::get_client_options(start_server_automatically);
let client = super::get_client(&name, client_options)?;
if conf.sample_rate != client.sample_rate() {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Sample rate {} Hz does not match the server rate {} Hz",
conf.sample_rate,
client.sample_rate()
),
));
}
if let BufferSize::Fixed(size) = conf.buffer_size {
if size != client.buffer_size() {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Buffer size {size} does not match the server buffer size {}",
client.buffer_size()
),
));
}
}
let mut stream =
Stream::new_output(client, conf.channels, data_callback, error_callback)?;
if connect_ports_automatically {
stream.connect_to_system_outputs()?;
}
Ok(stream)
};
if let Some(dur) = timeout {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
tx.send(build()).ok();
});
match rx.recv_timeout(dur) {
Ok(result) => result,
Err(_) => Err(Error::with_message(
ErrorKind::DeviceNotAvailable,
"timed out waiting for JACK server",
)),
}
} else {
build()
}
}
}
impl PartialEq for Device {
fn eq(&self, other: &Self) -> bool {
self.id().unwrap() == other.id().unwrap()
}
}
impl Eq for Device {}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = self.description().map_err(|_| fmt::Error)?;
f.write_str(desc.name())
}
}
impl Hash for Device {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id().unwrap().hash(state);
}
}