From 2303b258f65d5eda72942de6525722933f4e2b65 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 3 Nov 2024 02:44:58 +0200 Subject: [PATCH] wip: AudioEngine (new jack init) --- crates/tek_core/src/audio.rs | 144 +++++++++++----------- crates/tek_sequencer/src/arranger.rs | 27 +++- crates/tek_sequencer/src/arranger_cli.rs | 2 +- crates/tek_sequencer/src/sequencer.rs | 5 +- crates/tek_sequencer/src/sequencer_cli.rs | 10 +- 5 files changed, 108 insertions(+), 80 deletions(-) diff --git a/crates/tek_core/src/audio.rs b/crates/tek_core/src/audio.rs index 23aa5011..cce1e9f4 100644 --- a/crates/tek_core/src/audio.rs +++ b/crates/tek_core/src/audio.rs @@ -1,6 +1,79 @@ use crate::*; use jack::*; - +/// Just run thing with JACK. Returns the activated client. +pub fn jack_run(name: &str, app: &Arc>) -> Usually +where + T: Handle + Audio + Send + Sync + 'static, +{ + let options = ClientOptions::NO_START_SERVER; + let (client, _status) = Client::new(name, options)?; + let on_event = Notifications(Box::new({ + let _app = app.clone(); + move|_event|{/* FIXME: this deadlocks: app.lock().unwrap().handle(&event).unwrap(); */} + }) as Box); + let on_chunk = contrib::ClosureProcessHandler::new(Box::new({ + let app = app.clone(); + move|c: &Client, s: &ProcessScope|app.write().unwrap().process(c, s) + }) as BoxedAudioHandler); + Ok(client.activate_async(on_event, on_chunk)?) +} +/// Trait for things that wrap a JACK client. +pub trait AudioEngine { + fn activate( + self, + process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static + ) -> Usually>> where Self: Send + Sync + 'static; + fn client(&self) -> &Client; + fn transport(&self) -> Transport { + self.client().transport() + } + fn port_by_name(&self, name: &str) -> Option> { + self.client().port_by_name(name) + } + fn register_port(&self, name: &str, spec: PS) -> Usually> { + Ok(self.client().register_port(name, spec)?) + } +} +/// Wraps [Client] or [DynamicAsyncClient] in place. +pub enum JackClient { + Inactive(Client), + Activating, + Active(DynamicAsyncClient), +} +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 { + Self::Inactive(ref client) => client, + Self::Activating => panic!("jack client has not finished activation"), + Self::Active(ref client) => client.as_client(), + } + } + fn activate( + self, + mut process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static, + ) -> Usually>> + where + Self: Send + Sync + 'static + { + let client = Client::from(self); + let state = Arc::new(RwLock::new(Self::Activating)); + let event = Box::new(move |_| { /*TODO*/ }) as Box; + let events = Notifications(event); + let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|process(&state, c, s)}); + let frames = contrib::ClosureProcessHandler::new(frame as BoxedAudioHandler); + *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); + Ok(state) + } +} /// Trait for things that have a JACK process callback. pub trait Audio { fn process(&mut self, _: &Client, _: &ProcessScope) -> Control { @@ -47,79 +120,12 @@ pub trait AudioComponent: Component + Audio { /// All things that implement the required traits can be treated as `AudioComponent`. impl + Audio> AudioComponent for W {} -/// Wraps [Client] or [DynamicAsyncClient] in place. -pub enum JackClient { - Inactive(Client), - Active(DynamicAsyncClient), -} - -impl JackClient { - pub fn client(&self) -> &Client { - match self { - Self::Inactive(ref client) => client, - Self::Active(ref client) => client.as_client(), - } - } - pub fn transport(&self) -> Transport { - self.client().transport() - } - pub fn port_by_name(&self, name: &str) -> Option> { - self.client().port_by_name(name) - } - pub fn register_port(&self, name: &str, spec: PS) -> Usually> { - Ok(self.client().register_port(name, spec)?) - } - pub fn activate( - self, - state: &Arc>, - mut process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static, - ) -> Usually { - Ok(match self { - Self::Active(_) => self, - Self::Inactive(client) => Self::Active(client.activate_async( - Notifications( - Box::new(move |_| { /*TODO*/ }) as Box - ), - contrib::ClosureProcessHandler::new(Box::new({ - let state = state.clone(); - move |c: &Client, s: &ProcessScope| process(&state, c, s) - }) as BoxedAudioHandler), - )?), - }) - } -} - pub type DynamicAsyncClient = AsyncClient; -type DynamicAudioHandler = contrib::ClosureProcessHandler<(), BoxedAudioHandler>; +pub type DynamicAudioHandler = contrib::ClosureProcessHandler<(), BoxedAudioHandler>; pub type BoxedAudioHandler = Box Control + Send>; -/// Just run thing with JACK. Returns the activated client. -pub fn jack_run(name: &str, app: &Arc>) -> Usually -where - T: Handle + Audio + Send + Sync + 'static, -{ - let options = ClientOptions::NO_START_SERVER; - let (client, _status) = Client::new(name, options)?; - Ok(client.activate_async( - Notifications(Box::new({ - let _app = app.clone(); - move |_event| { - // FIXME: this deadlocks - //app.lock().unwrap().handle(&event).unwrap(); - } - }) as Box), - contrib::ClosureProcessHandler::new(Box::new({ - let app = app.clone(); - move |c: &Client, s: &ProcessScope| { - app.write().unwrap().process(c, s) - //Control::Continue - } - }) as BoxedAudioHandler), - )?) -} - /// `JackDevice` factory. Creates JACK `Client`s, performs port registration /// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. pub struct Jack { diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index 1b1d65a2..ab4e7291 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -50,6 +50,8 @@ pub enum ArrangerStatusBar { } /// Represents the tracks and scenes of the composition. pub struct Arrangement { + /// Global JACK client + pub jack: Arc, /// Global timebase pub clock: Arc, /// Name of arranger @@ -223,8 +225,14 @@ impl FocusGrid for Arranger { } /// General methods for arrangement impl Arrangement { - pub fn new (clock: &Arc, name: &str, phrases: &Arc>>) -> Self { + pub fn new ( + jack: &Arc, + clock: &Arc, + name: &str, + phrases: &Arc>> + ) -> Self { Self { + jack: jack.clone(), clock: clock.clone(), name: Arc::new(RwLock::new(name.into())), mode: ArrangementViewMode::Vertical(2), @@ -400,8 +408,12 @@ impl Arrangement { &mut self, name: Option<&str>, color: Option ) -> Usually<&mut ArrangementTrack> { self.tracks.push(name.map_or_else( - || ArrangementTrack::new(&self.clock, &self.track_default_name(), color), - |name| ArrangementTrack::new(&self.clock, name, color), + || ArrangementTrack::new( + &self.jack, &self.clock, &self.track_default_name(), color + ), + |name| ArrangementTrack::new( + &self.jack, &self.clock, name, color + ), )); let index = self.tracks.len() - 1; Ok(&mut self.tracks[index]) @@ -524,12 +536,17 @@ impl Arrangement { } } impl ArrangementTrack { - pub fn new (clock: &Arc, name: &str, color: Option) -> Self { + pub fn new ( + jack: &Arc, + clock: &Arc, + name: &str, + color: Option + ) -> Self { Self { name: Arc::new(RwLock::new(name.into())), width: name.len() + 2, color: color.unwrap_or_else(ItemColor::random), - player: PhrasePlayer::new(clock), + player: PhrasePlayer::new(jack, clock), } } pub fn longest_name (tracks: &[Self]) -> usize { diff --git a/crates/tek_sequencer/src/arranger_cli.rs b/crates/tek_sequencer/src/arranger_cli.rs index c5980da4..0d157c4e 100644 --- a/crates/tek_sequencer/src/arranger_cli.rs +++ b/crates/tek_sequencer/src/arranger_cli.rs @@ -23,7 +23,7 @@ impl ArrangerCli { let jack = JackClient::Inactive(jack); let transport = TransportToolbar::new(None, Some(jack.transport())); let phrases = Arc::new(RwLock::new(PhrasePool::new())); - let mut arrangement = Arrangement::new(&transport.clock, "", &phrases); + let mut arrangement = Arrangement::new(&jack, &transport.clock, "", &phrases); let transport = Arc::new(RwLock::new(transport)); if let Some(name) = self.name.as_ref() { *arrangement.name.write().unwrap() = name.clone(); diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index e23b4804..928be79e 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -391,7 +391,10 @@ impl Default for Phrase { impl PartialEq for Phrase { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } impl Eq for Phrase {} impl PhrasePlayer { - pub fn new (clock: &Arc) -> Self { + pub fn new ( + jack: &Arc, + clock: &Arc + ) -> Self { Self { clock: clock.clone(), phrase: None, diff --git a/crates/tek_sequencer/src/sequencer_cli.rs b/crates/tek_sequencer/src/sequencer_cli.rs index 56900cb9..a7281251 100644 --- a/crates/tek_sequencer/src/sequencer_cli.rs +++ b/crates/tek_sequencer/src/sequencer_cli.rs @@ -24,10 +24,10 @@ impl SequencerCli { let clock = transport.read().unwrap().clock.clone(); let sequencer = Sequencer { jack: None, + player: None, focus_cursor: (1, 1), phrases: Arc::new(RwLock::new(PhrasePool::new())), editor: PhraseEditor::new(), - player: PhrasePlayer::new(&clock), transport: self.transport.then_some(transport), clock, }; @@ -44,11 +44,13 @@ impl SequencerCli { } let sequencer = Arc::new(RwLock::new(sequencer)); let jack = jack.activate(&sequencer.clone(), Sequencer::callback)?; - let jack = Some(jack.into()); + let jack = Arc::new(jack); + let player = PhrasePlayer::new(&jack, &clock); if let Some(ref transport) = sequencer.read().unwrap().transport { - transport.write().unwrap().jack = jack.clone(); + transport.write().unwrap().jack = Some(jack.clone()); } - sequencer.write().unwrap().jack = jack.clone(); + sequencer.write().unwrap().jack = Some(jack.clone()); + sequencer.write().unwrap().player = player; Tui::run(sequencer).map(|_|()) } }