use std::ffi::CString;
use alsa::seq;
use alsa::Direction;
use crate::error::{IoError, IoResult};
use crate::midi_input::MidiInput;
use crate::midi_message::MidiMessage;
use crate::midi_output::MidiOutput;
pub struct AlsaSeqBackend {
seq: seq::Seq,
}
impl AlsaSeqBackend {
pub fn new(name: &str) -> IoResult<Self> {
let cname = CString::new(name)
.map_err(|_| IoError::Init(format!("name contains nul byte: {name}")))?;
let seq = seq::Seq::open(None, Some(Direction::Capture), true)
.map_err(|e| IoError::Init(format!("alsa seq open: {e}")))?;
seq.set_client_name(&cname)
.map_err(|e| IoError::Init(format!("alsa seq set_client_name: {e}")))?;
let mut port_info = seq::PortInfo::empty()
.map_err(|e| IoError::Init(format!("alsa seq port_info: {e}")))?;
port_info.set_capability(seq::PortCap::READ | seq::PortCap::SUBS_READ);
port_info.set_type(seq::PortType::MIDI_GENERIC | seq::PortType::APPLICATION);
port_info.set_name(&cname);
seq.create_port(&port_info)
.map_err(|e| IoError::Init(format!("alsa seq create_port: {e}")))?;
Ok(Self { seq })
}
pub fn new_output(name: &str) -> IoResult<Self> {
let cname = CString::new(name)
.map_err(|_| IoError::Init(format!("name contains nul byte: {name}")))?;
let seq = seq::Seq::open(None, Some(Direction::Playback), true)
.map_err(|e| IoError::Init(format!("alsa seq output open: {e}")))?;
seq.set_client_name(&cname)
.map_err(|e| IoError::Init(format!("alsa seq set_client_name: {e}")))?;
let mut port_info = seq::PortInfo::empty()
.map_err(|e| IoError::Init(format!("alsa seq port_info: {e}")))?;
port_info.set_capability(seq::PortCap::WRITE | seq::PortCap::SUBS_WRITE);
port_info.set_type(seq::PortType::MIDI_GENERIC | seq::PortType::APPLICATION);
port_info.set_name(&cname);
seq.create_port(&port_info)
.map_err(|e| IoError::Init(format!("alsa seq create_port: {e}")))?;
Ok(Self { seq })
}
}
impl MidiInput for AlsaSeqBackend {
fn poll(&mut self) -> IoResult<Vec<MidiMessage>> {
let mut events = Vec::new();
let mut input = self.seq.input();
loop {
match input.event_input() {
Ok(event) => {
if let Some(msg) = alsa_event_to_midi(&event) {
events.push(msg);
}
}
Err(e) => {
if e.errno() == 11 {
break;
}
return Err(IoError::Backend(format!("alsa seq poll: {e}")));
}
}
}
Ok(events)
}
}
impl MidiOutput for AlsaSeqBackend {
fn send(&mut self, message: &MidiMessage) -> IoResult<()> {
let mut ev = midi_to_alsa_event(message);
self.seq
.event_output(&mut ev)
.map_err(|e| IoError::Backend(format!("alsa seq output: {e}")))?;
self.seq
.drain_output()
.map_err(|e| IoError::Backend(format!("alsa seq drain: {e}")))?;
Ok(())
}
}
fn alsa_event_to_midi(ev: &seq::Event) -> Option<MidiMessage> {
let ev_type = ev.get_type();
match ev_type {
seq::EventType::Noteon => {
let data = ev.get_data::<seq::EvNote>().unwrap_or_default();
let status = 0x90 | data.channel;
Some(MidiMessage::new(status, data.note, data.velocity))
}
seq::EventType::Noteoff => {
let data = ev.get_data::<seq::EvNote>().unwrap_or_default();
let status = 0x80 | data.channel;
Some(MidiMessage::new(status, data.note, data.velocity))
}
seq::EventType::Keypress => {
let data = ev.get_data::<seq::EvNote>().unwrap_or_default();
let status = 0xA0 | data.channel;
Some(MidiMessage::new(status, data.note, data.velocity))
}
seq::EventType::Controller => {
let data = ev.get_data::<seq::EvCtrl>().unwrap_or_default();
let status = 0xB0 | data.channel;
Some(MidiMessage::new(status, data.param as u8, data.value as u8))
}
seq::EventType::Pgmchange => {
let data = ev.get_data::<seq::EvCtrl>().unwrap_or_default();
let status = 0xC0 | data.channel;
Some(MidiMessage::new(status, data.value as u8, 0))
}
seq::EventType::Chanpress => {
let data = ev.get_data::<seq::EvCtrl>().unwrap_or_default();
let status = 0xD0 | data.channel;
Some(MidiMessage::new(status, data.value as u8, 0))
}
seq::EventType::Pitchbend => {
let data = ev.get_data::<seq::EvCtrl>().unwrap_or_default();
let val = data.value;
let lsb = (val & 0x7F) as u8;
let msb = ((val >> 7) & 0x7F) as u8;
let status = 0xE0 | data.channel;
Some(MidiMessage::new(status, lsb, msb))
}
seq::EventType::Songpos => {
let data = ev.get_data::<seq::EvCtrl>().unwrap_or_default();
let pos = data.value as u16;
let lsb = (pos & 0x7F) as u8;
let msb = ((pos >> 7) & 0x7F) as u8;
Some(MidiMessage::new(0xF2, lsb, msb))
}
seq::EventType::Clock => Some(MidiMessage::new(0xF8, 0, 0)),
seq::EventType::Start => Some(MidiMessage::new(0xFA, 0, 0)),
seq::EventType::Continue => Some(MidiMessage::new(0xFB, 0, 0)),
seq::EventType::Stop => Some(MidiMessage::new(0xFC, 0, 0)),
_ => None,
}
}
fn midi_to_alsa_event(msg: &MidiMessage) -> seq::Event<'_> {
let status = msg.status();
match status {
0xF8 => seq::Event::new(seq::EventType::Clock, &seq::EvNote::default()),
0xFA => seq::Event::new(seq::EventType::Start, &seq::EvNote::default()),
0xFB => seq::Event::new(seq::EventType::Continue, &seq::EvNote::default()),
0xFC => seq::Event::new(seq::EventType::Stop, &seq::EvNote::default()),
s if s & 0xF0 == 0x90 => {
let data = seq::EvNote {
channel: s & 0x0F,
note: msg.data1(),
velocity: msg.data2(),
..seq::EvNote::default()
};
seq::Event::new(seq::EventType::Noteon, &data)
}
s if s & 0xF0 == 0x80 => {
let data = seq::EvNote {
channel: s & 0x0F,
note: msg.data1(),
velocity: msg.data2(),
..seq::EvNote::default()
};
seq::Event::new(seq::EventType::Noteoff, &data)
}
_ => seq::Event::new(seq::EventType::None, &seq::EvNote::default()),
}
}