wip: AudioEngine pt.2 (rewrite activation sanely)

This commit is contained in:
🪞👃🪞 2024-11-03 04:43:50 +02:00
parent 2303b258f6
commit 746e29aeb3
8 changed files with 104 additions and 106 deletions

View file

@ -1,22 +1,5 @@
use crate::*; use crate::*;
use jack::*; use jack::*;
/// Just run thing with JACK. Returns the activated client.
pub fn jack_run<T, E: Engine>(name: &str, app: &Arc<RwLock<T>>) -> Usually<DynamicAsyncClient>
where
T: Handle<E> + 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<dyn Fn(JackEvent) + Send + Sync>);
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. /// Trait for things that wrap a JACK client.
pub trait AudioEngine { pub trait AudioEngine {
fn activate( fn activate(
@ -36,10 +19,46 @@ pub trait AudioEngine {
} }
/// Wraps [Client] or [DynamicAsyncClient] in place. /// Wraps [Client] or [DynamicAsyncClient] in place.
pub enum JackClient { pub enum JackClient {
/// Before activation.
Inactive(Client), Inactive(Client),
/// During activation.
Activating, Activating,
/// After activation. Must not be dropped for JACK thread to persist.
Active(DynamicAsyncClient), Active(DynamicAsyncClient),
} }
pub type DynamicAsyncClient = AsyncClient<DynamicNotifications, DynamicAudioHandler>;
pub type DynamicAudioHandler = contrib::ClosureProcessHandler<(), BoxedAudioHandler>;
pub type BoxedAudioHandler = Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send>;
impl JackClient {
pub fn new (name: &str) -> Usually<Self> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
Ok(Self::Inactive(client))
}
pub fn activate_with <T: Audio + 'static> (
self,
init: impl FnOnce(&Arc<RwLock<JackClient>>)->T
)
-> Usually<Arc<RwLock<T>>>
{
let client = Arc::new(RwLock::new(self));
let target = Arc::new(RwLock::new(init(&client)));
let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
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<JackClient> for Client { impl From<JackClient> for Client {
fn from (jack: JackClient) -> Client { fn from (jack: JackClient) -> Client {
match jack { match jack {
@ -59,23 +78,23 @@ impl AudioEngine for JackClient {
} }
fn activate( fn activate(
self, self,
mut process: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static, mut cb: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static,
) -> Usually<Arc<RwLock<Self>>> ) -> Usually<Arc<RwLock<Self>>>
where where
Self: Send + Sync + 'static Self: Send + Sync + 'static
{ {
let client = Client::from(self); let client = Client::from(self);
let state = Arc::new(RwLock::new(Self::Activating)); let state = Arc::new(RwLock::new(Self::Activating));
let event = Box::new(move |_| { /*TODO*/ }) as Box<dyn Fn(JackEvent) + Send + Sync>; let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
let events = Notifications(event); 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); let frames = contrib::ClosureProcessHandler::new(frame as BoxedAudioHandler);
*state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?);
Ok(state) Ok(state)
} }
} }
/// Trait for things that have a JACK process callback. /// Trait for things that have a JACK process callback.
pub trait Audio { pub trait Audio: Send + Sync {
fn process(&mut self, _: &Client, _: &ProcessScope) -> Control { fn process(&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue Control::Continue
} }
@ -120,12 +139,6 @@ pub trait AudioComponent<E: Engine>: Component<E> + Audio {
/// All things that implement the required traits can be treated as `AudioComponent`. /// All things that implement the required traits can be treated as `AudioComponent`.
impl<E: Engine, W: Component<E> + Audio> AudioComponent<E> for W {} impl<E: Engine, W: Component<E> + Audio> AudioComponent<E> for W {}
pub type DynamicAsyncClient = AsyncClient<DynamicNotifications, DynamicAudioHandler>;
pub type DynamicAudioHandler = contrib::ClosureProcessHandler<(), BoxedAudioHandler>;
pub type BoxedAudioHandler = Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send>;
/// `JackDevice` factory. Creates JACK `Client`s, performs port registration /// `JackDevice` factory. Creates JACK `Client`s, performs port registration
/// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. /// and activation, and encapsulates a `AudioComponent` into a `JackDevice`.
pub struct Jack { pub struct Jack {

View file

@ -42,7 +42,7 @@ impl Plugin<Tui> {
} }
} }
impl<E> Audio for Plugin<E> { impl<E: Send + Sync> Audio for Plugin<E> {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
match self.plugin.as_mut() { match self.plugin.as_mut() {
Some(PluginKind::LV2(LV2Plugin { Some(PluginKind::LV2(LV2Plugin {

View file

@ -3,7 +3,7 @@ use crate::*;
/// Root level object for standalone `tek_arranger` /// Root level object for standalone `tek_arranger`
pub struct Arranger<E: Engine> { pub struct Arranger<E: Engine> {
/// JACK client handle (needs to not be dropped for standalone mode to work). /// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Option<Arc<JackClient>>, pub jack: Arc<RwLock<JackClient>>,
/// Which view is focused /// Which view is focused
pub focus_cursor: (usize, usize), pub focus_cursor: (usize, usize),
/// Controls the JACK transport. /// Controls the JACK transport.
@ -51,7 +51,7 @@ pub enum ArrangerStatusBar {
/// Represents the tracks and scenes of the composition. /// Represents the tracks and scenes of the composition.
pub struct Arrangement<E: Engine> { pub struct Arrangement<E: Engine> {
/// Global JACK client /// Global JACK client
pub jack: Arc<JackClient>, pub jack: Arc<RwLock<JackClient>>,
/// Global timebase /// Global timebase
pub clock: Arc<TransportTime>, pub clock: Arc<TransportTime>,
/// Name of arranger /// Name of arranger
@ -120,26 +120,27 @@ pub struct HorizontalArranger<'a, E: Engine>(pub &'a Arrangement<E>);
/// General methods for arranger /// General methods for arranger
impl<E: Engine> Arranger<E> { impl<E: Engine> Arranger<E> {
pub fn new ( pub fn new (
jack: &Arc<RwLock<JackClient>>,
transport: Option<Arc<RwLock<TransportToolbar<E>>>>, transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
arrangement: Arrangement<E>, arrangement: Arrangement<E>,
phrases: Arc<RwLock<PhrasePool<E>>>, phrases: Arc<RwLock<PhrasePool<E>>>,
) -> Self { ) -> Self {
let mut app = Self { let mut app = Self {
jack: None, jack: jack.clone(),
focus_cursor: (0, 1), focus_cursor: (0, 1),
phrases_split: 20, phrases_split: 20,
arrangement_split: 21, arrangement_split: 21,
editor: PhraseEditor::new(), editor: PhraseEditor::new(),
status: ArrangerStatusBar::ArrangementClip, status: ArrangerStatusBar::ArrangementClip,
transport: transport.clone(),
arrangement,
phrases,
size: Measure::new(),
clock: if let Some(ref transport) = transport { clock: if let Some(ref transport) = transport {
transport.read().unwrap().clock.clone() transport.read().unwrap().clock.clone()
} else { } else {
Arc::new(TransportTime::default()) Arc::new(TransportTime::default())
}, },
transport,
arrangement,
phrases,
size: Measure::new(),
}; };
app.update_focus(); app.update_focus();
app app
@ -226,7 +227,7 @@ impl<E: Engine> FocusGrid<ArrangerFocus> for Arranger<E> {
/// General methods for arrangement /// General methods for arrangement
impl<E: Engine> Arrangement<E> { impl<E: Engine> Arrangement<E> {
pub fn new ( pub fn new (
jack: &Arc<JackClient>, jack: &Arc<RwLock<JackClient>>,
clock: &Arc<TransportTime>, clock: &Arc<TransportTime>,
name: &str, name: &str,
phrases: &Arc<RwLock<PhrasePool<E>>> phrases: &Arc<RwLock<PhrasePool<E>>>
@ -537,16 +538,16 @@ impl<E: Engine> Arrangement<E> {
} }
impl ArrangementTrack { impl ArrangementTrack {
pub fn new ( pub fn new (
jack: &Arc<JackClient>, jack: &Arc<RwLock<JackClient>>,
clock: &Arc<TransportTime>, clock: &Arc<TransportTime>,
name: &str, name: &str,
color: Option<ItemColor> color: Option<ItemColor>
) -> Self { ) -> Self {
Self { Self {
name: Arc::new(RwLock::new(name.into())), name: Arc::new(RwLock::new(name.into())),
width: name.len() + 2, width: name.len() + 2,
color: color.unwrap_or_else(ItemColor::random), color: color.unwrap_or_else(ItemColor::random),
player: PhrasePlayer::new(jack, clock), player: PhrasePlayer::new(&jack, clock),
} }
} }
pub fn longest_name (tracks: &[Self]) -> usize { pub fn longest_name (tracks: &[Self]) -> usize {

View file

@ -9,7 +9,7 @@ pub type PhraseChunk = [Vec<PhraseMessage>];
/// Root level object for standalone `tek_sequencer` /// Root level object for standalone `tek_sequencer`
pub struct Sequencer<E: Engine> { pub struct Sequencer<E: Engine> {
/// JACK client handle (needs to not be dropped for standalone mode to work). /// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Option<Arc<JackClient>>, pub jack: Arc<RwLock<JackClient>>,
/// Which view is focused /// Which view is focused
pub focus_cursor: (usize, usize), pub focus_cursor: (usize, usize),
/// Controls the JACK transport. /// 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 Eq for Phrase {}
impl PhrasePlayer { impl PhrasePlayer {
pub fn new ( pub fn new (
jack: &Arc<JackClient>, jack: &Arc<RwLock<JackClient>>,
clock: &Arc<TransportTime> clock: &Arc<TransportTime>
) -> Self { ) -> Self {
Self { Self {

View file

@ -18,39 +18,31 @@ pub struct SequencerCli {
impl SequencerCli { impl SequencerCli {
fn run (&self) -> Usually<()> { fn run (&self) -> Usually<()> {
let jack = Client::new("tek_arranger", ClientOptions::NO_START_SERVER)?.0; let name = self.name.map(|name|name.as_str()).unwrap_or("tek_arranger");
let jack = JackClient::Inactive(jack); Tui::run(JackClient::new(name)?.activate_with(|jack|{
let transport = Arc::new(RwLock::new(TransportToolbar::new(None, Some(jack.transport())))); let mut transport = TransportToolbar::new(jack, None);
let clock = transport.read().unwrap().clock.clone(); let sequencer = Sequencer {
let sequencer = Sequencer { jack: jack.clone(),
jack: None, focus_cursor: (1, 1),
player: None, phrases: Arc::new(RwLock::new(PhrasePool::new())),
focus_cursor: (1, 1), editor: PhraseEditor::new(),
phrases: Arc::new(RwLock::new(PhrasePool::new())), clock: transport.clock.clone(),
editor: PhraseEditor::new(), player: Some(PhrasePlayer::new(jack, &transport.clock)),
transport: self.transport.then_some(transport), transport: self.transport.then_some(Arc::new(RwLock::new(transport))),
clock, };
}; if let Some(_) = self.name.as_ref() {
if let Some(_) = self.name.as_ref() { // TODO: sequencer.name = Arc::new(RwLock::new(name.clone()));
// TODO: sequencer.name = Arc::new(RwLock::new(name.clone())); }
} if let Some(_) = self.ppq {
if let Some(_) = self.ppq { // TODO: sequencer.ppq = ppq;
// TODO: sequencer.ppq = ppq; }
} if let Some(_) = self.length {
if let Some(_) = self.length { // TODO: if let Some(phrase) = sequencer.phrase.as_mut() {
// TODO: if let Some(phrase) = sequencer.phrase.as_mut() { //phrase.write().unwrap().length = length;
//phrase.write().unwrap().length = length; //}
//} }
} sequencer
let sequencer = Arc::new(RwLock::new(sequencer)); }))?;
let jack = jack.activate(&sequencer.clone(), Sequencer::callback)?; Ok(())
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(|_|())
} }
} }

View file

@ -26,14 +26,14 @@ impl TransportTime {
/// Stores and displays time-related state. /// Stores and displays time-related state.
pub struct TransportToolbar<E: Engine> { pub struct TransportToolbar<E: Engine> {
_engine: PhantomData<E>, _engine: PhantomData<E>,
/// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Arc<RwLock<JackClient>>,
/// JACK transport handle.
pub transport: Transport,
/// Current sample rate, tempo, and PPQ. /// Current sample rate, tempo, and PPQ.
pub clock: Arc<TransportTime>, pub clock: Arc<TransportTime>,
/// Enable metronome? /// Enable metronome?
pub metronome: bool, pub metronome: bool,
/// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Option<Arc<JackClient>>,
/// JACK transport handle.
pub transport: Option<Transport>,
/// Whether the toolbar is focused /// Whether the toolbar is focused
pub focused: bool, pub focused: bool,
/// Which part of the toolbar is focused /// Which part of the toolbar is focused
@ -43,17 +43,14 @@ pub struct TransportToolbar<E: Engine> {
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum TransportToolbarFocus { Bpm, Sync, PlayPause, Clock, Quant, } pub enum TransportToolbarFocus { Bpm, Sync, PlayPause, Clock, Quant, }
impl<E: Engine> TransportToolbar<E> { impl<E: Engine> TransportToolbar<E> {
pub fn new ( pub fn new (jack: &Arc<RwLock<JackClient>>, clock: Option<&Arc<TransportTime>>) -> Self {
clock: Option<&Arc<TransportTime>>,
transport: Option<Transport>,
) -> Self {
Self { Self {
_engine: Default::default(), _engine: Default::default(),
focused: false, focused: false,
focus: TransportToolbarFocus::PlayPause, focus: TransportToolbarFocus::PlayPause,
metronome: false, metronome: false,
jack: None, transport: jack.read().unwrap().transport(),
transport, jack: jack.clone(),
clock: match clock { clock: match clock {
Some(clock) => clock.clone(), Some(clock) => clock.clone(),
None => { None => {
@ -70,16 +67,15 @@ impl<E: Engine> TransportToolbar<E> {
} }
} }
pub fn toggle_play (&mut self) -> Usually<()> { 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 = self.clock.playing.read().unwrap().expect("1st sample has not been processed yet");
let playing = match playing { let playing = match playing {
TransportState::Stopped => { TransportState::Stopped => {
transport.start()?; self.transport.start()?;
Some(TransportState::Starting) Some(TransportState::Starting)
}, },
_ => { _ => {
transport.stop()?; self.transport.stop()?;
transport.locate(0)?; self.transport.locate(0)?;
Some(TransportState::Stopped) Some(TransportState::Stopped)
}, },
}; };

View file

@ -1,14 +1,10 @@
include!("lib.rs"); include!("lib.rs");
/// Application entrypoint. /// Application entrypoint.
pub fn main () -> Usually<()> { pub fn main () -> Usually<()> {
let jack = JackClient::Inactive( Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{
Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0 let mut transport = TransportToolbar::new(jack, None);
); transport.focused = true;
let mut transport = TransportToolbar::new(None, Some(jack.transport())); 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)?;
Ok(()) Ok(())
} }

View file

@ -1,10 +1,10 @@
use crate::*; use crate::*;
impl<E: Engine> Audio for TransportToolbar<E> { impl<E: Engine> Audio for TransportToolbar<E> {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { 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 CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times;
let _chunk_size = scope.n_frames() as usize; 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); self.clock.current.sample.set(transport.pos.frame() as f64);
let mut playing = self.clock.playing.write().unwrap(); let mut playing = self.clock.playing.write().unwrap();
let mut started = self.clock.started.write().unwrap(); let mut started = self.clock.started.write().unwrap();