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:
parent
31a89f1665
commit
c75c7d0409
23 changed files with 1022 additions and 363 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use clap::{Parser,Subcommand};
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Map MIDI signals to commands
|
||||
|
|
|
|||
|
|
@ -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)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
15
src/config/serializer/device.rs
Normal file
15
src/config/serializer/device.rs
Normal 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>,
|
||||
}
|
||||
17
src/config/serializer/event.rs
Normal file
17
src/config/serializer/event.rs
Normal 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>>,
|
||||
}
|
||||
13
src/config/serializer/eventenv.rs
Normal file
13
src/config/serializer/eventenv.rs
Normal 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>,
|
||||
}
|
||||
17
src/config/serializer/mod.rs
Normal file
17
src/config/serializer/mod.rs
Normal 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>,
|
||||
}
|
||||
11
src/config/serializer/run.rs
Normal file
11
src/config/serializer/run.rs
Normal 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
50
src/error.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
150
src/event.rs
150
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<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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
74
src/main.rs
74
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<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)
|
||||
}
|
||||
|
|
|
|||
190
src/midi/alsa.rs
190
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<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(())
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
78
src/run.rs
78
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<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
43
src/util/mod.rs
Normal 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
147
src/util/range.rs
Normal 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
82
src/util/remap.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue