From 029614631e02db4efa0682aef0cf0b32c457be0f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 13 Nov 2024 19:14:29 +0100 Subject: [PATCH] wip: refactor pt.21: api traits --- crates/tek_api/src/arrange.rs | 164 +++++++---- crates/tek_api/src/lib.rs | 12 + crates/tek_api/src/sampler.rs | 3 +- crates/tek_api/src/sequencer.rs | 44 +++ crates/tek_api/src/transport.rs | 46 ++- crates/tek_core/src/audio.rs | 431 +++------------------------- crates/tek_snd/src/lib.rs | 364 +++++++++++++++++++++++ crates/tek_snd/src/snd_arrange.rs | 35 ++- crates/tek_tui/src/tui_arranger.rs | 8 +- crates/tek_tui/src/tui_sequencer.rs | 9 - 10 files changed, 626 insertions(+), 490 deletions(-) diff --git a/crates/tek_api/src/arrange.rs b/crates/tek_api/src/arrange.rs index 5cfb1a93..53b28ac5 100644 --- a/crates/tek_api/src/arrange.rs +++ b/crates/tek_api/src/arrange.rs @@ -1,19 +1,104 @@ use crate::*; +pub trait ArrangerModelApi: JackModelApi + ClockModelApi { + fn name (&self) -> &Arc>; + fn phrases (&self) -> &Arc>; + fn tracks (&self) -> &Vec; + fn tracks_mut (&mut self) -> &mut Vec; + fn scenes (&self) -> &Vec; + fn scenes_mut (&mut self) -> &mut Vec; + + fn track_default_name (&self) -> String { + format!("Track {}", self.tracks().len() + 1) + } + fn track_add ( + &mut self, name: Option<&str>, color: Option + ) -> Usually<&mut ArrangerTrack> { + let name = name.map_or_else(||self.track_default_name(), |x|x.to_string()); + let track = ArrangerTrack { + width: name.len() + 2, + color: color.unwrap_or_else(||ItemColor::random()), + player: MIDIPlayer::new(&self.jack(), &self.clock(), name.as_str())?, + name: Arc::new(name.into()), + }; + self.tracks_mut().push(track); + let index = self.tracks().len() - 1; + Ok(&mut self.tracks_mut()[index]) + } + fn track_del (&mut self, index: usize) { + self.tracks_mut().remove(index); + for scene in self.scenes_mut().iter_mut() { + scene.clips.remove(index); + } + } + fn scene_default_name (&self) -> String { + format!("Scene {}", self.scenes().len() + 1) + } + fn scene_add ( + &mut self, name: Option<&str>, color: Option + ) -> Usually<&mut ArrangerScene> { + let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string()); + let scene = ArrangerScene { + name: Arc::new(name.into()), + clips: vec![None;self.tracks().len()], + color: color.unwrap_or_else(||ItemColor::random()), + }; + self.scenes_mut().push(scene); + let index = self.scenes().len() - 1; + Ok(&mut self.scenes_mut()[index]) + } + fn scene_del (&mut self, index: usize) { + self.scenes_mut().remove(index); + } +} + +impl JackModelApi for ArrangerModel { + fn jack (&self) -> &Arc> { + &self.jack + } +} + +impl ClockModelApi for ArrangerModel { + fn clock (&self) -> &Arc { + &self.clock + } +} + +impl ArrangerModelApi for ArrangerModel { + fn name (&self) -> &Arc> { + &self.name + } + fn phrases (&self) -> &Arc> { + &self.phrases + } + fn tracks (&self) -> &Vec { + &self.tracks + } + fn tracks_mut (&mut self) -> &mut Vec { + &mut self.tracks + } + fn scenes (&self) -> &Vec { + &self.scenes + } + fn scenes_mut (&mut self) -> &mut Vec { + &mut self.scenes + } +} + #[derive(Debug)] pub struct ArrangerModel { /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Arc>, + jack: Arc>, /// Global timebase - pub clock: Arc, + clock: Arc, /// Name of arranger - pub name: Arc>, + name: Arc>, /// Collection of phrases. - pub phrases: Arc>, + phrases: Arc>, /// Collection of tracks. - pub tracks: Vec, + tracks: Vec, /// Collection of scenes. - pub scenes: Vec, + scenes: Vec, } #[derive(Debug)] @@ -38,50 +123,21 @@ pub struct ArrangerScene { pub color: ItemColor, } -impl ArrangerModel { - pub fn is_stopped (&self) -> bool { - *self.clock.playing.read().unwrap() == Some(TransportState::Stopped) +impl ArrangerTrack { + pub fn longest_name (tracks: &[Self]) -> usize { + tracks.iter().map(|s|s.name.read().unwrap().len()).fold(0, usize::max) } - pub fn track_default_name (&self) -> String { - format!("Track {}", self.tracks.len() + 1) + + pub const MIN_WIDTH: usize = 3; + + pub fn width_inc (&mut self) { + self.width += 1; } - pub fn scene_default_name (&self) -> String { - format!("Scene {}", self.scenes.len() + 1) - } - pub fn track_add ( - &mut self, name: Option<&str>, color: Option - ) -> Usually<&mut ArrangerTrack> { - let name = name.map_or_else(||self.track_default_name(), |x|x.to_string()); - self.tracks.push(ArrangerTrack { - width: name.len() + 2, - color: color.unwrap_or_else(||ItemColor::random()), - player: MIDIPlayer::new(&self.jack, &self.clock, name.as_str())?, - name: Arc::new(name.into()), - }); - let index = self.tracks.len() - 1; - Ok(&mut self.tracks[index]) - } - pub fn scene_add ( - &mut self, name: Option<&str>, color: Option - ) -> Usually<&mut ArrangerScene> { - let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string()); - self.scenes.push(ArrangerScene { - name: Arc::new(name.into()), - clips: vec![None;self.tracks.len()], - color: color.unwrap_or_else(||ItemColor::random()), - }); - let index = self.scenes.len() - 1; - Ok(&mut self.scenes[index]) - } - pub fn track_del (&mut self, index: usize) { - self.tracks.remove(index); - for scene in self.scenes.iter_mut() { - scene.clips.remove(index); + pub fn width_dec (&mut self) { + if self.width > Self::MIN_WIDTH { + self.width -= 1; } } - pub fn scene_del (&mut self, index: usize) { - self.scenes.remove(index); - } } impl ArrangerScene { @@ -160,19 +216,3 @@ impl ArrangerScene { //}) //} } - -impl ArrangerTrack { - pub fn longest_name (tracks: &[Self]) -> usize { - tracks.iter().map(|s|s.name.read().unwrap().len()).fold(0, usize::max) - } - - pub const MIN_WIDTH: usize = 3; - pub fn width_inc (&mut self) { - self.width += 1; - } - pub fn width_dec (&mut self) { - if self.width > Self::MIN_WIDTH { - self.width -= 1; - } - } -} diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index 0db69c44..d7437d6c 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -38,3 +38,15 @@ submod! { transport transport_cmd } + +pub trait JackModelApi { + fn jack (&self) -> &Arc>; +} + +pub trait ClockModelApi { + fn clock (&self) -> &Arc; + fn is_stopped (&self) -> bool { + *self.clock().playing.read().unwrap() == Some(TransportState::Stopped) + } +} + diff --git a/crates/tek_api/src/sampler.rs b/crates/tek_api/src/sampler.rs index 11358035..b866676f 100644 --- a/crates/tek_api/src/sampler.rs +++ b/crates/tek_api/src/sampler.rs @@ -41,6 +41,7 @@ impl Sampler { }, _ => panic!("unexpected in sampler {name}: {edn:?}") }); + let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?; Ok(Sampler { jack: jack.clone(), name: name.into(), @@ -48,7 +49,7 @@ impl Sampler { unmapped: Default::default(), voices: Default::default(), buffer: Default::default(), - midi_in: jack.read().unwrap().register_port("in", MidiIn::default())?, + midi_in: midi_in, audio_outs: vec![], output_gain: 0. }) diff --git a/crates/tek_api/src/sequencer.rs b/crates/tek_api/src/sequencer.rs index 8f718fda..2f0df83e 100644 --- a/crates/tek_api/src/sequencer.rs +++ b/crates/tek_api/src/sequencer.rs @@ -1,5 +1,49 @@ use crate::*; +pub trait SequencerModelApi: JackModelApi + ClockModelApi { + fn phrases (&self) -> &PhrasePool; + fn player (&self) -> &MIDIPlayer; +} + +impl JackModelApi for SequencerModel { + fn jack (&self) -> &Arc> { + self.transport.jack() + } +} + +impl ClockModelApi for SequencerModel { + fn clock (&self) -> &Arc { + self.transport.clock() + } +} + +impl TransportModelApi for SequencerModel { + fn transport (&self) -> &jack::Transport { + &self.transport.transport() + } + fn metronome (&self) -> bool { + self.transport.metronome() + } +} + +impl SequencerModelApi for SequencerModel { + fn phrases (&self) -> &PhrasePool { + &self.phrases + } + fn player (&self) -> &MIDIPlayer { + &self.player + } +} + +pub struct SequencerModel { + /// State of the JACK transport. + transport: TransportModel, + /// State of the phrase pool. + phrases: PhrasePool, + /// State of the phrase player. + player: MIDIPlayer, +} + #[derive(Debug)] pub struct MIDIPlayer { /// Global timebase diff --git a/crates/tek_api/src/transport.rs b/crates/tek_api/src/transport.rs index 17daa765..49e9c7d5 100644 --- a/crates/tek_api/src/transport.rs +++ b/crates/tek_api/src/transport.rs @@ -1,16 +1,42 @@ use crate::*; -pub struct Transport { - pub jack: Arc>, - /// JACK transport handle. - pub transport: jack::Transport, - /// Current sample rate, tempo, and PPQ. - pub clock: Arc, - /// Enable metronome? - pub metronome: bool, +pub trait TransportModelApi: JackModelApi + ClockModelApi { + fn transport (&self) -> &jack::Transport; + fn metronome (&self) -> bool; } -impl Debug for Transport { +impl JackModelApi for TransportModel { + fn jack (&self) -> &Arc> { + &self.jack + } +} + +impl ClockModelApi for TransportModel { + fn clock (&self) -> &Arc { + &self.clock + } +} + +impl TransportModelApi for TransportModel { + fn transport (&self) -> &jack::Transport { + &self.transport + } + fn metronome (&self) -> bool { + self.metronome + } +} + +pub struct TransportModel { + jack: Arc>, + /// Current sample rate, tempo, and PPQ. + clock: Arc, + /// JACK transport handle. + transport: jack::Transport, + /// Enable metronome? + metronome: bool, +} + +impl Debug for TransportModel { fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { f.debug_struct("transport") .field("jack", &self.jack) @@ -21,7 +47,7 @@ impl Debug for Transport { } } -impl Transport { +impl TransportModel { pub fn toggle_play (&mut self) -> Usually<()> { let playing = self.clock.playing.read().unwrap().expect("1st sample has not been processed yet"); let playing = match playing { diff --git a/crates/tek_core/src/audio.rs b/crates/tek_core/src/audio.rs index fac1f602..176b62a6 100644 --- a/crates/tek_core/src/audio.rs +++ b/crates/tek_core/src/audio.rs @@ -1,36 +1,32 @@ use crate::*; use jack::*; -/// Trait for things that have a JACK process callback. -pub trait Audio: Send + Sync { - fn process(&mut self, _: &Client, _: &ProcessScope) -> Control { - Control::Continue - } - fn callback( - state: &Arc>, client: &Client, scope: &ProcessScope - ) -> Control where Self: Sized { - if let Ok(mut state) = state.write() { - state.process(client, scope) - } else { - Control::Quit - } - } +#[derive(Debug)] +/// Event enum for JACK events. +pub enum JackEvent { + ThreadInit, + Shutdown(ClientStatus, String), + Freewheel(bool), + SampleRate(Frames), + ClientRegistration(String, bool), + PortRegistration(PortId, bool), + PortRename(PortId, String, String), + PortsConnected(PortId, PortId, bool), + GraphReorder, + XRun, } -/// A UI component that may be associated with a JACK client by the `Jack` factory. -pub trait AudioComponent: Component + Audio { - /// Perform type erasure for collecting heterogeneous devices. - fn boxed(self) -> Box> - where - Self: Sized + 'static, - { - Box::new(self) - } +/// Wraps [Client] or [DynamicAsyncClient] in place. +#[derive(Debug)] +pub enum JackClient { + /// Before activation. + Inactive(Client), + /// During activation. + Activating, + /// After activation. Must not be dropped for JACK thread to persist. + Active(DynamicAsyncClient), } -/// All things that implement the required traits can be treated as `AudioComponent`. -impl + Audio> AudioComponent for W {} - /// Trait for things that wrap a JACK client. pub trait AudioEngine { @@ -82,64 +78,6 @@ pub trait AudioEngine { } } -/// Wraps [Client] or [DynamicAsyncClient] in place. -#[derive(Debug)] -pub enum JackClient { - /// Before activation. - Inactive(Client), - /// During activation. - Activating, - /// After activation. Must not be dropped for JACK thread to persist. - Active(DynamicAsyncClient), -} - -pub type DynamicAsyncClient = AsyncClient; - -pub type DynamicAudioHandler = contrib::ClosureProcessHandler<(), BoxedAudioHandler>; - -pub type BoxedAudioHandler = Box Control + Send>; - -impl JackClient { - pub fn new (name: &str) -> Usually { - let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; - Ok(Self::Inactive(client)) - } - pub fn activate_with ( - self, - init: impl FnOnce(&Arc>)->Usually - ) - -> Usually>> - { - let client = Arc::new(RwLock::new(self)); - let target = Arc::new(RwLock::new(init(&client)?)); - let event = Box::new(move|_|{/*TODO*/}) as Box; - let events = Notifications(event); - let frame = Box::new({ - let target = target.clone(); - move|c: &_, s: &_|if let Ok(mut target) = target.write() { - target.process(c, s) - } else { - Control::Quit - } - }); - let frames = contrib::ClosureProcessHandler::new(frame as BoxedAudioHandler); - let mut buffer = Self::Activating; - std::mem::swap(&mut*client.write().unwrap(), &mut buffer); - *client.write().unwrap() = Self::Active(Client::from(buffer).activate_async(events, frames)?); - Ok(target) - } -} - -impl From for Client { - fn from (jack: JackClient) -> Client { - match jack { - JackClient::Inactive(client) => client, - JackClient::Activating => panic!("jack client still activating"), - JackClient::Active(_) => panic!("jack client already activated"), - } - } -} - impl AudioEngine for JackClient { fn client(&self) -> &Client { match self { @@ -166,57 +104,27 @@ impl AudioEngine for JackClient { } } -#[derive(Debug)] -/// Event enum for JACK events. -pub enum JackEvent { - ThreadInit, - Shutdown(ClientStatus, String), - Freewheel(bool), - SampleRate(Frames), - ClientRegistration(String, bool), - PortRegistration(PortId, bool), - PortRename(PortId, String, String), - PortsConnected(PortId, PortId, bool), - GraphReorder, - XRun, -} +pub type DynamicAsyncClient = AsyncClient; -/// Trait for things that may expose JACK ports. -pub trait Ports { - fn audio_ins(&self) -> Usually>> { - Ok(vec![]) - } - fn audio_outs(&self) -> Usually>> { - Ok(vec![]) - } - fn midi_ins(&self) -> Usually>> { - Ok(vec![]) - } - fn midi_outs(&self) -> Usually>> { - Ok(vec![]) +pub type DynamicAudioHandler = contrib::ClosureProcessHandler<(), BoxedAudioHandler>; + +pub type BoxedAudioHandler = Box Control + Send>; + +impl JackClient { + pub fn new (name: &str) -> Usually { + let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; + Ok(Self::Inactive(client)) } } -fn register_ports( - client: &Client, - names: Vec, - spec: T, -) -> Usually>> { - names - .into_iter() - .try_fold(BTreeMap::new(), |mut ports, name| { - let port = client.register_port(&name, spec)?; - ports.insert(name, port); - Ok(ports) - }) -} - -fn query_ports(client: &Client, names: Vec) -> BTreeMap> { - names.into_iter().fold(BTreeMap::new(), |mut ports, name| { - let port = client.port_by_name(&name).unwrap(); - ports.insert(name, port); - ports - }) +impl From for Client { + fn from (jack: JackClient) -> Client { + match jack { + JackClient::Inactive(client) => client, + JackClient::Activating => panic!("jack client still activating"), + JackClient::Active(_) => panic!("jack client already activated"), + } + } } /// Notification handler used by the [Jack] factory @@ -271,264 +179,3 @@ impl NotificationHandler for Notifications { Control::Continue } } - -///// A [AudioComponent] bound to a JACK client and a set of ports. -//pub struct JackDevice { - ///// The active JACK client of this device. - //pub client: DynamicAsyncClient, - ///// The device state, encapsulated for sharing between threads. - //pub state: Arc>>>, - ///// Unowned copies of the device's JACK ports, for connecting to the device. - ///// The "real" readable/writable `Port`s are owned by the `state`. - //pub ports: UnownedJackPorts, -//} - -//impl std::fmt::Debug for JackDevice { - //fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - //f.debug_struct("JackDevice") - //.field("ports", &self.ports) - //.finish() - //} -//} - -//impl Widget for JackDevice { - //type Engine = E; - //fn layout(&self, to: E::Size) -> Perhaps { - //self.state.read().unwrap().layout(to) - //} - //fn render(&self, to: &mut E::Output) -> Usually<()> { - //self.state.read().unwrap().render(to) - //} -//} - -//impl Handle for JackDevice { - //fn handle(&mut self, from: &E::Input) -> Perhaps { - //self.state.write().unwrap().handle(from) - //} -//} - -//impl Ports for JackDevice { - //fn audio_ins(&self) -> Usually>> { - //Ok(self.ports.audio_ins.values().collect()) - //} - //fn audio_outs(&self) -> Usually>> { - //Ok(self.ports.audio_outs.values().collect()) - //} - //fn midi_ins(&self) -> Usually>> { - //Ok(self.ports.midi_ins.values().collect()) - //} - //fn midi_outs(&self) -> Usually>> { - //Ok(self.ports.midi_outs.values().collect()) - //} -//} - -//impl JackDevice { - ///// Returns a locked mutex of the state's contents. - //pub fn state(&self) -> LockResult>>> { - //self.state.read() - //} - ///// Returns a locked mutex of the state's contents. - //pub fn state_mut(&self) -> LockResult>>> { - //self.state.write() - //} - //pub fn connect_midi_in(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(port, self.midi_ins()?[index])?) - //} - //pub fn connect_midi_out(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(self.midi_outs()?[index], port)?) - //} - //pub fn connect_audio_in(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(port, self.audio_ins()?[index])?) - //} - //pub fn connect_audio_out(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(self.audio_outs()?[index], port)?) - //} -//} - -///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut]. -//#[derive(Default, Debug)] -//pub struct JackPorts { - //pub audio_ins: BTreeMap>, - //pub midi_ins: BTreeMap>, - //pub audio_outs: BTreeMap>, - //pub midi_outs: BTreeMap>, -//} - -///// Collection of JACK ports as [Unowned]. -//#[derive(Default, Debug)] -//pub struct UnownedJackPorts { - //pub audio_ins: BTreeMap>, - //pub midi_ins: BTreeMap>, - //pub audio_outs: BTreeMap>, - //pub midi_outs: BTreeMap>, -//} - -//impl JackPorts { - //pub fn clone_unowned(&self) -> UnownedJackPorts { - //let mut unowned = UnownedJackPorts::default(); - //for (name, port) in self.midi_ins.iter() { - //unowned.midi_ins.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.midi_outs.iter() { - //unowned.midi_outs.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.audio_ins.iter() { - //unowned.audio_ins.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.audio_outs.iter() { - //unowned - //.audio_outs - //.insert(name.clone(), port.clone_unowned()); - //} - //unowned - //} -//} - -///// Implement the `Ports` trait. -//#[macro_export] -//macro_rules! ports { - //($T:ty $({ $(audio: { - //$(ins: |$ai_arg:ident|$ai_impl:expr,)? - //$(outs: |$ao_arg:ident|$ao_impl:expr,)? - //})? $(midi: { - //$(ins: |$mi_arg:ident|$mi_impl:expr,)? - //$(outs: |$mo_arg:ident|$mo_impl:expr,)? - //})?})?) => { - //impl Ports for $T {$( - //$( - //$(fn audio_ins <'a> (&'a self) -> Usually>> { - //let cb = |$ai_arg:&'a Self|$ai_impl; - //cb(self) - //})? - //)? - //$( - //$(fn audio_outs <'a> (&'a self) -> Usually>> { - //let cb = (|$ao_arg:&'a Self|$ao_impl); - //cb(self) - //})? - //)? - //)? $( - //$( - //$(fn midi_ins <'a> (&'a self) -> Usually>> { - //let cb = (|$mi_arg:&'a Self|$mi_impl); - //cb(self) - //})? - //)? - //$( - //$(fn midi_outs <'a> (&'a self) -> Usually>> { - //let cb = (|$mo_arg:&'a Self|$mo_impl); - //cb(self) - //})? - //)? - //)?} - //}; -//} - -///// `JackDevice` factory. Creates JACK `Client`s, performs port registration -///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. -//pub struct Jack { - //pub client: Client, - //pub midi_ins: Vec, - //pub audio_ins: Vec, - //pub midi_outs: Vec, - //pub audio_outs: Vec, -//} - -//impl Jack { - //pub fn new(name: &str) -> Usually { - //Ok(Self { - //midi_ins: vec![], - //audio_ins: vec![], - //midi_outs: vec![], - //audio_outs: vec![], - //client: Client::new(name, ClientOptions::NO_START_SERVER)?.0, - //}) - //} - //pub fn run<'a: 'static, D, E>( - //self, - //state: impl FnOnce(JackPorts) -> Box, - //) -> Usually> - //where - //D: AudioComponent + Sized + 'static, - //E: Engine + 'static, - //{ - //let owned_ports = JackPorts { - //audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?, - //audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?, - //midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?, - //midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?, - //}; - //let midi_outs = owned_ports - //.midi_outs - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let midi_ins = owned_ports - //.midi_ins - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let audio_outs = owned_ports - //.audio_outs - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let audio_ins = owned_ports - //.audio_ins - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let state = Arc::new(RwLock::new(state(owned_ports) as Box>)); - //let client = self.client.activate_async( - //Notifications(Box::new({ - //let _state = state.clone(); - //move |_event| { - //// FIXME: this deadlocks - ////state.lock().unwrap().handle(&event).unwrap(); - //} - //}) as Box), - //contrib::ClosureProcessHandler::new(Box::new({ - //let state = state.clone(); - //move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s) - //}) as BoxedAudioHandler), - //)?; - //Ok(JackDevice { - //ports: UnownedJackPorts { - //audio_ins: query_ports(&client.as_client(), audio_ins), - //audio_outs: query_ports(&client.as_client(), audio_outs), - //midi_ins: query_ports(&client.as_client(), midi_ins), - //midi_outs: query_ports(&client.as_client(), midi_outs), - //}, - //client, - //state, - //}) - //} - //pub fn audio_in(mut self, name: &str) -> Self { - //self.audio_ins.push(name.to_string()); - //self - //} - //pub fn audio_out(mut self, name: &str) -> Self { - //self.audio_outs.push(name.to_string()); - //self - //} - //pub fn midi_in(mut self, name: &str) -> Self { - //self.midi_ins.push(name.to_string()); - //self - //} - //pub fn midi_out(mut self, name: &str) -> Self { - //self.midi_outs.push(name.to_string()); - //self - //} -//} diff --git a/crates/tek_snd/src/lib.rs b/crates/tek_snd/src/lib.rs index 95009e6e..0a64c9a1 100644 --- a/crates/tek_snd/src/lib.rs +++ b/crates/tek_snd/src/lib.rs @@ -10,3 +10,367 @@ submod! { snd_sequencer snd_transport } + +pub trait JackActivate: Sized { + fn activate_with ( + self, + init: impl FnOnce(&Arc>)->Usually + ) + -> Usually>>; +} + +impl JackActivate for JackClient { + fn activate_with ( + self, + init: impl FnOnce(&Arc>)->Usually + ) + -> Usually>> + { + let client = Arc::new(RwLock::new(self)); + let target = Arc::new(RwLock::new(init(&client)?)); + let event = Box::new(move|_|{/*TODO*/}) as Box; + let events = Notifications(event); + let frame = Box::new({ + let target = target.clone(); + move|c: &_, s: &_|if let Ok(mut target) = target.write() { + target.process(c, s) + } else { + Control::Quit + } + }); + let frames = contrib::ClosureProcessHandler::new(frame as BoxedAudioHandler); + let mut buffer = Self::Activating; + std::mem::swap(&mut*client.write().unwrap(), &mut buffer); + *client.write().unwrap() = Self::Active(Client::from(buffer).activate_async(events, frames)?); + Ok(target) + } +} + +/// Trait for things that have a JACK process callback. +pub trait Audio: Send + Sync { + fn process(&mut self, _: &Client, _: &ProcessScope) -> Control { + Control::Continue + } + fn callback( + state: &Arc>, client: &Client, scope: &ProcessScope + ) -> Control where Self: Sized { + if let Ok(mut state) = state.write() { + state.process(client, scope) + } else { + Control::Quit + } + } +} + +/// A UI component that may be associated with a JACK client by the `Jack` factory. +pub trait AudioComponent: Component + Audio { + /// Perform type erasure for collecting heterogeneous devices. + fn boxed(self) -> Box> + where + Self: Sized + 'static, + { + Box::new(self) + } +} + +/// All things that implement the required traits can be treated as `AudioComponent`. +impl + Audio> AudioComponent for W {} + +/// Trait for things that may expose JACK ports. +pub trait Ports { + fn audio_ins(&self) -> Usually>> { + Ok(vec![]) + } + fn audio_outs(&self) -> Usually>> { + Ok(vec![]) + } + fn midi_ins(&self) -> Usually>> { + Ok(vec![]) + } + fn midi_outs(&self) -> Usually>> { + Ok(vec![]) + } +} + +fn register_ports( + client: &Client, + names: Vec, + spec: T, +) -> Usually>> { + names + .into_iter() + .try_fold(BTreeMap::new(), |mut ports, name| { + let port = client.register_port(&name, spec)?; + ports.insert(name, port); + Ok(ports) + }) +} + +fn query_ports(client: &Client, names: Vec) -> BTreeMap> { + names.into_iter().fold(BTreeMap::new(), |mut ports, name| { + let port = client.port_by_name(&name).unwrap(); + ports.insert(name, port); + ports + }) +} + +///// A [AudioComponent] bound to a JACK client and a set of ports. +//pub struct JackDevice { + ///// The active JACK client of this device. + //pub client: DynamicAsyncClient, + ///// The device state, encapsulated for sharing between threads. + //pub state: Arc>>>, + ///// Unowned copies of the device's JACK ports, for connecting to the device. + ///// The "real" readable/writable `Port`s are owned by the `state`. + //pub ports: UnownedJackPorts, +//} + +//impl std::fmt::Debug for JackDevice { + //fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + //f.debug_struct("JackDevice") + //.field("ports", &self.ports) + //.finish() + //} +//} + +//impl Widget for JackDevice { + //type Engine = E; + //fn layout(&self, to: E::Size) -> Perhaps { + //self.state.read().unwrap().layout(to) + //} + //fn render(&self, to: &mut E::Output) -> Usually<()> { + //self.state.read().unwrap().render(to) + //} +//} + +//impl Handle for JackDevice { + //fn handle(&mut self, from: &E::Input) -> Perhaps { + //self.state.write().unwrap().handle(from) + //} +//} + +//impl Ports for JackDevice { + //fn audio_ins(&self) -> Usually>> { + //Ok(self.ports.audio_ins.values().collect()) + //} + //fn audio_outs(&self) -> Usually>> { + //Ok(self.ports.audio_outs.values().collect()) + //} + //fn midi_ins(&self) -> Usually>> { + //Ok(self.ports.midi_ins.values().collect()) + //} + //fn midi_outs(&self) -> Usually>> { + //Ok(self.ports.midi_outs.values().collect()) + //} +//} + +//impl JackDevice { + ///// Returns a locked mutex of the state's contents. + //pub fn state(&self) -> LockResult>>> { + //self.state.read() + //} + ///// Returns a locked mutex of the state's contents. + //pub fn state_mut(&self) -> LockResult>>> { + //self.state.write() + //} + //pub fn connect_midi_in(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(port, self.midi_ins()?[index])?) + //} + //pub fn connect_midi_out(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(self.midi_outs()?[index], port)?) + //} + //pub fn connect_audio_in(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(port, self.audio_ins()?[index])?) + //} + //pub fn connect_audio_out(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(self.audio_outs()?[index], port)?) + //} +//} + +///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut]. +//#[derive(Default, Debug)] +//pub struct JackPorts { + //pub audio_ins: BTreeMap>, + //pub midi_ins: BTreeMap>, + //pub audio_outs: BTreeMap>, + //pub midi_outs: BTreeMap>, +//} + +///// Collection of JACK ports as [Unowned]. +//#[derive(Default, Debug)] +//pub struct UnownedJackPorts { + //pub audio_ins: BTreeMap>, + //pub midi_ins: BTreeMap>, + //pub audio_outs: BTreeMap>, + //pub midi_outs: BTreeMap>, +//} + +//impl JackPorts { + //pub fn clone_unowned(&self) -> UnownedJackPorts { + //let mut unowned = UnownedJackPorts::default(); + //for (name, port) in self.midi_ins.iter() { + //unowned.midi_ins.insert(name.clone(), port.clone_unowned()); + //} + //for (name, port) in self.midi_outs.iter() { + //unowned.midi_outs.insert(name.clone(), port.clone_unowned()); + //} + //for (name, port) in self.audio_ins.iter() { + //unowned.audio_ins.insert(name.clone(), port.clone_unowned()); + //} + //for (name, port) in self.audio_outs.iter() { + //unowned + //.audio_outs + //.insert(name.clone(), port.clone_unowned()); + //} + //unowned + //} +//} + +///// Implement the `Ports` trait. +//#[macro_export] +//macro_rules! ports { + //($T:ty $({ $(audio: { + //$(ins: |$ai_arg:ident|$ai_impl:expr,)? + //$(outs: |$ao_arg:ident|$ao_impl:expr,)? + //})? $(midi: { + //$(ins: |$mi_arg:ident|$mi_impl:expr,)? + //$(outs: |$mo_arg:ident|$mo_impl:expr,)? + //})?})?) => { + //impl Ports for $T {$( + //$( + //$(fn audio_ins <'a> (&'a self) -> Usually>> { + //let cb = |$ai_arg:&'a Self|$ai_impl; + //cb(self) + //})? + //)? + //$( + //$(fn audio_outs <'a> (&'a self) -> Usually>> { + //let cb = (|$ao_arg:&'a Self|$ao_impl); + //cb(self) + //})? + //)? + //)? $( + //$( + //$(fn midi_ins <'a> (&'a self) -> Usually>> { + //let cb = (|$mi_arg:&'a Self|$mi_impl); + //cb(self) + //})? + //)? + //$( + //$(fn midi_outs <'a> (&'a self) -> Usually>> { + //let cb = (|$mo_arg:&'a Self|$mo_impl); + //cb(self) + //})? + //)? + //)?} + //}; +//} + +///// `JackDevice` factory. Creates JACK `Client`s, performs port registration +///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. +//pub struct Jack { + //pub client: Client, + //pub midi_ins: Vec, + //pub audio_ins: Vec, + //pub midi_outs: Vec, + //pub audio_outs: Vec, +//} + +//impl Jack { + //pub fn new(name: &str) -> Usually { + //Ok(Self { + //midi_ins: vec![], + //audio_ins: vec![], + //midi_outs: vec![], + //audio_outs: vec![], + //client: Client::new(name, ClientOptions::NO_START_SERVER)?.0, + //}) + //} + //pub fn run<'a: 'static, D, E>( + //self, + //state: impl FnOnce(JackPorts) -> Box, + //) -> Usually> + //where + //D: AudioComponent + Sized + 'static, + //E: Engine + 'static, + //{ + //let owned_ports = JackPorts { + //audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?, + //audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?, + //midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?, + //midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?, + //}; + //let midi_outs = owned_ports + //.midi_outs + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let midi_ins = owned_ports + //.midi_ins + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let audio_outs = owned_ports + //.audio_outs + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let audio_ins = owned_ports + //.audio_ins + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let state = Arc::new(RwLock::new(state(owned_ports) as Box>)); + //let client = self.client.activate_async( + //Notifications(Box::new({ + //let _state = state.clone(); + //move |_event| { + //// FIXME: this deadlocks + ////state.lock().unwrap().handle(&event).unwrap(); + //} + //}) as Box), + //contrib::ClosureProcessHandler::new(Box::new({ + //let state = state.clone(); + //move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s) + //}) as BoxedAudioHandler), + //)?; + //Ok(JackDevice { + //ports: UnownedJackPorts { + //audio_ins: query_ports(&client.as_client(), audio_ins), + //audio_outs: query_ports(&client.as_client(), audio_outs), + //midi_ins: query_ports(&client.as_client(), midi_ins), + //midi_outs: query_ports(&client.as_client(), midi_outs), + //}, + //client, + //state, + //}) + //} + //pub fn audio_in(mut self, name: &str) -> Self { + //self.audio_ins.push(name.to_string()); + //self + //} + //pub fn audio_out(mut self, name: &str) -> Self { + //self.audio_outs.push(name.to_string()); + //self + //} + //pub fn midi_in(mut self, name: &str) -> Self { + //self.midi_ins.push(name.to_string()); + //self + //} + //pub fn midi_out(mut self, name: &str) -> Self { + //self.midi_outs.push(name.to_string()); + //self + //} +//} diff --git a/crates/tek_snd/src/snd_arrange.rs b/crates/tek_snd/src/snd_arrange.rs index 13b7cf27..c1f6763a 100644 --- a/crates/tek_snd/src/snd_arrange.rs +++ b/crates/tek_snd/src/snd_arrange.rs @@ -1,18 +1,8 @@ use crate::*; -pub struct ArrangerAudio(pub Arc>); - -impl Audio for ArrangerAudio { +impl Audio for T { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - ArrangerRefAudio(&mut*self.0.write().unwrap()).process(client, scope) - } -} - -pub struct ArrangerRefAudio<'a>(pub &'a mut ArrangerModel); - -impl<'a> Audio for ArrangerRefAudio<'a> { - #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - for track in self.0.tracks.iter_mut() { + for track in self.tracks_mut().iter_mut() { if MIDIPlayerAudio::from(&mut track.player).process(client, scope) == Control::Quit { return Control::Quit } @@ -20,3 +10,24 @@ impl<'a> Audio for ArrangerRefAudio<'a> { Control::Continue } } + +//pub struct ArrangerAudio(pub Arc>); + +//impl Audio for ArrangerAudio { + //#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + //ArrangerRefAudio(&mut*self.0.write().unwrap()).process(client, scope) + //} +//} + +//pub struct ArrangerRefAudio<'a>(pub &'a mut ArrangerModel); + +//impl<'a> Audio for ArrangerRefAudio<'a> { + //#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + //for track in self.0.tracks.iter_mut() { + //if MIDIPlayerAudio::from(&mut track.player).process(client, scope) == Control::Quit { + //return Control::Quit + //} + //} + //Control::Continue + //} +//} diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index 5487edc9..d9bd6a12 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -24,7 +24,7 @@ impl TryFrom<&Arc>> for ArrangerApp { let phrases = Arc::new(RwLock::new(PhrasePool { phrases: vec![] })); let model = Arc::new(RwLock::new(Arranger { - arrangement: Arc::new(RwLock::new(ArrangerModel { + arrangement: Arc::new(RwLock::new(ArrangerModel { jack: jack.clone(), clock: clock.clone(), name: Arc::new(RwLock::new(String::new())), @@ -32,13 +32,13 @@ impl TryFrom<&Arc>> for ArrangerApp { tracks: vec![], scenes: vec![], })), - sequencer: Arc::new(RwLock::new(SequencerModel { + sequencer: Arc::new(RwLock::new(SequencerModel { transport: transport.clone(), phrases: phrases.clone(), player: Arc::new(RwLock::new(MIDIPlayer::new(jack, &clock, "preview")?)), })), - transport: transport.clone(), - phrases: phrases.clone(), + transport: transport.clone(), + phrases: phrases.clone(), })); Ok(Self::new( diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index 6f317de8..2369ecfa 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -43,15 +43,6 @@ impl TryFrom<&Arc>> for SequencerApp { } } -pub struct SequencerModel { - /// State of the JACK transport. - pub transport: Arc>, - /// State of the phrase pool. - pub phrases: Arc>, - /// State of the phrase player. - pub player: Arc>, -} - impl From<&Arc>> for SequencerView { fn from (model: &Arc>) -> Self { Self {