feat: additions and improvements

- add error handling
- conf: improve parsing method
- conf: change multiconnect to max_connections
- conf: add condition on value for event
- conf: add checks and default values on configs
- conf: rename shell param to cmd
- conf: add remap param
- midi: finish handling of all event types
- midi: implement regex device filter
- run: implement envmap
- alsa: implement timestamp
This commit is contained in:
zawz 2023-08-18 11:11:11 +02:00 committed by mateoferon
parent 31a89f1665
commit c75c7d0409
23 changed files with 1022 additions and 363 deletions

View file

@ -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"

View file

@ -1,4 +1,4 @@
use clap::{Parser,Subcommand};
use clap::Parser;
use std::path::PathBuf;
/// Map MIDI signals to commands

View file

@ -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<String>,
pub regex: Option<String>,
pub identifier: Identifier,
pub max_connections: Option<u32>,
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>
fn run_internal<'a, T>(&self, v: Option<T>) -> Result<Vec<std::process::ExitStatus>, 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())? ) ;
r.push( e.run(Event::new().make_env(None, false)?.to_map(e.envconf.as_ref()))? ) ;
}
}
Ok(r)
}
pub fn run_connect(&self) -> Result<Vec<std::process::ExitStatus>, std::io::Error> {
pub fn run_connect(&self) -> Result<Vec<std::process::ExitStatus>, Error> {
self.run_internal(self.connect.as_ref())
}
pub fn run_disconnect(&self) -> Result<Vec<std::process::ExitStatus>, std::io::Error> {
pub fn run_disconnect(&self) -> Result<Vec<std::process::ExitStatus>, Error> {
self.run_internal(self.disconnect.as_ref())
}
}
}
impl TryFrom<DeviceConfigSerializer> for DeviceConfig {
type Error = crate::Error;
fn try_from(v: DeviceConfigSerializer) -> Result<Self, Self::Error> {
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)?,
})
}
}

View file

@ -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<u8> = SmartSet {
set: BTreeSet::from((0..=127).collect::<BTreeSet<u8>>()),
};
static ref NULL_DEFAULT_MAP: SmartSet<u8> = SmartSet {
set: BTreeSet::from([0]),
};
static ref CHANNEL_DEFAULT_MAP: SmartSet<u8> = SmartSet {
set: BTreeSet::from((0..=15).collect::<BTreeSet<u8>>()),
};
static ref TRIGGER_NOTE_DEFAULT_MAP: SmartSet<u16> = SmartSet {
set: BTreeSet::from((1..=127).collect::<BTreeSet<u16>>()),
};
static ref TRIGGER_U8_DEFAULT_MAP: SmartSet<u16> = SmartSet {
set: BTreeSet::from((0..=127).collect::<BTreeSet<u16>>()),
};
static ref TRIGGER_U16_DEFAULT_MAP: SmartSet<u16> = SmartSet {
set: BTreeSet::from((0..=65535).collect::<BTreeSet<u16>>()),
};
static ref TRIGGER_NULL_DEFAULT_MAP: SmartSet<u16> = SmartSet {
set: BTreeSet::from([0]),
};
}
#[derive(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,
pub channel: SmartSet<u8>,
pub id: SmartSet<u8>,
pub remap: Option<Remapper<f64>>,
pub float: bool,
pub value: Option<SmartSet<u16>>,
}
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<EventConfigSerializer> for EventConfig {
type Error = crate::Error;
fn try_from(v: EventConfigSerializer) -> Result<Self, Self::Error> {
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)
}
}

View file

@ -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<DeviceConfig>,
}
impl TryFrom<ConfigSerializer> for Config {
type Error = crate::Error;
fn try_from(v: ConfigSerializer) -> Result<Self, Self::Error> {
Ok(Config {
devices: util::map_tryfrom(v.devices)?,
})
}
}
impl TryFrom<&[u8]> for Config {
type Error = crate::Error;
fn try_from(dat: &[u8]) -> Result<Self, Self::Error> {
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<Self, Self::Err> {
let c: ConfigSerializer = serde_yaml::from_str(dat)?;
Ok(Config::try_from(c)?)
}
}

View file

@ -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<Vec<String>>,
pub shell: Option<String>,
pub envconf: Option<HashMap<String, String>>,
pub args: Vec<String>,
pub envconf: Option<EventEnvMap>,
}
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");
let mut c = Command::new(&self.args[0]);
if self.args.len() > 1 {
c.args(&self.args[1..]);
}
c.envs(env).status()
}
}
impl TryFrom<RunConfigSerializer> for RunConfig {
type Error = crate::Error;
fn try_from(v: RunConfigSerializer) -> Result<Self, Self::Error> {
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,
}
)
}
}

View file

@ -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<String>,
pub regex: Option<String>,
pub addr: Option<String>,
pub connect: Option<Vec<RunConfigSerializer>>,
pub disconnect: Option<Vec<RunConfigSerializer>>,
pub events: Option<Vec<EventConfigSerializer>>,
pub max_connections: Option<u32>,
}

View file

@ -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<RunConfigSerializer>,
pub r#type: EventType,
pub channel: Option<SmartSet<u8>>,
pub id: Option<SmartSet<u8>>,
pub remap: Option<Range<f64>>,
pub float: Option<bool>,
pub value: Option<SmartSet<u16>>,
}

View file

@ -0,0 +1,13 @@
use serde::{Serialize,Deserialize};
#[derive(Serialize,Deserialize,Debug,Clone)]
#[serde(deny_unknown_fields)]
pub struct EventEnvSerializer {
pub channel: Option<String>,
pub id: Option<String>,
pub raw: Option<String>,
pub rawvalue: Option<String>,
pub timestamp: Option<String>,
pub value: Option<String>,
}

View file

@ -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<DeviceConfigSerializer>,
}

View file

@ -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<Vec<String>>,
pub cmd: Option<String>,
pub envconf: Option<EventEnvSerializer>,
}

50
src/error.rs Normal file
View file

@ -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<alsa::Error> for Error {
fn from(value: alsa::Error) -> Self {
Self::from(AlsaError::from(value))
}
}

View file

@ -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<SystemTime>,
}
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<u8> for EventType {
fn from(v: u8) -> Self {
if ! (0b1000..=0b1111).contains(&v) {
@ -62,7 +122,7 @@ impl From<u8> 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<f64>>, float: bool) -> Result<EventEnv, Error>
{
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::<i64>(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
}
}

View file

@ -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<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();
}
use crate::Error;
#[derive(Debug,Default)]
pub struct EventMap<'a> {
//TODO: vec support
pub map: HashMap<u32, Vec<&'a EventConfig>>,
}
@ -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

View file

@ -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<dyn Error>> {
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<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(())
let conf = Config::try_from(&dat[..])?;
run::run_config(&conf)
}

View file

@ -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<PortSubscribe>,
connect_addr: Option<Addr>,
start_time: Option<SystemTime>,
stop_trigger: [i32;2],
}
@ -42,10 +51,10 @@ impl Drop for MidiInputAlsa {
}
impl MidiInputAlsa {
fn init_trigger(&mut self) -> Result<(), Box<dyn Error>> {
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<i32, alsa::Error> {
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<i32, Box<dyn Error>> {
let mut pinfo = PortInfo::empty().unwrap();
fn create_port(&mut self, port_name: &CStr, queue_id: i32) -> Result<i32, AlsaError> {
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<dyn Error>> {
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::<bool>() as self::libc::size_t) } == -1 {
todo!()
Err(Error::Pipe)
}
else {
Ok(())
}
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 {
fn alsa_input_handler<F, D>(&mut self, callback: F, mut userdata: D) -> Result<(), Error>
where F: Fn(&Self, alsa::seq::Event, &mut D) -> Result<bool, Error> {
// 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::<bool>() 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::<bool>() 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<F, D>(&mut self, callback: F, userdata: D) -> Result<(), Box<dyn Error>>
fn handle_input_internal<F, D>(&mut self, callback: F, userdata: D) -> Result<(), Error>
where F: Fn(Option<SystemTime>, &[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<SystemTime> = 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<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!(),
};
fn new(client_name: &str) -> Result<Self, Error> {
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<dyn Error>> {
fn close(mut self) -> Result<(), 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(),
fn ports_handle(&self) -> Result<Vec<MidiPortHandler>, Error> {
get_ports(&self.seq, PortCap::READ | PortCap::SUBS_READ).iter().map(
|x| -> Result<MidiPortHandler, Error> {
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<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(),
fn ports(&self) -> Result<Vec<MidiPort<Addr>>, Error> {
get_ports(&self.seq, PortCap::READ | PortCap::SUBS_READ).iter().map(|x| -> Result<MidiPort<Addr>, 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<Addr> 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<dyn Error>> {
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<MidiPortHandler>) -> Result<(), Box<dyn Error>> {
let ports = self.ports();
fn device_events(&mut self, ts: mpsc::Sender<MidiPortHandler>) -> 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<Addr> for MidiInputAlsa {
// 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();
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<Addr> for MidiInputAlsa {
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 signal_stop_input(&self) -> Result<(), Error> {
Self::signal_stop_input_internal(self.stop_trigger[1])
}
fn handle_input<F, D>(&mut self, callback: F, (rs, ts): (mpsc::Receiver<bool>, mpsc::Sender<bool>), userdata: D) -> Result<(), Box<dyn Error>>
where
fn handle_input<F, D>(&mut self, callback: F, (rs, ts): (mpsc::Receiver<bool>, mpsc::Sender<bool>), userdata: D) -> Result<(), Error>
where
F: Fn(Option<SystemTime>, &[u8], &mut D) + Send,
D: Send,
{
thread::scope( |sc| -> Result<(), Box<dyn Error>> {
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(())
})
}

View file

@ -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<T>{
#[derive(Debug,Clone)]
pub enum PortFilter {
All,
Name(String),
Regex(String),
Regex(regex::Regex),
Addr(MidiAddrHandler),
}
@ -54,43 +56,39 @@ impl From<MidiPortHandler> 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<T> {
fn new(client_name: &str) -> Result<Self, Box<dyn Error>>
fn new(client_name: &str) -> Result<Self, Error>
where Self: Sized;
fn close(self) -> Result<(), Box<dyn Error>>;
fn close(self) -> Result<(), Error>;
fn ports(&self) -> Vec<MidiPort<T>>;
fn ports_handle(&self) -> Vec<MidiPortHandler>;
fn ports(&self) -> Result<Vec<MidiPort<T>>, Error>;
fn ports_handle(&self) -> Result<Vec<MidiPortHandler>, Error>;
fn filter_ports<'a>(&self, ports: Vec<MidiPort<T>>, filter: PortFilter) -> Vec<MidiPort<T>>;
fn filter_ports(&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 connect(&mut self, port_addr: &T, port_name: &str) -> Result<(), Error>;
fn device_events(&mut self, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Box<dyn Error>>;
fn device_events(&mut self, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Error>;
}
pub trait MidiInputHandler {
fn signal_stop_input(&self) -> Result<(), Box<dyn Error>>;
fn signal_stop_input(&self) -> Result<(), Error>;
fn handle_input<F, D>(&mut self, callback: F, rts: (mpsc::Receiver<bool>, mpsc::Sender<bool>), userdata: D) -> Result<(), Box<dyn Error>>
where
fn handle_input<F, D>(&mut self, callback: F, rts: (mpsc::Receiver<bool>, mpsc::Sender<bool>), userdata: D) -> Result<(), Error>
where
F: Fn(Option<SystemTime>, &[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<Self, Box<dyn Error>> {
pub fn new(name: &str) -> Result<Self, Error> {
Self::new_with_driver(name, MidiHandlerDriver::ALSA)
}
pub fn new_with_driver(name: &str, driver: MidiHandlerDriver) -> Result<Self, Box<dyn Error>> {
pub fn new_with_driver(name: &str, driver: MidiHandlerDriver) -> Result<Self, Error> {
match driver {
MidiHandlerDriver::ALSA => Ok(MidiHandler::ALSA(MidiInputAlsa::new(name)?)),
}
}
pub fn ports(&self) -> Result<Vec<MidiPortHandler>, Box<dyn Error>> {
pub fn ports(&self) -> Result<Vec<MidiPortHandler>, 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!{
pub fn try_connect(&self, addr: MidiPortHandler, filter: PortFilter) -> Result<Option<MidiHandler>, Error> {
let r: Result<Option<MidiHandler>, 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>> {
pub fn run(&mut self, eventmap: &EventMap, (rs,ts): (mpsc::Receiver<bool>, mpsc::Sender<bool>)) -> Result<(), Error> {
handler_fcall!{
self, handle_inputport ,(eventmap,(rs,ts)),
ALSA
}
}
pub fn stop(&self) -> Result<(), Box<dyn Error>> {
pub fn stop(&self) -> Result<(), Error> {
handler_fcall!{
self, handle_signal_stop, (),
ALSA
}
}
}
pub fn device_events(&mut self, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Box<dyn Error>> {
pub fn device_events(&mut self, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Error> {
handler_fcall!{
self, device_events, ts,
ALSA
}
}
}
}
fn handle_port_list<T, A>(input: &T, _: ()) -> Result<Vec<MidiPortHandler>, Box<dyn Error>>
fn handle_port_list<T, A>(input: &T, _: ()) -> Result<Vec<MidiPortHandler>, Error>
where T: MidiInput<A>
{
Ok(input.ports_handle())
input.ports_handle()
}
fn handle_inputport<T>(input: &mut T, (eventmap, (rs, ts)): (&EventMap, (mpsc::Receiver<bool>, mpsc::Sender<bool>))) -> Result<(), Box<dyn Error>>
fn handle_inputport<T>(input: &mut T, (eventmap, (rs, ts)): (&EventMap, (mpsc::Receiver<bool>, mpsc::Sender<bool>))) -> 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<T>(input: &T, _: ()) -> Result<(), Box<dyn Error>>
fn handle_signal_stop<T>(input: &T, _: ()) -> Result<(), Error>
where T: MidiInputHandler
{
input.signal_stop_input()
}
fn device_events<T, A>(input: &mut T, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Box<dyn Error>>
fn device_events<T, A>(input: &mut T, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Error>
where T: MidiInput<A>
{
input.device_events(ts)

View file

@ -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<Arc<Mutex<(u32, u32)>>>);
type DeviceRunResult<'a> =(thread::ScopedJoinHandle<'a, ()>, mpsc::Sender<bool>);
pub fn cross_shell(cmd: &str) -> Vec<String> {
if cfg!(target_os = "windows") {
vec!("cmd", "/C", cmd)
@ -8,3 +21,68 @@ pub fn cross_shell(cmd: &str) -> Vec<String> {
|x| x.to_string()
).collect()
}
pub fn run_config(conf: &Config) -> Result<(), Error> {
let cfevmap: Vec<DeviceRunItem> = 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::<MidiPortHandler>();
let mut threads: Vec<(thread::ScopedJoinHandle<'_, ()>, mpsc::Sender<bool>)> = 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<Option<DeviceRunResult<'a>>, 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::<bool>();
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)
}

43
src/util/mod.rs Normal file
View file

@ -0,0 +1,43 @@
pub mod smartset;
pub mod range;
pub mod remap;
pub type SmartSet<T> = smartset::SmartSet<T>;
pub type Range<T> = range::Range<T>;
pub type Remapper<T> = remap::Remapper<T>;
macro_rules! visit_from {
( $obj:ident , $( ($fct:ident, $type:ty) ),+ $(,)?) => {
$(
fn $fct<E>(self, value: $type) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok($obj::from(value))
}
)*
};
}
pub(crate) use visit_from;
pub fn map_tryfrom<S,D>(v: Vec<S>) -> Result<Vec<D>, <D as TryFrom<S>>::Error>
where
D: TryFrom<S>,
{
v.into_iter().map(|x| D::try_from(x)).collect::<Result<Vec<D>, <D as TryFrom<S>>::Error>>()
}
pub fn map_opt_tryfrom<S,D>(v: Option<Vec<S>>) -> Result<Option<Vec<D>>, <D as TryFrom<S>>::Error>
where
D: TryFrom<S>,
{
match v {
Some(v) => {
Ok( Some(v.into_iter().map(|x| D::try_from(x)).collect::<Result<Vec<D>, <D as TryFrom<S>>::Error>>()?) )
}
None => Ok(None),
}
}

147
src/util/range.rs Normal file
View file

@ -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<T>
where
T: Num+Copy + FromStr + PartialOrd,
{
start: T,
end: T,
}
pub fn parse_range<T>(s: &str) -> Result<Range<T>, <T as FromStr>::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<T> Range<T>
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<T,U> From<U> for Range<T>
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::<T>::new(ti, ti)
}
}
impl<T> FromStr for Range<T>
where
T: Num+Copy + FromStr + PartialOrd,
{
type Err = <T as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_range(s)
}
}
use std::marker::PhantomData;
use std::fmt;
use serde::de::{self,Deserialize, Deserializer, Visitor};
struct RangeVisitor<T>
where
T: Num+Copy + FromStr + PartialOrd,
{
marker: PhantomData<fn() -> Range<T>>
}
impl<T> RangeVisitor<T>
where
T: Num+Copy + FromStr + PartialOrd,
{
fn new() -> Self {
Self {
marker: PhantomData
}
}
}
impl<'de, T> Visitor<'de> for RangeVisitor<T>
where
T: Num+Copy+PartialOrd + FromStr +NumCast + Deserialize<'de> + std::fmt::Debug,
<T as FromStr>::Err: std::fmt::Display,
{
type Value = Range<T>;
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<E>(self, value: &str) -> Result<Self::Value, E>
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<T>
where
T: Num+Copy+NumCast+PartialOrd + Deserialize<'de> + FromStr + 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(RangeVisitor::<T>::new())
}
}

82
src/util/remap.rs Normal file
View file

@ -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<T>
where
T: Num+ToPrimitive+Copy+PartialOrd+NumCast + FromStr + ops::Add + ops::Sub + ops::Div + ops::Mul,
{
src: Range<T>,
dst: Range<T>,
}
impl<T> Remapper<T>
where
T: Num+ToPrimitive+Copy+PartialOrd+NumCast + FromStr + ops::Add + ops::Sub + ops::Div + ops::Mul + std::fmt::Debug,
{
pub fn src(&self) -> &Range<T> {
&self.src
}
pub fn dst(&self) -> &Range<T> {
&self.dst
}
pub fn new(src: Range<T>, dst: Range<T>) -> 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<T> Remapper<T>
where
T: Num+ToPrimitive+Copy+PartialOrd+NumCast + FromStr + ops::Add + ops::Sub + ops::Div + ops::Mul + std::fmt::Debug,
{
pub fn remap_to<U>(&self, v: T) -> Option<U>
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<T>,Option<T>) = (
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),
}
}
}

View file

@ -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<T>(s: &str) -> Result<BTreeSet<T>, <T as std::str::FromStr>::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<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 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<T>
where
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
where
T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign,
{
pub set: BTreeSet<T>
}
impl<T> SmartSet<T>
impl<T> SmartSet<T>
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<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>
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,
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::<T>::new();
@ -80,10 +81,11 @@ where
}
}
// impl<T,U> From<&[U]> for SmartSet<T>
// TODO: figure out how to do impl priority/exclusion
// 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,
// 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::<T>::new();
@ -94,9 +96,9 @@ where
// }
// }
impl<T> FromStr for SmartSet<T>
impl<T> FromStr for SmartSet<T>
where
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign,
{
type Err = <T as FromStr>::Err;
@ -109,7 +111,7 @@ where
impl<T> IntoIterator for SmartSet<T>
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<Self::Item>;
@ -121,7 +123,7 @@ where
impl<'a, T> IntoIterator for &'a SmartSet<T>
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<T>
where
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
T: Num+Ord+Copy + std::str::FromStr + ops::AddAssign,
{
marker: PhantomData<fn() -> SmartSet<T>>
}
@ -145,7 +147,7 @@ where
impl<T> SmartSetVisitor<T>
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<T>
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,
<T as FromStr>::Err: std::fmt::Display,
{
type Value = SmartSet<T>;
@ -222,7 +224,7 @@ where
// 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: Num+Ord+Copy+NumCast + Deserialize<'de> + std::str::FromStr + ops::AddAssign + std::fmt::Debug,
<T as FromStr>::Err: std::fmt::Display,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>

View file

@ -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