First working DI
This commit is contained in:
parent
ca8ee979ea
commit
809e0c5b10
6 changed files with 109 additions and 48 deletions
|
@ -1,9 +1,14 @@
|
||||||
use crate::{config::NanoKeys, interaction_server::InteractionServer};
|
use crate::{config::NanoKeys, interaction_server::InteractionServer};
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use midir::{Ignore, MidiInput};
|
use midir::MidiInput;
|
||||||
use regex::Regex;
|
use shaku::{module, HasComponent, Interface};
|
||||||
use std::thread::{self, JoinHandle};
|
use std::marker::PhantomData;
|
||||||
use std::time;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::midi_client::MidiPort;
|
||||||
|
use super::midi_service::MidiService;
|
||||||
|
use super::midi_service_impl::{MidiServiceImpl, MidiServiceImplParameters};
|
||||||
|
use super::midir_client_impl::{MidirClientImpl, MidirPortImpl};
|
||||||
|
|
||||||
const CLIENT_NAME: &str = "PicoKontroller";
|
const CLIENT_NAME: &str = "PicoKontroller";
|
||||||
const PORT_NAME: &str = "PicoController Input";
|
const PORT_NAME: &str = "PicoController Input";
|
||||||
|
@ -16,11 +21,10 @@ pub struct MidiClient {
|
||||||
pub type KeyEvent = (NanoKeys, KeyState);
|
pub type KeyEvent = (NanoKeys, KeyState);
|
||||||
pub type KeyState = u8;
|
pub type KeyState = u8;
|
||||||
|
|
||||||
impl InteractionServer for MidiClient {
|
module! {
|
||||||
fn start(self) -> JoinHandle<()> {
|
MyModule<MP: MidiPort + Interface> {
|
||||||
thread::spawn(move || {
|
components = [MidiServiceImpl<MidirPortImpl>, MidirClientImpl],
|
||||||
self.exec();
|
providers = [],
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,34 +36,20 @@ impl MidiClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec(self) {
|
pub fn start(self) -> () {
|
||||||
let port_filter = Regex::new(&self.device_name).expect("Creating RegEx failed");
|
let module: MyModule<MidirPortImpl> = MyModule::builder()
|
||||||
|
.with_component_parameters::<MidiServiceImpl<MidirPortImpl>>(
|
||||||
|
MidiServiceImplParameters::<MidirPortImpl> {
|
||||||
|
device_name: "nanoKONTROL2".to_string(),
|
||||||
|
out_channel: self.out_channel,
|
||||||
|
phantom: PhantomData,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
let mut midi_in = MidiInput::new(CLIENT_NAME).expect("Creating MIDI device failed");
|
let service: Arc<dyn MidiService<MidirPortImpl>> = module.resolve();
|
||||||
midi_in.ignore(Ignore::None);
|
let handle = service.start();
|
||||||
|
let _ = handle.join();
|
||||||
let in_ports = midi_in.ports();
|
|
||||||
let in_port = in_ports
|
|
||||||
.iter()
|
|
||||||
.find(|p| port_filter.is_match(&midi_in.port_name(p).unwrap()))
|
|
||||||
.expect("Couldn't find a port matching the supplied RegEx");
|
|
||||||
|
|
||||||
// _conn_in needs to be a named parameter, because it needs to be kept alive until the end of the scope
|
|
||||||
let _conn_in = midi_in.connect(
|
|
||||||
in_port,
|
|
||||||
PORT_NAME,
|
|
||||||
move |_, message, _| {
|
|
||||||
let key = NanoKeys::try_from(message[1]).unwrap();
|
|
||||||
let state = message[2];
|
|
||||||
let _ = self.out_channel.send((key, state));
|
|
||||||
},
|
|
||||||
(),
|
|
||||||
);
|
|
||||||
loop {
|
|
||||||
// sleep forever, callback functions happen on separate thread
|
|
||||||
// couldn't reuse this thread, since _conn_in needs to be alive in this scope
|
|
||||||
std::thread::sleep(time::Duration::from_secs(u64::MAX));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use shaku::{self, Interface};
|
use shaku::{self, Interface};
|
||||||
|
|
||||||
pub type MidiConnectionCallback = Box<dyn Fn(&[u8]) + Send + 'static>;
|
pub type MidiConnectionCallback = Box<dyn Fn(&[u8]) + Send>;
|
||||||
|
|
||||||
#[cfg_attr(test, mockall::automock)]
|
#[cfg_attr(test, mockall::automock)]
|
||||||
pub trait MidiClient<MP: MidiPort>: Interface {
|
pub trait MidiClient<MP: MidiPort + Interface>: Interface {
|
||||||
fn ports(&self) -> Vec<MP>;
|
fn ports(&self) -> Vec<MP>;
|
||||||
fn connect(&self, port: MP, port_name: &str, callback: MidiConnectionCallback) -> ();
|
fn connect(&self, port: &MP, port_name: &str, callback: MidiConnectionCallback) -> ();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, mockall::automock)]
|
#[cfg_attr(test, mockall::automock)]
|
||||||
pub trait MidiPort : Interface{
|
pub trait MidiPort: Interface {
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
11
src/clients/midi_service.rs
Normal file
11
src/clients/midi_service.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
use std::{sync::Arc, thread::JoinHandle};
|
||||||
|
|
||||||
|
use shaku::Interface;
|
||||||
|
|
||||||
|
use super::midi_client::MidiPort;
|
||||||
|
|
||||||
|
pub trait MidiService<MP: MidiPort + Interface>: Interface {
|
||||||
|
fn exec(self: Arc<Self>);
|
||||||
|
fn list_ports(&self) -> ();
|
||||||
|
fn start(self: Arc<Self>) -> JoinHandle<()>;
|
||||||
|
}
|
59
src/clients/midi_service_impl.rs
Normal file
59
src/clients/midi_service_impl.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use super::midi_client::{MidiClient, MidiPort};
|
||||||
|
use super::midi_service::MidiService;
|
||||||
|
use crate::config::NanoKeys;
|
||||||
|
use crossbeam_channel::Sender;
|
||||||
|
use regex::Regex;
|
||||||
|
use shaku::{self, Component, Interface};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread::{self, JoinHandle};
|
||||||
|
|
||||||
|
const CLIENT_NAME: &str = "PicoKontroller";
|
||||||
|
const PORT_NAME: &str = "PicoController Input";
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
#[shaku(interface = MidiService<MP>)]
|
||||||
|
pub struct MidiServiceImpl<MP: MidiPort + Interface> {
|
||||||
|
#[shaku(default)]
|
||||||
|
device_name: String,
|
||||||
|
#[shaku(default = unimplemented!())]
|
||||||
|
out_channel: Sender<KeyEvent>,
|
||||||
|
#[shaku(inject)]
|
||||||
|
midi_client: Arc<dyn MidiClient<MP>>,
|
||||||
|
#[shaku(default = PhantomData)]
|
||||||
|
phantom: PhantomData<MP>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type KeyEvent = (NanoKeys, KeyState);
|
||||||
|
pub type KeyState = u8;
|
||||||
|
|
||||||
|
impl<MP: MidiPort + Interface> MidiService<MP> for MidiServiceImpl<MP> {
|
||||||
|
fn exec(self: Arc<Self>) {
|
||||||
|
let port_filter = Regex::new(&self.device_name).expect("Creating RegEx failed");
|
||||||
|
let ports = self.midi_client.ports();
|
||||||
|
|
||||||
|
let port = ports
|
||||||
|
.iter()
|
||||||
|
.find(|p| port_filter.is_match(&p.name()))
|
||||||
|
.expect("Couldn't find a port matching the supplied RegEx");
|
||||||
|
|
||||||
|
let channel = self.out_channel.clone();
|
||||||
|
|
||||||
|
let callback = Box::new(move |message: &[u8]| {
|
||||||
|
let key = NanoKeys::try_from(message[1]).unwrap();
|
||||||
|
let state = message[2];
|
||||||
|
let _ = channel.send((key, state));
|
||||||
|
});
|
||||||
|
|
||||||
|
self.midi_client.connect(&port, PORT_NAME, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_ports(&self) -> () {}
|
||||||
|
|
||||||
|
fn start(self: Arc<Self>) -> JoinHandle<()> {
|
||||||
|
let foo = self.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
foo.exec();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use midir::{MidiInput, MidiInputPort, Ignore};
|
use midir::{Ignore, MidiInput, MidiInputPort};
|
||||||
use shaku::{self, Component};
|
use shaku::{self, Component};
|
||||||
|
|
||||||
use super::midi_client::{MidiClient, MidiConnectionCallback, MidiPort};
|
use super::midi_client::{MidiClient, MidiConnectionCallback, MidiPort};
|
||||||
|
@ -13,6 +13,7 @@ fn create_input(client_name: &str) -> MidiInput {
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
#[shaku(interface = MidiClient<MidirPortImpl>)]
|
#[shaku(interface = MidiClient<MidirPortImpl>)]
|
||||||
pub struct MidirClientImpl {
|
pub struct MidirClientImpl {
|
||||||
|
#[shaku(default)]
|
||||||
client_name: String,
|
client_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,9 +26,10 @@ impl MidiClient<MidirPortImpl> for MidirClientImpl {
|
||||||
.map(|p| MidirPortImpl::new(p.clone(), self.client_name.clone())) // Cast to Box<dyn MidiPort>
|
.map(|p| MidirPortImpl::new(p.clone(), self.client_name.clone())) // Cast to Box<dyn MidiPort>
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connect(
|
fn connect(
|
||||||
&self,
|
&self,
|
||||||
port: MidirPortImpl,
|
port: &MidirPortImpl,
|
||||||
port_name: &str,
|
port_name: &str,
|
||||||
callback: MidiConnectionCallback,
|
callback: MidiConnectionCallback,
|
||||||
) -> () {
|
) -> () {
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -1,19 +1,18 @@
|
||||||
mod config;
|
mod config;
|
||||||
mod router;
|
|
||||||
mod interaction_server;
|
mod interaction_server;
|
||||||
|
mod router;
|
||||||
|
|
||||||
mod controllers {
|
mod controllers {
|
||||||
|
pub mod exec;
|
||||||
pub mod mpris;
|
pub mod mpris;
|
||||||
pub mod pulse;
|
pub mod pulse;
|
||||||
pub mod exec;
|
|
||||||
}
|
}
|
||||||
mod clients;
|
mod clients;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use clients::midi::MidiClient;
|
||||||
use crossbeam_channel::unbounded;
|
use crossbeam_channel::unbounded;
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use interaction_server::InteractionServer;
|
|
||||||
use clients::midi::MidiClient;
|
|
||||||
use std::{path::PathBuf, process::exit, sync::Arc};
|
use std::{path::PathBuf, process::exit, sync::Arc};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
@ -47,6 +46,6 @@ fn main() {
|
||||||
|
|
||||||
router::run(receiver, Arc::clone(&arc_cfg));
|
router::run(receiver, Arc::clone(&arc_cfg));
|
||||||
|
|
||||||
let server = midi.start();
|
midi.start();
|
||||||
let _ = server.join();
|
println!("fooo");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue