From 394355331d75cd3b859f06c0a9d8e4f19b082d2e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 4 Jul 2024 00:02:22 +0300 Subject: [PATCH] wip: connect devices --- src/core.rs | 4 +- src/core/jack.rs | 109 ++++++++++++++++++++++++++++++++++++++++ src/core/port.rs | 4 +- src/core/render.rs | 32 +++++++----- src/main.rs | 40 ++++++++------- src/model/plugin.rs | 41 ++++++--------- src/model/plugin/lv2.rs | 51 ++++--------------- src/model/sampler.rs | 78 +++++++++++++++------------- src/model/track.rs | 20 ++++---- src/view/chain.rs | 1 + 10 files changed, 235 insertions(+), 145 deletions(-) diff --git a/src/core.rs b/src/core.rs index 6bfec915..0060133e 100644 --- a/src/core.rs +++ b/src/core.rs @@ -24,14 +24,14 @@ pub trait Component: Render + Handle {} impl Component for T {} /// A UI component that may have presence on the JACK grap. -pub trait Device: Render + Handle + Ports + Send + Sync { +pub trait Device: Render + Handle + Process + Ports + Send + Sync { fn boxed (self) -> Box where Self: Sized + 'static { Box::new(self) } } /// All things that implement the required traits can be treated as `Device`. -impl Device for T {} +impl Device for T {} // Reexport macros: pub use crate::{ diff --git a/src/core/jack.rs b/src/core/jack.rs index f4e2a894..582413f8 100644 --- a/src/core/jack.rs +++ b/src/core/jack.rs @@ -1,5 +1,114 @@ use crate::core::*; +pub struct Jack { + pub client: JackClient, + pub ports: JackPorts, +} + +pub enum JackClient { + Active(DynamicAsyncClient), + Inactive(Client), +} + +#[derive(Default)] +pub struct JackPorts { + pub audio_ins: BTreeMap>, + pub midi_ins: BTreeMap>, + pub audio_outs: BTreeMap>, + pub midi_outs: BTreeMap>, +} + +ports!(Jack { + audio: { + ins: |s|Ok(s.ports.audio_ins.values().collect()), + outs: |s|Ok(s.ports.audio_outs.values().collect()), + } + midi: { + ins: |s|Ok(s.ports.midi_ins.values().collect()), + outs: |s|Ok(s.ports.midi_outs.values().collect()), + } +}); + +impl Jack { + pub fn new (name: &str) -> Usually { + let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; + Ok(Self { client: JackClient::Inactive(client), ports: JackPorts::default(), }) + } + pub fn ports_from_lv2 (self, plugin: &::livi::Plugin) -> Usually { + let counts = plugin.port_counts(); + let mut jack = self; + for i in 0..counts.atom_sequence_inputs { + jack = jack.register_midi_in(&format!("midi-in-{i}"))? + } + for i in 0..counts.atom_sequence_outputs { + jack = jack.register_midi_out(&format!("midi-out-{i}"))?; + } + for i in 0..counts.audio_inputs { + jack = jack.register_audio_in(&format!("audio-in-{i}"))? + } + for i in 0..counts.audio_outputs { + jack = jack.register_audio_out(&format!("audio-out-{i}"))?; + } + Ok(jack) + } + pub fn register_midi_out (mut self, name: &str) -> Usually { + self.ports.midi_outs.insert(name.to_string(), match &self.client { + JackClient::Inactive(c) => c.register_port(name, MidiOut::default())?, + JackClient::Active(c) => c.as_client().register_port(name, MidiOut::default())?, + }); + Ok(self) + } + pub fn register_midi_in (mut self, name: &str) -> Usually { + self.ports.midi_ins.insert(name.to_string(), match &self.client { + JackClient::Inactive(c) => c.register_port(name, MidiIn::default())?, + JackClient::Active(c) => c.as_client().register_port(name, MidiIn::default())?, + }); + Ok(self) + } + pub fn register_audio_out (mut self, name: &str) -> Usually { + self.ports.audio_outs.insert(name.to_string(), match &self.client { + JackClient::Inactive(c) => c.register_port(name, AudioOut::default())?, + JackClient::Active(c) => c.as_client().register_port(name, AudioOut::default())?, + }); + Ok(self) + } + pub fn register_audio_in (mut self, name: &str) -> Usually { + self.ports.audio_ins.insert(name.to_string(), match &self.client { + JackClient::Inactive(c) => c.register_port(name, AudioIn::default())?, + JackClient::Active(c) => c.as_client().register_port(name, AudioIn::default())?, + }); + Ok(self) + } + pub fn run ( + mut self, state: impl FnOnce(JackPorts)->Box + ) -> Usually>>> where + T: Device + Process + Sized + 'static + { + let mut ports = JackPorts::default(); + std::mem::swap(&mut self.ports, &mut ports); + let state = Arc::new(Mutex::new(state(ports) as Box)); + self.client = match self.client { + JackClient::Active(_) => panic!("already running"), + JackClient::Inactive(client) => JackClient::Active(client.activate_async( + Notifications(Box::new({ + let _state = state.clone(); + move|_event|{ + // FIXME: this deadlocks + //state.lock().unwrap().handle(&event).unwrap(); + } + }) as Box), + ClosureProcessHandler::new(Box::new({ + let state = state.clone(); + move|c: &Client, s: &ProcessScope|{ + state.lock().unwrap().process(c, s) + } + }) as BoxedProcessHandler) + )?) + }; + Ok(state) + } +} + pub fn jack_run (name: &str, app: &Arc>) -> Usually where T: Handle + Process + Send + 'static { diff --git a/src/core/port.rs b/src/core/port.rs index e39eedd3..a39c01c6 100644 --- a/src/core/port.rs +++ b/src/core/port.rs @@ -2,10 +2,10 @@ use crate::core::*; /// Trait for things that may expose JACK ports. pub trait Ports { - fn audio_ins (&self) -> Usually>> { + fn audio_ins <'a> (&'a self) -> Usually>> { Ok(vec![]) } - fn audio_outs (&self) -> Usually>> { + fn audio_outs <'a> (&'a self) -> Usually>> { Ok(vec![]) } fn midi_ins <'a> (&'a self) -> Usually>> { diff --git a/src/core/render.rs b/src/core/render.rs index 84a3dd64..56a3fc1d 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -1,6 +1,19 @@ use crate::core::*; use ratatui::widgets::WidgetRef; +pub trait Blit { + // Render something to X, Y coordinates in a buffer, ignoring width/height. + fn blit (&self, buf: &mut Buffer, x: u16, y: u16, style: Option