extern crate libc; extern crate alsa; use std::{mem, thread}; use std::ffi::{CString, CStr}; use std::time::SystemTime; use std::sync::mpsc; use crate::midi::{MidiInput,MidiInputHandler,MidiPort,PortFilter,MidiPortHandler,MidiAddrHandler}; use alsa::{Seq, Direction}; use alsa::seq::{ClientIter, PortIter, MidiEvent, PortInfo, PortSubscribe, Addr, QueueTempo, EventType, PortCap, PortType}; use std::error::Error; pub type DeviceAddr = alsa::seq::Addr; pub fn get_ports(s: &Seq, capability: PortCap) -> Vec { ClientIter::new(s).flat_map(|c| PortIter::new(s, c.get_client())) .filter(|p| p.get_capability().contains(capability)) .collect() } mod helpers { pub fn poll(fds: &mut [super::libc::pollfd], timeout: i32) -> i32 { unsafe { super::libc::poll(fds.as_mut_ptr(), fds.len() as super::libc::nfds_t, timeout) } } } pub struct MidiInputAlsa { seq: Seq, queue_id: i32, subscription: Option, connect_addr: Option, stop_trigger: [i32;2], } impl Drop for MidiInputAlsa { fn drop(&mut self) { self.close_internal(); } } impl MidiInputAlsa { fn init_trigger(&mut self) -> Result<(), Box> { let mut trigger_fds = [-1, -1]; if unsafe { self::libc::pipe(trigger_fds.as_mut_ptr()) } == -1 { todo!() } else { self.stop_trigger = trigger_fds; Ok(()) } } fn init_queue(&mut self) -> i32 { let mut queue_id = 0; // Create the input queue if !cfg!(feature = "avoid_timestamping") { queue_id = self.seq.alloc_named_queue(unsafe { CStr::from_bytes_with_nul_unchecked(b"midir queue\0") }).unwrap(); // Set arbitrary tempo (mm=100) and resolution (240) let qtempo = QueueTempo::empty().unwrap(); qtempo.set_tempo(600_000); qtempo.set_ppq(240); self.seq.set_queue_tempo(queue_id, &qtempo).unwrap(); let _ = self.seq.drain_output(); } queue_id } fn start_input_queue(&mut self, queue_id: i32) { if !cfg!(feature = "avoid_timestamping") { let _ = self.seq.control_queue(queue_id, EventType::Start, 0, None); let _ = self.seq.drain_output(); } } fn create_port(&mut self, port_name: &CStr, queue_id: i32) -> Result> { let mut pinfo = PortInfo::empty().unwrap(); // these functions are private, and the values are zeroed already by `empty()` //pinfo.set_client(0); //pinfo.set_port(0); pinfo.set_capability(PortCap::WRITE | PortCap::SUBS_WRITE); pinfo.set_type(PortType::MIDI_GENERIC | PortType::APPLICATION); pinfo.set_midi_channels(16); if !cfg!(feature = "avoid_timestamping") { pinfo.set_timestamping(true); pinfo.set_timestamp_real(true); pinfo.set_timestamp_queue(queue_id); } pinfo.set_name(port_name); match self.seq.create_port(&pinfo) { Ok(_) => Ok(pinfo.get_port()), Err(v) => Err(Box::new(v)) } } fn close_internal(&mut self) { if let Some(ref subscription) = self.subscription { let _ = self.seq.unsubscribe_port(subscription.get_sender(), subscription.get_dest()); } // Stop and free the input queue if !cfg!(feature = "avoid_timestamping") { let _ = self.seq.control_queue(self.queue_id, EventType::Stop, 0, None); let _ = self.seq.drain_output(); let _ = self.seq.free_queue(self.queue_id); } for fd in self.stop_trigger { if fd >= 0 { unsafe { self::libc::close(fd) }; } } } fn signal_stop_input_internal(stop_trigger: i32) -> Result<(), Box> { if unsafe { self::libc::write(stop_trigger, &false as *const bool as *const _, mem::size_of::() as self::libc::size_t) } == -1 { todo!() } Ok(()) } fn alsa_input_handler(&mut self, callback: F, mut userdata: D) -> Result<(), Box> where F: Fn(&Self, alsa::seq::Event, &mut D) -> bool { // fd defitions use self::alsa::PollDescriptors; use self::libc::pollfd; const INVALID_POLLFD: pollfd = pollfd { fd: -1, events: 0, revents: 0, }; let mut seq_input = self.seq.input(); // make poll fds let poll_desc_info = (&self.seq, Some(Direction::Capture)); let mut poll_fds = vec![INVALID_POLLFD; poll_desc_info.count()+1]; poll_fds[0] = pollfd { fd: self.stop_trigger[0], events: self::libc::POLLIN, revents: 0, }; poll_desc_info.fill(&mut poll_fds[1..]).unwrap(); loop { if let Ok(0) = seq_input.event_input_pending(true) { // No data pending: wait if helpers::poll(&mut poll_fds, -1) >= 0 { // Read stop event from triggerer if poll_fds[0].revents & self::libc::POLLIN != 0 { let mut pollread = false; let _res = unsafe { self::libc::read(poll_fds[0].fd, mem::transmute(&mut pollread), mem::size_of::() as self::libc::size_t) }; if pollread == false { break; } } } continue; } // get event let ev = seq_input.event_input()?; // handle disconnect event on watched port if ev.get_type() == EventType::PortUnsubscribed { if let Some(c) = ev.get_data::() { if c.sender == self.connect_addr.unwrap() { break; } } } if (callback)(self, ev, &mut userdata) { break; } } Ok(()) } fn handle_input_internal(&mut self, callback: F, userdata: D) -> Result<(), Box> where F: Fn(Option, &[u8], &mut D) + Send { let decoder = MidiEvent::new(0).unwrap(); decoder.enable_running_status(false); let message = vec!(); let buffer: [u8;12] = [0;12]; let continue_sysex = false; self.alsa_input_handler(|_, mut ev, (message, buffer, continue_sysex, userdata)| { if !*continue_sysex { message.clear() } let do_decode = match ev.get_type() { EventType::PortSubscribed | EventType::PortUnsubscribed | EventType::Qframe | EventType::Tick | EventType::Clock | EventType::Sensing => false, EventType::Sysex => { message.extend_from_slice(ev.get_ext().unwrap()); *continue_sysex = *message.last().unwrap() != 0xF7; false } _ => true }; // NOTE: SysEx messages have already been "decoded" at this point! if do_decode { let nbytes = decoder.decode(buffer, &mut ev).unwrap(); if nbytes > 0 { message.extend_from_slice(&buffer[0..nbytes+1]); } } if message.len() == 0 || *continue_sysex { return false; } let alsa_time = ev.get_time().unwrap(); let secs = alsa_time.as_secs(); let nsecs = alsa_time.subsec_nanos(); let timestamp = ( secs as u64 * 1_000_000 ) + ( nsecs as u64 / 1_000 ); //TODO: translate to SystemTime? (callback)(None, &message, userdata); false } , (message, buffer, continue_sysex, userdata))?; Ok(()) } } impl MidiInput for MidiInputAlsa { fn new(client_name: &str) -> Result> { let seq = match Seq::open(None, None, true) { Ok(s) => s, Err(_) => todo!(), }; let c_client_name = CString::new(client_name)?; seq.set_client_name(&c_client_name)?; Ok(MidiInputAlsa { seq: seq, queue_id: 0, subscription: None, connect_addr: None, stop_trigger: [-1,-1], }) } fn close(mut self) -> Result<(), Box> { self.close_internal(); Ok(()) } fn ports_handle(&self) -> Vec { get_ports(&self.seq, PortCap::READ | PortCap::SUBS_READ).iter().map(|x| { let cinfo = self.seq.get_any_client_info(x.get_client()).unwrap(); MidiPortHandler::ALSA( MidiPort{ name: cinfo.get_name().unwrap().to_string()+":"+x.get_name().unwrap(), addr: x.addr(), }) }).collect() } fn ports(&self) -> Vec> { get_ports(&self.seq, PortCap::READ | PortCap::SUBS_READ).iter().map(|x| { let cinfo = self.seq.get_any_client_info(x.get_client()).unwrap(); MidiPort { name: cinfo.get_name().unwrap().to_string()+":"+x.get_name().unwrap(), addr: x.addr(), } }).collect() } fn filter_ports(&self, mut ports: Vec>, filter: PortFilter) -> Vec> { ports.retain( |p| { match &filter { PortFilter::Name(s) => p.name.find(s).is_some(), PortFilter::Addr(MidiAddrHandler::ALSA(s)) => p.addr == *s, _ => todo!(), } } ); ports } fn connect(&mut self, port_addr: &Addr, port_name: &str) -> Result<(), Box> { let src_pinfo = self.seq.get_any_port_info(*port_addr)?; let queue_id = self.init_queue(); let c_port_name = CString::new(port_name)?; let vport = self.create_port(&c_port_name, queue_id)?; let sub = PortSubscribe::empty().unwrap(); sub.set_sender(src_pinfo.addr()); sub.set_dest(Addr { client: self.seq.client_id().unwrap(), port: vport}); self.seq.subscribe_port(&sub)?; self.subscription = Some(sub); self.init_trigger()?; self.connect_addr = Some(*port_addr); self.start_input_queue(queue_id); Ok(()) } fn device_events(&mut self, ts: mpsc::Sender) -> Result<(), Box> { let ports = self.ports(); let port = self.filter_ports(ports, PortFilter::Name("System:Announce".to_string())); self.connect(&port[0].addr, "rmidimap-alsa-announce")?; self.alsa_input_handler(|s, ev, _|{ // handle disconnect event on watched port match ev.get_type() { // EventType::PortStart | EventType::ClientStart | EventType::PortExit | EventType::ClientExit => { EventType::PortStart => { if let Some(a) = ev.get_data::() { let p = s.ports(); let pp = s.filter_ports(p, PortFilter::Addr( MidiAddrHandler::ALSA(a.clone()) )); if pp.len() > 0 { ts.send(MidiPortHandler::ALSA(pp[0].clone())).unwrap(); } }; false } _ => false, } }, ())?; self.close_internal(); Ok(()) } } impl MidiInputHandler for MidiInputAlsa { fn signal_stop_input(&self) -> Result<(), Box> { if unsafe { self::libc::write(self.stop_trigger[1], &false as *const bool as *const _, mem::size_of::() as self::libc::size_t) } == -1 { todo!() } Ok(()) } fn handle_input(&mut self, callback: F, (rs, ts): (mpsc::Receiver, mpsc::Sender), userdata: D) -> Result<(), Box> where F: Fn(Option, &[u8], &mut D) + Send, D: Send, { thread::scope( |sc| -> Result<(), Box> { let stop_trigger = self.stop_trigger[1]; let t = sc.spawn(move || { let userdata = userdata; self.handle_input_internal(callback, userdata).unwrap(); ts.send(false).unwrap(); }); match rs.recv()? { true => Self::signal_stop_input_internal(stop_trigger)?, false => () }; t.join().unwrap(); Ok(()) }) } }