refactor: rework internal structure and add minor features
~ rework midi backend structure + add documentation + implement connect by address + add log_devices and log_events options
This commit is contained in:
parent
f01e867914
commit
e61163da88
21 changed files with 616 additions and 341 deletions
112
FORMAT.md
Normal file
112
FORMAT.md
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
# Format
|
||||||
|
|
||||||
|
yaml configuration format
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Log all device connections
|
||||||
|
[ log_devices: <bool> | default = false ]
|
||||||
|
|
||||||
|
# Midi backend to use. Currently only alsa
|
||||||
|
[ driver: alsa ]
|
||||||
|
|
||||||
|
# Device definitions
|
||||||
|
devices:
|
||||||
|
[ - <device_config> ... ]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `<device_config>`
|
||||||
|
|
||||||
|
Definition of one device with its config and corresponding events.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Find device by name with literal string
|
||||||
|
[ name: <string> ]
|
||||||
|
|
||||||
|
# Find device by name with regex
|
||||||
|
[ regex: <regex> ]
|
||||||
|
|
||||||
|
# Find device by exact address
|
||||||
|
[ addr: <string> ]
|
||||||
|
|
||||||
|
# Max number of devices to connect for this device definition.
|
||||||
|
[ max_connections: <int> | default = inf ]
|
||||||
|
|
||||||
|
# Max length of event queue for processing.
|
||||||
|
[ queue_length: <int> | default = 256 ]
|
||||||
|
|
||||||
|
# Time interval between executions.
|
||||||
|
# Actual interval can be longer if execution is longer than this value.
|
||||||
|
# Supports time extensions, example: 1s, 100ms...
|
||||||
|
[ interval: <duration> | default = 0 ]
|
||||||
|
|
||||||
|
# Log all midi events of device
|
||||||
|
[ log_events: <bool> | default = false ]
|
||||||
|
|
||||||
|
# Commands to run on device connect
|
||||||
|
connect:
|
||||||
|
[ - <run_config> ... ]
|
||||||
|
|
||||||
|
# Commands to run on device disconnect
|
||||||
|
disconnect:
|
||||||
|
[ - <run_config> ... ]
|
||||||
|
|
||||||
|
# Definitions of executions on MIDI events
|
||||||
|
events:
|
||||||
|
[ - <event_config> ... ]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `<event_config>`
|
||||||
|
|
||||||
|
Definition of one MIDI event condition and its corresponding executions.
|
||||||
|
```yaml
|
||||||
|
# Max number of devices to connect for this device definition.
|
||||||
|
[ max_connections: <int> | default = inf ]
|
||||||
|
|
||||||
|
# Max length of event queue for processing.
|
||||||
|
[ queue_length: <int> | default = 256 ]
|
||||||
|
|
||||||
|
# Time interval between executions.
|
||||||
|
# Actual interval can be longer if execution is longer than this value.
|
||||||
|
# Supports time extensions, example: 1s, 100ms...
|
||||||
|
[ interval: <duration> | default = 0ms ]
|
||||||
|
|
||||||
|
# Commands to run on device connect
|
||||||
|
connect:
|
||||||
|
[ - <run_config> ... ]
|
||||||
|
|
||||||
|
# Commands to run on device disconnect
|
||||||
|
disconnect:
|
||||||
|
[ - <run_config> ... ]
|
||||||
|
|
||||||
|
# Definitions of executions on MIDI events
|
||||||
|
events:
|
||||||
|
[ - <event_config> ... ]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `<event_config>`
|
||||||
|
|
||||||
|
Definition of one MIDI event condition and its corresponding executions.
|
||||||
|
```yaml
|
||||||
|
# Max number of devices to connect for this device definition.
|
||||||
|
[ max_connections: <int> | default = inf ]
|
||||||
|
|
||||||
|
# Max length of event queue for processing.
|
||||||
|
[ queue_length: <int> | default = 256 ]
|
||||||
|
|
||||||
|
# Time interval between executions.
|
||||||
|
# Actual interval can be longer if execution is longer than this value.
|
||||||
|
# Supports time extensions, example: 1s, 100ms...
|
||||||
|
[ interval: <duration> | default = 0ms ]
|
||||||
|
|
||||||
|
# Commands to run on device connect
|
||||||
|
connect:
|
||||||
|
[ - <run_config> ... ]
|
||||||
|
|
||||||
|
# Commands to run on device disconnect
|
||||||
|
disconnect:
|
||||||
|
[ - <run_config> ... ]
|
||||||
|
|
||||||
|
# Definitions of executions on MIDI events
|
||||||
|
events:
|
||||||
|
[ - <event_config> ... ]
|
||||||
|
```
|
||||||
40
README.md
Normal file
40
README.md
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# rmidimap
|
||||||
|
|
||||||
|
Map MIDI signals to command with a simple yaml file.
|
||||||
|
|
||||||
|
See [format]() and [examples](/examples/).
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
Simply execute `rmidimap <FILE>` to start with the desired map file.
|
||||||
|
|
||||||
|
# Features
|
||||||
|
|
||||||
|
### MIDI backends
|
||||||
|
|
||||||
|
Only Linux+ALSA currently.
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
rmidimap runs with very low processing overhead.
|
||||||
|
Processing overhead has been measured at 100-200µs, while execution spawning was measured to 1-4ms.
|
||||||
|
|
||||||
|
### Device connection
|
||||||
|
|
||||||
|
Connect to devices by name or regex, and run commands on connect or disconnect.
|
||||||
|
|
||||||
|
### Command queue and interval
|
||||||
|
|
||||||
|
With the parameters `queue_length` and `interval`,
|
||||||
|
you can limit event throughput to reduce system load associated with the command being run.
|
||||||
|
|
||||||
|
# Building from source
|
||||||
|
|
||||||
|
You need rustc and cargo to build the project.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
- Clone this repository
|
||||||
|
- `cargo build -r`
|
||||||
|
- `sudo mv target/release/rmidimap /usr/local/bin/rmidimap`
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
|
log_devices: true
|
||||||
devices:
|
devices:
|
||||||
|
- name: 'System'
|
||||||
|
log_events: true
|
||||||
- name: 'VMPK'
|
- name: 'VMPK'
|
||||||
|
log_events: true
|
||||||
max_connections: 1
|
max_connections: 1
|
||||||
queue_length: 3
|
queue_length: 3
|
||||||
interval: 100ms
|
interval: 100ms
|
||||||
|
|
@ -23,6 +23,7 @@ pub struct DeviceConfig {
|
||||||
pub events: Option<Vec<EventConfig>>,
|
pub events: Option<Vec<EventConfig>>,
|
||||||
pub queue_length: usize,
|
pub queue_length: usize,
|
||||||
pub interval: Duration,
|
pub interval: Duration,
|
||||||
|
pub log: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeviceConfig {
|
impl DeviceConfig {
|
||||||
|
|
@ -71,6 +72,7 @@ impl TryFrom<DeviceConfigSerializer> for DeviceConfig {
|
||||||
events: util::map_opt_tryfrom(v.events)?,
|
events: util::map_opt_tryfrom(v.events)?,
|
||||||
queue_length: v.queue_length.unwrap_or(256),
|
queue_length: v.queue_length.unwrap_or(256),
|
||||||
interval: v.interval.map(|x| x.unwrap()).unwrap_or_else(|| Duration::new(0, 0)),
|
interval: v.interval.map(|x| x.unwrap()).unwrap_or_else(|| Duration::new(0, 0)),
|
||||||
|
log: v.log_events.unwrap_or(false),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,15 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
pub type DeviceConfig = device::DeviceConfig;
|
pub use device::DeviceConfig;
|
||||||
pub type EventConfig = event::EventConfig;
|
pub use event::EventConfig;
|
||||||
pub type RunConfig = run::RunConfig;
|
pub use run::RunConfig;
|
||||||
pub type EventEnvMap = serializer::EventEnvSerializer;
|
pub type EventEnvMap = serializer::EventEnvSerializer;
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
pub log: bool,
|
||||||
|
pub driver: Option<crate::midi::MidiDriver>,
|
||||||
pub devices: Vec<DeviceConfig>,
|
pub devices: Vec<DeviceConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,6 +25,8 @@ impl TryFrom<ConfigSerializer> for Config {
|
||||||
type Error = crate::Error;
|
type Error = crate::Error;
|
||||||
fn try_from(v: ConfigSerializer) -> Result<Self, Self::Error> {
|
fn try_from(v: ConfigSerializer) -> Result<Self, Self::Error> {
|
||||||
Ok(Config {
|
Ok(Config {
|
||||||
|
log: v.log_devices.unwrap_or(false),
|
||||||
|
driver: v.driver,
|
||||||
devices: util::map_tryfrom(v.devices)?,
|
devices: util::map_tryfrom(v.devices)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,4 +33,5 @@ pub struct DeviceConfigSerializer {
|
||||||
pub max_connections: Option<u32>,
|
pub max_connections: Option<u32>,
|
||||||
pub queue_length: Option<usize>,
|
pub queue_length: Option<usize>,
|
||||||
pub interval: Option<DurationWrapper>,
|
pub interval: Option<DurationWrapper>,
|
||||||
|
pub log_events: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,17 @@ pub mod device;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
pub mod eventenv;
|
pub mod eventenv;
|
||||||
|
|
||||||
pub type DeviceConfigSerializer = device::DeviceConfigSerializer;
|
pub use device::DeviceConfigSerializer;
|
||||||
pub type EventConfigSerializer = event::EventConfigSerializer;
|
pub use event::EventConfigSerializer;
|
||||||
pub type RunConfigSerializer = run::RunConfigSerializer;
|
pub use run::RunConfigSerializer;
|
||||||
pub type EventEnvSerializer = eventenv::EventEnvSerializer;
|
pub use eventenv::EventEnvSerializer;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize,Clone,Debug)]
|
#[derive(Deserialize,Clone,Debug)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct ConfigSerializer {
|
pub struct ConfigSerializer {
|
||||||
|
pub log_devices: Option<bool>,
|
||||||
|
pub driver: Option<crate::midi::MidiDriver>,
|
||||||
pub devices: Vec<DeviceConfigSerializer>,
|
pub devices: Vec<DeviceConfigSerializer>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
src/constant.rs
Normal file
4
src/constant.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
pub const CLIENT_NAME: &str = "rmidimap";
|
||||||
|
pub const CLIENT_NAME_HANDLER: &str = "rmidimap-handler";
|
||||||
|
pub const CLIENT_NAME_EVENT: &str = "rmidimap-event-watcher";
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use std::ffi::NulError;
|
use std::ffi::NulError;
|
||||||
|
use std::num::ParseIntError;
|
||||||
use std::process::ExitStatus;
|
use std::process::ExitStatus;
|
||||||
use std::sync::mpsc::RecvError;
|
use std::sync::mpsc::RecvError;
|
||||||
use std::time::SystemTimeError;
|
use std::time::SystemTimeError;
|
||||||
|
|
||||||
use crate::midi::alsa::AlsaError;
|
use crate::midi::backend::alsa::AlsaError;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -25,6 +26,8 @@ pub enum Error {
|
||||||
Regex(#[from] regex::Error),
|
Regex(#[from] regex::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SystemTime(#[from] SystemTimeError),
|
SystemTime(#[from] SystemTimeError),
|
||||||
|
#[error(transparent)]
|
||||||
|
ParseInt(#[from] ParseIntError),
|
||||||
#[error("execution failure")]
|
#[error("execution failure")]
|
||||||
ExecStatus(ExitStatus),
|
ExecStatus(ExitStatus),
|
||||||
#[error("remap value is too large. Maximum value is {}", i64::MAX)]
|
#[error("remap value is too large. Maximum value is {}", i64::MAX)]
|
||||||
|
|
|
||||||
24
src/event.rs
24
src/event.rs
|
|
@ -7,7 +7,6 @@ use crate::Error;
|
||||||
|
|
||||||
use serde::{Serialize,Deserialize};
|
use serde::{Serialize,Deserialize};
|
||||||
|
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|
@ -116,6 +115,20 @@ struct EventEnvRef<'a> {
|
||||||
pub value: &'a str,
|
pub value: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<'a> std::fmt::Display for Event<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{{ \"type\": \"{}\", \"channel\": {}, \"id\": {}, \"value\": {}, \"raw\": \"{}\" }}",
|
||||||
|
self.r#type, self.channel, self.id, self.value, bytes_to_strhex(self.raw, " "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for EventBuf {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.as_event().fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl EventBuf {
|
impl EventBuf {
|
||||||
pub fn as_event(&self) -> Event {
|
pub fn as_event(&self) -> Event {
|
||||||
Event {
|
Event {
|
||||||
|
|
@ -155,10 +168,13 @@ impl From<u8> for EventType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bytes_to_strhex(bytes: &[u8]) -> String {
|
fn bytes_to_strhex(bytes: &[u8], separator: &str) -> String {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
for &byte in bytes {
|
for &byte in bytes {
|
||||||
write!(&mut s, "{:X} ", byte).expect("unexpected write error");
|
write!(&mut s, "{:02X}{}", byte, separator).expect("unexpected write error");
|
||||||
|
}
|
||||||
|
if s.ends_with(separator) {
|
||||||
|
return s.trim_end_matches(separator).to_string();
|
||||||
}
|
}
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
@ -185,7 +201,7 @@ impl<'a> Event<'a> {
|
||||||
channel: self.channel.to_string(),
|
channel: self.channel.to_string(),
|
||||||
id: self.id.to_string(),
|
id: self.id.to_string(),
|
||||||
rawvalue: self.value.to_string(),
|
rawvalue: self.value.to_string(),
|
||||||
raw: bytes_to_strhex(self.raw),
|
raw: bytes_to_strhex(self.raw, " "),
|
||||||
timestamp: self.timestamp.unwrap_or(SystemTime::now()).duration_since(SystemTime::UNIX_EPOCH)?.as_secs_f64().to_string(),
|
timestamp: self.timestamp.unwrap_or(SystemTime::now()).duration_since(SystemTime::UNIX_EPOCH)?.as_secs_f64().to_string(),
|
||||||
value: match (remap,float) {
|
value: match (remap,float) {
|
||||||
(Some(r),true) => r.remap(self.value as f64).to_string(),
|
(Some(r),true) => r.remap(self.value as f64).to_string(),
|
||||||
|
|
|
||||||
14
src/main.rs
14
src/main.rs
|
|
@ -9,6 +9,7 @@ pub mod midi;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod constant;
|
||||||
|
|
||||||
type Error = error::Error;
|
type Error = error::Error;
|
||||||
|
|
||||||
|
|
@ -18,11 +19,16 @@ use clap::Parser;
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use cli::Cli;
|
use cli::Cli;
|
||||||
|
use midi::MidiHandler;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let c = Cli::parse();
|
let c = Cli::parse();
|
||||||
|
|
||||||
if c.list {
|
if c.list {
|
||||||
err_handle(run::list_devices());
|
let mut handler = MidiHandler::new(constant::CLIENT_NAME).unwrap();
|
||||||
|
err_handle(
|
||||||
|
handler.builder_handler(run::ListDevicesBuilder, ())
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let map_file = err_handle(
|
let map_file = err_handle(
|
||||||
|
|
@ -52,5 +58,9 @@ fn run_file(filepath: &Path) -> Result<(), Error> {
|
||||||
println!("Load file {}", filepath.to_str().unwrap_or("<unknown>"));
|
println!("Load file {}", filepath.to_str().unwrap_or("<unknown>"));
|
||||||
let dat = std::fs::read( filepath )?;
|
let dat = std::fs::read( filepath )?;
|
||||||
let conf = Config::try_from(&dat[..])?;
|
let conf = Config::try_from(&dat[..])?;
|
||||||
run::run_config(&conf)
|
let mut handler = match conf.driver {
|
||||||
|
Some(v) => MidiHandler::new_with_driver(constant::CLIENT_NAME, v),
|
||||||
|
None => MidiHandler::new(constant::CLIENT_NAME),
|
||||||
|
}?;
|
||||||
|
handler.builder_handler(run::RunConfigBuilder, &conf)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,75 @@
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
extern crate alsa;
|
extern crate alsa;
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
use std::{mem, thread};
|
use std::{mem, thread};
|
||||||
use std::ffi::{CString, CStr};
|
use std::ffi::{CString, CStr};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
use crate::midi::{MidiInput,MidiInputHandler,MidiPort,PortFilter,MidiPortHandler,MidiAddrHandler};
|
use crate::midi::{MidiInput,MidiPort,PortFilter};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
use crate::util::InternalTryFrom;
|
||||||
|
|
||||||
use alsa::{Seq, Direction};
|
use alsa::{Seq, Direction};
|
||||||
use alsa::seq::{ClientIter, PortIter, MidiEvent, PortInfo, PortSubscribe, Addr, QueueTempo, EventType, PortCap, PortType};
|
use alsa::seq::{ClientIter, PortIter, MidiEvent, PortInfo, PortSubscribe, Addr, QueueTempo, EventType, PortCap, PortType};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub type DeviceAddr = alsa::seq::Addr;
|
#[derive(Debug,Clone,PartialEq,Eq)]
|
||||||
|
pub struct DeviceAddr(Addr);
|
||||||
|
|
||||||
|
const ANNOUNCE_ADDR: &str = "System:Announce";
|
||||||
|
const CLIENT_NAME_ANNOUNCE: &str = "rmidimap-alsa-announce";
|
||||||
|
|
||||||
|
impl std::fmt::Display for DeviceAddr {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:>3}:{}", self.client(), self.port())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Addr> for DeviceAddr {
|
||||||
|
fn from(value: Addr) -> Self {
|
||||||
|
DeviceAddr(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for DeviceAddr {
|
||||||
|
type Err = AlsaError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
// todo!()
|
||||||
|
// let mut osep = ;
|
||||||
|
if let Some(sep) = s.find(':') {
|
||||||
|
let (p1,p2) = (&s[..sep], &s[sep+1..] );
|
||||||
|
Ok(DeviceAddr(
|
||||||
|
Addr {
|
||||||
|
client: p1.parse().map_err(|_| AlsaError::AddrParse(s.to_string()))?,
|
||||||
|
port: p2.parse().map_err(|_| AlsaError::AddrParse(s.to_string()))?,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Err(AlsaError::AddrParse(s.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InternalTryFrom<String> for DeviceAddr {
|
||||||
|
fn i_try_from(s: String) -> Result<Self, crate::Error> {
|
||||||
|
return Ok(Self::from_str(&s[..])?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeviceAddr {
|
||||||
|
pub fn unwrap(&self) -> Addr {
|
||||||
|
return self.0;
|
||||||
|
}
|
||||||
|
pub fn client(&self) -> i32 {
|
||||||
|
self.0.client
|
||||||
|
}
|
||||||
|
pub fn port(&self) -> i32 {
|
||||||
|
self.0.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_ports(s: &Seq, capability: PortCap) -> Vec<PortInfo> {
|
pub fn get_ports(s: &Seq, capability: PortCap) -> Vec<PortInfo> {
|
||||||
ClientIter::new(s).flat_map(|c| PortIter::new(s, c.get_client()))
|
ClientIter::new(s).flat_map(|c| PortIter::new(s, c.get_client()))
|
||||||
|
|
@ -31,8 +87,10 @@ mod helpers {
|
||||||
pub enum AlsaError {
|
pub enum AlsaError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ALSA(#[from] alsa::Error),
|
ALSA(#[from] alsa::Error),
|
||||||
#[error("alsa decode error")]
|
#[error("internal alsa decode error")]
|
||||||
Decode,
|
Decode,
|
||||||
|
#[error("failed to parse '{0}' as an ALSA address")]
|
||||||
|
AddrParse(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MidiInputAlsa {
|
pub struct MidiInputAlsa {
|
||||||
|
|
@ -63,26 +121,22 @@ impl MidiInputAlsa {
|
||||||
|
|
||||||
|
|
||||||
fn init_queue(&mut self) -> Result<i32, alsa::Error> {
|
fn init_queue(&mut self) -> Result<i32, alsa::Error> {
|
||||||
let mut queue_id = 0;
|
|
||||||
// Create the input queue
|
// Create the input queue
|
||||||
if !cfg!(feature = "avoid_timestamping") {
|
let queue_id = self.seq.alloc_named_queue(unsafe { CStr::from_bytes_with_nul_unchecked(b"midir queue\0") })?;
|
||||||
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)
|
||||||
// Set arbitrary tempo (mm=100) and resolution (240)
|
let qtempo = QueueTempo::empty()?;
|
||||||
let qtempo = QueueTempo::empty()?;
|
qtempo.set_tempo(600_000);
|
||||||
qtempo.set_tempo(600_000);
|
qtempo.set_ppq(240);
|
||||||
qtempo.set_ppq(240);
|
self.seq.set_queue_tempo(queue_id, &qtempo)?;
|
||||||
self.seq.set_queue_tempo(queue_id, &qtempo)?;
|
let _ = self.seq.drain_output();
|
||||||
let _ = self.seq.drain_output();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(queue_id)
|
Ok(queue_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_input_queue(&mut self, queue_id: i32) {
|
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.control_queue(queue_id, EventType::Start, 0, None);
|
let _ = self.seq.drain_output();
|
||||||
let _ = self.seq.drain_output();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_port(&mut self, port_name: &CStr, queue_id: i32) -> Result<i32, AlsaError> {
|
fn create_port(&mut self, port_name: &CStr, queue_id: i32) -> Result<i32, AlsaError> {
|
||||||
|
|
@ -94,11 +148,9 @@ impl MidiInputAlsa {
|
||||||
pinfo.set_type(PortType::MIDI_GENERIC | PortType::APPLICATION);
|
pinfo.set_type(PortType::MIDI_GENERIC | PortType::APPLICATION);
|
||||||
pinfo.set_midi_channels(16);
|
pinfo.set_midi_channels(16);
|
||||||
|
|
||||||
if !cfg!(feature = "avoid_timestamping") {
|
pinfo.set_timestamping(true);
|
||||||
pinfo.set_timestamping(true);
|
pinfo.set_timestamp_real(true);
|
||||||
pinfo.set_timestamp_real(true);
|
pinfo.set_timestamp_queue(queue_id);
|
||||||
pinfo.set_timestamp_queue(queue_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
pinfo.set_name(port_name);
|
pinfo.set_name(port_name);
|
||||||
match self.seq.create_port(&pinfo) {
|
match self.seq.create_port(&pinfo) {
|
||||||
|
|
@ -114,11 +166,10 @@ impl MidiInputAlsa {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop and free the input queue
|
// 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.control_queue(self.queue_id, EventType::Stop, 0, None);
|
let _ = self.seq.drain_output();
|
||||||
let _ = self.seq.drain_output();
|
let _ = self.seq.free_queue(self.queue_id);
|
||||||
let _ = self.seq.free_queue(self.queue_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
for fd in self.stop_trigger {
|
for fd in self.stop_trigger {
|
||||||
if fd >= 0 {
|
if fd >= 0 {
|
||||||
|
|
@ -190,7 +241,7 @@ impl MidiInputAlsa {
|
||||||
Ok(true) => break,
|
Ok(true) => break,
|
||||||
Ok(false) => (),
|
Ok(false) => (),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("ALSA CALLBACK ERROR: {:?}", e);
|
eprintln!("ALSA CALLBACK ERROR: {}", e);
|
||||||
eprintln!("continuing execution");
|
eprintln!("continuing execution");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -296,7 +347,8 @@ impl MidiInputAlsa {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MidiInput<Addr> for MidiInputAlsa {
|
impl MidiInput for MidiInputAlsa {
|
||||||
|
type DeviceAddr = DeviceAddr;
|
||||||
fn new(client_name: &str) -> Result<Self, Error> {
|
fn new(client_name: &str) -> Result<Self, Error> {
|
||||||
let seq = Seq::open(None, None, true)?;
|
let seq = Seq::open(None, None, true)?;
|
||||||
|
|
||||||
|
|
@ -318,45 +370,33 @@ impl MidiInput<Addr> for MidiInputAlsa {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ports(&self) -> Result<Vec<MidiPort<DeviceAddr>>, Error> {
|
||||||
fn ports_handle(&self) -> Result<Vec<MidiPortHandler>, Error> {
|
get_ports(&self.seq, PortCap::READ | PortCap::SUBS_READ).iter().map(|x| -> Result<MidiPort<DeviceAddr>, 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) -> 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())?;
|
let cinfo = self.seq.get_any_client_info(x.get_client())?;
|
||||||
Ok(MidiPort {
|
Ok(MidiPort {
|
||||||
name: cinfo.get_name()?.to_string()+":"+x.get_name()?,
|
name: cinfo.get_name()?.to_string()+":"+x.get_name()?,
|
||||||
addr: x.addr(),
|
addr: x.addr().into(),
|
||||||
})
|
})
|
||||||
}).collect()
|
}).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filter_ports(&self, mut ports: Vec<MidiPort<Addr>>, filter: PortFilter) -> Vec<MidiPort<Addr>> {
|
fn filter_ports(&self, mut ports: Vec<MidiPort<DeviceAddr>>, filter: PortFilter<Self::DeviceAddr>) -> Vec<MidiPort<DeviceAddr>> {
|
||||||
ports.retain(
|
ports.retain(
|
||||||
|p| {
|
|p| {
|
||||||
match &filter {
|
match &filter {
|
||||||
PortFilter::All => true,
|
PortFilter::All => true,
|
||||||
PortFilter::Name(s) => p.name.contains(s),
|
PortFilter::Name(s) => p.name.contains(s),
|
||||||
PortFilter::Regex(s) => s.is_match(&p.name),
|
PortFilter::Regex(s) => s.is_match(&p.name),
|
||||||
PortFilter::Addr(MidiAddrHandler::ALSA(s)) => p.addr == *s,
|
PortFilter::Addr(s) => p.addr == *s,
|
||||||
_ => panic!("unexpected error"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
ports
|
ports
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connect(&mut self, port_addr: &Addr, port_name: &str) -> Result<(), Error> {
|
fn connect(&mut self, port_addr: &DeviceAddr, port_name: &str) -> Result<(), Error> {
|
||||||
let src_pinfo = self.seq.get_any_port_info(*port_addr)?;
|
let addr = port_addr.unwrap();
|
||||||
|
let src_pinfo = self.seq.get_any_port_info(addr)?;
|
||||||
let queue_id = self.init_queue()?;
|
let queue_id = self.init_queue()?;
|
||||||
let c_port_name = CString::new(port_name)?;
|
let c_port_name = CString::new(port_name)?;
|
||||||
let vport = self.create_port(&c_port_name, queue_id)?;
|
let vport = self.create_port(&c_port_name, queue_id)?;
|
||||||
|
|
@ -367,25 +407,25 @@ impl MidiInput<Addr> for MidiInputAlsa {
|
||||||
self.seq.subscribe_port(&sub)?;
|
self.seq.subscribe_port(&sub)?;
|
||||||
self.subscription = Some(sub);
|
self.subscription = Some(sub);
|
||||||
self.init_trigger()?;
|
self.init_trigger()?;
|
||||||
self.connect_addr = Some(*port_addr);
|
self.connect_addr = Some(addr);
|
||||||
self.start_input_queue(queue_id);
|
self.start_input_queue(queue_id);
|
||||||
self.start_time = Some(std::time::SystemTime::now());
|
self.start_time = Some(std::time::SystemTime::now());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_events(&mut self, ts: mpsc::Sender<Option<MidiPortHandler>>, (tss, rss): (mpsc::Sender<bool>, mpsc::Receiver<bool>)) -> Result<(), Error> {
|
fn device_events(&mut self, ts: mpsc::Sender<Option<MidiPort<Self::DeviceAddr>>>, (tss, rss): (mpsc::Sender<bool>, mpsc::Receiver<bool>)) -> Result<(), Error> {
|
||||||
let ports = self.ports()?;
|
let ports = self.ports()?;
|
||||||
let port = self.filter_ports(ports, PortFilter::Name("System:Announce".to_string()));
|
let port = self.filter_ports(ports, PortFilter::Name(ANNOUNCE_ADDR.to_string()));
|
||||||
self.connect(&port[0].addr, "rmidimap-alsa-announce")?;
|
self.connect(&port[0].addr, CLIENT_NAME_ANNOUNCE)?;
|
||||||
self.threaded_alsa_input(move |s: &Self, ev: alsa::seq::Event, _| -> Result<bool, Error> {
|
self.threaded_alsa_input(move |s: &Self, ev: alsa::seq::Event, _| -> Result<bool, Error> {
|
||||||
// handle disconnect event on watched port
|
// handle disconnect event on watched port
|
||||||
match ev.get_type() {
|
match ev.get_type() {
|
||||||
EventType::PortStart => {
|
EventType::PortStart => {
|
||||||
if let Some(a) = ev.get_data::<alsa::seq::Addr>() {
|
if let Some(a) = ev.get_data::<alsa::seq::Addr>() {
|
||||||
let p = s.ports()?;
|
let p = s.ports()?;
|
||||||
let pp = s.filter_ports(p, PortFilter::Addr( MidiAddrHandler::ALSA(a) ));
|
let pp = s.filter_ports(p, PortFilter::Addr( a.into() ) );
|
||||||
if !pp.is_empty() {
|
if !pp.is_empty() {
|
||||||
ts.send(Some(MidiPortHandler::ALSA(pp[0].clone()))).expect("unexpected send() error");
|
ts.send(Some(pp[0].clone())).expect("unexpected send() error");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(false)
|
Ok(false)
|
||||||
|
|
@ -396,10 +436,7 @@ impl MidiInput<Addr> for MidiInputAlsa {
|
||||||
self.close_internal();
|
self.close_internal();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiInputHandler for MidiInputAlsa
|
|
||||||
{
|
|
||||||
fn signal_stop_input(&self) -> Result<(), Error> {
|
fn signal_stop_input(&self) -> Result<(), Error> {
|
||||||
Self::signal_stop_input_internal(self.stop_trigger[1])
|
Self::signal_stop_input_internal(self.stop_trigger[1])
|
||||||
}
|
}
|
||||||
|
|
@ -416,4 +453,3 @@ impl MidiInputHandler for MidiInputAlsa
|
||||||
}, (ts, rs), userdata)
|
}, (ts, rs), userdata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
3
src/midi/backend/mod.rs
Normal file
3
src/midi/backend/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod alsa;
|
||||||
|
|
||||||
|
pub use alsa::MidiInputAlsa;
|
||||||
27
src/midi/builder.rs
Normal file
27
src/midi/builder.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use crate::util::InternalTryFrom;
|
||||||
|
|
||||||
|
pub use super::input::{MidiInput,MidiInputHandler};
|
||||||
|
|
||||||
|
pub trait Builder<D, R> {
|
||||||
|
fn build<T>(&self) -> fn(&T, D) -> R
|
||||||
|
where
|
||||||
|
T: MidiInputHandler+MidiInput+Send+'static,
|
||||||
|
<T as MidiInputHandler>::DeviceAddr: std::fmt::Display+'static+InternalTryFrom<String>,
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! builder {
|
||||||
|
( $name:ident, $fct:ident, $intype:ty, $rettype: ty ) => {
|
||||||
|
pub struct $name;
|
||||||
|
impl Builder<$intype, $rettype> for $name {
|
||||||
|
fn build<T>(&self) -> fn(&T, $intype) -> $rettype
|
||||||
|
where
|
||||||
|
T: MidiInputHandler+Send+'static,
|
||||||
|
<T as MidiInputHandler>::DeviceAddr: std::fmt::Display+'static+InternalTryFrom<String>,
|
||||||
|
{
|
||||||
|
$fct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use builder;
|
||||||
14
src/midi/driver.rs
Normal file
14
src/midi/driver.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize,Debug,Clone,Copy,Eq,PartialEq)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum MidiDriver {
|
||||||
|
ALSA,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MidiDriver {
|
||||||
|
// auto-detection of driver
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::ALSA
|
||||||
|
}
|
||||||
|
}
|
||||||
150
src/midi/input.rs
Normal file
150
src/midi/input.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
use crate::util::InternalTryFrom;
|
||||||
|
use crate::{Error, constant};
|
||||||
|
use crate::config::DeviceConfig;
|
||||||
|
use crate::eventmap::EventMap;
|
||||||
|
use crate::event::{Event, EventBuf};
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{SystemTime, Instant};
|
||||||
|
use std::sync::{mpsc, Mutex, Arc};
|
||||||
|
|
||||||
|
use queues::{CircularBuffer, IsQueue};
|
||||||
|
|
||||||
|
use super::{PortFilter, MidiPort};
|
||||||
|
|
||||||
|
pub trait MidiInput
|
||||||
|
where
|
||||||
|
<Self as MidiInput>::DeviceAddr: Clone+Send+FromStr,
|
||||||
|
<Self as MidiInput>::DeviceAddr: InternalTryFrom<std::string::String>
|
||||||
|
{
|
||||||
|
type DeviceAddr;
|
||||||
|
fn new(client_name: &str) -> Result<Self, Error>
|
||||||
|
where Self: Sized;
|
||||||
|
|
||||||
|
fn close(self) -> Result<(), Error>;
|
||||||
|
|
||||||
|
fn ports(&self) -> Result<Vec<MidiPort<Self::DeviceAddr>>, Error>;
|
||||||
|
|
||||||
|
fn filter_ports(&self, ports: Vec<MidiPort<Self::DeviceAddr>>, filter: PortFilter<Self::DeviceAddr>) -> Vec<MidiPort<Self::DeviceAddr>>;
|
||||||
|
|
||||||
|
fn connect(&mut self, port_addr: &Self::DeviceAddr, port_name: &str) -> Result<(), Error>;
|
||||||
|
|
||||||
|
fn device_events(&mut self, ts: mpsc::Sender<Option<MidiPort<Self::DeviceAddr>>>, ss: (mpsc::Sender<bool>, mpsc::Receiver<bool>)) -> Result<(), Error>;
|
||||||
|
|
||||||
|
fn signal_stop_input(&self) -> Result<(), Error>;
|
||||||
|
|
||||||
|
fn handle_input<F, D>(&mut self, callback: F, rts: (mpsc::Sender<bool>, mpsc::Receiver<bool>), userdata: D) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
F: Fn(&Self, &[u8], Option<SystemTime>, &mut D) + Send + Sync,
|
||||||
|
D: Send,
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MidiInputHandler
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
<Self as MidiInputHandler>::DeviceAddr: Clone+Send+FromStr,
|
||||||
|
{
|
||||||
|
type DeviceAddr;
|
||||||
|
fn new(client_name: &str) -> Result<Self, Error>;
|
||||||
|
fn ports(&self) -> Result<Vec<MidiPort<Self::DeviceAddr>>, Error>;
|
||||||
|
fn try_connect(&self, port: MidiPort<Self::DeviceAddr>, filter: PortFilter<Self::DeviceAddr> ) -> Result<Option<Self>, Error>;
|
||||||
|
fn run(&mut self, conf: &DeviceConfig, eventmap: &EventMap, trs: (mpsc::Sender<bool>, mpsc::Receiver<bool>)) -> Result<(), Error>;
|
||||||
|
fn device_events(&mut self, ts: mpsc::Sender<Option<MidiPort<Self::DeviceAddr>>>, ss: (mpsc::Sender<bool>,mpsc::Receiver<bool>)) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic implementation
|
||||||
|
|
||||||
|
impl<T> MidiInputHandler for T
|
||||||
|
where
|
||||||
|
T: MidiInput + Send,
|
||||||
|
<T as MidiInput>::DeviceAddr: FromStr,
|
||||||
|
{
|
||||||
|
type DeviceAddr = T::DeviceAddr;
|
||||||
|
|
||||||
|
|
||||||
|
fn new(client_name: &str) -> Result<Self, Error> {
|
||||||
|
MidiInput::new(client_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ports(&self) -> Result<Vec<MidiPort<Self::DeviceAddr>>, Error> {
|
||||||
|
MidiInput::ports(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_connect(&self, port: MidiPort<Self::DeviceAddr>, filter: PortFilter<T::DeviceAddr> ) -> Result<Option<Self>, Error> {
|
||||||
|
let portmap = self.ports()?;
|
||||||
|
let pv = self.filter_ports(portmap, PortFilter::Addr(port.addr));
|
||||||
|
let pv = self.filter_ports(pv, filter);
|
||||||
|
if pv.len() > 0 {
|
||||||
|
let port = &pv[0];
|
||||||
|
let mut v = T::new(constant::CLIENT_NAME_HANDLER)?;
|
||||||
|
v.connect(&port.addr, constant::CLIENT_NAME_HANDLER)?;
|
||||||
|
Ok(Some(v))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device_events(&mut self, ts: mpsc::Sender<Option<MidiPort<Self::DeviceAddr>>>, ss: (mpsc::Sender<bool>,mpsc::Receiver<bool>)) -> Result<(), Error> {
|
||||||
|
self.device_events(ts, ss)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self, conf: &DeviceConfig, eventmap: &EventMap, (ts, rs): (mpsc::Sender<bool>, mpsc::Receiver<bool>)) -> Result<(), Error> {
|
||||||
|
thread::scope(|s| -> Result<(), Error> {
|
||||||
|
|
||||||
|
// parking signal for runner, true = stop
|
||||||
|
let (pts,prs) = mpsc::channel::<bool>();
|
||||||
|
|
||||||
|
// event queue populated by the main thread and consumed by the exec thread
|
||||||
|
let evq = Arc::new(Mutex::new(CircularBuffer::<EventBuf>::new(conf.queue_length)));
|
||||||
|
|
||||||
|
// background execution loop
|
||||||
|
let rq = evq.clone();
|
||||||
|
let exec_thread = s.spawn(move || -> Result<(),Error> {
|
||||||
|
loop {
|
||||||
|
if prs.recv()? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
// nest the lock into a scope to release it before run
|
||||||
|
let (ev,start): (EventBuf,Instant) = {
|
||||||
|
let mut evq = rq.lock().unwrap();
|
||||||
|
if evq.size() > 0 {
|
||||||
|
(evq.remove().unwrap(), Instant::now())
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
eventmap.run_event(&ev.as_event()).unwrap_or_else(|e| eprintln!("ERROR: error on run: {}", e) );
|
||||||
|
// wait until interval has been reached
|
||||||
|
let elapsed_time = start.elapsed();
|
||||||
|
if elapsed_time < conf.interval {
|
||||||
|
thread::sleep(conf.interval - elapsed_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
self.handle_input(|_,m,t,(evq,pts)| {
|
||||||
|
let mut event: EventBuf = Event::from(m).into();
|
||||||
|
event.timestamp = t;
|
||||||
|
if conf.log {
|
||||||
|
println!("{}: event: {}", constant::CLIENT_NAME, event);
|
||||||
|
}
|
||||||
|
let mut evq = evq.lock().unwrap();
|
||||||
|
evq.add(event).unwrap();
|
||||||
|
pts.send(false).expect("unexpected write error");
|
||||||
|
}, (ts,rs), (evq,pts.clone()))?;
|
||||||
|
|
||||||
|
pts.send(true).expect("unexpected write error");
|
||||||
|
let _ = exec_thread.join();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
274
src/midi/mod.rs
274
src/midi/mod.rs
|
|
@ -1,273 +1,45 @@
|
||||||
pub mod alsa;
|
pub mod backend;
|
||||||
|
|
||||||
use queues::{CircularBuffer, IsQueue};
|
pub mod port;
|
||||||
|
pub mod portfilter;
|
||||||
|
pub mod input;
|
||||||
|
pub mod builder;
|
||||||
|
pub mod driver;
|
||||||
|
|
||||||
use crate::config::device::Identifier;
|
|
||||||
use super::midi::alsa::MidiInputAlsa;
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
|
||||||
use crate::config::DeviceConfig;
|
pub use driver::MidiDriver;
|
||||||
use crate::eventmap::EventMap;
|
pub use builder::Builder;
|
||||||
use crate::event::{Event, EventBuf};
|
pub use port::MidiPort;
|
||||||
|
pub use portfilter::PortFilter;
|
||||||
use std::thread;
|
pub use input::{MidiInput,MidiInputHandler};
|
||||||
use std::time::{SystemTime, Instant};
|
|
||||||
use std::sync::{mpsc, Mutex, Arc};
|
|
||||||
|
|
||||||
#[derive(Eq,PartialEq,Debug,Clone)]
|
|
||||||
pub struct MidiPort<T>{
|
|
||||||
pub name: String,
|
|
||||||
pub addr: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
|
||||||
pub enum PortFilter {
|
|
||||||
All,
|
|
||||||
Name(String),
|
|
||||||
Regex(regex::Regex),
|
|
||||||
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>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for MidiPortHandler {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
MidiPortHandler::ALSA(p) => write!(f, "{} {}:{}", p.name, p.addr.client, p.addr.port),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum MidiHandler {
|
pub enum MidiHandler {
|
||||||
ALSA(MidiInputAlsa),
|
ALSA(backend::MidiInputAlsa),
|
||||||
}
|
|
||||||
|
|
||||||
impl From<MidiPortHandler> for MidiAddrHandler {
|
|
||||||
fn from(a: MidiPortHandler) -> Self {
|
|
||||||
match a {
|
|
||||||
MidiPortHandler::ALSA(p) => MidiAddrHandler::ALSA(p.addr),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&DeviceConfig> for PortFilter {
|
|
||||||
fn from(conf: &DeviceConfig) -> Self {
|
|
||||||
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"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MidiInput<T> {
|
|
||||||
fn new(client_name: &str) -> Result<Self, Error>
|
|
||||||
where Self: Sized;
|
|
||||||
|
|
||||||
fn close(self) -> Result<(), Error>;
|
|
||||||
|
|
||||||
fn ports(&self) -> Result<Vec<MidiPort<T>>, Error>;
|
|
||||||
fn ports_handle(&self) -> Result<Vec<MidiPortHandler>, Error>;
|
|
||||||
|
|
||||||
fn filter_ports(&self, ports: Vec<MidiPort<T>>, filter: PortFilter) -> Vec<MidiPort<T>>;
|
|
||||||
|
|
||||||
fn connect(&mut self, port_addr: &T, port_name: &str) -> Result<(), Error>;
|
|
||||||
|
|
||||||
fn device_events(&mut self, ts: mpsc::Sender<Option<MidiPortHandler>>, ss: (mpsc::Sender<bool>, mpsc::Receiver<bool>)) -> Result<(), Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MidiInputHandler {
|
|
||||||
fn signal_stop_input(&self) -> Result<(), Error>;
|
|
||||||
|
|
||||||
fn handle_input<F, D>(&mut self, callback: F, rts: (mpsc::Sender<bool>, mpsc::Receiver<bool>), userdata: D) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
F: Fn(&Self, &[u8], Option<SystemTime>, &mut D) + Send + Sync,
|
|
||||||
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)?;
|
|
||||||
match &mut h {
|
|
||||||
MidiHandler::$handler(v) => {
|
|
||||||
v.connect(&port.addr, "rmidimap-handler")?;
|
|
||||||
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 {
|
impl MidiHandler {
|
||||||
pub fn new(name: &str) -> Result<Self, Error> {
|
pub fn new(name: &str) -> Result<Self, Error> {
|
||||||
Self::new_with_driver(name, MidiHandlerDriver::ALSA)
|
Self::new_with_driver(name, MidiDriver::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_driver(name: &str, driver: MidiHandlerDriver) -> Result<Self, Error> {
|
pub fn new_with_driver(name: &str, driver: MidiDriver) -> Result<Self, Error> {
|
||||||
match driver {
|
match driver {
|
||||||
MidiHandlerDriver::ALSA => Ok(MidiHandler::ALSA(MidiInputAlsa::new(name)?)),
|
MidiDriver::ALSA => Ok(MidiHandler::ALSA(MidiInput::new(name)?)),
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ports(&self) -> Result<Vec<MidiPortHandler>, Error> {
|
// wrap generic functions into builder because functions with generic traits cannot be passed as arguments
|
||||||
handler_fcall!{
|
pub fn builder_handler<B, D, R>(&mut self, builder: B, data: D) -> R
|
||||||
self, handle_port_list ,(),
|
where
|
||||||
ALSA
|
B: Builder<D,R>,
|
||||||
}
|
D: Send,
|
||||||
}
|
{
|
||||||
|
match self {
|
||||||
pub fn try_connect(&self, addr: MidiPortHandler, filter: PortFilter) -> Result<Option<MidiHandler>, Error> {
|
MidiHandler::ALSA(v) => builder.build()(v, data),
|
||||||
let r: Result<Option<MidiHandler>, Error> = handler_try_connect!{
|
|
||||||
self, filter, addr,
|
|
||||||
ALSA
|
|
||||||
};
|
|
||||||
r
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&mut self, conf: &DeviceConfig, eventmap: &EventMap, trs: (mpsc::Sender<bool>, mpsc::Receiver<bool>)) -> Result<(), Error> {
|
|
||||||
handler_fcall!{
|
|
||||||
self, handle_inputport, (conf, eventmap,trs),
|
|
||||||
ALSA
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(&self) -> Result<(), Error> {
|
|
||||||
handler_fcall!{
|
|
||||||
self, handle_signal_stop, (),
|
|
||||||
ALSA
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn device_events(&mut self, ts: mpsc::Sender<Option<MidiPortHandler>>, (tss, rss): (mpsc::Sender<bool>,mpsc::Receiver<bool>)) -> Result<(), Error> {
|
|
||||||
handler_fcall!{
|
|
||||||
self, device_events, (ts,tss,rss),
|
|
||||||
ALSA
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_port_list<T, A>(input: &T, _: ()) -> Result<Vec<MidiPortHandler>, Error>
|
|
||||||
where T: MidiInput<A>
|
|
||||||
{
|
|
||||||
input.ports_handle()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_inputport<T>(input: &mut T, (conf, eventmap, (ts, rs)): (&DeviceConfig, &EventMap, (mpsc::Sender<bool>, mpsc::Receiver<bool>))) -> Result<(), Error>
|
|
||||||
where T: MidiInputHandler + Send
|
|
||||||
{
|
|
||||||
thread::scope(|s| -> Result<(), Error> {
|
|
||||||
|
|
||||||
// parking signal for runner, true = stop
|
|
||||||
let (pts,prs) = mpsc::channel::<bool>();
|
|
||||||
|
|
||||||
// event queue populated by the main thread and consumed by the exec thread
|
|
||||||
let evq = Arc::new(Mutex::new(CircularBuffer::<EventBuf>::new(conf.queue_length)));
|
|
||||||
|
|
||||||
// background execution loop
|
|
||||||
let rq = evq.clone();
|
|
||||||
let exec_thread = s.spawn(move || -> Result<(),Error> {
|
|
||||||
loop {
|
|
||||||
if prs.recv()? {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
// nest the lock into a scope to release it before run
|
|
||||||
let (ev,start): (EventBuf,Instant) = {
|
|
||||||
let mut evq = rq.lock().unwrap();
|
|
||||||
if evq.size() > 0 {
|
|
||||||
(evq.remove().unwrap(), Instant::now())
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
eventmap.run_event(&ev.as_event()).unwrap_or_else(|e| eprintln!("ERROR: error on run: {}", e) );
|
|
||||||
// wait until interval has been reached
|
|
||||||
let elapsed_time = start.elapsed();
|
|
||||||
if elapsed_time < conf.interval {
|
|
||||||
thread::sleep(conf.interval - elapsed_time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
input.handle_input(|_,m,t,(evq,pts)| {
|
|
||||||
let mut event: EventBuf = Event::from(m).into();
|
|
||||||
event.timestamp = t;
|
|
||||||
let mut evq = evq.lock().unwrap();
|
|
||||||
evq.add(event).unwrap();
|
|
||||||
pts.send(false).expect("unexpected write error");
|
|
||||||
}, (ts,rs), (evq,pts.clone()))?;
|
|
||||||
|
|
||||||
pts.send(true).expect("unexpected write error");
|
|
||||||
let _ = exec_thread.join();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
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,tss,rss): (mpsc::Sender<Option<MidiPortHandler>>, mpsc::Sender<bool>, mpsc::Receiver<bool>)) -> Result<(), Error>
|
|
||||||
where T: MidiInput<A>
|
|
||||||
{
|
|
||||||
input.device_events(ts, (tss, rss))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
21
src/midi/port.rs
Normal file
21
src/midi/port.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Eq,PartialEq,Debug,Clone)]
|
||||||
|
pub struct MidiPort<T>
|
||||||
|
where
|
||||||
|
T: Clone
|
||||||
|
{
|
||||||
|
pub name: String,
|
||||||
|
pub addr: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T> Display for MidiPort<T>
|
||||||
|
where
|
||||||
|
T: Display+Clone
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}\t{}", self.addr, self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/midi/portfilter.rs
Normal file
28
src/midi/portfilter.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
use crate::Error;
|
||||||
|
use crate::config::DeviceConfig;
|
||||||
|
use crate::config::device::Identifier;
|
||||||
|
use crate::util::InternalTryFrom;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug,Clone)]
|
||||||
|
pub enum PortFilter<T>{
|
||||||
|
All,
|
||||||
|
Name(String),
|
||||||
|
Regex(regex::Regex),
|
||||||
|
Addr(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<T> InternalTryFrom<&DeviceConfig> for PortFilter<T>
|
||||||
|
where
|
||||||
|
T: InternalTryFrom<String>,
|
||||||
|
{
|
||||||
|
fn i_try_from(conf: &DeviceConfig) -> Result<Self, Error> {
|
||||||
|
Ok(match &conf.identifier {
|
||||||
|
Identifier::All => PortFilter::All,
|
||||||
|
Identifier::Name(s) => PortFilter::Name(s.clone()),
|
||||||
|
Identifier::Regex(s) => PortFilter::Regex(s.clone()),
|
||||||
|
Identifier::Addr(s) => PortFilter::Addr(T::i_try_from(s.to_string())?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/run.rs
53
src/run.rs
|
|
@ -5,10 +5,12 @@ use std::sync::{Mutex,Arc};
|
||||||
use libc::SIGUSR1;
|
use libc::SIGUSR1;
|
||||||
use signal_hook::iterator::Signals;
|
use signal_hook::iterator::Signals;
|
||||||
|
|
||||||
use crate::Error;
|
use crate::util::InternalTryFrom;
|
||||||
use crate::midi::{PortFilter,MidiHandler,MidiPortHandler};
|
use crate::{Error, constant};
|
||||||
|
use crate::midi::{PortFilter,MidiInputHandler, MidiPort, Builder};
|
||||||
use crate::config::{Config,DeviceConfig};
|
use crate::config::{Config,DeviceConfig};
|
||||||
use crate::eventmap::EventMap;
|
use crate::eventmap::EventMap;
|
||||||
|
use crate::midi::builder::builder;
|
||||||
|
|
||||||
type DeviceRunItem<'a> = (&'a DeviceConfig, EventMap<'a>, Option<Arc<Mutex<(u32, u32)>>>);
|
type DeviceRunItem<'a> = (&'a DeviceConfig, EventMap<'a>, Option<Arc<Mutex<(u32, u32)>>>);
|
||||||
type DeviceRunResult<'a> =(thread::ScopedJoinHandle<'a, Result<(), Error>>, mpsc::Sender<bool>);
|
type DeviceRunResult<'a> =(thread::ScopedJoinHandle<'a, Result<(), Error>>, mpsc::Sender<bool>);
|
||||||
|
|
@ -24,25 +26,34 @@ pub fn cross_shell(cmd: &str) -> Vec<String> {
|
||||||
).collect()
|
).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_devices() -> Result<(), Error> {
|
builder!(ListDevicesBuilder, list_devices, (), Result<(), Error>);
|
||||||
let input = MidiHandler::new("rmidimap")?;
|
builder!(RunConfigBuilder, run_config, &Config, Result<(), Error>);
|
||||||
let ports = input.ports()?;
|
|
||||||
|
pub fn list_devices<T>(input: &T, _: ()) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: MidiInputHandler+Send+'static,
|
||||||
|
<T as MidiInputHandler>::DeviceAddr: 'static+std::fmt::Display,
|
||||||
|
{
|
||||||
|
let ports = MidiInputHandler::ports(input)?;
|
||||||
|
println!(" Addr\t Name");
|
||||||
for p in ports {
|
for p in ports {
|
||||||
println!("{}", p);
|
println!("{}", p);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_config(conf: &Config) -> Result<(), Error> {
|
pub fn run_config<T>(input: &T, conf: &Config) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: MidiInputHandler+Send+'static,
|
||||||
|
<T as MidiInputHandler>::DeviceAddr: 'static+std::fmt::Display+InternalTryFrom<String>,
|
||||||
|
{
|
||||||
let cfevmap: Vec<DeviceRunItem> = conf.devices.iter().map(|x|
|
let cfevmap: Vec<DeviceRunItem> = conf.devices.iter().map(|x|
|
||||||
(x, EventMap::from(x),
|
(x, EventMap::from(x),
|
||||||
x.max_connections.map(|v| (Arc::new(Mutex::new((0,v)))))
|
x.max_connections.map(|v| (Arc::new(Mutex::new((0,v)))))
|
||||||
)
|
)
|
||||||
).collect();
|
).collect();
|
||||||
|
|
||||||
let input = MidiHandler::new("rmidimap")?;
|
let (tdev,rdev) = mpsc::channel::<Option<MidiPort<T::DeviceAddr>>>();
|
||||||
|
|
||||||
let (tdev,rdev) = mpsc::channel::<Option<MidiPortHandler>>();
|
|
||||||
let (tsd,rsd) = mpsc::channel::<bool>();
|
let (tsd,rsd) = mpsc::channel::<bool>();
|
||||||
|
|
||||||
let ntsd = tsd.clone();
|
let ntsd = tsd.clone();
|
||||||
|
|
@ -66,11 +77,11 @@ pub fn run_config(conf: &Config) -> Result<(), Error> {
|
||||||
let mut threads: Vec<DeviceRunResult> = Vec::new();
|
let mut threads: Vec<DeviceRunResult> = Vec::new();
|
||||||
let ports = input.ports()?;
|
let ports = input.ports()?;
|
||||||
for p in ports {
|
for p in ports {
|
||||||
if let Some(v) = try_connect_process(&input, s, &p, &cfevmap)? { threads.push(v) }
|
if let Some(v) = try_connect_process(input, s, &p, &cfevmap)? { threads.push(v) }
|
||||||
}
|
}
|
||||||
|
|
||||||
let event_thread = s.spawn(move || {
|
let event_thread = s.spawn(move || {
|
||||||
let mut input = MidiHandler::new("rmidimap-event-watcher").unwrap();
|
let mut input = T::new(constant::CLIENT_NAME_EVENT).unwrap();
|
||||||
let r = input.device_events(tdev.clone(), (tsd,rsd));
|
let r = input.device_events(tdev.clone(), (tsd,rsd));
|
||||||
tdev.send(None).unwrap();
|
tdev.send(None).unwrap();
|
||||||
r
|
r
|
||||||
|
|
@ -81,7 +92,11 @@ pub fn run_config(conf: &Config) -> Result<(), Error> {
|
||||||
if p.is_none() {
|
if p.is_none() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if let Some(v) = try_connect_process(&input, s, &p.unwrap(), &cfevmap)? { threads.push(v) }
|
let p = p.unwrap();
|
||||||
|
if conf.log {
|
||||||
|
println!("{}: device connect: {}", constant::CLIENT_NAME, p);
|
||||||
|
}
|
||||||
|
if let Some(v) = try_connect_process(input, s, &p, &cfevmap)? { threads.push(v) }
|
||||||
};
|
};
|
||||||
event_thread.join().unwrap()?;
|
event_thread.join().unwrap()?;
|
||||||
for (thread,ss) in threads {
|
for (thread,ss) in threads {
|
||||||
|
|
@ -93,13 +108,17 @@ pub fn run_config(conf: &Config) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_connect_process<'a>(
|
fn try_connect_process<'a, T>(
|
||||||
input: &MidiHandler,
|
input: &T,
|
||||||
s: &'a thread::Scope<'a, '_>,
|
s: &'a thread::Scope<'a, '_>,
|
||||||
p: &MidiPortHandler,
|
p: &MidiPort<T::DeviceAddr>,
|
||||||
cfevmap: &'a[DeviceRunItem<'a>],
|
cfevmap: &'a[DeviceRunItem<'a>],
|
||||||
)
|
)
|
||||||
-> Result<Option<DeviceRunResult<'a>>, Error> {
|
-> Result<Option<DeviceRunResult<'a>>, Error>
|
||||||
|
where
|
||||||
|
T: MidiInputHandler+Send+'static,
|
||||||
|
<T as MidiInputHandler>::DeviceAddr: 'static+InternalTryFrom<String>,
|
||||||
|
{
|
||||||
for (dev, eventmap, counter) in cfevmap {
|
for (dev, eventmap, counter) in cfevmap {
|
||||||
// device counter is full
|
// device counter is full
|
||||||
if let Some(m) = counter {
|
if let Some(m) = counter {
|
||||||
|
|
@ -109,7 +128,7 @@ fn try_connect_process<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut c) = input.try_connect(p.clone(), PortFilter::from(*dev))? {
|
if let Some(mut c) = input.try_connect(p.clone(), PortFilter::i_try_from(*dev)?)? {
|
||||||
// increase device counter
|
// increase device counter
|
||||||
if let Some(m) = counter {
|
if let Some(m) = counter {
|
||||||
let mut m = m.lock().unwrap();
|
let mut m = m.lock().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -41,3 +41,10 @@ where
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait InternalTryFrom<T>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
fn i_try_from(value: T) -> Result<Self, crate::Error>;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue