#![forbid(unsafe_code)]
pub mod metrics;
pub use metrics::{JackMetrics, MetricsSnapshot};
pub mod midi_util;
pub use midi_util::{SysExEvent, SysExReassembler, is_realtime, is_status, midi_message_len};
#[cfg(feature = "jack-backend")]
pub mod midi;
#[cfg(feature = "jack-backend")]
pub use midi::{JackMidiInput, JackMidiOutput, MIDI_ENTRY_MAX, MidiEntry};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JackTransportState {
Rolling,
Stopped,
Starting,
}
#[derive(Debug, Clone)]
pub struct JackTransportPosition {
pub frame: u64,
pub bpm: Option<f64>,
}
#[cfg(not(feature = "jack-backend"))]
mod stub {
use super::{JackTransportPosition, JackTransportState};
use oxisound_core::{InputStream, OutputStream, OxiSoundError, StreamConfig, StreamStats};
const STUB_MSG: &str = "oxisound-jack was built without the `jack-backend` feature. \
Enable it to link libjack2.";
#[derive(Debug)]
pub struct JackDevice;
impl JackDevice {
pub fn new(_client_name: &str) -> Result<Self, OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn sample_rate(&self) -> u32 {
0
}
pub fn buffer_size(&self) -> u32 {
0
}
pub fn open_output(
&self,
_config: StreamConfig,
) -> Result<JackOutputStream, OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn open_input(&self, _config: StreamConfig) -> Result<JackInputStream, OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn open_output_callback<F>(
&self,
_config: StreamConfig,
_callback: F,
) -> Result<JackCallbackOutputStream, OxiSoundError>
where
F: FnMut(&mut [f32]) + Send + 'static,
{
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn connect_ports(&self, _src: &str, _dst: &str) -> Result<(), OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn auto_connect_output(&self, _port_name: &str) -> Result<(), OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn transport_state(&self) -> JackTransportState {
JackTransportState::Stopped
}
pub fn transport_position(&self) -> JackTransportPosition {
JackTransportPosition {
frame: 0,
bpm: None,
}
}
pub fn set_freewheel(&self, _enabled: bool) -> Result<(), OxiSoundError> {
Err(OxiSoundError::Unsupported(
"JACK freewheel is not available in the jack 0.13.5 safe API (upstream TODO). \
Track: https://github.com/RustAudio/rust-jack/issues"
.into(),
))
}
}
pub struct JackOutputStream {
_private: (),
}
impl OutputStream for JackOutputStream {
fn write(&mut self, _samples: &[f32]) -> Result<(), OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
fn stats(&self) -> StreamStats {
StreamStats::default()
}
}
impl JackOutputStream {
pub fn connect_ports(&self, _src: &str, _dst: &str) -> Result<(), OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn auto_connect(&self) -> Result<(), OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn cpu_load(&self) -> f32 {
0.0
}
pub fn list_ports(&self) -> Vec<String> {
Vec::new()
}
pub fn list_input_ports(&self, _pattern: Option<&str>) -> Vec<String> {
Vec::new()
}
pub fn list_output_ports(&self, _pattern: Option<&str>) -> Vec<String> {
Vec::new()
}
pub fn current_sample_rate(&self) -> u32 {
0
}
pub fn xrun_count(&self) -> u64 {
0
}
pub fn current_buffer_size(&self) -> u32 {
0
}
}
pub struct JackInputStream {
_private: (),
}
impl InputStream for JackInputStream {
fn read(&mut self, _samples: &mut [f32]) -> Result<usize, OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
fn stats(&self) -> StreamStats {
StreamStats::default()
}
}
impl JackInputStream {
pub fn connect_ports(&self, _src: &str, _dst: &str) -> Result<(), OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn auto_connect(&self) -> Result<(), OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn cpu_load(&self) -> f32 {
0.0
}
pub fn list_ports(&self) -> Vec<String> {
Vec::new()
}
pub fn list_input_ports(&self, _pattern: Option<&str>) -> Vec<String> {
Vec::new()
}
pub fn list_output_ports(&self, _pattern: Option<&str>) -> Vec<String> {
Vec::new()
}
pub fn current_sample_rate(&self) -> u32 {
0
}
pub fn xrun_count(&self) -> u64 {
0
}
pub fn current_buffer_size(&self) -> u32 {
0
}
}
pub struct JackCallbackOutputStream {
_private: (),
}
impl JackCallbackOutputStream {
pub fn stats(&self) -> StreamStats {
StreamStats::default()
}
pub fn connect_ports(&self, _src: &str, _dst: &str) -> Result<(), OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn auto_connect(&self) -> Result<(), OxiSoundError> {
Err(OxiSoundError::Unsupported(STUB_MSG.into()))
}
pub fn cpu_load(&self) -> f32 {
0.0
}
pub fn list_ports(&self) -> Vec<String> {
Vec::new()
}
pub fn list_input_ports(&self, _pattern: Option<&str>) -> Vec<String> {
Vec::new()
}
pub fn list_output_ports(&self, _pattern: Option<&str>) -> Vec<String> {
Vec::new()
}
pub fn current_sample_rate(&self) -> u32 {
0
}
pub fn xrun_count(&self) -> u64 {
0
}
pub fn current_buffer_size(&self) -> u32 {
0
}
}
}
#[cfg(not(feature = "jack-backend"))]
pub use stub::{JackCallbackOutputStream, JackDevice, JackInputStream, JackOutputStream};
#[cfg(feature = "jack-backend")]
mod client;
#[cfg(feature = "jack-backend")]
pub use client::{JackCallbackOutputStream, JackDevice, JackInputStream, JackOutputStream};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn jack_transport_state_variants() {
let states = [
JackTransportState::Rolling,
JackTransportState::Stopped,
JackTransportState::Starting,
];
assert_eq!(states.len(), 3);
assert_ne!(states[0], states[1]);
assert_ne!(states[1], states[2]);
assert_ne!(states[0], states[2]);
}
#[test]
fn jack_transport_position_fields() {
let pos = JackTransportPosition {
frame: 48_000,
bpm: Some(120.0),
};
assert_eq!(pos.frame, 48_000);
assert!((pos.bpm.unwrap() - 120.0).abs() < f64::EPSILON);
}
#[test]
fn jack_transport_position_no_bpm() {
let pos = JackTransportPosition {
frame: 0,
bpm: None,
};
assert_eq!(pos.frame, 0);
assert!(pos.bpm.is_none());
}
#[cfg(not(feature = "jack-backend"))]
#[test]
fn jack_device_without_feature_returns_unsupported() {
let result = JackDevice::new("test-client");
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.kind(), "unsupported");
}
#[cfg(not(feature = "jack-backend"))]
#[test]
fn jack_device_stub_transport_state_is_stopped() {
assert_eq!(JackTransportState::Stopped, JackTransportState::Stopped);
}
#[cfg(not(feature = "jack-backend"))]
#[test]
fn jack_device_stub_set_freewheel_unsupported() {
let stub_msg = "JACK freewheel is not available in the jack 0.13.5 safe API (upstream TODO). \
Track: https://github.com/RustAudio/rust-jack/issues";
assert!(stub_msg.contains("0.13.5"));
}
#[cfg(not(feature = "jack-backend"))]
#[test]
fn jack_stream_has_cpu_load_and_port_methods() {
fn assert_output_stream_api(s: &JackOutputStream) {
assert_eq!(s.cpu_load(), 0.0);
assert!(s.list_ports().is_empty());
assert!(s.list_input_ports(None).is_empty());
assert!(s.list_output_ports(None).is_empty());
assert!(s.list_input_ports(Some("system:capture_")).is_empty());
assert!(s.list_output_ports(Some("system:playback_")).is_empty());
}
fn assert_input_stream_api(s: &JackInputStream) {
assert_eq!(s.cpu_load(), 0.0);
assert!(s.list_ports().is_empty());
assert!(s.list_input_ports(None).is_empty());
assert!(s.list_output_ports(None).is_empty());
}
fn assert_callback_stream_api(s: &JackCallbackOutputStream) {
assert_eq!(s.cpu_load(), 0.0);
assert!(s.list_ports().is_empty());
assert!(s.list_input_ports(None).is_empty());
assert!(s.list_output_ports(None).is_empty());
}
let _ = assert_output_stream_api as fn(&JackOutputStream);
let _ = assert_input_stream_api as fn(&JackInputStream);
let _ = assert_callback_stream_api as fn(&JackCallbackOutputStream);
}
#[cfg(not(feature = "jack-backend"))]
#[test]
fn jack_stub_port_methods_accept_pattern() {
fn check_output_pattern_param<F: Fn(&JackOutputStream)>(_f: F) {}
fn check_input_pattern_param<F: Fn(&JackInputStream)>(_f: F) {}
check_output_pattern_param(|s| {
let _ = s.list_input_ports(Some("system:"));
let _ = s.list_output_ports(Some("system:"));
});
check_input_pattern_param(|s| {
let _ = s.list_input_ports(Some("system:capture_"));
});
}
}