diff --git a/crates/tek_core/src/audio.rs b/crates/tek_core/src/audio.rs index cce1e9f4..4057ad2b 100644 --- a/crates/tek_core/src/audio.rs +++ b/crates/tek_core/src/audio.rs @@ -1,22 +1,5 @@ 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( @@ -36,10 +19,46 @@ pub trait AudioEngine { } /// Wraps [Client] or [DynamicAsyncClient] in place. 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>)->T + ) + -> 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 { @@ -59,23 +78,23 @@ impl AudioEngine for JackClient { } fn activate( self, - mut process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static, + mut cb: 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 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 frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&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 { +pub trait Audio: Send + Sync { fn process(&mut self, _: &Client, _: &ProcessScope) -> Control { Control::Continue } @@ -120,12 +139,6 @@ pub trait AudioComponent: Component + Audio { /// All things that implement the required traits can be treated as `AudioComponent`. impl + Audio> AudioComponent for W {} -pub type DynamicAsyncClient = AsyncClient; - -pub type DynamicAudioHandler = contrib::ClosureProcessHandler<(), BoxedAudioHandler>; - -pub type BoxedAudioHandler = Box Control + Send>; - /// `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_mixer/src/plugin.rs b/crates/tek_mixer/src/plugin.rs index c6652daf..b3b277c3 100644 --- a/crates/tek_mixer/src/plugin.rs +++ b/crates/tek_mixer/src/plugin.rs @@ -42,7 +42,7 @@ impl Plugin { } } -impl Audio for Plugin { +impl Audio for Plugin { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { match self.plugin.as_mut() { Some(PluginKind::LV2(LV2Plugin { diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index ab4e7291..2ab22242 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -3,7 +3,7 @@ use crate::*; /// Root level object for standalone `tek_arranger` pub struct Arranger { /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Option>, + pub jack: Arc>, /// Which view is focused pub focus_cursor: (usize, usize), /// Controls the JACK transport. @@ -51,7 +51,7 @@ pub enum ArrangerStatusBar { /// Represents the tracks and scenes of the composition. pub struct Arrangement { /// Global JACK client - pub jack: Arc, + pub jack: Arc>, /// Global timebase pub clock: Arc, /// Name of arranger @@ -120,26 +120,27 @@ pub struct HorizontalArranger<'a, E: Engine>(pub &'a Arrangement); /// General methods for arranger impl Arranger { pub fn new ( + jack: &Arc>, transport: Option>>>, arrangement: Arrangement, phrases: Arc>>, ) -> Self { let mut app = Self { - jack: None, - focus_cursor: (0, 1), - phrases_split: 20, + jack: jack.clone(), + focus_cursor: (0, 1), + phrases_split: 20, arrangement_split: 21, - editor: PhraseEditor::new(), - status: ArrangerStatusBar::ArrangementClip, + editor: PhraseEditor::new(), + status: ArrangerStatusBar::ArrangementClip, + transport: transport.clone(), + arrangement, + phrases, + size: Measure::new(), clock: if let Some(ref transport) = transport { transport.read().unwrap().clock.clone() } else { Arc::new(TransportTime::default()) }, - transport, - arrangement, - phrases, - size: Measure::new(), }; app.update_focus(); app @@ -226,7 +227,7 @@ impl FocusGrid for Arranger { /// General methods for arrangement impl Arrangement { pub fn new ( - jack: &Arc, + jack: &Arc>, clock: &Arc, name: &str, phrases: &Arc>> @@ -537,16 +538,16 @@ impl Arrangement { } impl ArrangementTrack { pub fn new ( - jack: &Arc, + 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(jack, clock), + name: Arc::new(RwLock::new(name.into())), + width: name.len() + 2, + color: color.unwrap_or_else(ItemColor::random), + player: PhrasePlayer::new(&jack, clock), } } pub fn longest_name (tracks: &[Self]) -> usize { diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index 928be79e..5a85e6e3 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -9,7 +9,7 @@ pub type PhraseChunk = [Vec]; /// Root level object for standalone `tek_sequencer` pub struct Sequencer { /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Option>, + pub jack: Arc>, /// Which view is focused pub focus_cursor: (usize, usize), /// Controls the JACK transport. @@ -392,7 +392,7 @@ impl PartialEq for Phrase { fn eq (&self, other: &Self) -> bool { self.uuid == o impl Eq for Phrase {} impl PhrasePlayer { pub fn new ( - jack: &Arc, + jack: &Arc>, clock: &Arc ) -> Self { Self { diff --git a/crates/tek_sequencer/src/sequencer_cli.rs b/crates/tek_sequencer/src/sequencer_cli.rs index a7281251..e9b0dd35 100644 --- a/crates/tek_sequencer/src/sequencer_cli.rs +++ b/crates/tek_sequencer/src/sequencer_cli.rs @@ -18,39 +18,31 @@ pub struct SequencerCli { impl SequencerCli { fn run (&self) -> Usually<()> { - let jack = Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0; - let jack = JackClient::Inactive(jack); - let transport = Arc::new(RwLock::new(TransportToolbar::new(None, Some(jack.transport())))); - 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(), - transport: self.transport.then_some(transport), - clock, - }; - if let Some(_) = self.name.as_ref() { - // TODO: sequencer.name = Arc::new(RwLock::new(name.clone())); - } - if let Some(_) = self.ppq { - // TODO: sequencer.ppq = ppq; - } - if let Some(_) = self.length { - // TODO: if let Some(phrase) = sequencer.phrase.as_mut() { - //phrase.write().unwrap().length = length; - //} - } - let sequencer = Arc::new(RwLock::new(sequencer)); - let jack = jack.activate(&sequencer.clone(), Sequencer::callback)?; - let jack = Arc::new(jack); - let player = PhrasePlayer::new(&jack, &clock); - if let Some(ref transport) = sequencer.read().unwrap().transport { - transport.write().unwrap().jack = Some(jack.clone()); - } - sequencer.write().unwrap().jack = Some(jack.clone()); - sequencer.write().unwrap().player = player; - Tui::run(sequencer).map(|_|()) + let name = self.name.map(|name|name.as_str()).unwrap_or("tek_arranger"); + Tui::run(JackClient::new(name)?.activate_with(|jack|{ + let mut transport = TransportToolbar::new(jack, None); + let sequencer = Sequencer { + jack: jack.clone(), + focus_cursor: (1, 1), + phrases: Arc::new(RwLock::new(PhrasePool::new())), + editor: PhraseEditor::new(), + clock: transport.clock.clone(), + player: Some(PhrasePlayer::new(jack, &transport.clock)), + transport: self.transport.then_some(Arc::new(RwLock::new(transport))), + }; + if let Some(_) = self.name.as_ref() { + // TODO: sequencer.name = Arc::new(RwLock::new(name.clone())); + } + if let Some(_) = self.ppq { + // TODO: sequencer.ppq = ppq; + } + if let Some(_) = self.length { + // TODO: if let Some(phrase) = sequencer.phrase.as_mut() { + //phrase.write().unwrap().length = length; + //} + } + sequencer + }))?; + Ok(()) } } diff --git a/crates/tek_sequencer/src/transport.rs b/crates/tek_sequencer/src/transport.rs index b9d6fd3c..ac0561a2 100644 --- a/crates/tek_sequencer/src/transport.rs +++ b/crates/tek_sequencer/src/transport.rs @@ -26,14 +26,14 @@ impl TransportTime { /// Stores and displays time-related state. pub struct TransportToolbar { _engine: PhantomData, + /// JACK client handle (needs to not be dropped for standalone mode to work). + pub jack: Arc>, + /// JACK transport handle. + pub transport: Transport, /// Current sample rate, tempo, and PPQ. pub clock: Arc, /// Enable metronome? pub metronome: bool, - /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Option>, - /// JACK transport handle. - pub transport: Option, /// Whether the toolbar is focused pub focused: bool, /// Which part of the toolbar is focused @@ -43,17 +43,14 @@ pub struct TransportToolbar { #[derive(Clone, Copy, PartialEq)] pub enum TransportToolbarFocus { Bpm, Sync, PlayPause, Clock, Quant, } impl TransportToolbar { - pub fn new ( - clock: Option<&Arc>, - transport: Option, - ) -> Self { + pub fn new (jack: &Arc>, clock: Option<&Arc>) -> Self { Self { _engine: Default::default(), focused: false, focus: TransportToolbarFocus::PlayPause, metronome: false, - jack: None, - transport, + transport: jack.read().unwrap().transport(), + jack: jack.clone(), clock: match clock { Some(clock) => clock.clone(), None => { @@ -70,16 +67,15 @@ impl TransportToolbar { } } pub fn toggle_play (&mut self) -> Usually<()> { - let transport = self.transport.as_ref().unwrap(); let playing = self.clock.playing.read().unwrap().expect("1st sample has not been processed yet"); let playing = match playing { TransportState::Stopped => { - transport.start()?; + self.transport.start()?; Some(TransportState::Starting) }, _ => { - transport.stop()?; - transport.locate(0)?; + self.transport.stop()?; + self.transport.locate(0)?; Some(TransportState::Stopped) }, }; diff --git a/crates/tek_sequencer/src/transport_cli.rs b/crates/tek_sequencer/src/transport_cli.rs index 508c5978..e8c7e1a2 100644 --- a/crates/tek_sequencer/src/transport_cli.rs +++ b/crates/tek_sequencer/src/transport_cli.rs @@ -1,14 +1,10 @@ include!("lib.rs"); /// Application entrypoint. pub fn main () -> Usually<()> { - let jack = JackClient::Inactive( - Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0 - ); - let mut transport = TransportToolbar::new(None, Some(jack.transport())); - transport.focused = true; - let transport = Arc::new(RwLock::new(transport)); - let jack = jack.activate(&transport.clone(), TransportToolbar::callback)?; - transport.write().unwrap().jack = Some(jack.into()); - Tui::run(transport)?; + Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{ + let mut transport = TransportToolbar::new(jack, None); + transport.focused = true; + transport + }))?; Ok(()) } diff --git a/crates/tek_sequencer/src/transport_snd.rs b/crates/tek_sequencer/src/transport_snd.rs index 7bf799a7..3c6af6bb 100644 --- a/crates/tek_sequencer/src/transport_snd.rs +++ b/crates/tek_sequencer/src/transport_snd.rs @@ -1,10 +1,10 @@ use crate::*; impl Audio for TransportToolbar { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let times = scope.cycle_times().unwrap(); + let times = scope.cycle_times().unwrap(); let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times; let _chunk_size = scope.n_frames() as usize; - let transport = self.transport.as_ref().unwrap().query().unwrap(); + let transport = self.transport.query().unwrap(); self.clock.current.sample.set(transport.pos.frame() as f64); let mut playing = self.clock.playing.write().unwrap(); let mut started = self.clock.started.write().unwrap();