First working DI

This commit is contained in:
GHOSCHT 2024-12-24 01:49:50 +01:00
parent ca8ee979ea
commit 809e0c5b10
Signed by: ghoscht
GPG key ID: 2C2C1C62A5388E82
6 changed files with 109 additions and 48 deletions

View file

@ -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>>(
let mut midi_in = MidiInput::new(CLIENT_NAME).expect("Creating MIDI device failed"); MidiServiceImplParameters::<MidirPortImpl> {
midi_in.ignore(Ignore::None); device_name: "nanoKONTROL2".to_string(),
out_channel: self.out_channel,
let in_ports = midi_in.ports(); phantom: PhantomData,
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));
}, },
(), )
); .build();
loop {
// sleep forever, callback functions happen on separate thread let service: Arc<dyn MidiService<MidirPortImpl>> = module.resolve();
// couldn't reuse this thread, since _conn_in needs to be alive in this scope let handle = service.start();
std::thread::sleep(time::Duration::from_secs(u64::MAX)); let _ = handle.join();
}
} }
} }

View file

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

View 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<()>;
}

View 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();
})
}
}

View file

@ -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,
) -> () { ) -> () {

View file

@ -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");
} }