use std::ffi::CStr;
use alsa::seq;
use alsa::Direction;
use crate::error::{IoError, IoResult};
use crate::midi_backend::MidiBackend;
use crate::midi_message::MidiMessage;
pub struct AlsaSeqBackend {
seq: seq::Seq,
}
impl AlsaSeqBackend {
pub fn new(name: &str) -> IoResult<Self> {
let cname = CStr::from_bytes_until_nul(name.as_bytes())
.map_err(|_| IoError::Init(format!("name contains nul byte: {name}")))?;
let seq = seq::Seq::open(Some(cname), Some(Direction::Capture), true)
.map_err(|e| IoError::Init(format!("alsa seq open: {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 })
}
}
impl MidiBackend 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)
}
}
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,
}
}