#![cfg(feature = "jack-backend")]
use std::sync::{
Arc,
atomic::{AtomicU64, Ordering},
};
use ringbuf::{
HeapCons, HeapProd, HeapRb,
traits::{Consumer, Producer, Split},
};
use oxisound_core::OxiSoundError;
use crate::client::JackDevice;
use crate::midi_util::SysExReassembler;
pub const MIDI_ENTRY_MAX: usize = 16;
#[derive(Clone, Copy)]
pub struct MidiEntry {
pub time: u32,
pub len: u8,
pub data: [u8; MIDI_ENTRY_MAX],
}
pub struct JackMidiOutput {
producer: HeapProd<MidiEntry>,
frames_processed: Arc<AtomicU64>,
_active: jack::AsyncClient<(), JackMidiOutputHandler>,
}
impl JackMidiOutput {
pub fn send_raw(&mut self, time: u32, bytes: &[u8]) -> Result<(), OxiSoundError> {
for chunk in bytes.chunks(MIDI_ENTRY_MAX) {
let len = chunk.len();
let mut data = [0u8; MIDI_ENTRY_MAX];
data[..len].copy_from_slice(chunk);
let entry = MidiEntry {
time,
len: len as u8,
data,
};
self.producer
.try_push(entry)
.map_err(|_| OxiSoundError::Underrun("JACK MIDI output ring full".to_owned()))?;
}
Ok(())
}
pub fn frames_processed(&self) -> u64 {
self.frames_processed.load(Ordering::Relaxed)
}
}
impl oxisound_core::MidiOutput for JackMidiOutput {
fn send(&mut self, msg: &oxisound_core::MidiMessage) -> Result<(), OxiSoundError> {
self.send_raw(0, &msg.to_bytes())
}
}
struct JackMidiOutputHandler {
port: jack::Port<jack::MidiOut>,
consumer: HeapCons<MidiEntry>,
frames_processed: Arc<AtomicU64>,
}
impl jack::ProcessHandler for JackMidiOutputHandler {
fn process(&mut self, _client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control {
let mut writer = self.port.writer(ps);
while let Some(entry) = self.consumer.try_pop() {
let msg = jack::RawMidi {
time: entry.time,
bytes: &entry.data[..entry.len as usize],
};
let _ = writer.write(&msg);
}
self.frames_processed.fetch_add(1, Ordering::Relaxed);
jack::Control::Continue
}
}
pub struct JackMidiInput {
consumer: HeapCons<MidiEntry>,
frames_processed: Arc<AtomicU64>,
sysex: SysExReassembler,
_active: jack::AsyncClient<(), JackMidiInputHandler>,
}
impl JackMidiInput {
pub fn try_recv(&mut self) -> Option<MidiEntry> {
self.consumer.try_pop()
}
pub fn recv_with_sysex(&mut self) -> (Vec<MidiEntry>, Vec<crate::midi_util::SysExEvent>) {
let mut regular = Vec::new();
let mut sysex_events = Vec::new();
while let Some(entry) = self.consumer.try_pop() {
let bytes = &entry.data[..entry.len as usize];
if !bytes.is_empty() && (bytes[0] == 0xF0 || self.sysex.in_sysex()) {
sysex_events.extend(self.sysex.feed(bytes));
} else {
regular.push(entry);
}
}
(regular, sysex_events)
}
pub fn frames_processed(&self) -> u64 {
self.frames_processed.load(Ordering::Relaxed)
}
}
struct JackMidiInputHandler {
port: jack::Port<jack::MidiIn>,
producer: HeapProd<MidiEntry>,
frames_processed: Arc<AtomicU64>,
}
impl jack::ProcessHandler for JackMidiInputHandler {
fn process(&mut self, _client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control {
for msg in self.port.iter(ps) {
let len = msg.bytes.len().min(MIDI_ENTRY_MAX);
let mut data = [0u8; MIDI_ENTRY_MAX];
data[..len].copy_from_slice(&msg.bytes[..len]);
let entry = MidiEntry {
time: msg.time,
len: len as u8,
data,
};
let _ = self.producer.try_push(entry);
}
self.frames_processed.fetch_add(1, Ordering::Relaxed);
jack::Control::Continue
}
}
impl JackDevice {
pub fn open_midi_output(&self, port_name: &str) -> Result<JackMidiOutput, OxiSoundError> {
let (client, _status) = jack::Client::new(port_name, jack::ClientOptions::NO_START_SERVER)
.map_err(|e| OxiSoundError::Device(format!("JACK client open failed: {e}")))?;
let port = client
.register_port("midi_out", jack::MidiOut::default())
.map_err(|e| OxiSoundError::Device(format!("JACK port register failed: {e}")))?;
let capacity = 512usize;
let rb = HeapRb::<MidiEntry>::new(capacity);
let (producer, consumer) = rb.split();
let frames_processed = Arc::new(AtomicU64::new(0));
let handler = JackMidiOutputHandler {
port,
consumer,
frames_processed: Arc::clone(&frames_processed),
};
let active = client
.activate_async((), handler)
.map_err(|e| OxiSoundError::Device(format!("JACK activate_async failed: {e}")))?;
Ok(JackMidiOutput {
producer,
frames_processed,
_active: active,
})
}
pub fn open_midi_input(&self, port_name: &str) -> Result<JackMidiInput, OxiSoundError> {
let (client, _status) = jack::Client::new(port_name, jack::ClientOptions::NO_START_SERVER)
.map_err(|e| OxiSoundError::Device(format!("JACK client open failed: {e}")))?;
let port = client
.register_port("midi_in", jack::MidiIn::default())
.map_err(|e| OxiSoundError::Device(format!("JACK port register failed: {e}")))?;
let capacity = 512usize;
let rb = HeapRb::<MidiEntry>::new(capacity);
let (producer, consumer) = rb.split();
let frames_processed = Arc::new(AtomicU64::new(0));
let handler = JackMidiInputHandler {
port,
producer,
frames_processed: Arc::clone(&frames_processed),
};
let active = client
.activate_async((), handler)
.map_err(|e| OxiSoundError::Device(format!("JACK activate_async failed: {e}")))?;
Ok(JackMidiInput {
consumer,
frames_processed,
sysex: SysExReassembler::new(),
_active: active,
})
}
}
#[cfg(test)]
mod tests {
#[test]
#[ignore]
fn test_jack_midi_output_integration() {
}
#[test]
#[ignore]
fn test_jack_midi_input_integration() {
}
}