use sim_kernel::{Error, Result, Symbol};
use sim_lib_audio_graph_core::Transport;
use sim_lib_stream_clock::Clock;
use sim_lib_stream_core::StreamMedia;
use sim_lib_stream_host::HostDirection;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct JackTiming {
sample_rate_hz: u32,
block_frames: usize,
input_latency_frames: u32,
output_latency_frames: u32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct JackTransportState {
rolling: bool,
sample_pos: u64,
tempo_bpm: f64,
ppq_pos: f64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct JackClient {
id: Symbol,
name: String,
timing: JackTiming,
audio_inputs: usize,
audio_outputs: usize,
midi_inputs: usize,
midi_outputs: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct JackPort {
id: Symbol,
client: Symbol,
name: String,
media: StreamMedia,
direction: HostDirection,
index: usize,
}
impl JackTiming {
pub fn new(
sample_rate_hz: u32,
block_frames: usize,
input_latency_frames: u32,
output_latency_frames: u32,
) -> Result<Self> {
if sample_rate_hz == 0 {
return Err(Error::Eval(
"JACK sample rate must be greater than zero".to_owned(),
));
}
if block_frames == 0 {
return Err(Error::Eval(
"JACK block size must be greater than zero".to_owned(),
));
}
Ok(Self {
sample_rate_hz,
block_frames,
input_latency_frames,
output_latency_frames,
})
}
pub fn pro_audio_default() -> Self {
Self::new(48_000, 128, 128, 128).expect("valid JACK timing")
}
pub fn sample_rate_hz(self) -> u32 {
self.sample_rate_hz
}
pub fn block_frames(self) -> usize {
self.block_frames
}
pub fn input_latency_frames(self) -> u32 {
self.input_latency_frames
}
pub fn output_latency_frames(self) -> u32 {
self.output_latency_frames
}
pub fn frame_clock(self) -> Result<Clock> {
Clock::frame(jack_clock_symbol(), u64::from(self.sample_rate_hz))
}
}
impl JackTransportState {
pub fn stopped(sample_pos: u64) -> Self {
Self {
rolling: false,
sample_pos,
tempo_bpm: 120.0,
ppq_pos: 0.0,
}
}
pub fn rolling(sample_pos: u64, tempo_bpm: f64, ppq_pos: f64) -> Result<Self> {
if !tempo_bpm.is_finite() || tempo_bpm <= 0.0 {
return Err(Error::Eval(
"JACK transport tempo must be finite and positive".to_owned(),
));
}
if !ppq_pos.is_finite() {
return Err(Error::Eval(
"JACK transport PPQ position must be finite".to_owned(),
));
}
Ok(Self {
rolling: true,
sample_pos,
tempo_bpm,
ppq_pos,
})
}
pub fn rolling_flag(self) -> bool {
self.rolling
}
pub fn sample_pos(self) -> u64 {
self.sample_pos
}
pub fn tempo_bpm(self) -> f64 {
self.tempo_bpm
}
pub fn ppq_pos(self) -> f64 {
self.ppq_pos
}
pub fn to_graph_transport(self) -> Transport {
Transport {
playing: self.rolling,
sample_pos: self.sample_pos,
tempo_bpm: self.tempo_bpm,
ppq_pos: self.ppq_pos,
}
}
}
impl JackClient {
pub fn new(
name: impl Into<String>,
timing: JackTiming,
audio_inputs: usize,
audio_outputs: usize,
midi_inputs: usize,
midi_outputs: usize,
) -> Result<Self> {
let name = name.into();
if name.is_empty() {
return Err(Error::Eval("JACK client name must not be empty".to_owned()));
}
if audio_inputs == 0 && audio_outputs == 0 {
return Err(Error::Eval(
"JACK client must register at least one audio port".to_owned(),
));
}
Ok(Self {
id: Symbol::new(format!("jack/{name}/client")),
name,
timing,
audio_inputs,
audio_outputs,
midi_inputs,
midi_outputs,
})
}
pub fn sim_default() -> Result<Self> {
Self::new("SIM", JackTiming::pro_audio_default(), 2, 2, 1, 1)
}
pub fn id(&self) -> &Symbol {
&self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn timing(&self) -> JackTiming {
self.timing
}
pub fn audio_inputs(&self) -> usize {
self.audio_inputs
}
pub fn audio_outputs(&self) -> usize {
self.audio_outputs
}
pub fn midi_inputs(&self) -> usize {
self.midi_inputs
}
pub fn midi_outputs(&self) -> usize {
self.midi_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 ports(&self) -> Vec<JackPort> {
let mut ports = Vec::new();
ports.extend(self.audio_ports(HostDirection::Input, self.audio_inputs, "audio_in"));
ports.extend(self.audio_ports(HostDirection::Output, self.audio_outputs, "audio_out"));
ports.extend(self.midi_ports(HostDirection::Input, self.midi_inputs, "midi_in"));
ports.extend(self.midi_ports(HostDirection::Output, self.midi_outputs, "midi_out"));
ports
}
fn audio_ports(&self, direction: HostDirection, count: usize, stem: &str) -> Vec<JackPort> {
self.numbered_ports(StreamMedia::Pcm, direction, count, stem)
}
fn midi_ports(&self, direction: HostDirection, count: usize, stem: &str) -> Vec<JackPort> {
self.numbered_ports(StreamMedia::Midi, direction, count, stem)
}
fn numbered_ports(
&self,
media: StreamMedia,
direction: HostDirection,
count: usize,
stem: &str,
) -> Vec<JackPort> {
(0..count)
.map(|index| {
let name = format!("{stem}_{index}");
JackPort::new(
Symbol::new(format!("jack/{}/{}", self.name, name)),
self.id.clone(),
name,
media,
direction,
index,
)
})
.collect()
}
}
impl JackPort {
pub fn new(
id: Symbol,
client: Symbol,
name: impl Into<String>,
media: StreamMedia,
direction: HostDirection,
index: usize,
) -> Self {
Self {
id,
client,
name: name.into(),
media,
direction,
index,
}
}
pub fn id(&self) -> &Symbol {
&self.id
}
pub fn client(&self) -> &Symbol {
&self.client
}
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 jack_clock_symbol() -> Symbol {
Symbol::qualified("clock", "jack")
}