This commit is contained in:
zawz 2023-08-07 21:50:22 +02:00
commit 31a89f1665
15 changed files with 1324 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
Cargo.lock

21
Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "rmidimap"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
#midir = "0.9"
#midir = { path = "midir" }
regex = "1.8"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"
num-traits = "0.2"
num = "0.4"
lazy_static = "1.4"
clap = { version = "4.1", features = ["derive"] }
[target.'cfg(target_os = "linux")'.dependencies]
alsa = "0.7.0"
libc = "0.2.21"

11
src/cli.rs Normal file
View file

@ -0,0 +1,11 @@
use clap::{Parser,Subcommand};
use std::path::PathBuf;
/// Map MIDI signals to commands
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
pub struct Cli {
#[clap(value_parser)]
pub map_file: PathBuf,
}

48
src/config/device.rs Normal file
View file

@ -0,0 +1,48 @@
use crate::config::{RunConfig,EventConfig};
use crate::event::Event;
use serde::Deserialize;
#[derive(Deserialize,Debug,Clone)]
pub struct DeviceConfig {
pub name: Option<String>,
pub regex: Option<String>,
pub connect: Option<Vec<RunConfig>>,
pub disconnect: Option<Vec<RunConfig>>,
pub events: Option<Vec<EventConfig>>,
pub multiconnect: Option<bool>,
}
//impl DeviceConfig {
// fn connect(&self, port: &MidiInputPort) {
// let mut midi_in = MidiInput::new("midi inputs")?;
// midi_in.ignore(Ignore::None);
// let _conn_in = midi_in.connect(in_port, "midir-read-input", move |_, message, emap| {
// let event = event::Event::from(message);
// emap.run_event(&event).unwrap();
// }, eventmap)?;
// }
//}
impl DeviceConfig {
fn run_internal<'a, T>(&self, v: Option<T>) -> Result<Vec<std::process::ExitStatus>, std::io::Error>
where
T: IntoIterator<Item = &'a RunConfig>
{
let mut r = Vec::new();
if let Some(ev) = v {
for e in ev {
r.push( e.run(Event::new().gen_env())? ) ;
}
}
Ok(r)
}
pub fn run_connect(&self) -> Result<Vec<std::process::ExitStatus>, std::io::Error> {
self.run_internal(self.connect.as_ref())
}
pub fn run_disconnect(&self) -> Result<Vec<std::process::ExitStatus>, std::io::Error> {
self.run_internal(self.disconnect.as_ref())
}
}

26
src/config/event.rs Normal file
View file

@ -0,0 +1,26 @@
use crate::config::RunConfig;
use crate::event::EventType;
use crate::event::Event;
use crate::util::SmartSet;
use serde::Deserialize;
#[derive(Deserialize,Debug,Clone)]
pub struct EventConfig {
pub run: Vec<RunConfig>,
pub r#type: EventType,
pub channel: Option<SmartSet<u8>>,
pub id: Option<SmartSet<u8>>,
// pub channels: BTreeSet<u8>,
// pub ids: BTreeSet<u8>,
// TODO: rework for value conditions (for pitch) ?
//values: BTreeSet<u8>,
//values: Condition,
}
impl EventConfig {
pub fn matches(&self, event: &Event) -> bool {
//TODO: value conditions don't exist yet
true
}
}

14
src/config/mod.rs Normal file
View file

@ -0,0 +1,14 @@
pub mod event;
pub mod device;
pub mod run;
pub type DeviceConfig = device::DeviceConfig;
pub type EventConfig = event::EventConfig;
pub type RunConfig = run::RunConfig;
use serde::Deserialize;
#[derive(Deserialize,Clone,Debug)]
pub struct Config {
pub devices: Vec<DeviceConfig>,
}

29
src/config/run.rs Normal file
View file

@ -0,0 +1,29 @@
use std::collections::HashMap;
use std::process::Command;
use serde::{Serialize,Deserialize};
#[derive(Serialize,Deserialize,Debug,Clone)]
pub struct RunConfig {
pub args: Option<Vec<String>>,
pub shell: Option<String>,
pub envconf: Option<HashMap<String, String>>,
}
impl RunConfig {
pub fn run(&self, env: HashMap<&str, String>) -> Result<std::process::ExitStatus, std::io::Error> {
// TODO: proper error handling
if self.args.is_some() {
let args = self.args.as_ref().unwrap();
Command::new(&args[0]).args(&args[1..]).envs(env).status()
}
else if self.shell.is_some() {
let args = crate::run::cross_shell(self.shell.as_ref().unwrap());
Command::new(&args[0]).args(&args[1..]).envs(env).status()
}
else {
panic!("unexpected execution failure");
}
}
}

129
src/event.rs Normal file
View file

@ -0,0 +1,129 @@
use std::{collections::HashMap, time::SystemTime};
use std::fmt::Write;
use serde::{Serialize,Deserialize};
pub fn event_to_key(r#type: EventType, channel: u8, id: u8) -> u32 {
(r#type as u32)*256*256 + (channel as u32)*256 + (id as u32)
}
#[repr(u8)]
#[derive(Serialize,Deserialize,Debug,Copy,Clone)]
pub enum EventType {
Unknown = 0b0000,
NoteOff = 0b1000,
NoteOn = 0b1001,
PolyphonicKeyPressure = 0b1010,
Controller = 0b1011,
ProgramChange = 0b1100,
ChannelPressure = 0b1101,
PitchBend = 0b1110,
System = 0b1111,
}
impl EventType {
pub fn has_id(&self) -> bool {
match self {
EventType::Unknown | EventType::ProgramChange | EventType::PitchBend | EventType::System => false,
_ => true,
}
}
pub fn has_channel(&self) -> bool {
match self {
EventType::Unknown | EventType::ProgramChange | EventType::System => false,
_ => true,
}
}
}
#[derive(Debug)]
pub struct Event<'a> {
pub r#type: EventType,
pub channel: u8,
pub id: u8,
pub value: u16,
pub raw: &'a [u8],
pub timestamp: Option<SystemTime>,
}
impl From<u8> for EventType {
fn from(v: u8) -> Self {
if ! (0b1000..=0b1111).contains(&v) {
// not in defined space: unknown
EventType::Unknown
}
else {
// safe since all valid cases are defined
unsafe { std::mem::transmute(v) }
}
}
}
fn bytes_to_strhex(bytes: &[u8]) -> String {
let mut s = String::new();
for &byte in bytes {
write!(&mut s, "{:X} ", byte).expect("Unable to write");
}
s
}
impl<'a> Event<'a> {
pub fn new() -> Self {
Event {
r#type: EventType::Unknown,
channel: 0,
id: 0,
value: 0,
raw: &[],
timestamp: None,
}
}
pub fn key(&self) -> u32 {
event_to_key(self.r#type, self.channel, self.id)
}
pub fn gen_env(&self) -> HashMap<&str, String> {
let mut ret = HashMap::new();
//TODO: type?
ret.insert("channel", self.channel.to_string());
ret.insert("id", self.id.to_string());
ret.insert("value", self.value.to_string());
ret.insert("raw", bytes_to_strhex(self.raw));
ret.insert("timestamp", self.timestamp.unwrap_or(SystemTime::now()).duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs_f64().to_string());
ret
}
}
impl<'a> From<&'a [u8]> for Event<'a> {
fn from(v: &'a [u8]) -> Event<'a> {
let channel = v[0]%16;
let event_type = EventType::from(v[0]/16);
let (id, value) = match event_type {
EventType::PitchBend => {
(0, (v[2] as u16)*256 + (v[1] as u16) )
},
EventType::Unknown => {
match v.len() > 0 {
true => eprintln!("warn: unknown signal type: {}", v[0]),
false => eprintln!("warn: empty signal"),
};
(0,0)
}
EventType::System => (0,0),
EventType::PolyphonicKeyPressure |
EventType::ChannelPressure |
EventType::ProgramChange => {
todo!()
}
EventType::NoteOn | EventType::NoteOff | EventType::Controller => (v[1],(v[2] as u16)),
};
Event {
r#type: event_type,
channel,
id,
value,
raw: v,
timestamp: None,
}
}
}

112
src/eventmap.rs Normal file
View file

@ -0,0 +1,112 @@
use std::collections::HashMap;
use crate::config::{EventConfig,DeviceConfig};
use crate::event::{EventType,Event};
use crate::util::SmartSet;
use std::collections::BTreeSet;
use lazy_static::lazy_static;
lazy_static! {
static ref NOTE_DEFAULT_MAP: SmartSet<u8> = SmartSet {
set: BTreeSet::from((0..=127).collect::<BTreeSet<u8>>()),
};
static ref NOTE_DEFAULT_MAP_SIZE: usize = NOTE_DEFAULT_MAP.len();
static ref NULL_DEFAULT_MAP: SmartSet<u8> = SmartSet {
set: BTreeSet::from([0]),
};
static ref NULL_DEFAULT_MAP_SIZE: usize = NULL_DEFAULT_MAP.len();
static ref CHANNEL_DEFAULT_MAP: SmartSet<u8> = SmartSet {
set: BTreeSet::from((0..=15).collect::<BTreeSet<u8>>()),
};
static ref CHANNEL_DEFAULT_MAP_SIZE: usize = CHANNEL_DEFAULT_MAP.len();
}
#[derive(Debug,Default)]
pub struct EventMap<'a> {
//TODO: vec support
pub map: HashMap<u32, Vec<&'a EventConfig>>,
}
fn event_to_key(r#type: EventType, channel: u8, id: u8) -> u32 {
(r#type as u32)*256*256 + (channel as u32)*256 + (id as u32)
}
pub fn count_events(events: &[EventConfig]) -> usize {
events.iter().map(|x| {
let nchannel = match x.r#type.has_channel() {
true => x.channel.as_ref().map_or(*CHANNEL_DEFAULT_MAP_SIZE, |x| x.len()),
false => *CHANNEL_DEFAULT_MAP_SIZE,
};
let nid = match x.r#type.has_id() {
true => x.id.as_ref().map_or(*NOTE_DEFAULT_MAP_SIZE, |x| x.len()),
false => *NULL_DEFAULT_MAP_SIZE,
};
nchannel * nid
}).sum()
}
impl<'a> EventMap<'a> {
pub fn add_events(&mut self, events: &'a [EventConfig]) {
for event in events {
for &channel in match event.r#type.has_id() {
true => event.channel.as_ref().unwrap_or(&CHANNEL_DEFAULT_MAP),
false => &CHANNEL_DEFAULT_MAP,
} {
for &id in
match event.r#type.has_id() {
true => event.id.as_ref().unwrap_or(&NOTE_DEFAULT_MAP),
false => &NULL_DEFAULT_MAP,
} {
let key = event_to_key(event.r#type, channel, id);
if let Some(v) = self.map.get_mut(&key) {
v.push(event);
}
else {
self.map.insert(key, Vec::from([event]));
}
}
}
}
}
pub fn run_event(&self, event: &Event) -> Result<(), std::io::Error > {
let key = event_to_key(event.r#type, event.channel, event.id);
if let Some(v) = self.map.get(&key) {
for ev in v {
for r in &ev.run {
r.run(event.gen_env())?;
}
}
}
Ok(())
}
}
impl<'a> From<&'a [EventConfig]> for EventMap<'a> {
fn from(events: &'a [EventConfig]) -> Self {
// init hashmap with size for optimizing
let size = count_events(events);
let mut ret = EventMap { map: HashMap::with_capacity(size) };
// insert references
ret.add_events(events);
ret
}
}
impl<'a> From<&'a DeviceConfig> for EventMap<'a> {
fn from(device: &'a DeviceConfig) -> Self {
// init hashmap with size for optimizing
let size = count_events(device.events.as_ref().map(|x| &x[..]).unwrap_or(&[]));
//let size = events.iter().map(|x| x.channels.len()*x.ids.len() ).sum();
let mut ret = EventMap { map: HashMap::with_capacity(size) };
// insert references
if let Some(x) = device.events.as_ref() {
ret.add_events(x);
}
ret
}
}

85
src/main.rs Normal file
View file

@ -0,0 +1,85 @@
use std::sync::mpsc;
use std::error::Error;
use std::path::Path;
use std::thread;
use std::sync::Mutex;
use std::rc::Rc;
pub mod config;
pub mod run;
pub mod event;
pub mod eventmap;
pub mod midi;
pub mod util;
pub mod cli;
use clap::Parser;
use midi::{MidiHandler,MidiPortHandler};
use config::{Config,DeviceConfig};
use eventmap::EventMap;
use cli::Cli;
fn main() {
let c = Cli::parse();
match run(&c.map_file) {
Ok(_) => (),
Err(err) => println!("Error: {}", err)
}
}
fn run(filepath: &Path) -> Result<(), Box<dyn Error>> {
println!("Load file {}", filepath.to_str().unwrap());
let dat = std::fs::read( filepath )?;
let conf: Config = serde_yaml::from_slice(&dat)?;
let cfevmap: Vec<(&DeviceConfig, EventMap, Rc<Mutex<bool>>)> = conf.devices.iter().map(|x|
(x, eventmap::EventMap::from(x), Rc::new(Mutex::new(false)))
).collect();
let input = MidiHandler::new("rmidimap")?;
thread::scope(|s| -> Result<(), Box<dyn Error>> {
let (tdev,rdev) = mpsc::channel::<MidiPortHandler>();
let mut threads: Vec<(thread::ScopedJoinHandle<'_, ()>, mpsc::Sender<bool>)> = Vec::new();
let ports = input.ports()?;
// TODO: centralize connection process in one place
// TODO: "multiconnect=false" handling
for p in ports {
for (dev, eventmap, m) in &cfevmap {
if let Some(mut c) = input.try_connect(p.clone(), midi::PortFilter::from(*dev))? {
let (sts,srs) = mpsc::channel::<bool>();
let nsts = sts.clone();
let t = s.spawn( move || {
dev.run_connect().unwrap();
c.run(eventmap, (srs,nsts)).unwrap();
dev.run_disconnect().unwrap();
});
threads.push((t, sts));
break;
}
}
}
let _event_thread = s.spawn(|| {
let mut input = MidiHandler::new("rmidimap-event-watcher").unwrap();
input.device_events(tdev).unwrap();
});
loop {
let e = rdev.recv()?;
for (dev, eventmap, m) in &cfevmap {
if let Some(mut c) = input.try_connect(e.clone(), midi::PortFilter::from(*dev))? {
let (sts,srs) = mpsc::channel::<bool>();
let nsts = sts.clone();
let t = s.spawn( move || {
dev.run_connect().unwrap();
c.run(eventmap, (srs,nsts)).unwrap();
dev.run_disconnect().unwrap();
});
threads.push((t, sts));
break;
}
}
}
})?;
Ok(())
}

366
src/midi/alsa.rs Normal file
View file

@ -0,0 +1,366 @@
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<PortInfo> {
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<PortSubscribe>,
connect_addr: Option<Addr>,
stop_trigger: [i32;2],
}
impl Drop for MidiInputAlsa {
fn drop(&mut self) {
self.close_internal();
}
}
impl MidiInputAlsa {
fn init_trigger(&mut self) -> Result<(), Box<dyn Error>> {
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<i32, Box<dyn Error>> {
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<dyn Error>> {
if unsafe { self::libc::write(stop_trigger, &false as *const bool as *const _, mem::size_of::<bool>() as self::libc::size_t) } == -1 {
todo!()
}
Ok(())
}
fn alsa_input_handler<F, D>(&mut self, callback: F, mut userdata: D) -> Result<(), Box<dyn Error>>
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::<bool>() 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::<alsa::seq::Connect>() {
if c.sender == self.connect_addr.unwrap() {
break;
}
}
}
if (callback)(self, ev, &mut userdata) {
break;
}
}
Ok(())
}
fn handle_input_internal<F, D>(&mut self, callback: F, userdata: D) -> Result<(), Box<dyn Error>>
where F: Fn(Option<SystemTime>, &[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<Addr> for MidiInputAlsa {
fn new(client_name: &str) -> Result<Self, Box<dyn Error>> {
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<dyn Error>> {
self.close_internal();
Ok(())
}
fn ports_handle(&self) -> Vec<MidiPortHandler> {
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<MidiPort<Addr>> {
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<MidiPort<Addr>>, filter: PortFilter) -> Vec<MidiPort<Addr>> {
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<dyn Error>> {
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<MidiPortHandler>) -> Result<(), Box<dyn Error>> {
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::<alsa::seq::Addr>() {
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<dyn Error>> {
if unsafe { self::libc::write(self.stop_trigger[1], &false as *const bool as *const _, mem::size_of::<bool>() as self::libc::size_t) } == -1 {
todo!()
}
Ok(())
}
fn handle_input<F, D>(&mut self, callback: F, (rs, ts): (mpsc::Receiver<bool>, mpsc::Sender<bool>), userdata: D) -> Result<(), Box<dyn Error>>
where
F: Fn(Option<SystemTime>, &[u8], &mut D) + Send,
D: Send,
{
thread::scope( |sc| -> Result<(), Box<dyn Error>> {
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(())
})
}
}

217
src/midi/mod.rs Normal file
View file

@ -0,0 +1,217 @@
pub mod alsa;
use crate::midi::alsa::MidiInputAlsa;
extern crate libc;
use crate::config::DeviceConfig;
use crate::eventmap::EventMap;
use crate::event::Event;
use std::error::Error;
use std::time::SystemTime;
use std::sync::mpsc;
#[derive(Eq,PartialEq,Debug,Clone)]
pub struct MidiPort<T>{
pub name: String,
pub addr: T,
}
#[derive(Debug,Clone)]
pub enum PortFilter {
Name(String),
Regex(String),
Addr(MidiAddrHandler),
}
#[derive(Debug,Clone,Eq,PartialEq)]
pub enum MidiHandlerDriver {
ALSA,
}
#[derive(Debug,Clone,Eq,PartialEq)]
pub enum MidiAddrHandler {
ALSA(alsa::DeviceAddr),
}
#[derive(Debug,Clone,Eq,PartialEq)]
pub enum MidiPortHandler {
ALSA(MidiPort<alsa::DeviceAddr>),
}
pub enum MidiHandler {
ALSA(MidiInputAlsa),
}
impl From<MidiPortHandler> for MidiAddrHandler {
fn from(a: MidiPortHandler) -> Self {
match a {
MidiPortHandler::ALSA(p) => MidiAddrHandler::ALSA(p.addr),
}
}
}
impl From<&DeviceConfig> for PortFilter {
fn from(conf: &DeviceConfig) -> Self {
if conf.name.is_some() {
PortFilter::Name(conf.name.clone().unwrap_or(String::new()))
}
else {
todo!()
}
}
}
mod helper {
}
pub trait MidiInput<T> {
fn new(client_name: &str) -> Result<Self, Box<dyn Error>>
where Self: Sized;
fn close(self) -> Result<(), Box<dyn Error>>;
fn ports(&self) -> Vec<MidiPort<T>>;
fn ports_handle(&self) -> Vec<MidiPortHandler>;
fn filter_ports<'a>(&self, ports: Vec<MidiPort<T>>, filter: PortFilter) -> Vec<MidiPort<T>>;
fn connect(&mut self, port_addr: &T, port_name: &str) -> Result<(), Box<dyn Error>>;
fn device_events(&mut self, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Box<dyn Error>>;
}
pub trait MidiInputHandler {
fn signal_stop_input(&self) -> Result<(), Box<dyn Error>>;
fn handle_input<F, D>(&mut self, callback: F, rts: (mpsc::Receiver<bool>, mpsc::Sender<bool>), userdata: D) -> Result<(), Box<dyn Error>>
where
F: Fn(Option<SystemTime>, &[u8], &mut D) + Send,
D: Send,
;
}
macro_rules! handler_try_connect {
( $m:expr , $filter:expr, $port:expr, $( $handler:ident ),+ ) => {
match $m {
$(
MidiHandler::$handler(v) => {
match $port {
MidiPortHandler::$handler(_) => {
let maddr = MidiAddrHandler::from($port);
let portmap = v.ports();
let pv = v.filter_ports(portmap, PortFilter::Addr(maddr));
let pv = v.filter_ports(pv, $filter);
if pv.len() > 0 {
let port = &pv[0];
let mut h = MidiHandler::new_with_driver("rmidimap-handler", MidiHandlerDriver::$handler)?;
println!("Connect to device {}", port.name);
match &mut h {
MidiHandler::$handler(v) => {
v.connect(&port.addr, "rmidimap-handler").unwrap();
Ok(Some(h))
}
_ => panic!("unexpected midi driver failure"),
}
}
else {
Ok(None)
}
},
_ => panic!("unexpected midi driver failure"),
}
}
)*
}
};
}
macro_rules! handler_fcall {
( $m:expr , $fct:expr , $arg:expr , $( $handler:ident ),+ ) => {
match $m {
$(
MidiHandler::$handler(v) => $fct(v, $arg),
)*
}
};
}
impl MidiHandler {
pub fn new(name: &str) -> Result<Self, Box<dyn Error>> {
Self::new_with_driver(name, MidiHandlerDriver::ALSA)
}
pub fn new_with_driver(name: &str, driver: MidiHandlerDriver) -> Result<Self, Box<dyn Error>> {
match driver {
MidiHandlerDriver::ALSA => Ok(MidiHandler::ALSA(MidiInputAlsa::new(name)?)),
}
}
pub fn ports(&self) -> Result<Vec<MidiPortHandler>, Box<dyn Error>> {
handler_fcall!{
self, handle_port_list ,(),
ALSA
}
}
pub fn try_connect(&self, addr: MidiPortHandler, filter: PortFilter) -> Result<Option<MidiHandler>, Box<dyn Error>> {
let r: Result<Option<MidiHandler>, Box<dyn Error>> = handler_try_connect!{
self, filter, addr,
ALSA
};
r
}
pub fn run(&mut self, eventmap: &EventMap, (rs,ts): (mpsc::Receiver<bool>, mpsc::Sender<bool>)) -> Result<(), Box<dyn Error>> {
handler_fcall!{
self, handle_inputport ,(eventmap,(rs,ts)),
ALSA
}
}
pub fn stop(&self) -> Result<(), Box<dyn Error>> {
handler_fcall!{
self, handle_signal_stop, (),
ALSA
}
}
pub fn device_events(&mut self, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Box<dyn Error>> {
handler_fcall!{
self, device_events, ts,
ALSA
}
}
}
fn handle_port_list<T, A>(input: &T, _: ()) -> Result<Vec<MidiPortHandler>, Box<dyn Error>>
where T: MidiInput<A>
{
Ok(input.ports_handle())
}
fn handle_inputport<T>(input: &mut T, (eventmap, (rs, ts)): (&EventMap, (mpsc::Receiver<bool>, mpsc::Sender<bool>))) -> Result<(), Box<dyn Error>>
where T: MidiInputHandler
{
input.handle_input(|t,m,_| {
let mut event = Event::from(m);
event.timestamp = t;
eventmap.run_event(&event).unwrap();
}, (rs,ts), ())?;
Ok(())
}
fn handle_signal_stop<T>(input: &T, _: ()) -> Result<(), Box<dyn Error>>
where T: MidiInputHandler
{
input.signal_stop_input()
}
fn device_events<T, A>(input: &mut T, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Box<dyn Error>>
where T: MidiInput<A>
{
input.device_events(ts)
}

10
src/run.rs Normal file
View file

@ -0,0 +1,10 @@
pub fn cross_shell(cmd: &str) -> Vec<String> {
if cfg!(target_os = "windows") {
vec!("cmd", "/C", cmd)
} else {
vec!("sh", "-c", cmd)
}
.iter().map(
|x| x.to_string()
).collect()
}

234
src/util.rs Normal file
View file

@ -0,0 +1,234 @@
use std::collections::BTreeSet;
use std::str::FromStr;
use num::{Num,NumCast};
pub fn parse_int_set<T>(s: &str) -> Result<BTreeSet<T>, <T as std::str::FromStr>::Err>
where
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
{
let mut r: BTreeSet<T> = BTreeSet::new();
let parts: Vec<&str> = s.split(',').collect();
for p in parts {
if p.len() > 0 {
if let Some(sep) = p.find('-') {
let (p1,p2) = (&s[..sep], &s[sep+1..] );
let (low,high): (T,T) = ( p1.parse()?, p2.parse()? );
let (mut low,high) = match low <= high {
true => (low,high),
false => (high,low),
};
while low <= high {
r.insert(low);
low += T::one();
}
}
else {
r.insert(p.parse()?);
}
}
}
Ok(r)
}
#[derive(Debug,Clone)]
pub struct SmartSet<T>
where
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
{
pub set: BTreeSet<T>
}
impl<T> SmartSet<T>
where
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
{
pub fn new() -> Self {
Self {
set: BTreeSet::new(),
}
}
pub fn len(&self) -> usize {
self.set.len()
}
}
// impl<T> From<T> for SmartSet<T>
// where
// T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
// {
// fn from(i: T) -> Self {
// let mut r = SmartSet::new();
// r.set.insert(i);
// r
// }
// }
impl<T,U> From<U> for SmartSet<T>
where
T: Num+Ord+Copy+NumCast + std::str::FromStr + std::ops::AddAssign,
U: Num+Ord+Copy+num::ToPrimitive + std::str::FromStr + std::ops::AddAssign,
{
fn from(i: U) -> Self {
let mut r = SmartSet::<T>::new();
r.set.insert(num::NumCast::from(i).unwrap());
r
}
}
// impl<T,U> From<&[U]> for SmartSet<T>
// where
// T: Num+Ord+Copy+NumCast + std::str::FromStr + std::ops::AddAssign,
// U: Num+Ord+Copy+num::ToPrimitive + std::str::FromStr + std::ops::AddAssign,
// {
// fn from(i: &[U]) -> Self {
// let mut r = SmartSet::<T>::new();
// for v in i {
// r.set.insert(num::NumCast::from(v).unwrap());
// }
// r
// }
// }
impl<T> FromStr for SmartSet<T>
where
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
{
type Err = <T as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(SmartSet {
set: parse_int_set(s)?,
})
}
}
impl<T> IntoIterator for SmartSet<T>
where
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
{
type Item = T;
type IntoIter = std::collections::btree_set::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.set.into_iter()
}
}
impl<'a, T> IntoIterator for &'a SmartSet<T>
where
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
{
type Item = &'a T;
type IntoIter = std::collections::btree_set::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.set.iter()
}
}
use std::marker::PhantomData;
use std::fmt;
use serde::de::{self,Deserialize, Deserializer, Visitor};
struct SmartSetVisitor<T>
where
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
{
marker: PhantomData<fn() -> SmartSet<T>>
}
impl<T> SmartSetVisitor<T>
where
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
{
fn new() -> Self {
Self {
marker: PhantomData
}
}
}
macro_rules! visit_from {
( $( ($fct:ident, $type:ty) ),+ $(,)?) => {
$(
fn $fct<E>(self, value: $type) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(SmartSet::from(value))
}
)*
};
}
impl<'de, T> Visitor<'de> for SmartSetVisitor<T>
where
T: Num+Ord+Copy+NumCast + Deserialize<'de> + std::str::FromStr + std::ops::AddAssign + std::fmt::Debug,
<T as FromStr>::Err: std::fmt::Display,
{
type Value = SmartSet<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a set of integer")
}
visit_from!{
(visit_i8, i8),
(visit_i16, i16),
(visit_i32, i32),
(visit_i64, i64),
(visit_u8, u8),
(visit_u16, u16),
(visit_u32, u32),
(visit_u64, u64),
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
SmartSet::from_str(value).map_err(serde::de::Error::custom)
}
fn visit_seq<Z>(self, mut seq: Z) -> Result<Self::Value, Z::Error>
where
Z: serde::de::SeqAccess<'de>,
{
let _len = seq.size_hint();
let mut r: SmartSet<T> = SmartSet::new();
loop {
if let Ok(Some(value)) = seq.next_element::<String>() {
r.set.extend(&SmartSet::<T>::from_str(&value).map_err(serde::de::Error::custom)?.set);
}
else if let Some(value) = seq.next_element()? {
r.set.insert(value);
}
else {
break;
}
}
Ok(r)
}
}
// This is the trait that informs Serde how to deserialize MyMap.
impl<'de, T> Deserialize<'de> for SmartSet<T>
where
T: Num+Ord+Copy+NumCast + Deserialize<'de> + std::str::FromStr + std::ops::AddAssign + std::fmt::Debug,
<T as FromStr>::Err: std::fmt::Display,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(SmartSetVisitor::<T>::new())
}
}

20
test/vmpk.yml Normal file
View file

@ -0,0 +1,20 @@
devices:
- name: 'VMPK Output:out'
multiconnect: false
connect:
- args: [ "sh", "-c", "echo Hello world!" ]
disconnect:
- args: [ "sh", "-c", "echo Bye!" ]
events:
- type: Controller
run:
- args: [ "sh", "-c", "echo [$channel] Controller $id $value" ]
- type: NoteOff
run:
- args: [ "sh", "-c", "echo [$channel] NoteOff $id" ]
- type: NoteOn
run:
- args: [ "sh", "-c", "echo [$channel] NoteOn $id $value" ]
- type: PitchBend
run:
- args: [ "sh", "-c", "echo [$channel] PitchBend $value" ]