init
This commit is contained in:
commit
31a89f1665
15 changed files with 1324 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
||||
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "rmidimap"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
#midir = "0.9"
|
||||
#midir = { path = "midir" }
|
||||
regex = "1.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
num-traits = "0.2"
|
||||
num = "0.4"
|
||||
lazy_static = "1.4"
|
||||
clap = { version = "4.1", features = ["derive"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
alsa = "0.7.0"
|
||||
libc = "0.2.21"
|
||||
11
src/cli.rs
Normal file
11
src/cli.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use clap::{Parser,Subcommand};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Map MIDI signals to commands
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
#[clap(value_parser)]
|
||||
pub map_file: PathBuf,
|
||||
}
|
||||
|
||||
48
src/config/device.rs
Normal file
48
src/config/device.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use crate::config::{RunConfig,EventConfig};
|
||||
use crate::event::Event;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize,Debug,Clone)]
|
||||
pub struct DeviceConfig {
|
||||
pub name: Option<String>,
|
||||
pub regex: Option<String>,
|
||||
pub connect: Option<Vec<RunConfig>>,
|
||||
pub disconnect: Option<Vec<RunConfig>>,
|
||||
pub events: Option<Vec<EventConfig>>,
|
||||
pub multiconnect: Option<bool>,
|
||||
}
|
||||
|
||||
//impl DeviceConfig {
|
||||
// fn connect(&self, port: &MidiInputPort) {
|
||||
// let mut midi_in = MidiInput::new("midi inputs")?;
|
||||
// midi_in.ignore(Ignore::None);
|
||||
// let _conn_in = midi_in.connect(in_port, "midir-read-input", move |_, message, emap| {
|
||||
// let event = event::Event::from(message);
|
||||
// emap.run_event(&event).unwrap();
|
||||
// }, eventmap)?;
|
||||
// }
|
||||
//}
|
||||
|
||||
impl DeviceConfig {
|
||||
fn run_internal<'a, T>(&self, v: Option<T>) -> Result<Vec<std::process::ExitStatus>, std::io::Error>
|
||||
where
|
||||
T: IntoIterator<Item = &'a RunConfig>
|
||||
{
|
||||
let mut r = Vec::new();
|
||||
if let Some(ev) = v {
|
||||
for e in ev {
|
||||
r.push( e.run(Event::new().gen_env())? ) ;
|
||||
}
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
pub fn run_connect(&self) -> Result<Vec<std::process::ExitStatus>, std::io::Error> {
|
||||
self.run_internal(self.connect.as_ref())
|
||||
}
|
||||
|
||||
pub fn run_disconnect(&self) -> Result<Vec<std::process::ExitStatus>, std::io::Error> {
|
||||
self.run_internal(self.disconnect.as_ref())
|
||||
}
|
||||
}
|
||||
26
src/config/event.rs
Normal file
26
src/config/event.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use crate::config::RunConfig;
|
||||
use crate::event::EventType;
|
||||
use crate::event::Event;
|
||||
use crate::util::SmartSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize,Debug,Clone)]
|
||||
pub struct EventConfig {
|
||||
pub run: Vec<RunConfig>,
|
||||
pub r#type: EventType,
|
||||
pub channel: Option<SmartSet<u8>>,
|
||||
pub id: Option<SmartSet<u8>>,
|
||||
// pub channels: BTreeSet<u8>,
|
||||
// pub ids: BTreeSet<u8>,
|
||||
// TODO: rework for value conditions (for pitch) ?
|
||||
//values: BTreeSet<u8>,
|
||||
//values: Condition,
|
||||
}
|
||||
|
||||
impl EventConfig {
|
||||
pub fn matches(&self, event: &Event) -> bool {
|
||||
//TODO: value conditions don't exist yet
|
||||
true
|
||||
}
|
||||
}
|
||||
14
src/config/mod.rs
Normal file
14
src/config/mod.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
pub mod event;
|
||||
pub mod device;
|
||||
pub mod run;
|
||||
|
||||
pub type DeviceConfig = device::DeviceConfig;
|
||||
pub type EventConfig = event::EventConfig;
|
||||
pub type RunConfig = run::RunConfig;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize,Clone,Debug)]
|
||||
pub struct Config {
|
||||
pub devices: Vec<DeviceConfig>,
|
||||
}
|
||||
29
src/config/run.rs
Normal file
29
src/config/run.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use std::collections::HashMap;
|
||||
use std::process::Command;
|
||||
|
||||
use serde::{Serialize,Deserialize};
|
||||
|
||||
#[derive(Serialize,Deserialize,Debug,Clone)]
|
||||
pub struct RunConfig {
|
||||
pub args: Option<Vec<String>>,
|
||||
pub shell: Option<String>,
|
||||
pub envconf: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl RunConfig {
|
||||
pub fn run(&self, env: HashMap<&str, String>) -> Result<std::process::ExitStatus, std::io::Error> {
|
||||
// TODO: proper error handling
|
||||
if self.args.is_some() {
|
||||
let args = self.args.as_ref().unwrap();
|
||||
Command::new(&args[0]).args(&args[1..]).envs(env).status()
|
||||
}
|
||||
else if self.shell.is_some() {
|
||||
let args = crate::run::cross_shell(self.shell.as_ref().unwrap());
|
||||
Command::new(&args[0]).args(&args[1..]).envs(env).status()
|
||||
}
|
||||
else {
|
||||
panic!("unexpected execution failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
129
src/event.rs
Normal file
129
src/event.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
use std::{collections::HashMap, time::SystemTime};
|
||||
use std::fmt::Write;
|
||||
|
||||
use serde::{Serialize,Deserialize};
|
||||
|
||||
pub fn event_to_key(r#type: EventType, channel: u8, id: u8) -> u32 {
|
||||
(r#type as u32)*256*256 + (channel as u32)*256 + (id as u32)
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Serialize,Deserialize,Debug,Copy,Clone)]
|
||||
pub enum EventType {
|
||||
Unknown = 0b0000,
|
||||
NoteOff = 0b1000,
|
||||
NoteOn = 0b1001,
|
||||
PolyphonicKeyPressure = 0b1010,
|
||||
Controller = 0b1011,
|
||||
ProgramChange = 0b1100,
|
||||
ChannelPressure = 0b1101,
|
||||
PitchBend = 0b1110,
|
||||
System = 0b1111,
|
||||
}
|
||||
|
||||
impl EventType {
|
||||
pub fn has_id(&self) -> bool {
|
||||
match self {
|
||||
EventType::Unknown | EventType::ProgramChange | EventType::PitchBend | EventType::System => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
pub fn has_channel(&self) -> bool {
|
||||
match self {
|
||||
EventType::Unknown | EventType::ProgramChange | EventType::System => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Event<'a> {
|
||||
pub r#type: EventType,
|
||||
pub channel: u8,
|
||||
pub id: u8,
|
||||
pub value: u16,
|
||||
pub raw: &'a [u8],
|
||||
pub timestamp: Option<SystemTime>,
|
||||
}
|
||||
|
||||
impl From<u8> for EventType {
|
||||
fn from(v: u8) -> Self {
|
||||
if ! (0b1000..=0b1111).contains(&v) {
|
||||
// not in defined space: unknown
|
||||
EventType::Unknown
|
||||
}
|
||||
else {
|
||||
// safe since all valid cases are defined
|
||||
unsafe { std::mem::transmute(v) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes_to_strhex(bytes: &[u8]) -> String {
|
||||
let mut s = String::new();
|
||||
for &byte in bytes {
|
||||
write!(&mut s, "{:X} ", byte).expect("Unable to write");
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
impl<'a> Event<'a> {
|
||||
pub fn new() -> Self {
|
||||
Event {
|
||||
r#type: EventType::Unknown,
|
||||
channel: 0,
|
||||
id: 0,
|
||||
value: 0,
|
||||
raw: &[],
|
||||
timestamp: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(&self) -> u32 {
|
||||
event_to_key(self.r#type, self.channel, self.id)
|
||||
}
|
||||
pub fn gen_env(&self) -> HashMap<&str, String> {
|
||||
let mut ret = HashMap::new();
|
||||
//TODO: type?
|
||||
ret.insert("channel", self.channel.to_string());
|
||||
ret.insert("id", self.id.to_string());
|
||||
ret.insert("value", self.value.to_string());
|
||||
ret.insert("raw", bytes_to_strhex(self.raw));
|
||||
ret.insert("timestamp", self.timestamp.unwrap_or(SystemTime::now()).duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs_f64().to_string());
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [u8]> for Event<'a> {
|
||||
fn from(v: &'a [u8]) -> Event<'a> {
|
||||
let channel = v[0]%16;
|
||||
let event_type = EventType::from(v[0]/16);
|
||||
let (id, value) = match event_type {
|
||||
EventType::PitchBend => {
|
||||
(0, (v[2] as u16)*256 + (v[1] as u16) )
|
||||
},
|
||||
EventType::Unknown => {
|
||||
match v.len() > 0 {
|
||||
true => eprintln!("warn: unknown signal type: {}", v[0]),
|
||||
false => eprintln!("warn: empty signal"),
|
||||
};
|
||||
(0,0)
|
||||
}
|
||||
EventType::System => (0,0),
|
||||
EventType::PolyphonicKeyPressure |
|
||||
EventType::ChannelPressure |
|
||||
EventType::ProgramChange => {
|
||||
todo!()
|
||||
}
|
||||
EventType::NoteOn | EventType::NoteOff | EventType::Controller => (v[1],(v[2] as u16)),
|
||||
};
|
||||
Event {
|
||||
r#type: event_type,
|
||||
channel,
|
||||
id,
|
||||
value,
|
||||
raw: v,
|
||||
timestamp: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
112
src/eventmap.rs
Normal file
112
src/eventmap.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::config::{EventConfig,DeviceConfig};
|
||||
use crate::event::{EventType,Event};
|
||||
use crate::util::SmartSet;
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
static ref NOTE_DEFAULT_MAP: SmartSet<u8> = SmartSet {
|
||||
set: BTreeSet::from((0..=127).collect::<BTreeSet<u8>>()),
|
||||
};
|
||||
static ref NOTE_DEFAULT_MAP_SIZE: usize = NOTE_DEFAULT_MAP.len();
|
||||
static ref NULL_DEFAULT_MAP: SmartSet<u8> = SmartSet {
|
||||
set: BTreeSet::from([0]),
|
||||
};
|
||||
static ref NULL_DEFAULT_MAP_SIZE: usize = NULL_DEFAULT_MAP.len();
|
||||
static ref CHANNEL_DEFAULT_MAP: SmartSet<u8> = SmartSet {
|
||||
set: BTreeSet::from((0..=15).collect::<BTreeSet<u8>>()),
|
||||
};
|
||||
static ref CHANNEL_DEFAULT_MAP_SIZE: usize = CHANNEL_DEFAULT_MAP.len();
|
||||
}
|
||||
|
||||
#[derive(Debug,Default)]
|
||||
pub struct EventMap<'a> {
|
||||
//TODO: vec support
|
||||
pub map: HashMap<u32, Vec<&'a EventConfig>>,
|
||||
}
|
||||
|
||||
fn event_to_key(r#type: EventType, channel: u8, id: u8) -> u32 {
|
||||
(r#type as u32)*256*256 + (channel as u32)*256 + (id as u32)
|
||||
}
|
||||
|
||||
pub fn count_events(events: &[EventConfig]) -> usize {
|
||||
events.iter().map(|x| {
|
||||
let nchannel = match x.r#type.has_channel() {
|
||||
true => x.channel.as_ref().map_or(*CHANNEL_DEFAULT_MAP_SIZE, |x| x.len()),
|
||||
false => *CHANNEL_DEFAULT_MAP_SIZE,
|
||||
};
|
||||
let nid = match x.r#type.has_id() {
|
||||
true => x.id.as_ref().map_or(*NOTE_DEFAULT_MAP_SIZE, |x| x.len()),
|
||||
false => *NULL_DEFAULT_MAP_SIZE,
|
||||
};
|
||||
nchannel * nid
|
||||
}).sum()
|
||||
}
|
||||
|
||||
impl<'a> EventMap<'a> {
|
||||
pub fn add_events(&mut self, events: &'a [EventConfig]) {
|
||||
for event in events {
|
||||
for &channel in match event.r#type.has_id() {
|
||||
true => event.channel.as_ref().unwrap_or(&CHANNEL_DEFAULT_MAP),
|
||||
false => &CHANNEL_DEFAULT_MAP,
|
||||
} {
|
||||
for &id in
|
||||
match event.r#type.has_id() {
|
||||
true => event.id.as_ref().unwrap_or(&NOTE_DEFAULT_MAP),
|
||||
false => &NULL_DEFAULT_MAP,
|
||||
} {
|
||||
let key = event_to_key(event.r#type, channel, id);
|
||||
if let Some(v) = self.map.get_mut(&key) {
|
||||
v.push(event);
|
||||
}
|
||||
else {
|
||||
self.map.insert(key, Vec::from([event]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_event(&self, event: &Event) -> Result<(), std::io::Error > {
|
||||
let key = event_to_key(event.r#type, event.channel, event.id);
|
||||
if let Some(v) = self.map.get(&key) {
|
||||
for ev in v {
|
||||
for r in &ev.run {
|
||||
r.run(event.gen_env())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [EventConfig]> for EventMap<'a> {
|
||||
fn from(events: &'a [EventConfig]) -> Self {
|
||||
// init hashmap with size for optimizing
|
||||
let size = count_events(events);
|
||||
let mut ret = EventMap { map: HashMap::with_capacity(size) };
|
||||
// insert references
|
||||
ret.add_events(events);
|
||||
ret
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl<'a> From<&'a DeviceConfig> for EventMap<'a> {
|
||||
fn from(device: &'a DeviceConfig) -> Self {
|
||||
// init hashmap with size for optimizing
|
||||
let size = count_events(device.events.as_ref().map(|x| &x[..]).unwrap_or(&[]));
|
||||
//let size = events.iter().map(|x| x.channels.len()*x.ids.len() ).sum();
|
||||
let mut ret = EventMap { map: HashMap::with_capacity(size) };
|
||||
// insert references
|
||||
if let Some(x) = device.events.as_ref() {
|
||||
ret.add_events(x);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
}
|
||||
85
src/main.rs
Normal file
85
src/main.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
use std::sync::mpsc;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::thread;
|
||||
use std::sync::Mutex;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub mod config;
|
||||
pub mod run;
|
||||
pub mod event;
|
||||
pub mod eventmap;
|
||||
pub mod midi;
|
||||
pub mod util;
|
||||
pub mod cli;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use midi::{MidiHandler,MidiPortHandler};
|
||||
use config::{Config,DeviceConfig};
|
||||
use eventmap::EventMap;
|
||||
use cli::Cli;
|
||||
|
||||
fn main() {
|
||||
let c = Cli::parse();
|
||||
match run(&c.map_file) {
|
||||
Ok(_) => (),
|
||||
Err(err) => println!("Error: {}", err)
|
||||
}
|
||||
}
|
||||
|
||||
fn run(filepath: &Path) -> Result<(), Box<dyn Error>> {
|
||||
println!("Load file {}", filepath.to_str().unwrap());
|
||||
let dat = std::fs::read( filepath )?;
|
||||
|
||||
let conf: Config = serde_yaml::from_slice(&dat)?;
|
||||
let cfevmap: Vec<(&DeviceConfig, EventMap, Rc<Mutex<bool>>)> = conf.devices.iter().map(|x|
|
||||
(x, eventmap::EventMap::from(x), Rc::new(Mutex::new(false)))
|
||||
).collect();
|
||||
|
||||
let input = MidiHandler::new("rmidimap")?;
|
||||
|
||||
thread::scope(|s| -> Result<(), Box<dyn Error>> {
|
||||
let (tdev,rdev) = mpsc::channel::<MidiPortHandler>();
|
||||
let mut threads: Vec<(thread::ScopedJoinHandle<'_, ()>, mpsc::Sender<bool>)> = Vec::new();
|
||||
let ports = input.ports()?;
|
||||
// TODO: centralize connection process in one place
|
||||
// TODO: "multiconnect=false" handling
|
||||
for p in ports {
|
||||
for (dev, eventmap, m) in &cfevmap {
|
||||
if let Some(mut c) = input.try_connect(p.clone(), midi::PortFilter::from(*dev))? {
|
||||
let (sts,srs) = mpsc::channel::<bool>();
|
||||
let nsts = sts.clone();
|
||||
let t = s.spawn( move || {
|
||||
dev.run_connect().unwrap();
|
||||
c.run(eventmap, (srs,nsts)).unwrap();
|
||||
dev.run_disconnect().unwrap();
|
||||
});
|
||||
threads.push((t, sts));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let _event_thread = s.spawn(|| {
|
||||
let mut input = MidiHandler::new("rmidimap-event-watcher").unwrap();
|
||||
input.device_events(tdev).unwrap();
|
||||
});
|
||||
loop {
|
||||
let e = rdev.recv()?;
|
||||
for (dev, eventmap, m) in &cfevmap {
|
||||
if let Some(mut c) = input.try_connect(e.clone(), midi::PortFilter::from(*dev))? {
|
||||
let (sts,srs) = mpsc::channel::<bool>();
|
||||
let nsts = sts.clone();
|
||||
let t = s.spawn( move || {
|
||||
dev.run_connect().unwrap();
|
||||
c.run(eventmap, (srs,nsts)).unwrap();
|
||||
dev.run_disconnect().unwrap();
|
||||
});
|
||||
threads.push((t, sts));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
366
src/midi/alsa.rs
Normal file
366
src/midi/alsa.rs
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
extern crate libc;
|
||||
extern crate alsa;
|
||||
|
||||
use std::{mem, thread};
|
||||
use std::ffi::{CString, CStr};
|
||||
use std::time::SystemTime;
|
||||
use std::sync::mpsc;
|
||||
|
||||
use crate::midi::{MidiInput,MidiInputHandler,MidiPort,PortFilter,MidiPortHandler,MidiAddrHandler};
|
||||
|
||||
use alsa::{Seq, Direction};
|
||||
use alsa::seq::{ClientIter, PortIter, MidiEvent, PortInfo, PortSubscribe, Addr, QueueTempo, EventType, PortCap, PortType};
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
pub type DeviceAddr = alsa::seq::Addr;
|
||||
|
||||
pub fn get_ports(s: &Seq, capability: PortCap) -> Vec<PortInfo> {
|
||||
ClientIter::new(s).flat_map(|c| PortIter::new(s, c.get_client()))
|
||||
.filter(|p| p.get_capability().contains(capability))
|
||||
.collect()
|
||||
}
|
||||
|
||||
mod helpers {
|
||||
pub fn poll(fds: &mut [super::libc::pollfd], timeout: i32) -> i32 {
|
||||
unsafe { super::libc::poll(fds.as_mut_ptr(), fds.len() as super::libc::nfds_t, timeout) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MidiInputAlsa {
|
||||
seq: Seq,
|
||||
queue_id: i32,
|
||||
subscription: Option<PortSubscribe>,
|
||||
connect_addr: Option<Addr>,
|
||||
stop_trigger: [i32;2],
|
||||
}
|
||||
|
||||
impl Drop for MidiInputAlsa {
|
||||
fn drop(&mut self) {
|
||||
self.close_internal();
|
||||
}
|
||||
}
|
||||
|
||||
impl MidiInputAlsa {
|
||||
fn init_trigger(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
let mut trigger_fds = [-1, -1];
|
||||
if unsafe { self::libc::pipe(trigger_fds.as_mut_ptr()) } == -1 {
|
||||
todo!()
|
||||
} else {
|
||||
self.stop_trigger = trigger_fds;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn init_queue(&mut self) -> i32 {
|
||||
let mut queue_id = 0;
|
||||
// Create the input queue
|
||||
if !cfg!(feature = "avoid_timestamping") {
|
||||
queue_id = self.seq.alloc_named_queue(unsafe { CStr::from_bytes_with_nul_unchecked(b"midir queue\0") }).unwrap();
|
||||
// Set arbitrary tempo (mm=100) and resolution (240)
|
||||
let qtempo = QueueTempo::empty().unwrap();
|
||||
qtempo.set_tempo(600_000);
|
||||
qtempo.set_ppq(240);
|
||||
self.seq.set_queue_tempo(queue_id, &qtempo).unwrap();
|
||||
let _ = self.seq.drain_output();
|
||||
}
|
||||
|
||||
queue_id
|
||||
}
|
||||
|
||||
fn start_input_queue(&mut self, queue_id: i32) {
|
||||
if !cfg!(feature = "avoid_timestamping") {
|
||||
let _ = self.seq.control_queue(queue_id, EventType::Start, 0, None);
|
||||
let _ = self.seq.drain_output();
|
||||
}
|
||||
}
|
||||
|
||||
fn create_port(&mut self, port_name: &CStr, queue_id: i32) -> Result<i32, Box<dyn Error>> {
|
||||
let mut pinfo = PortInfo::empty().unwrap();
|
||||
// these functions are private, and the values are zeroed already by `empty()`
|
||||
//pinfo.set_client(0);
|
||||
//pinfo.set_port(0);
|
||||
pinfo.set_capability(PortCap::WRITE | PortCap::SUBS_WRITE);
|
||||
pinfo.set_type(PortType::MIDI_GENERIC | PortType::APPLICATION);
|
||||
pinfo.set_midi_channels(16);
|
||||
|
||||
if !cfg!(feature = "avoid_timestamping") {
|
||||
pinfo.set_timestamping(true);
|
||||
pinfo.set_timestamp_real(true);
|
||||
pinfo.set_timestamp_queue(queue_id);
|
||||
}
|
||||
|
||||
pinfo.set_name(port_name);
|
||||
match self.seq.create_port(&pinfo) {
|
||||
Ok(_) => Ok(pinfo.get_port()),
|
||||
Err(v) => Err(Box::new(v))
|
||||
}
|
||||
}
|
||||
|
||||
fn close_internal(&mut self)
|
||||
{
|
||||
if let Some(ref subscription) = self.subscription {
|
||||
let _ = self.seq.unsubscribe_port(subscription.get_sender(), subscription.get_dest());
|
||||
}
|
||||
|
||||
// Stop and free the input queue
|
||||
if !cfg!(feature = "avoid_timestamping") {
|
||||
let _ = self.seq.control_queue(self.queue_id, EventType::Stop, 0, None);
|
||||
let _ = self.seq.drain_output();
|
||||
let _ = self.seq.free_queue(self.queue_id);
|
||||
}
|
||||
|
||||
for fd in self.stop_trigger {
|
||||
if fd >= 0 {
|
||||
unsafe { self::libc::close(fd) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn signal_stop_input_internal(stop_trigger: i32) -> Result<(), Box<dyn Error>> {
|
||||
if unsafe { self::libc::write(stop_trigger, &false as *const bool as *const _, mem::size_of::<bool>() as self::libc::size_t) } == -1 {
|
||||
todo!()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn alsa_input_handler<F, D>(&mut self, callback: F, mut userdata: D) -> Result<(), Box<dyn Error>>
|
||||
where F: Fn(&Self, alsa::seq::Event, &mut D) -> bool {
|
||||
// fd defitions
|
||||
use self::alsa::PollDescriptors;
|
||||
use self::libc::pollfd;
|
||||
const INVALID_POLLFD: pollfd = pollfd {
|
||||
fd: -1,
|
||||
events: 0,
|
||||
revents: 0,
|
||||
};
|
||||
|
||||
let mut seq_input = self.seq.input();
|
||||
|
||||
// make poll fds
|
||||
let poll_desc_info = (&self.seq, Some(Direction::Capture));
|
||||
let mut poll_fds = vec![INVALID_POLLFD; poll_desc_info.count()+1];
|
||||
poll_fds[0] = pollfd {
|
||||
fd: self.stop_trigger[0],
|
||||
events: self::libc::POLLIN,
|
||||
revents: 0,
|
||||
};
|
||||
poll_desc_info.fill(&mut poll_fds[1..]).unwrap();
|
||||
|
||||
loop {
|
||||
if let Ok(0) = seq_input.event_input_pending(true) {
|
||||
// No data pending: wait
|
||||
if helpers::poll(&mut poll_fds, -1) >= 0 {
|
||||
// Read stop event from triggerer
|
||||
if poll_fds[0].revents & self::libc::POLLIN != 0 {
|
||||
let mut pollread = false;
|
||||
let _res = unsafe { self::libc::read(poll_fds[0].fd, mem::transmute(&mut pollread), mem::size_of::<bool>() as self::libc::size_t) };
|
||||
if pollread == false {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// get event
|
||||
let ev = seq_input.event_input()?;
|
||||
|
||||
// handle disconnect event on watched port
|
||||
if ev.get_type() == EventType::PortUnsubscribed {
|
||||
if let Some(c) = ev.get_data::<alsa::seq::Connect>() {
|
||||
if c.sender == self.connect_addr.unwrap() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (callback)(self, ev, &mut userdata) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_input_internal<F, D>(&mut self, callback: F, userdata: D) -> Result<(), Box<dyn Error>>
|
||||
where F: Fn(Option<SystemTime>, &[u8], &mut D) + Send {
|
||||
let decoder = MidiEvent::new(0).unwrap();
|
||||
decoder.enable_running_status(false);
|
||||
|
||||
let message = vec!();
|
||||
let buffer: [u8;12] = [0;12];
|
||||
let continue_sysex = false;
|
||||
|
||||
self.alsa_input_handler(|_, mut ev, (message, buffer, continue_sysex, userdata)| {
|
||||
if !*continue_sysex { message.clear() }
|
||||
|
||||
let do_decode = match ev.get_type() {
|
||||
EventType::PortSubscribed |
|
||||
EventType::PortUnsubscribed |
|
||||
EventType::Qframe |
|
||||
EventType::Tick |
|
||||
EventType::Clock |
|
||||
EventType::Sensing => false,
|
||||
EventType::Sysex => {
|
||||
message.extend_from_slice(ev.get_ext().unwrap());
|
||||
*continue_sysex = *message.last().unwrap() != 0xF7;
|
||||
false
|
||||
}
|
||||
_ => true
|
||||
};
|
||||
|
||||
// NOTE: SysEx messages have already been "decoded" at this point!
|
||||
if do_decode {
|
||||
let nbytes = decoder.decode(buffer, &mut ev).unwrap();
|
||||
if nbytes > 0 {
|
||||
message.extend_from_slice(&buffer[0..nbytes+1]);
|
||||
}
|
||||
}
|
||||
|
||||
if message.len() == 0 || *continue_sysex { return false; }
|
||||
|
||||
let alsa_time = ev.get_time().unwrap();
|
||||
let secs = alsa_time.as_secs();
|
||||
let nsecs = alsa_time.subsec_nanos();
|
||||
let timestamp = ( secs as u64 * 1_000_000 ) + ( nsecs as u64 / 1_000 );
|
||||
//TODO: translate to SystemTime?
|
||||
(callback)(None, &message, userdata);
|
||||
false
|
||||
}
|
||||
, (message, buffer, continue_sysex, userdata))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MidiInput<Addr> for MidiInputAlsa {
|
||||
fn new(client_name: &str) -> Result<Self, Box<dyn Error>> {
|
||||
let seq = match Seq::open(None, None, true) {
|
||||
Ok(s) => s,
|
||||
Err(_) => todo!(),
|
||||
};
|
||||
|
||||
let c_client_name = CString::new(client_name)?;
|
||||
seq.set_client_name(&c_client_name)?;
|
||||
|
||||
Ok(MidiInputAlsa {
|
||||
seq: seq,
|
||||
queue_id: 0,
|
||||
subscription: None,
|
||||
connect_addr: None,
|
||||
stop_trigger: [-1,-1],
|
||||
})
|
||||
}
|
||||
|
||||
fn close(mut self) -> Result<(), Box<dyn Error>> {
|
||||
self.close_internal();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn ports_handle(&self) -> Vec<MidiPortHandler> {
|
||||
get_ports(&self.seq, PortCap::READ | PortCap::SUBS_READ).iter().map(|x| {
|
||||
let cinfo = self.seq.get_any_client_info(x.get_client()).unwrap();
|
||||
MidiPortHandler::ALSA( MidiPort{
|
||||
name: cinfo.get_name().unwrap().to_string()+":"+x.get_name().unwrap(),
|
||||
addr: x.addr(),
|
||||
})
|
||||
}).collect()
|
||||
}
|
||||
|
||||
fn ports(&self) -> Vec<MidiPort<Addr>> {
|
||||
get_ports(&self.seq, PortCap::READ | PortCap::SUBS_READ).iter().map(|x| {
|
||||
let cinfo = self.seq.get_any_client_info(x.get_client()).unwrap();
|
||||
MidiPort {
|
||||
name: cinfo.get_name().unwrap().to_string()+":"+x.get_name().unwrap(),
|
||||
addr: x.addr(),
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
fn filter_ports(&self, mut ports: Vec<MidiPort<Addr>>, filter: PortFilter) -> Vec<MidiPort<Addr>> {
|
||||
ports.retain(
|
||||
|p| {
|
||||
match &filter {
|
||||
PortFilter::Name(s) => p.name.find(s).is_some(),
|
||||
PortFilter::Addr(MidiAddrHandler::ALSA(s)) => p.addr == *s,
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
);
|
||||
ports
|
||||
}
|
||||
|
||||
fn connect(&mut self, port_addr: &Addr, port_name: &str) -> Result<(), Box<dyn Error>> {
|
||||
let src_pinfo = self.seq.get_any_port_info(*port_addr)?;
|
||||
let queue_id = self.init_queue();
|
||||
let c_port_name = CString::new(port_name)?;
|
||||
let vport = self.create_port(&c_port_name, queue_id)?;
|
||||
|
||||
let sub = PortSubscribe::empty().unwrap();
|
||||
sub.set_sender(src_pinfo.addr());
|
||||
sub.set_dest(Addr { client: self.seq.client_id().unwrap(), port: vport});
|
||||
self.seq.subscribe_port(&sub)?;
|
||||
self.subscription = Some(sub);
|
||||
self.init_trigger()?;
|
||||
self.connect_addr = Some(*port_addr);
|
||||
self.start_input_queue(queue_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn device_events(&mut self, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Box<dyn Error>> {
|
||||
let ports = self.ports();
|
||||
let port = self.filter_ports(ports, PortFilter::Name("System:Announce".to_string()));
|
||||
self.connect(&port[0].addr, "rmidimap-alsa-announce")?;
|
||||
self.alsa_input_handler(|s, ev, _|{
|
||||
// handle disconnect event on watched port
|
||||
match ev.get_type() {
|
||||
// EventType::PortStart | EventType::ClientStart | EventType::PortExit | EventType::ClientExit => {
|
||||
EventType::PortStart => {
|
||||
if let Some(a) = ev.get_data::<alsa::seq::Addr>() {
|
||||
let p = s.ports();
|
||||
let pp = s.filter_ports(p, PortFilter::Addr( MidiAddrHandler::ALSA(a.clone()) ));
|
||||
if pp.len() > 0 {
|
||||
ts.send(MidiPortHandler::ALSA(pp[0].clone())).unwrap();
|
||||
}
|
||||
};
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}, ())?;
|
||||
self.close_internal();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MidiInputHandler for MidiInputAlsa
|
||||
{
|
||||
fn signal_stop_input(&self) -> Result<(), Box<dyn Error>> {
|
||||
if unsafe { self::libc::write(self.stop_trigger[1], &false as *const bool as *const _, mem::size_of::<bool>() as self::libc::size_t) } == -1 {
|
||||
todo!()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_input<F, D>(&mut self, callback: F, (rs, ts): (mpsc::Receiver<bool>, mpsc::Sender<bool>), userdata: D) -> Result<(), Box<dyn Error>>
|
||||
where
|
||||
F: Fn(Option<SystemTime>, &[u8], &mut D) + Send,
|
||||
D: Send,
|
||||
{
|
||||
thread::scope( |sc| -> Result<(), Box<dyn Error>> {
|
||||
let stop_trigger = self.stop_trigger[1];
|
||||
let t = sc.spawn(move || {
|
||||
let userdata = userdata;
|
||||
self.handle_input_internal(callback, userdata).unwrap();
|
||||
ts.send(false).unwrap();
|
||||
});
|
||||
match rs.recv()? {
|
||||
true => Self::signal_stop_input_internal(stop_trigger)?,
|
||||
false => ()
|
||||
};
|
||||
t.join().unwrap();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
217
src/midi/mod.rs
Normal file
217
src/midi/mod.rs
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
pub mod alsa;
|
||||
|
||||
use crate::midi::alsa::MidiInputAlsa;
|
||||
|
||||
extern crate libc;
|
||||
|
||||
use crate::config::DeviceConfig;
|
||||
use crate::eventmap::EventMap;
|
||||
use crate::event::Event;
|
||||
|
||||
use std::error::Error;
|
||||
use std::time::SystemTime;
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[derive(Eq,PartialEq,Debug,Clone)]
|
||||
pub struct MidiPort<T>{
|
||||
pub name: String,
|
||||
pub addr: T,
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub enum PortFilter {
|
||||
Name(String),
|
||||
Regex(String),
|
||||
Addr(MidiAddrHandler),
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone,Eq,PartialEq)]
|
||||
pub enum MidiHandlerDriver {
|
||||
ALSA,
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone,Eq,PartialEq)]
|
||||
pub enum MidiAddrHandler {
|
||||
ALSA(alsa::DeviceAddr),
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone,Eq,PartialEq)]
|
||||
pub enum MidiPortHandler {
|
||||
ALSA(MidiPort<alsa::DeviceAddr>),
|
||||
}
|
||||
|
||||
pub enum MidiHandler {
|
||||
ALSA(MidiInputAlsa),
|
||||
}
|
||||
|
||||
impl From<MidiPortHandler> for MidiAddrHandler {
|
||||
fn from(a: MidiPortHandler) -> Self {
|
||||
match a {
|
||||
MidiPortHandler::ALSA(p) => MidiAddrHandler::ALSA(p.addr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&DeviceConfig> for PortFilter {
|
||||
fn from(conf: &DeviceConfig) -> Self {
|
||||
if conf.name.is_some() {
|
||||
PortFilter::Name(conf.name.clone().unwrap_or(String::new()))
|
||||
}
|
||||
else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod helper {
|
||||
}
|
||||
|
||||
|
||||
pub trait MidiInput<T> {
|
||||
fn new(client_name: &str) -> Result<Self, Box<dyn Error>>
|
||||
where Self: Sized;
|
||||
|
||||
fn close(self) -> Result<(), Box<dyn Error>>;
|
||||
|
||||
fn ports(&self) -> Vec<MidiPort<T>>;
|
||||
fn ports_handle(&self) -> Vec<MidiPortHandler>;
|
||||
|
||||
fn filter_ports<'a>(&self, ports: Vec<MidiPort<T>>, filter: PortFilter) -> Vec<MidiPort<T>>;
|
||||
|
||||
fn connect(&mut self, port_addr: &T, port_name: &str) -> Result<(), Box<dyn Error>>;
|
||||
|
||||
fn device_events(&mut self, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Box<dyn Error>>;
|
||||
}
|
||||
|
||||
pub trait MidiInputHandler {
|
||||
fn signal_stop_input(&self) -> Result<(), Box<dyn Error>>;
|
||||
|
||||
fn handle_input<F, D>(&mut self, callback: F, rts: (mpsc::Receiver<bool>, mpsc::Sender<bool>), userdata: D) -> Result<(), Box<dyn Error>>
|
||||
where
|
||||
F: Fn(Option<SystemTime>, &[u8], &mut D) + Send,
|
||||
D: Send,
|
||||
;
|
||||
}
|
||||
|
||||
macro_rules! handler_try_connect {
|
||||
( $m:expr , $filter:expr, $port:expr, $( $handler:ident ),+ ) => {
|
||||
match $m {
|
||||
$(
|
||||
MidiHandler::$handler(v) => {
|
||||
match $port {
|
||||
MidiPortHandler::$handler(_) => {
|
||||
let maddr = MidiAddrHandler::from($port);
|
||||
let portmap = v.ports();
|
||||
let pv = v.filter_ports(portmap, PortFilter::Addr(maddr));
|
||||
let pv = v.filter_ports(pv, $filter);
|
||||
if pv.len() > 0 {
|
||||
let port = &pv[0];
|
||||
let mut h = MidiHandler::new_with_driver("rmidimap-handler", MidiHandlerDriver::$handler)?;
|
||||
println!("Connect to device {}", port.name);
|
||||
match &mut h {
|
||||
MidiHandler::$handler(v) => {
|
||||
v.connect(&port.addr, "rmidimap-handler").unwrap();
|
||||
Ok(Some(h))
|
||||
}
|
||||
_ => panic!("unexpected midi driver failure"),
|
||||
}
|
||||
}
|
||||
else {
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
_ => panic!("unexpected midi driver failure"),
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! handler_fcall {
|
||||
( $m:expr , $fct:expr , $arg:expr , $( $handler:ident ),+ ) => {
|
||||
match $m {
|
||||
$(
|
||||
MidiHandler::$handler(v) => $fct(v, $arg),
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl MidiHandler {
|
||||
pub fn new(name: &str) -> Result<Self, Box<dyn Error>> {
|
||||
Self::new_with_driver(name, MidiHandlerDriver::ALSA)
|
||||
}
|
||||
|
||||
pub fn new_with_driver(name: &str, driver: MidiHandlerDriver) -> Result<Self, Box<dyn Error>> {
|
||||
match driver {
|
||||
MidiHandlerDriver::ALSA => Ok(MidiHandler::ALSA(MidiInputAlsa::new(name)?)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ports(&self) -> Result<Vec<MidiPortHandler>, Box<dyn Error>> {
|
||||
handler_fcall!{
|
||||
self, handle_port_list ,(),
|
||||
ALSA
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_connect(&self, addr: MidiPortHandler, filter: PortFilter) -> Result<Option<MidiHandler>, Box<dyn Error>> {
|
||||
let r: Result<Option<MidiHandler>, Box<dyn Error>> = handler_try_connect!{
|
||||
self, filter, addr,
|
||||
ALSA
|
||||
};
|
||||
r
|
||||
}
|
||||
|
||||
pub fn run(&mut self, eventmap: &EventMap, (rs,ts): (mpsc::Receiver<bool>, mpsc::Sender<bool>)) -> Result<(), Box<dyn Error>> {
|
||||
handler_fcall!{
|
||||
self, handle_inputport ,(eventmap,(rs,ts)),
|
||||
ALSA
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> Result<(), Box<dyn Error>> {
|
||||
handler_fcall!{
|
||||
self, handle_signal_stop, (),
|
||||
ALSA
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device_events(&mut self, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Box<dyn Error>> {
|
||||
handler_fcall!{
|
||||
self, device_events, ts,
|
||||
ALSA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_port_list<T, A>(input: &T, _: ()) -> Result<Vec<MidiPortHandler>, Box<dyn Error>>
|
||||
where T: MidiInput<A>
|
||||
{
|
||||
Ok(input.ports_handle())
|
||||
}
|
||||
|
||||
fn handle_inputport<T>(input: &mut T, (eventmap, (rs, ts)): (&EventMap, (mpsc::Receiver<bool>, mpsc::Sender<bool>))) -> Result<(), Box<dyn Error>>
|
||||
where T: MidiInputHandler
|
||||
{
|
||||
input.handle_input(|t,m,_| {
|
||||
let mut event = Event::from(m);
|
||||
event.timestamp = t;
|
||||
eventmap.run_event(&event).unwrap();
|
||||
}, (rs,ts), ())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_signal_stop<T>(input: &T, _: ()) -> Result<(), Box<dyn Error>>
|
||||
where T: MidiInputHandler
|
||||
{
|
||||
input.signal_stop_input()
|
||||
}
|
||||
|
||||
fn device_events<T, A>(input: &mut T, ts: mpsc::Sender<MidiPortHandler>) -> Result<(), Box<dyn Error>>
|
||||
where T: MidiInput<A>
|
||||
{
|
||||
input.device_events(ts)
|
||||
}
|
||||
|
||||
10
src/run.rs
Normal file
10
src/run.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
pub fn cross_shell(cmd: &str) -> Vec<String> {
|
||||
if cfg!(target_os = "windows") {
|
||||
vec!("cmd", "/C", cmd)
|
||||
} else {
|
||||
vec!("sh", "-c", cmd)
|
||||
}
|
||||
.iter().map(
|
||||
|x| x.to_string()
|
||||
).collect()
|
||||
}
|
||||
234
src/util.rs
Normal file
234
src/util.rs
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use num::{Num,NumCast};
|
||||
|
||||
pub fn parse_int_set<T>(s: &str) -> Result<BTreeSet<T>, <T as std::str::FromStr>::Err>
|
||||
where
|
||||
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
|
||||
{
|
||||
|
||||
let mut r: BTreeSet<T> = BTreeSet::new();
|
||||
let parts: Vec<&str> = s.split(',').collect();
|
||||
for p in parts {
|
||||
if p.len() > 0 {
|
||||
if let Some(sep) = p.find('-') {
|
||||
let (p1,p2) = (&s[..sep], &s[sep+1..] );
|
||||
let (low,high): (T,T) = ( p1.parse()?, p2.parse()? );
|
||||
let (mut low,high) = match low <= high {
|
||||
true => (low,high),
|
||||
false => (high,low),
|
||||
};
|
||||
while low <= high {
|
||||
r.insert(low);
|
||||
low += T::one();
|
||||
}
|
||||
}
|
||||
else {
|
||||
r.insert(p.parse()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct SmartSet<T>
|
||||
where
|
||||
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
|
||||
{
|
||||
pub set: BTreeSet<T>
|
||||
}
|
||||
|
||||
impl<T> SmartSet<T>
|
||||
where
|
||||
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
set: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.set.len()
|
||||
}
|
||||
}
|
||||
|
||||
// impl<T> From<T> for SmartSet<T>
|
||||
// where
|
||||
// T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
|
||||
// {
|
||||
// fn from(i: T) -> Self {
|
||||
// let mut r = SmartSet::new();
|
||||
// r.set.insert(i);
|
||||
// r
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<T,U> From<U> for SmartSet<T>
|
||||
where
|
||||
T: Num+Ord+Copy+NumCast + std::str::FromStr + std::ops::AddAssign,
|
||||
U: Num+Ord+Copy+num::ToPrimitive + std::str::FromStr + std::ops::AddAssign,
|
||||
{
|
||||
fn from(i: U) -> Self {
|
||||
let mut r = SmartSet::<T>::new();
|
||||
r.set.insert(num::NumCast::from(i).unwrap());
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
// impl<T,U> From<&[U]> for SmartSet<T>
|
||||
// where
|
||||
// T: Num+Ord+Copy+NumCast + std::str::FromStr + std::ops::AddAssign,
|
||||
// U: Num+Ord+Copy+num::ToPrimitive + std::str::FromStr + std::ops::AddAssign,
|
||||
// {
|
||||
// fn from(i: &[U]) -> Self {
|
||||
// let mut r = SmartSet::<T>::new();
|
||||
// for v in i {
|
||||
// r.set.insert(num::NumCast::from(v).unwrap());
|
||||
// }
|
||||
// r
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<T> FromStr for SmartSet<T>
|
||||
where
|
||||
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
|
||||
{
|
||||
type Err = <T as FromStr>::Err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(SmartSet {
|
||||
set: parse_int_set(s)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoIterator for SmartSet<T>
|
||||
where
|
||||
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
|
||||
{
|
||||
type Item = T;
|
||||
type IntoIter = std::collections::btree_set::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.set.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoIterator for &'a SmartSet<T>
|
||||
where
|
||||
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
|
||||
{
|
||||
type Item = &'a T;
|
||||
type IntoIter = std::collections::btree_set::Iter<'a, T>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.set.iter()
|
||||
}
|
||||
}
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::fmt;
|
||||
use serde::de::{self,Deserialize, Deserializer, Visitor};
|
||||
|
||||
struct SmartSetVisitor<T>
|
||||
where
|
||||
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
|
||||
{
|
||||
marker: PhantomData<fn() -> SmartSet<T>>
|
||||
}
|
||||
|
||||
|
||||
impl<T> SmartSetVisitor<T>
|
||||
where
|
||||
T: Num+Ord+Copy + std::str::FromStr + std::ops::AddAssign,
|
||||
{
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
marker: PhantomData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! visit_from {
|
||||
( $( ($fct:ident, $type:ty) ),+ $(,)?) => {
|
||||
$(
|
||||
fn $fct<E>(self, value: $type) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(SmartSet::from(value))
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl<'de, T> Visitor<'de> for SmartSetVisitor<T>
|
||||
where
|
||||
T: Num+Ord+Copy+NumCast + Deserialize<'de> + std::str::FromStr + std::ops::AddAssign + std::fmt::Debug,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
type Value = SmartSet<T>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a set of integer")
|
||||
}
|
||||
|
||||
visit_from!{
|
||||
(visit_i8, i8),
|
||||
(visit_i16, i16),
|
||||
(visit_i32, i32),
|
||||
(visit_i64, i64),
|
||||
(visit_u8, u8),
|
||||
(visit_u16, u16),
|
||||
(visit_u32, u32),
|
||||
(visit_u64, u64),
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
SmartSet::from_str(value).map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
fn visit_seq<Z>(self, mut seq: Z) -> Result<Self::Value, Z::Error>
|
||||
where
|
||||
Z: serde::de::SeqAccess<'de>,
|
||||
{
|
||||
let _len = seq.size_hint();
|
||||
let mut r: SmartSet<T> = SmartSet::new();
|
||||
|
||||
loop {
|
||||
if let Ok(Some(value)) = seq.next_element::<String>() {
|
||||
r.set.extend(&SmartSet::<T>::from_str(&value).map_err(serde::de::Error::custom)?.set);
|
||||
}
|
||||
else if let Some(value) = seq.next_element()? {
|
||||
r.set.insert(value);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
// This is the trait that informs Serde how to deserialize MyMap.
|
||||
impl<'de, T> Deserialize<'de> for SmartSet<T>
|
||||
where
|
||||
T: Num+Ord+Copy+NumCast + Deserialize<'de> + std::str::FromStr + std::ops::AddAssign + std::fmt::Debug,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(SmartSetVisitor::<T>::new())
|
||||
}
|
||||
}
|
||||
20
test/vmpk.yml
Normal file
20
test/vmpk.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
devices:
|
||||
- name: 'VMPK Output:out'
|
||||
multiconnect: false
|
||||
connect:
|
||||
- args: [ "sh", "-c", "echo Hello world!" ]
|
||||
disconnect:
|
||||
- args: [ "sh", "-c", "echo Bye!" ]
|
||||
events:
|
||||
- type: Controller
|
||||
run:
|
||||
- args: [ "sh", "-c", "echo [$channel] Controller $id $value" ]
|
||||
- type: NoteOff
|
||||
run:
|
||||
- args: [ "sh", "-c", "echo [$channel] NoteOff $id" ]
|
||||
- type: NoteOn
|
||||
run:
|
||||
- args: [ "sh", "-c", "echo [$channel] NoteOn $id $value" ]
|
||||
- type: PitchBend
|
||||
run:
|
||||
- args: [ "sh", "-c", "echo [$channel] PitchBend $value" ]
|
||||
Loading…
Reference in a new issue