diff --git a/Cargo.toml b/Cargo.toml index 8a75507..3ff5b7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,6 @@ 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" @@ -15,6 +13,8 @@ num-traits = "0.2" num = "0.4" lazy_static = "1.4" clap = { version = "4.1", features = ["derive"] } +thiserror = "1.0" +enum-display-derive = "0.1" [target.'cfg(target_os = "linux")'.dependencies] alsa = "0.7.0" diff --git a/src/cli.rs b/src/cli.rs index 1bf44d8..30c18b9 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,4 @@ -use clap::{Parser,Subcommand}; +use clap::Parser; use std::path::PathBuf; /// Map MIDI signals to commands diff --git a/src/config/device.rs b/src/config/device.rs index ad19899..eac0452 100644 --- a/src/config/device.rs +++ b/src/config/device.rs @@ -1,48 +1,71 @@ -use crate::config::{RunConfig,EventConfig}; use crate::event::Event; +use crate::util; +use crate::Error; +use super::{RunConfig,EventConfig}; +use super::serializer::DeviceConfigSerializer; -use serde::Deserialize; +#[derive(Debug,Clone)] +pub enum Identifier { + All, + Name(String), + Regex(regex::Regex), + Addr(String), +} -#[derive(Deserialize,Debug,Clone)] +#[derive(Debug,Clone)] pub struct DeviceConfig { - pub name: Option, - pub regex: Option, + pub identifier: Identifier, + pub max_connections: Option, pub connect: Option>, pub disconnect: Option>, pub events: Option>, - pub multiconnect: Option, } -//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) -> Result, std::io::Error> + fn run_internal<'a, T>(&self, v: Option) -> Result, Error> where T: IntoIterator { let mut r = Vec::new(); if let Some(ev) = v { for e in ev { - r.push( e.run(Event::new().gen_env())? ) ; + r.push( e.run(Event::new().make_env(None, false)?.to_map(e.envconf.as_ref()))? ) ; } } Ok(r) } - pub fn run_connect(&self) -> Result, std::io::Error> { + pub fn run_connect(&self) -> Result, Error> { self.run_internal(self.connect.as_ref()) } - pub fn run_disconnect(&self) -> Result, std::io::Error> { + pub fn run_disconnect(&self) -> Result, Error> { self.run_internal(self.disconnect.as_ref()) } -} \ No newline at end of file +} + +impl TryFrom for DeviceConfig { + type Error = crate::Error; + fn try_from(v: DeviceConfigSerializer) -> Result { + Ok(DeviceConfig { + identifier: { + if v.name.is_some() { + Identifier::Name(v.name.unwrap()) + } + else if v.regex.is_some() { + Identifier::Regex(regex::Regex::new(&v.regex.unwrap())?) + } + else if v.addr.is_some() { + Identifier::Addr(v.addr.unwrap()) + } + else { + Identifier::All + } + }, + max_connections: v.max_connections, + connect: util::map_opt_tryfrom(v.connect)?, + disconnect: util::map_opt_tryfrom(v.disconnect)?, + events: util::map_opt_tryfrom(v.events)?, + }) + } +} diff --git a/src/config/event.rs b/src/config/event.rs index ddffd09..e435562 100644 --- a/src/config/event.rs +++ b/src/config/event.rs @@ -1,26 +1,80 @@ -use crate::config::RunConfig; -use crate::event::EventType; -use crate::event::Event; -use crate::util::SmartSet; +use super::RunConfig; +use crate::event::{Event,EventType}; +use crate::util::{self, SmartSet, Range, Remapper}; -use serde::Deserialize; +use super::serializer::EventConfigSerializer; -#[derive(Deserialize,Debug,Clone)] +use std::collections::BTreeSet; + +use lazy_static::lazy_static; + +lazy_static! { + static ref ID_DEFAULT_MAP: SmartSet = SmartSet { + set: BTreeSet::from((0..=127).collect::>()), + }; + static ref NULL_DEFAULT_MAP: SmartSet = SmartSet { + set: BTreeSet::from([0]), + }; + static ref CHANNEL_DEFAULT_MAP: SmartSet = SmartSet { + set: BTreeSet::from((0..=15).collect::>()), + }; + static ref TRIGGER_NOTE_DEFAULT_MAP: SmartSet = SmartSet { + set: BTreeSet::from((1..=127).collect::>()), + }; + static ref TRIGGER_U8_DEFAULT_MAP: SmartSet = SmartSet { + set: BTreeSet::from((0..=127).collect::>()), + }; + static ref TRIGGER_U16_DEFAULT_MAP: SmartSet = SmartSet { + set: BTreeSet::from((0..=65535).collect::>()), + }; + static ref TRIGGER_NULL_DEFAULT_MAP: SmartSet = SmartSet { + set: BTreeSet::from([0]), + }; +} + +#[derive(Debug,Clone)] pub struct EventConfig { pub run: Vec, pub r#type: EventType, - pub channel: Option>, - pub id: Option>, - // pub channels: BTreeSet, - // pub ids: BTreeSet, - // TODO: rework for value conditions (for pitch) ? - //values: BTreeSet, - //values: Condition, + pub channel: SmartSet, + pub id: SmartSet, + pub remap: Option>, + pub float: bool, + pub value: Option>, } impl EventConfig { - pub fn matches(&self, event: &Event) -> bool { - //TODO: value conditions don't exist yet - true + pub fn match_value(&self, event: &Event) -> bool { + match &self.value { + Some(v) => v.set.contains(&event.value), + None => true, + } + } +} + +impl TryFrom for EventConfig { + type Error = crate::Error; + fn try_from(v: EventConfigSerializer) -> Result { + let r = EventConfig { + run: util::map_tryfrom(v.run)?, + r#type: v.r#type, + channel: match v.r#type.has_channel() { + true => v.channel.unwrap_or_else(|| CHANNEL_DEFAULT_MAP.clone()), + false => NULL_DEFAULT_MAP.clone(), + }, + id: match v.r#type.has_id() { + true => v.id.unwrap_or_else(|| ID_DEFAULT_MAP.clone()), + false => NULL_DEFAULT_MAP.clone(), + }, + remap: v.remap.map(|x| Remapper::new(Range::new(v.r#type.min_value() as f64, v.r#type.max_value() as f64), x )), + float: v.float.unwrap_or(false), + value: v.value, + }; + if let Some(remap) = &r.remap { + let range = remap.src(); + if range.start() < i64::MIN as f64 { return Err(Self::Error::RemapTooLow(range.start())) } + if range.end() > i64::MAX as f64 { return Err(Self::Error::RemapTooBig(range.end())) } + } + Ok(r) } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 8cece01..0fa8582 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,14 +1,45 @@ pub mod event; pub mod device; pub mod run; +pub mod serializer; + +use serializer::ConfigSerializer; + +use std::str::FromStr; + +use crate::util; pub type DeviceConfig = device::DeviceConfig; pub type EventConfig = event::EventConfig; pub type RunConfig = run::RunConfig; +pub type EventEnvMap = serializer::EventEnvSerializer; -use serde::Deserialize; - -#[derive(Deserialize,Clone,Debug)] +#[derive(Clone,Debug)] pub struct Config { pub devices: Vec, } + +impl TryFrom for Config { + type Error = crate::Error; + fn try_from(v: ConfigSerializer) -> Result { + Ok(Config { + devices: util::map_tryfrom(v.devices)?, + }) + } +} + +impl TryFrom<&[u8]> for Config { + type Error = crate::Error; + fn try_from(dat: &[u8]) -> Result { + let c: ConfigSerializer = serde_yaml::from_slice(dat)?; + Ok(Config::try_from(c)?) + } +} + +impl FromStr for Config { + type Err = crate::Error; + fn from_str(dat: &str) -> Result { + let c: ConfigSerializer = serde_yaml::from_str(dat)?; + Ok(Config::try_from(c)?) + } +} diff --git a/src/config/run.rs b/src/config/run.rs index 021f199..b499a00 100644 --- a/src/config/run.rs +++ b/src/config/run.rs @@ -1,29 +1,42 @@ use std::collections::HashMap; use std::process::Command; -use serde::{Serialize,Deserialize}; +use super::serializer::RunConfigSerializer; +use super::EventEnvMap; -#[derive(Serialize,Deserialize,Debug,Clone)] +#[derive(Debug,Clone)] pub struct RunConfig { - pub args: Option>, - pub shell: Option, - pub envconf: Option>, + pub args: Vec, + pub envconf: Option, } impl RunConfig { pub fn run(&self, env: HashMap<&str, String>) -> Result { - // 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"); + let mut c = Command::new(&self.args[0]); + if self.args.len() > 1 { + c.args(&self.args[1..]); } + c.envs(env).status() } } +impl TryFrom for RunConfig { + type Error = crate::Error; + fn try_from(v: RunConfigSerializer) -> Result { + let args = if v.args.is_some() { + v.args.unwrap() + } + else if v.cmd.is_some() { + crate::run::cross_shell(v.cmd.as_ref().unwrap()) + } + else { + return Err(crate::Error::from(crate::error::ConfigError::RunMissingArgs)); + }; + Ok( + RunConfig { + args, + envconf: v.envconf, + } + ) + } +} diff --git a/src/config/serializer/device.rs b/src/config/serializer/device.rs new file mode 100644 index 0000000..e8812ae --- /dev/null +++ b/src/config/serializer/device.rs @@ -0,0 +1,15 @@ +use super::{RunConfigSerializer,EventConfigSerializer}; + +use serde::Deserialize; + +#[derive(Deserialize,Debug,Clone)] +#[serde(deny_unknown_fields)] +pub struct DeviceConfigSerializer { + pub name: Option, + pub regex: Option, + pub addr: Option, + pub connect: Option>, + pub disconnect: Option>, + pub events: Option>, + pub max_connections: Option, +} diff --git a/src/config/serializer/event.rs b/src/config/serializer/event.rs new file mode 100644 index 0000000..a5dfd5a --- /dev/null +++ b/src/config/serializer/event.rs @@ -0,0 +1,17 @@ +use super::RunConfigSerializer; +use crate::event::EventType; +use crate::util::{SmartSet,Range}; + +use serde::Deserialize; + +#[derive(Deserialize,Debug,Clone)] +#[serde(deny_unknown_fields)] +pub struct EventConfigSerializer { + pub run: Vec, + pub r#type: EventType, + pub channel: Option>, + pub id: Option>, + pub remap: Option>, + pub float: Option, + pub value: Option>, +} diff --git a/src/config/serializer/eventenv.rs b/src/config/serializer/eventenv.rs new file mode 100644 index 0000000..757e331 --- /dev/null +++ b/src/config/serializer/eventenv.rs @@ -0,0 +1,13 @@ + +use serde::{Serialize,Deserialize}; + +#[derive(Serialize,Deserialize,Debug,Clone)] +#[serde(deny_unknown_fields)] +pub struct EventEnvSerializer { + pub channel: Option, + pub id: Option, + pub raw: Option, + pub rawvalue: Option, + pub timestamp: Option, + pub value: Option, +} \ No newline at end of file diff --git a/src/config/serializer/mod.rs b/src/config/serializer/mod.rs new file mode 100644 index 0000000..a7d62f2 --- /dev/null +++ b/src/config/serializer/mod.rs @@ -0,0 +1,17 @@ +pub mod event; +pub mod device; +pub mod run; +pub mod eventenv; + +pub type DeviceConfigSerializer = device::DeviceConfigSerializer; +pub type EventConfigSerializer = event::EventConfigSerializer; +pub type RunConfigSerializer = run::RunConfigSerializer; +pub type EventEnvSerializer = eventenv::EventEnvSerializer; + +use serde::Deserialize; + +#[derive(Deserialize,Clone,Debug)] +#[serde(deny_unknown_fields)] +pub struct ConfigSerializer { + pub devices: Vec, +} diff --git a/src/config/serializer/run.rs b/src/config/serializer/run.rs new file mode 100644 index 0000000..c06ff84 --- /dev/null +++ b/src/config/serializer/run.rs @@ -0,0 +1,11 @@ +use super::EventEnvSerializer; + +use serde::{Serialize,Deserialize}; + +#[derive(Serialize,Deserialize,Debug,Clone)] +#[serde(deny_unknown_fields)] +pub struct RunConfigSerializer { + pub args: Option>, + pub cmd: Option, + pub envconf: Option, +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..c1d5c75 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,50 @@ +use std::ffi::NulError; +use std::process::ExitStatus; +use std::sync::mpsc::RecvError; +use std::time::SystemTimeError; + +use crate::midi::alsa::AlsaError; + +use thiserror::Error; + +#[derive(Error,Debug)] +pub enum Error { + #[error(transparent)] + IO(#[from] std::io::Error), + #[error(transparent)] + Serde(#[from] serde_yaml::Error), + #[error(transparent)] + ALSA(#[from] AlsaError), + #[error(transparent)] + Recv(#[from] RecvError), + #[error(transparent)] + CStringNul(#[from] NulError), + #[error(transparent)] + Config(#[from] ConfigError), + #[error(transparent)] + Regex(#[from] regex::Error), + #[error(transparent)] + SystemTime(#[from] SystemTimeError), + #[error("execution failure")] + ExecStatus(ExitStatus), + #[error("remap value is too large. Maximum value is {}", i64::MAX)] + RemapTooBig(f64), + #[error("remap value is too low. Minimum value is {}", i64::MIN)] + RemapTooLow(f64), + #[error("pipe error")] + Pipe, + #[error("unknown error")] + Unknown, +} + +#[derive(Error,Debug)] +pub enum ConfigError { + #[error("run config is missing execution configuration, either \"args\" or \"cmd\" has to be specified")] + RunMissingArgs, +} + +impl From for Error { + fn from(value: alsa::Error) -> Self { + Self::from(AlsaError::from(value)) + } +} diff --git a/src/event.rs b/src/event.rs index 26c5777..712d86d 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,15 +1,34 @@ use std::{collections::HashMap, time::SystemTime}; -use std::fmt::Write; +use std::fmt::{Write,Display}; + +use crate::config::EventEnvMap; +use crate::util::Remapper; +use crate::Error; use serde::{Serialize,Deserialize}; + +use lazy_static::lazy_static; + +lazy_static! { + static ref EVENT_ENV_DEFAULT: EventEnvRef<'static> = EventEnvRef { + channel: "channel", + id: "id", + raw: "raw", + rawvalue: "rawvalue", + timestamp: "timestamp", + value: "value", + }; +} + 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)] +#[derive(Serialize,Deserialize,Debug,Copy,Clone,Default,Display)] pub enum EventType { + #[default] Unknown = 0b0000, NoteOff = 0b1000, NoteOn = 0b1001, @@ -23,20 +42,42 @@ pub enum EventType { impl EventType { pub fn has_id(&self) -> bool { - match self { - EventType::Unknown | EventType::ProgramChange | EventType::PitchBend | EventType::System => false, - _ => true, - } + !matches!(self, EventType::Unknown | EventType::ChannelPressure | EventType::PitchBend | EventType::System ) } pub fn has_channel(&self) -> bool { + !matches!(self, EventType::Unknown | EventType::System ) + } + pub fn min_value(&self) -> i32 { match self { - EventType::Unknown | EventType::ProgramChange | EventType::System => false, - _ => true, + EventType::NoteOff | + EventType::NoteOn | + EventType::Controller + => 0, + EventType::PolyphonicKeyPressure | + EventType::ChannelPressure + => 127, + EventType::PitchBend + => 0, + _ => 0, + } + } + pub fn max_value(&self) -> i32 { + match self { + EventType::NoteOff | + EventType::NoteOn | + EventType::Controller + => 127, + EventType::PolyphonicKeyPressure | + EventType::ChannelPressure + => 127, + EventType::PitchBend + => 32767, + _ => 0, } } } -#[derive(Debug)] +#[derive(Debug,Default)] pub struct Event<'a> { pub r#type: EventType, pub channel: u8, @@ -46,6 +87,25 @@ pub struct Event<'a> { pub timestamp: Option, } +pub struct EventEnv { + pub channel: String, + pub id: String, + pub raw: String, + pub rawvalue: String, + pub timestamp: String, + pub value: String, +} + +#[derive(Clone,Debug)] +struct EventEnvRef<'a> { + pub channel: &'a str, + pub id: &'a str, + pub raw: &'a str, + pub rawvalue: &'a str, + pub timestamp: &'a str, + pub value: &'a str, +} + impl From for EventType { fn from(v: u8) -> Self { if ! (0b1000..=0b1111).contains(&v) { @@ -62,7 +122,7 @@ impl From for EventType { 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"); + write!(&mut s, "{:X} ", byte).expect("unexpected write error"); } s } @@ -82,40 +142,44 @@ impl<'a> Event<'a> { 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 + + pub fn make_env(&self, remap: Option<&Remapper>, float: bool) -> Result + { + Ok(EventEnv { + channel: self.channel.to_string(), + id: self.id.to_string(), + rawvalue: self.value.to_string(), + raw: bytes_to_strhex(self.raw), + timestamp: self.timestamp.unwrap_or(SystemTime::now()).duration_since(SystemTime::UNIX_EPOCH)?.as_secs_f64().to_string(), + value: match (remap,float) { + (Some(r),true) => r.remap(self.value as f64).to_string(), + (Some(r),false) => r.remap_to::(self.value as f64).unwrap().to_string(), + _ => self.value.to_string(), + } + }) } } impl<'a> From<&'a [u8]> for Event<'a> { fn from(v: &'a [u8]) -> Event<'a> { - let channel = v[0]%16; + if v.is_empty() { + eprintln!("warning: empty signal"); + return Default::default(); + } let event_type = EventType::from(v[0]/16); + let channel = if event_type.has_channel() { v[0]%16 } else { 0 }; 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"), - }; + eprintln!("warning: unknown signal type: {}", v[0]); (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)), + EventType::ChannelPressure => (0,v[1] as u16), + EventType::ProgramChange => (v[1],0), + EventType::NoteOn | EventType::NoteOff | EventType::PolyphonicKeyPressure | EventType::Controller => (v[1],(v[2] as u16)), }; Event { r#type: event_type, @@ -127,3 +191,29 @@ impl<'a> From<&'a [u8]> for Event<'a> { } } } + +impl EventEnv { + pub fn to_map(self, m: Option<&EventEnvMap>) -> HashMap<&str,String> { + let mut r = HashMap::new(); + let keys: EventEnvRef = match m { + Some(v) => { + EventEnvRef { + channel: v.channel.as_ref().map(|x| &x[..]).unwrap_or(EVENT_ENV_DEFAULT.channel), + id: v.id.as_ref().map(|x| &x[..]).unwrap_or(EVENT_ENV_DEFAULT.id), + raw: v.raw.as_ref().map(|x| &x[..]).unwrap_or(EVENT_ENV_DEFAULT.raw), + rawvalue: v.rawvalue.as_ref().map(|x| &x[..]).unwrap_or(EVENT_ENV_DEFAULT.rawvalue), + timestamp: v.timestamp.as_ref().map(|x| &x[..]).unwrap_or(EVENT_ENV_DEFAULT.timestamp), + value: v.value.as_ref().map(|x| &x[..]).unwrap_or(EVENT_ENV_DEFAULT.value), + } + } + _ => EVENT_ENV_DEFAULT.clone(), + }; + r.insert(keys.channel, self.channel); + r.insert(keys.id, self.id); + r.insert(keys.raw, self.raw); + r.insert(keys.rawvalue, self.rawvalue); + r.insert(keys.timestamp, self.timestamp); + r.insert(keys.value, self.value); + r + } +} diff --git a/src/eventmap.rs b/src/eventmap.rs index baff5de..a2ad23d 100644 --- a/src/eventmap.rs +++ b/src/eventmap.rs @@ -2,30 +2,10 @@ 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 = SmartSet { - set: BTreeSet::from((0..=127).collect::>()), - }; - static ref NOTE_DEFAULT_MAP_SIZE: usize = NOTE_DEFAULT_MAP.len(); - static ref NULL_DEFAULT_MAP: SmartSet = SmartSet { - set: BTreeSet::from([0]), - }; - static ref NULL_DEFAULT_MAP_SIZE: usize = NULL_DEFAULT_MAP.len(); - static ref CHANNEL_DEFAULT_MAP: SmartSet = SmartSet { - set: BTreeSet::from((0..=15).collect::>()), - }; - static ref CHANNEL_DEFAULT_MAP_SIZE: usize = CHANNEL_DEFAULT_MAP.len(); -} +use crate::Error; #[derive(Debug,Default)] pub struct EventMap<'a> { - //TODO: vec support pub map: HashMap>, } @@ -35,14 +15,8 @@ fn event_to_key(r#type: EventType, channel: u8, id: u8) -> 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, - }; + let nchannel = x.channel.len(); + let nid = x.id.len(); nchannel * nid }).sum() } @@ -50,15 +24,8 @@ pub fn count_events(events: &[EventConfig]) -> usize { 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, - } { + for &channel in &event.channel { + for &id in &event.id { let key = event_to_key(event.r#type, channel, id); if let Some(v) = self.map.get_mut(&key) { v.push(event); @@ -71,12 +38,14 @@ impl<'a> EventMap<'a> { } } - pub fn run_event(&self, event: &Event) -> Result<(), std::io::Error > { + pub fn run_event(&self, event: &Event) -> Result<(), 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())?; + if ev.match_value(event) { + for r in &ev.run { + r.run(event.make_env(ev.remap.as_ref(), ev.float )?.to_map(r.envconf.as_ref()))?; + } } } } @@ -103,7 +72,7 @@ impl<'a> From<&'a DeviceConfig> for EventMap<'a> { //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() { + if let Some(x) = device.events.as_ref() { ret.add_events(x); } ret diff --git a/src/main.rs b/src/main.rs index 13b4f1f..a32cb20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,5 @@ -use std::sync::mpsc; -use std::error::Error; -use std::path::Path; -use std::thread; -use std::sync::Mutex; -use std::rc::Rc; +#[macro_use] +extern crate enum_display_derive; pub mod config; pub mod run; @@ -12,74 +8,28 @@ pub mod eventmap; pub mod midi; pub mod util; pub mod cli; +pub mod error; + +type Error = error::Error; + +use std::path::Path; use clap::Parser; -use midi::{MidiHandler,MidiPortHandler}; -use config::{Config,DeviceConfig}; -use eventmap::EventMap; +use config::Config; use cli::Cli; fn main() { let c = Cli::parse(); - match run(&c.map_file) { + match run_file(&c.map_file) { Ok(_) => (), Err(err) => println!("Error: {}", err) } } -fn run(filepath: &Path) -> Result<(), Box> { +fn run_file(filepath: &Path) -> Result<(), 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>)> = 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> { - let (tdev,rdev) = mpsc::channel::(); - let mut threads: Vec<(thread::ScopedJoinHandle<'_, ()>, mpsc::Sender)> = 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::(); - 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::(); - 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(()) + let conf = Config::try_from(&dat[..])?; + run::run_config(&conf) } diff --git a/src/midi/alsa.rs b/src/midi/alsa.rs index b699881..aec0497 100644 --- a/src/midi/alsa.rs +++ b/src/midi/alsa.rs @@ -7,11 +7,11 @@ use std::time::SystemTime; use std::sync::mpsc; use crate::midi::{MidiInput,MidiInputHandler,MidiPort,PortFilter,MidiPortHandler,MidiAddrHandler}; +use crate::error::Error; use alsa::{Seq, Direction}; use alsa::seq::{ClientIter, PortIter, MidiEvent, PortInfo, PortSubscribe, Addr, QueueTempo, EventType, PortCap, PortType}; - -use std::error::Error; +use thiserror::Error; pub type DeviceAddr = alsa::seq::Addr; @@ -27,11 +27,20 @@ mod helpers { } } +#[derive(Error,Debug)] +pub enum AlsaError { + #[error(transparent)] + ALSA(#[from] alsa::Error), + #[error("alsa decode error")] + Decode, +} + pub struct MidiInputAlsa { seq: Seq, queue_id: i32, subscription: Option, connect_addr: Option, + start_time: Option, stop_trigger: [i32;2], } @@ -42,10 +51,10 @@ impl Drop for MidiInputAlsa { } impl MidiInputAlsa { - fn init_trigger(&mut self) -> Result<(), Box> { + fn init_trigger(&mut self) -> Result<(), Error> { let mut trigger_fds = [-1, -1]; if unsafe { self::libc::pipe(trigger_fds.as_mut_ptr()) } == -1 { - todo!() + Err(Error::Pipe) } else { self.stop_trigger = trigger_fds; Ok(()) @@ -53,20 +62,20 @@ impl MidiInputAlsa { } - fn init_queue(&mut self) -> i32 { + fn init_queue(&mut self) -> Result { 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(); + queue_id = self.seq.alloc_named_queue(unsafe { CStr::from_bytes_with_nul_unchecked(b"midir queue\0") })?; // Set arbitrary tempo (mm=100) and resolution (240) - let qtempo = QueueTempo::empty().unwrap(); + let qtempo = QueueTempo::empty()?; qtempo.set_tempo(600_000); qtempo.set_ppq(240); - self.seq.set_queue_tempo(queue_id, &qtempo).unwrap(); + self.seq.set_queue_tempo(queue_id, &qtempo)?; let _ = self.seq.drain_output(); } - - queue_id + + Ok(queue_id) } fn start_input_queue(&mut self, queue_id: i32) { @@ -76,34 +85,34 @@ impl MidiInputAlsa { } } - fn create_port(&mut self, port_name: &CStr, queue_id: i32) -> Result> { - let mut pinfo = PortInfo::empty().unwrap(); + fn create_port(&mut self, port_name: &CStr, queue_id: i32) -> Result { + let mut pinfo = PortInfo::empty()?; // 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)) + Err(v) => Err(AlsaError::from(v)), } } - fn close_internal(&mut self) + 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); @@ -118,15 +127,17 @@ impl MidiInputAlsa { } } - fn signal_stop_input_internal(stop_trigger: i32) -> Result<(), Box> { + fn signal_stop_input_internal(stop_trigger: i32) -> Result<(), Error> { if unsafe { self::libc::write(stop_trigger, &false as *const bool as *const _, mem::size_of::() as self::libc::size_t) } == -1 { - todo!() + Err(Error::Pipe) + } + else { + Ok(()) } - Ok(()) } - fn alsa_input_handler(&mut self, callback: F, mut userdata: D) -> Result<(), Box> - where F: Fn(&Self, alsa::seq::Event, &mut D) -> bool { + fn alsa_input_handler(&mut self, callback: F, mut userdata: D) -> Result<(), Error> + where F: Fn(&Self, alsa::seq::Event, &mut D) -> Result { // fd defitions use self::alsa::PollDescriptors; use self::libc::pollfd; @@ -146,7 +157,7 @@ impl MidiInputAlsa { events: self::libc::POLLIN, revents: 0, }; - poll_desc_info.fill(&mut poll_fds[1..]).unwrap(); + poll_desc_info.fill(&mut poll_fds[1..])?; loop { if let Ok(0) = seq_input.event_input_pending(true) { @@ -154,9 +165,9 @@ impl MidiInputAlsa { 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 { + let mut pollread = false; + let _res = unsafe { self::libc::read(poll_fds[0].fd, &mut pollread as *mut bool as *mut libc::c_void, mem::size_of::() as self::libc::size_t) }; + if !pollread { break; } } @@ -175,28 +186,38 @@ impl MidiInputAlsa { } } - if (callback)(self, ev, &mut userdata) { - break; + match (callback)(self, ev, &mut userdata) { + Ok(true) => break, + Ok(false) => (), + Err(e) => { + eprintln!("ALSA CALLBACK ERROR: {:?}", e); + eprintln!("continuing execution"); + }, } } Ok(()) } - fn handle_input_internal(&mut self, callback: F, userdata: D) -> Result<(), Box> + fn handle_input_internal(&mut self, callback: F, userdata: D) -> Result<(), Error> where F: Fn(Option, &[u8], &mut D) + Send { - let decoder = MidiEvent::new(0).unwrap(); + let decoder = MidiEvent::new(0)?; decoder.enable_running_status(false); let message = vec!(); let buffer: [u8;12] = [0;12]; let continue_sysex = false; + let ts = match self.start_time.as_ref() { + Some(v) => *v, + _ => SystemTime::now(), + }; + 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::PortUnsubscribed | EventType::Qframe | EventType::Tick | EventType::Clock | @@ -211,21 +232,17 @@ impl MidiInputAlsa { // NOTE: SysEx messages have already been "decoded" at this point! if do_decode { - let nbytes = decoder.decode(buffer, &mut ev).unwrap(); + let nbytes = decoder.decode(buffer, &mut ev).map_err(|_| AlsaError::Decode)?; if nbytes > 0 { message.extend_from_slice(&buffer[0..nbytes+1]); } } - if message.len() == 0 || *continue_sysex { return false; } + if message.is_empty() || *continue_sysex { return Ok(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 + let ts: Option = ev.get_time().map(|v| ts+v); + (callback)(ts, message, userdata); + Ok(false) } , (message, buffer, continue_sysex, userdata))?; Ok(()) @@ -233,47 +250,46 @@ impl MidiInputAlsa { } impl MidiInput for MidiInputAlsa { - fn new(client_name: &str) -> Result> { - let seq = match Seq::open(None, None, true) { - Ok(s) => s, - Err(_) => todo!(), - }; - + fn new(client_name: &str) -> Result { + let seq = Seq::open(None, None, true)?; + let c_client_name = CString::new(client_name)?; seq.set_client_name(&c_client_name)?; - + Ok(MidiInputAlsa { - seq: seq, + seq, queue_id: 0, subscription: None, connect_addr: None, + start_time: None, stop_trigger: [-1,-1], }) } - fn close(mut self) -> Result<(), Box> { + fn close(mut self) -> Result<(), Error> { 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(), + fn ports_handle(&self) -> Result, Error> { + get_ports(&self.seq, PortCap::READ | PortCap::SUBS_READ).iter().map( + |x| -> Result { + let cinfo = self.seq.get_any_client_info(x.get_client())?; + Ok(MidiPortHandler::ALSA( MidiPort{ + name: cinfo.get_name()?.to_string()+":"+x.get_name()?, 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(), + fn ports(&self) -> Result>, Error> { + get_ports(&self.seq, PortCap::READ | PortCap::SUBS_READ).iter().map(|x| -> Result, Error> { + let cinfo = self.seq.get_any_client_info(x.get_client())?; + Ok(MidiPort { + name: cinfo.get_name()?.to_string()+":"+x.get_name()?, addr: x.addr(), - } + }) }).collect() } @@ -281,34 +297,36 @@ impl MidiInput for MidiInputAlsa { ports.retain( |p| { match &filter { - PortFilter::Name(s) => p.name.find(s).is_some(), + PortFilter::All => true, + PortFilter::Name(s) => p.name.contains(s), + PortFilter::Regex(s) => s.is_match(&p.name), PortFilter::Addr(MidiAddrHandler::ALSA(s)) => p.addr == *s, - _ => todo!(), } } ); ports } - fn connect(&mut self, port_addr: &Addr, port_name: &str) -> Result<(), Box> { + fn connect(&mut self, port_addr: &Addr, port_name: &str) -> Result<(), Error> { let src_pinfo = self.seq.get_any_port_info(*port_addr)?; - let queue_id = self.init_queue(); + 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(); + let sub = PortSubscribe::empty()?; sub.set_sender(src_pinfo.addr()); - sub.set_dest(Addr { client: self.seq.client_id().unwrap(), port: vport}); + sub.set_dest(Addr { client: self.seq.client_id()?, 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); + self.start_time = Some(std::time::SystemTime::now()); Ok(()) } - fn device_events(&mut self, ts: mpsc::Sender) -> Result<(), Box> { - let ports = self.ports(); + fn device_events(&mut self, ts: mpsc::Sender) -> Result<(), 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, _|{ @@ -317,15 +335,15 @@ impl MidiInput for MidiInputAlsa { // 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(); + let p = s.ports()?; + let pp = s.filter_ports(p, PortFilter::Addr( MidiAddrHandler::ALSA(a) )); + if !pp.is_empty() { + ts.send(MidiPortHandler::ALSA(pp[0].clone())).expect("unexpected send() error"); } }; - false + Ok(false) } - _ => false, + _ => Ok(false), } }, ())?; self.close_internal(); @@ -335,30 +353,28 @@ impl MidiInput for MidiInputAlsa { 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 signal_stop_input(&self) -> Result<(), Error> { + Self::signal_stop_input_internal(self.stop_trigger[1]) } - fn handle_input(&mut self, callback: F, (rs, ts): (mpsc::Receiver, mpsc::Sender), userdata: D) -> Result<(), Box> - where + fn handle_input(&mut self, callback: F, (rs, ts): (mpsc::Receiver, mpsc::Sender), userdata: D) -> Result<(), Error> + where F: Fn(Option, &[u8], &mut D) + Send, D: Send, { - thread::scope( |sc| -> Result<(), Box> { + thread::scope( |sc| -> Result<(), Error> { let stop_trigger = self.stop_trigger[1]; - let t = sc.spawn(move || { + let t = sc.spawn(move || -> Result<(), Error> { let userdata = userdata; - self.handle_input_internal(callback, userdata).unwrap(); - ts.send(false).unwrap(); + self.handle_input_internal(callback, userdata)?; + ts.send(false).expect("unexpected send() error"); + Ok(()) }); match rs.recv()? { true => Self::signal_stop_input_internal(stop_trigger)?, false => () }; - t.join().unwrap(); + t.join().expect("unexpected thread error")?; Ok(()) }) } diff --git a/src/midi/mod.rs b/src/midi/mod.rs index 94f224b..6f906b4 100644 --- a/src/midi/mod.rs +++ b/src/midi/mod.rs @@ -1,6 +1,8 @@ pub mod alsa; +use crate::config::device::Identifier; use crate::midi::alsa::MidiInputAlsa; +use crate::Error; extern crate libc; @@ -8,7 +10,6 @@ use crate::config::DeviceConfig; use crate::eventmap::EventMap; use crate::event::Event; -use std::error::Error; use std::time::SystemTime; use std::sync::mpsc; @@ -20,8 +21,9 @@ pub struct MidiPort{ #[derive(Debug,Clone)] pub enum PortFilter { + All, Name(String), - Regex(String), + Regex(regex::Regex), Addr(MidiAddrHandler), } @@ -54,43 +56,39 @@ impl From for MidiAddrHandler { 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!() + match &conf.identifier { + Identifier::All => PortFilter::All, + Identifier::Name(s) => PortFilter::Name(s.clone()), + Identifier::Regex(s) => PortFilter::Regex(s.clone()), + _ => todo!("match type not implemented"), } } } -mod helper { -} - - pub trait MidiInput { - fn new(client_name: &str) -> Result> + fn new(client_name: &str) -> Result where Self: Sized; - fn close(self) -> Result<(), Box>; + fn close(self) -> Result<(), Error>; - fn ports(&self) -> Vec>; - fn ports_handle(&self) -> Vec; + fn ports(&self) -> Result>, Error>; + fn ports_handle(&self) -> Result, Error>; - fn filter_ports<'a>(&self, ports: Vec>, filter: PortFilter) -> Vec>; + fn filter_ports(&self, ports: Vec>, filter: PortFilter) -> Vec>; - fn connect(&mut self, port_addr: &T, port_name: &str) -> Result<(), Box>; + fn connect(&mut self, port_addr: &T, port_name: &str) -> Result<(), Error>; - fn device_events(&mut self, ts: mpsc::Sender) -> Result<(), Box>; + fn device_events(&mut self, ts: mpsc::Sender) -> Result<(), Error>; } pub trait MidiInputHandler { - fn signal_stop_input(&self) -> Result<(), Box>; + fn signal_stop_input(&self) -> Result<(), Error>; - fn handle_input(&mut self, callback: F, rts: (mpsc::Receiver, mpsc::Sender), userdata: D) -> Result<(), Box> - where + fn handle_input(&mut self, callback: F, rts: (mpsc::Receiver, mpsc::Sender), userdata: D) -> Result<(), Error> + where F: Fn(Option, &[u8], &mut D) + Send, D: Send, - ; + ; } macro_rules! handler_try_connect { @@ -101,16 +99,15 @@ macro_rules! handler_try_connect { match $port { MidiPortHandler::$handler(_) => { let maddr = MidiAddrHandler::from($port); - let portmap = v.ports(); + 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(); + MidiHandler::$handler(v) => { + v.connect(&port.addr, "rmidimap-handler")?; Ok(Some(h)) } _ => panic!("unexpected midi driver failure"), @@ -139,77 +136,82 @@ macro_rules! handler_fcall { } impl MidiHandler { - pub fn new(name: &str) -> Result> { + pub fn new(name: &str) -> Result { Self::new_with_driver(name, MidiHandlerDriver::ALSA) } - pub fn new_with_driver(name: &str, driver: MidiHandlerDriver) -> Result> { + pub fn new_with_driver(name: &str, driver: MidiHandlerDriver) -> Result { match driver { MidiHandlerDriver::ALSA => Ok(MidiHandler::ALSA(MidiInputAlsa::new(name)?)), } } - pub fn ports(&self) -> Result, Box> { + pub fn ports(&self) -> Result, Error> { handler_fcall!{ self, handle_port_list ,(), ALSA } } - pub fn try_connect(&self, addr: MidiPortHandler, filter: PortFilter) -> Result, Box> { - let r: Result, Box> = handler_try_connect!{ + pub fn try_connect(&self, addr: MidiPortHandler, filter: PortFilter) -> Result, Error> { + let r: Result, Error> = handler_try_connect!{ self, filter, addr, ALSA }; r } - pub fn run(&mut self, eventmap: &EventMap, (rs,ts): (mpsc::Receiver, mpsc::Sender)) -> Result<(), Box> { + pub fn run(&mut self, eventmap: &EventMap, (rs,ts): (mpsc::Receiver, mpsc::Sender)) -> Result<(), Error> { handler_fcall!{ self, handle_inputport ,(eventmap,(rs,ts)), ALSA } } - pub fn stop(&self) -> Result<(), Box> { + pub fn stop(&self) -> Result<(), Error> { handler_fcall!{ self, handle_signal_stop, (), ALSA - } + } } - pub fn device_events(&mut self, ts: mpsc::Sender) -> Result<(), Box> { + pub fn device_events(&mut self, ts: mpsc::Sender) -> Result<(), Error> { handler_fcall!{ self, device_events, ts, ALSA - } + } } } -fn handle_port_list(input: &T, _: ()) -> Result, Box> +fn handle_port_list(input: &T, _: ()) -> Result, Error> where T: MidiInput { - Ok(input.ports_handle()) + input.ports_handle() } -fn handle_inputport(input: &mut T, (eventmap, (rs, ts)): (&EventMap, (mpsc::Receiver, mpsc::Sender))) -> Result<(), Box> +fn handle_inputport(input: &mut T, (eventmap, (rs, ts)): (&EventMap, (mpsc::Receiver, mpsc::Sender))) -> Result<(), Error> where T: MidiInputHandler { input.handle_input(|t,m,_| { let mut event = Event::from(m); event.timestamp = t; - eventmap.run_event(&event).unwrap(); + match eventmap.run_event(&event) { + Ok(_) => (), + Err(e) => { + eprintln!("ERROR: error on run: {}", e) + }, + } }, (rs,ts), ())?; Ok(()) } -fn handle_signal_stop(input: &T, _: ()) -> Result<(), Box> +fn handle_signal_stop(input: &T, _: ()) -> Result<(), Error> where T: MidiInputHandler { input.signal_stop_input() } -fn device_events(input: &mut T, ts: mpsc::Sender) -> Result<(), Box> +fn device_events(input: &mut T, ts: mpsc::Sender) -> Result<(), Error> where T: MidiInput { input.device_events(ts) diff --git a/src/run.rs b/src/run.rs index 6222841..cd04ff6 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,3 +1,16 @@ +use std::sync::mpsc; +use std::thread; +use std::sync::{Mutex,Arc}; + +use crate::Error; + +use crate::midi::{PortFilter,MidiHandler,MidiPortHandler}; +use crate::config::{Config,DeviceConfig}; +use crate::eventmap::EventMap; + +type DeviceRunItem<'a> = (&'a DeviceConfig, EventMap<'a>, Option>>); +type DeviceRunResult<'a> =(thread::ScopedJoinHandle<'a, ()>, mpsc::Sender); + pub fn cross_shell(cmd: &str) -> Vec { if cfg!(target_os = "windows") { vec!("cmd", "/C", cmd) @@ -8,3 +21,68 @@ pub fn cross_shell(cmd: &str) -> Vec { |x| x.to_string() ).collect() } + +pub fn run_config(conf: &Config) -> Result<(), Error> { + let cfevmap: Vec = conf.devices.iter().map(|x| + (x, EventMap::from(x), + x.max_connections.map(|v| (Arc::new(Mutex::new((0,v))))) + ) + ).collect(); + + let input = MidiHandler::new("rmidimap")?; + + thread::scope(|s| -> Result<(), Error> { + let (tdev,rdev) = mpsc::channel::(); + let mut threads: Vec<(thread::ScopedJoinHandle<'_, ()>, mpsc::Sender)> = Vec::new(); + let ports = input.ports()?; + for p in ports { + if let Some(v) = try_connect_process(&input, s, &p, &cfevmap)? { threads.push(v) } + } + let _event_thread = s.spawn(|| { + let mut input = MidiHandler::new("rmidimap-event-watcher").unwrap(); + input.device_events(tdev).unwrap(); + }); + loop { + let p = rdev.recv()?; + if let Some(v) = try_connect_process(&input, s, &p, &cfevmap)? { threads.push(v) } + } + })?; + Ok(()) +} + +fn try_connect_process<'a>( + input: &MidiHandler, + s: &'a thread::Scope<'a, '_>, + p: &MidiPortHandler, + cfevmap: &'a[DeviceRunItem<'a>], + ) + -> Result>, Error> { + for (dev, eventmap, m) in cfevmap { + if let Some(m) = m { + let m = m.lock().unwrap(); + if m.0 >= m.1 { + continue; + } + } + if let Some(mut c) = input.try_connect(p.clone(), PortFilter::from(*dev))? { + if let Some(m) = m { + let mut m = m.lock().unwrap(); + m.0 += 1; + } + let (sts,srs) = mpsc::channel::(); + let nsts = sts.clone(); + let mm = m.as_ref().map(Arc::clone); + let t = s.spawn( move || { + dev.run_connect().unwrap(); + c.run(eventmap, (srs,nsts)).unwrap(); + if let Some(m) = mm { + let mut m = m.lock().unwrap(); + m.0 -= 1; + } + dev.run_disconnect().unwrap(); + }); + return Ok(Some((t, sts))); + } + } + Ok(None) +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..325f0e9 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,43 @@ + +pub mod smartset; +pub mod range; +pub mod remap; + +pub type SmartSet = smartset::SmartSet; +pub type Range = range::Range; +pub type Remapper = remap::Remapper; + + +macro_rules! visit_from { + ( $obj:ident , $( ($fct:ident, $type:ty) ),+ $(,)?) => { + $( + fn $fct(self, value: $type) -> Result + where + E: de::Error, + { + Ok($obj::from(value)) + } + )* + }; +} + +pub(crate) use visit_from; + +pub fn map_tryfrom(v: Vec) -> Result, >::Error> +where + D: TryFrom, +{ + v.into_iter().map(|x| D::try_from(x)).collect::, >::Error>>() +} + +pub fn map_opt_tryfrom(v: Option>) -> Result>, >::Error> +where + D: TryFrom, +{ + match v { + Some(v) => { + Ok( Some(v.into_iter().map(|x| D::try_from(x)).collect::, >::Error>>()?) ) + } + None => Ok(None), + } +} diff --git a/src/util/range.rs b/src/util/range.rs new file mode 100644 index 0000000..62c7cfe --- /dev/null +++ b/src/util/range.rs @@ -0,0 +1,147 @@ +use std::str::FromStr; +use std::cmp::PartialOrd; + +use num::{Num,NumCast}; + +use super::visit_from; + +// Trait aliases are unstable +//trait smartsetnum = T: Num+Copy + FromStr; + +#[derive(Debug,Clone,Copy)] +pub struct Range +where + T: Num+Copy + FromStr + PartialOrd, +{ + start: T, + end: T, +} + + +pub fn parse_range(s: &str) -> Result, ::Err> +where +T: Num+Copy + FromStr + PartialOrd, +{ + let mut osep = s.find(':'); + if osep.is_none() { + osep = s.find('-'); + } + let r = if let Some(sep) = osep { + let (p1,p2) = (&s[..sep], &s[sep+1..] ); + ( p1.parse()?, p2.parse()? ) + } + else { + let n = s.parse()?; + (n, n) + }; + Ok(Range::new(r.0, r.1)) +} + +impl Range +where +T: Num+Copy + FromStr + PartialOrd, +{ + pub fn new(start: T, end: T) -> Self { + Self { + start, + end, + } + } + + pub fn start(&self) -> T { + self.start + } + + pub fn end(&self) -> T { + self.end + } +} + +impl From for Range +where + T: Num+Copy+NumCast + FromStr + PartialOrd, + U: Num+Copy+num::ToPrimitive + FromStr, +{ + fn from(i: U) -> Self { + let ti: T = num::NumCast::from(i).unwrap(); + Range::::new(ti, ti) + } +} + +impl FromStr for Range +where + T: Num+Copy + FromStr + PartialOrd, +{ + type Err = ::Err; + + fn from_str(s: &str) -> Result { + parse_range(s) + } +} + +use std::marker::PhantomData; +use std::fmt; +use serde::de::{self,Deserialize, Deserializer, Visitor}; + +struct RangeVisitor +where + T: Num+Copy + FromStr + PartialOrd, +{ + marker: PhantomData Range> +} + + +impl RangeVisitor +where + T: Num+Copy + FromStr + PartialOrd, +{ + fn new() -> Self { + Self { + marker: PhantomData + } + } +} + +impl<'de, T> Visitor<'de> for RangeVisitor +where + T: Num+Copy+PartialOrd + FromStr +NumCast + Deserialize<'de> + std::fmt::Debug, + ::Err: std::fmt::Display, +{ + type Value = Range; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a set of integer") + } + + visit_from!{ Range , + (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(self, value: &str) -> Result + where + E: de::Error, + { + Range::from_str(value).map_err(serde::de::Error::custom) + } +} + +// This is the trait that informs Serde how to deserialize MyMap. +impl<'de, T> Deserialize<'de> for Range +where + T: Num+Copy+NumCast+PartialOrd + Deserialize<'de> + FromStr + std::fmt::Debug, + ::Err: std::fmt::Display, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(RangeVisitor::::new()) + } +} diff --git a/src/util/remap.rs b/src/util/remap.rs new file mode 100644 index 0000000..8733903 --- /dev/null +++ b/src/util/remap.rs @@ -0,0 +1,82 @@ + +use num::{Num,NumCast,ToPrimitive, Bounded}; +use std::str::FromStr; +use std::ops; + +use super::Range; + +// Trait aliases are unstable +//trait remapnum = T: Num+ToPrimitive+Copy+PartialOrd + FromStr + ops::Add + ops::Sub + ops::Div + ops::Mul; + +#[derive(Debug,Clone)] +pub struct Remapper +where + T: Num+ToPrimitive+Copy+PartialOrd+NumCast + FromStr + ops::Add + ops::Sub + ops::Div + ops::Mul, +{ + src: Range, + dst: Range, +} + +impl Remapper +where + T: Num+ToPrimitive+Copy+PartialOrd+NumCast + FromStr + ops::Add + ops::Sub + ops::Div + ops::Mul + std::fmt::Debug, +{ + + pub fn src(&self) -> &Range { + &self.src + } + + pub fn dst(&self) -> &Range { + &self.dst + } + + pub fn new(src: Range, dst: Range) -> Self { + Self { + src, + dst, + } + } + + pub fn remap(&self, v: T) -> T + { + // compute actual value in source type + let r: T = (v-self.src.start())*(self.dst.end()-self.dst.start()) + / (self.src.end()-self.src.start()) + + self.dst.start(); + r + } +} + +impl Remapper +where + T: Num+ToPrimitive+Copy+PartialOrd+NumCast + FromStr + ops::Add + ops::Sub + ops::Div + ops::Mul + std::fmt::Debug, +{ + pub fn remap_to(&self, v: T) -> Option + where + U: Num+NumCast+Copy+Bounded + FromStr, + { + // compute actual value in source type + let r: T = self.remap(v); + + // find min/max values of target type + let (rmin,rmax): (Option,Option) = ( + num::NumCast::from(U::min_value()), + num::NumCast::from(U::max_value()), + ); + + // if target min/max can be casted onto source, bound output to those + match (rmin,rmax) { + (Some(min),Some(max)) => { + if r >= max { + Some(U::max_value()) + } else if r <= min { + Some(U::min_value()) + } + else { + num::NumCast::from(r) + } + } + _ => num::NumCast::from(r), + } + } +} diff --git a/src/util.rs b/src/util/smartset.rs similarity index 74% rename from src/util.rs rename to src/util/smartset.rs index 80ae699..2d0ca9b 100644 --- a/src/util.rs +++ b/src/util/smartset.rs @@ -2,27 +2,39 @@ use std::collections::BTreeSet; use std::str::FromStr; +use std::ops; use num::{Num,NumCast}; +// Trait aliases are unstable +//trait smartsetnum = T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign; + pub fn parse_int_set(s: &str) -> Result, ::Err> -where - T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign, +where + T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign, { let mut r: BTreeSet = BTreeSet::new(); let parts: Vec<&str> = s.split(',').collect(); for p in parts { if p.len() > 0 { - if let Some(sep) = p.find('-') { + let mut osep = s.find(':'); + if osep.is_none() { + osep = s.find('-'); + } + if let Some(sep) = osep { 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); + loop { r.insert(low); + if low >= high { + break; + } low += T::one(); } } @@ -36,15 +48,15 @@ where #[derive(Debug,Clone)] pub struct SmartSet -where - T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign, +where + T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign, { pub set: BTreeSet } -impl SmartSet +impl SmartSet where - T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign, + T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign, { pub fn new() -> Self { Self { @@ -57,21 +69,10 @@ where } } -// impl From for SmartSet -// 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 From for SmartSet +impl From for SmartSet where - T: Num+Ord+Copy+NumCast + std::str::FromStr + std::ops::AddAssign, - U: Num+Ord+Copy+num::ToPrimitive + std::str::FromStr + std::ops::AddAssign, + T: Num+Ord+Copy+NumCast + std::str::FromStr + ops::AddAssign, + U: Num+Ord+Copy+num::ToPrimitive + std::str::FromStr + ops::AddAssign, { fn from(i: U) -> Self { let mut r = SmartSet::::new(); @@ -80,10 +81,11 @@ where } } -// impl From<&[U]> for SmartSet +// TODO: figure out how to do impl priority/exclusion +// impl From<&[U]> for SmartSet // where -// T: Num+Ord+Copy+NumCast + std::str::FromStr + std::ops::AddAssign, -// U: Num+Ord+Copy+num::ToPrimitive + std::str::FromStr + std::ops::AddAssign, +// T: Num+Ord+Copy+NumCast + std::str::FromStr + ops::AddAssign, +// U: Num+Ord+Copy+num::ToPrimitive + std::str::FromStr + ops::AddAssign, // { // fn from(i: &[U]) -> Self { // let mut r = SmartSet::::new(); @@ -94,9 +96,9 @@ where // } // } -impl FromStr for SmartSet +impl FromStr for SmartSet where - T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign, + T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign, { type Err = ::Err; @@ -109,7 +111,7 @@ where impl IntoIterator for SmartSet where - T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign, + T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign, { type Item = T; type IntoIter = std::collections::btree_set::IntoIter; @@ -121,7 +123,7 @@ where impl<'a, T> IntoIterator for &'a SmartSet where - T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign, + T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign, { type Item = &'a T; type IntoIter = std::collections::btree_set::Iter<'a, T>; @@ -137,7 +139,7 @@ use serde::de::{self,Deserialize, Deserializer, Visitor}; struct SmartSetVisitor where - T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign, + T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign, { marker: PhantomData SmartSet> } @@ -145,7 +147,7 @@ where impl SmartSetVisitor where - T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign, + T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign, { fn new() -> Self { Self { @@ -163,13 +165,13 @@ macro_rules! visit_from { { Ok(SmartSet::from(value)) } - )* + )* }; } impl<'de, T> Visitor<'de> for SmartSetVisitor where - T: Num+Ord+Copy+NumCast + Deserialize<'de> + std::str::FromStr + std::ops::AddAssign + std::fmt::Debug, + T: Num+Ord+Copy+NumCast + Deserialize<'de> + std::str::FromStr + ops::AddAssign + std::fmt::Debug, ::Err: std::fmt::Display, { type Value = SmartSet; @@ -222,7 +224,7 @@ where // This is the trait that informs Serde how to deserialize MyMap. impl<'de, T> Deserialize<'de> for SmartSet where - T: Num+Ord+Copy+NumCast + Deserialize<'de> + std::str::FromStr + std::ops::AddAssign + std::fmt::Debug, + T: Num+Ord+Copy+NumCast + Deserialize<'de> + std::str::FromStr + ops::AddAssign + std::fmt::Debug, ::Err: std::fmt::Display, { fn deserialize(deserializer: D) -> Result diff --git a/test/vmpk.yml b/test/vmpk.yml index 7f4a775..cf17dac 100644 --- a/test/vmpk.yml +++ b/test/vmpk.yml @@ -1,20 +1,56 @@ devices: - - name: 'VMPK Output:out' - multiconnect: false + - name: 'VMPK' + max_connections: 1 connect: - args: [ "sh", "-c", "echo Hello world!" ] disconnect: - args: [ "sh", "-c", "echo Bye!" ] events: + - type: ProgramChange + run: + - cmd: "echo [$channel] ProgramChange $id" - type: Controller + remap: 0:100 run: - - args: [ "sh", "-c", "echo [$channel] Controller $id $value" ] + - cmd: "echo [$channel] Controller $id $value $rawvalue" - type: NoteOff + id: 25-30 run: - - args: [ "sh", "-c", "echo [$channel] NoteOff $id" ] + - cmd: "echo [$channel] NoteOff $id" - type: NoteOn + channel: 0 run: - - args: [ "sh", "-c", "echo [$channel] NoteOn $id $value" ] + - cmd: "echo [$channel] NoteOn $id $value $raw" - type: PitchBend + remap: 0-100 + float: true + value: 0-65535 run: - - args: [ "sh", "-c", "echo [$channel] PitchBend $value" ] + - cmd: "echo [$channel] PitchBend $value $raw $toto" + envconf: + timestamp: toto + - name: 'VMPK' + max_connections: 1 + connect: + - args: [ "sh", "-c", "echo Hello world! 2" ] + disconnect: + - args: [ "sh", "-c", "echo Bye! 2" ] + events: + - type: NoteOff + id: 25-30 + run: + - args: [ "sh", "-c", "echo 2 [$channel] NoteOff $id" ] + - type: NoteOn + channel: 0 + #channel: -1 + remap: -1 + run: + - args: [ "sh", "-c", "echo 2 [$channel] NoteOn $id $value" ] + - type: PitchBend + remap: 0-100 + float: true + value: 0-65535 + run: + - args: [ "sh", "-c", "echo [$channel] PitchBend $value $raw $toto" ] + envconf: + timestamp: toto