diff --git a/src/sing.rs b/src/sing.rs index cd76eb2..9899fbe 100644 --- a/src/sing.rs +++ b/src/sing.rs @@ -1,6 +1,9 @@ pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; use crate::{*, time::PerfModel}; -use JackState::*; + +mod jack; pub use self::jack::*; +mod jack_event; pub use self::jack_event::*; +mod jack_perf; pub use self::jack_perf::*; /// Trait for thing that has a JACK process callback. pub trait Audio { @@ -22,58 +25,6 @@ pub trait Audio { } } -/// Wraps [JackState], and through it [jack::Client] when connected. -/// -/// ``` -/// let jack = tengri::sing::Jack::default(); -/// ``` -#[derive(Clone, Debug, Default)] pub struct Jack<'j> ( - pub(crate) Arc>> -); - -/// This is a connection which may be [Inactive], [Activating], or [Active]. -/// In the [Active] and [Inactive] states, [JackState::client] returns a -/// [jack::Client], which you can use to talk to the JACK API. -/// -/// ``` -/// let state = tengri::sing::JackState::default(); -/// ``` -#[derive(Debug, Default)] pub enum JackState<'j> { - /// Unused - #[default] Inert, - /// Before activation. - Inactive(Client), - /// During activation. - Activating, - /// After activation. Must not be dropped for JACK thread to persist. - Active(DynamicAsyncClient<'j>), -} - -/// Event enum for JACK events. -/// -/// ``` -/// let event = tengri::sing::JackEvent::XRun; // kerpop -/// ``` -#[derive(Debug, Clone, PartialEq)] pub enum JackEvent { - ThreadInit, - Shutdown(ClientStatus, Arc), - Freewheel(bool), - SampleRate(Frames), - ClientRegistration(Arc, bool), - PortRegistration(PortId, bool), - PortRename(PortId, Arc, Arc), - PortsConnected(PortId, PortId, bool), - GraphReorder, - XRun, -} - -/// Generic notification handler that emits [JackEvent] -/// -/// ``` -/// let notify = tengri::sing::JackNotify(|_|{}); -/// ``` -pub struct JackNotify(pub T); - /// Running JACK [AsyncClient] with maximum type erasure. /// /// One [Box] contains function that handles [JackEvent]s. @@ -92,134 +43,6 @@ pub type DynamicAudioHandler<'j> = pub type BoxedAudioHandler<'j> = Box Control + Send + Sync + 'j>; -/// Notification handler wrapper for [BoxedJackEventHandler]. -pub type DynamicNotifications<'j> = - JackNotify>; - -/// Boxed [JackEvent] callback. -pub type BoxedJackEventHandler<'j> = - Box; - -impl<'j> HasJack<'j> for Jack<'j> { fn jack (&self) -> &Jack<'j> { self } } - -impl<'j> HasJack<'j> for &Jack<'j> { fn jack (&self) -> &Jack<'j> { self } } - -/// Implement [Jack] constructor and methods -impl<'j> Jack<'j> { - /// Register new [Client] and wrap it for shared use. - pub fn new_run + Audio + Send + Sync + 'static> ( - name: &impl AsRef, - init: impl FnOnce(Jack<'j>)->Usually - ) -> Usually>> { - Jack::new(name)?.run(init) - } - - pub fn new (name: &impl AsRef) -> Usually { - let client = Client::new(name.as_ref(), ClientOptions::NO_START_SERVER)?.0; - Ok(Jack(Arc::new(RwLock::new(JackState::Inactive(client))))) - } - - pub fn run + Audio + Send + Sync + 'static> - (self, init: impl FnOnce(Self)->Usually) -> Usually>> - { - let client_state = self.0.clone(); - let app: Arc> = Arc::new(RwLock::new(init(self)?)); - let mut state = Activating; - std::mem::swap(&mut*client_state.write().unwrap(), &mut state); - if let Inactive(client) = state { - // This is the misc notifications handler. It's a struct that wraps a [Box] - // which performs type erasure on a callback that takes [JackEvent], which is - // one of the available misc notifications. - let notify = JackNotify(Box::new({ - let app = app.clone(); - move|event|(&mut*app.write().unwrap()).handle(event) - }) as BoxedJackEventHandler); - // This is the main processing handler. It's a struct that wraps a [Box] - // which performs type erasure on a callback that takes [Client] and [ProcessScope] - // and passes them down to the `app`'s `process` callback, which in turn - // implements audio and MIDI input and output on a realtime basis. - let process = ::jack::contrib::ClosureProcessHandler::new(Box::new({ - let app = app.clone(); - move|c: &_, s: &_|if let Ok(mut app) = app.write() { - app.process(c, s) - } else { - Control::Quit - } - }) as BoxedAudioHandler); - // Launch a client with the two handlers. - *client_state.write().unwrap() = Active( - client.activate_async(notify, process)? - ); - } else { - unreachable!(); - } - Ok(app) - } - - /// Run something with the client. - pub fn with_client (&self, op: impl FnOnce(&Client)->T) -> T { - match &*self.0.read().unwrap() { - Inert => panic!("jack client not activated"), - Inactive(client) => op(client), - Activating => panic!("jack client has not finished activation"), - Active(client) => op(client.as_client()), - } - } -} - -impl NotificationHandler for JackNotify { - fn thread_init(&self, _: &Client) { - self.0(JackEvent::ThreadInit); - } - unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) { - self.0(JackEvent::Shutdown(status, reason.into())); - } - fn freewheel(&mut self, _: &Client, enabled: bool) { - self.0(JackEvent::Freewheel(enabled)); - } - fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control { - self.0(JackEvent::SampleRate(frames)); - Control::Quit - } - fn client_registration(&mut self, _: &Client, name: &str, reg: bool) { - self.0(JackEvent::ClientRegistration(name.into(), reg)); - } - fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) { - self.0(JackEvent::PortRegistration(id, reg)); - } - fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { - self.0(JackEvent::PortRename(id, old.into(), new.into())); - Control::Continue - } - fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) { - self.0(JackEvent::PortsConnected(a, b, are)); - } - fn graph_reorder(&mut self, _: &Client) -> Control { - self.0(JackEvent::GraphReorder); - Control::Continue - } - fn xrun(&mut self, _: &Client) -> Control { - self.0(JackEvent::XRun); - Control::Continue - } -} - -impl JackPerfModel for PerfModel { - fn update_from_jack_scope (&self, t0: Option, scope: &ProcessScope) { - if let Some(t0) = t0 { - let t1 = self.clock.raw(); - self.used.store( - self.clock.delta_as_nanos(t0, t1) as f64, - Relaxed, - ); - self.window.store( - scope.cycle_times().unwrap().period_usecs as f64, - Relaxed, - ); - } - } -} - /// Things that can provide a [jack::Client] reference. /// /// ``` @@ -266,10 +89,6 @@ pub trait HasJack<'j>: Send + Sync { } } -pub trait JackPerfModel { - fn update_from_jack_scope (&self, t0: Option, scope: &ProcessScope); -} - /// Implement [Audio]: provide JACK callbacks. #[macro_export] macro_rules! impl_audio { diff --git a/src/sing/jack.rs b/src/sing/jack.rs new file mode 100644 index 0000000..a833244 --- /dev/null +++ b/src/sing/jack.rs @@ -0,0 +1,98 @@ +use crate::{*, sing::*, time::PerfModel}; +pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; + +use JackState::*; + +/// Wraps [JackState], and through it [jack::Client] when connected. +/// +/// ``` +/// let jack = tengri::sing::Jack::default(); +/// ``` +#[derive(Clone, Debug, Default)] pub struct Jack<'j> ( + pub(crate) Arc>> +); + +/// This is a connection which may be [Inactive], [Activating], or [Active]. +/// In the [Active] and [Inactive] states, [JackState::client] returns a +/// [jack::Client], which you can use to talk to the JACK API. +/// +/// ``` +/// let state = tengri::sing::JackState::default(); +/// ``` +#[derive(Debug, Default)] pub enum JackState<'j> { + /// Unused + #[default] Inert, + /// Before activation. + Inactive(Client), + /// During activation. + Activating, + /// After activation. Must not be dropped for JACK thread to persist. + Active(DynamicAsyncClient<'j>), +} + +/// Implement [Jack] constructor and methods +impl<'j> Jack<'j> { + /// Register new [Client] and wrap it for shared use. + pub fn new_run + Audio + Send + Sync + 'static> ( + name: impl AsRef, + init: impl FnOnce(Jack<'j>)->Usually + ) -> Usually>> { + Jack::new(name)?.run(init) + } + + pub fn new (name: impl AsRef) -> Usually { + let client = Client::new(name.as_ref(), ClientOptions::NO_START_SERVER)?.0; + Ok(Jack(Arc::new(RwLock::new(JackState::Inactive(client))))) + } + + pub fn run + Audio + Send + Sync + 'static> + (self, init: impl FnOnce(Self)->Usually) -> Usually>> + { + let client_state = self.0.clone(); + let app: Arc> = Arc::new(RwLock::new(init(self)?)); + let mut state = Activating; + std::mem::swap(&mut*client_state.write().unwrap(), &mut state); + if let Inactive(client) = state { + // This is the misc notifications handler. It's a struct that wraps a [Box] + // which performs type erasure on a callback that takes [JackEvent], which is + // one of the available misc notifications. + let notify = JackNotify(Box::new({ + let app = app.clone(); + move|event|(&mut*app.write().unwrap()).handle(event) + }) as BoxedJackEventHandler); + // This is the main processing handler. It's a struct that wraps a [Box] + // which performs type erasure on a callback that takes [Client] and [ProcessScope] + // and passes them down to the `app`'s `process` callback, which in turn + // implements audio and MIDI input and output on a realtime basis. + let process = ::jack::contrib::ClosureProcessHandler::new(Box::new({ + let app = app.clone(); + move|c: &_, s: &_|if let Ok(mut app) = app.write() { + app.process(c, s) + } else { + Control::Quit + } + }) as BoxedAudioHandler); + // Launch a client with the two handlers. + *client_state.write().unwrap() = Active( + client.activate_async(notify, process)? + ); + } else { + unreachable!(); + } + Ok(app) + } + + /// Run something with the client. + pub fn with_client (&self, op: impl FnOnce(&Client)->T) -> T { + match &*self.0.read().unwrap() { + Inert => panic!("jack client not activated"), + Inactive(client) => op(client), + Activating => panic!("jack client has not finished activation"), + Active(client) => op(client.as_client()), + } + } +} + +impl<'j> HasJack<'j> for Jack<'j> { fn jack (&self) -> &Jack<'j> { self } } + +impl<'j> HasJack<'j> for &Jack<'j> { fn jack (&self) -> &Jack<'j> { self } } diff --git a/src/sing/jack_event.rs b/src/sing/jack_event.rs new file mode 100644 index 0000000..7802842 --- /dev/null +++ b/src/sing/jack_event.rs @@ -0,0 +1,72 @@ +use crate::{*, time::PerfModel}; +pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; + +/// Event enum for JACK events. +/// +/// ``` +/// let event = tengri::sing::JackEvent::XRun; // kerpop +/// ``` +#[derive(Debug, Clone, PartialEq)] pub enum JackEvent { + ThreadInit, + Shutdown(ClientStatus, Arc), + Freewheel(bool), + SampleRate(Frames), + ClientRegistration(Arc, bool), + PortRegistration(PortId, bool), + PortRename(PortId, Arc, Arc), + PortsConnected(PortId, PortId, bool), + GraphReorder, + XRun, +} + +/// Generic notification handler that emits [JackEvent] +/// +/// ``` +/// let notify = tengri::sing::JackNotify(|_|{}); +/// ``` +pub struct JackNotify(pub T); + +/// Notification handler wrapper for [BoxedJackEventHandler]. +pub type DynamicNotifications<'j> = + JackNotify>; + +/// Boxed [JackEvent] callback. +pub type BoxedJackEventHandler<'j> = + Box; + +impl NotificationHandler for JackNotify { + fn thread_init(&self, _: &Client) { + self.0(JackEvent::ThreadInit); + } + unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) { + self.0(JackEvent::Shutdown(status, reason.into())); + } + fn freewheel(&mut self, _: &Client, enabled: bool) { + self.0(JackEvent::Freewheel(enabled)); + } + fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control { + self.0(JackEvent::SampleRate(frames)); + Control::Quit + } + fn client_registration(&mut self, _: &Client, name: &str, reg: bool) { + self.0(JackEvent::ClientRegistration(name.into(), reg)); + } + fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) { + self.0(JackEvent::PortRegistration(id, reg)); + } + fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { + self.0(JackEvent::PortRename(id, old.into(), new.into())); + Control::Continue + } + fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) { + self.0(JackEvent::PortsConnected(a, b, are)); + } + fn graph_reorder(&mut self, _: &Client) -> Control { + self.0(JackEvent::GraphReorder); + Control::Continue + } + fn xrun(&mut self, _: &Client) -> Control { + self.0(JackEvent::XRun); + Control::Continue + } +} diff --git a/src/sing/jack_perf.rs b/src/sing/jack_perf.rs new file mode 100644 index 0000000..ee0d7d6 --- /dev/null +++ b/src/sing/jack_perf.rs @@ -0,0 +1,22 @@ +use crate::{*, time::PerfModel}; +pub use ::jack::{*, contrib::{*, ClosureProcessHandler}}; + +pub trait JackPerfModel { + fn update_from_jack_scope (&self, t0: Option, scope: &ProcessScope); +} + +impl JackPerfModel for PerfModel { + fn update_from_jack_scope (&self, t0: Option, scope: &ProcessScope) { + if let Some(t0) = t0 { + let t1 = self.clock.raw(); + self.used.store( + self.clock.delta_as_nanos(t0, t1) as f64, + Relaxed, + ); + self.window.store( + scope.cycle_times().unwrap().period_usecs as f64, + Relaxed, + ); + } + } +} diff --git a/src/space/iter.rs b/src/space/iter.rs index c27b73c..fa5d3de 100644 --- a/src/space/iter.rs +++ b/src/space/iter.rs @@ -1,6 +1,20 @@ use super::*; -/// Iterate over a collection of renderables: +/// Iterate over a collection of the same kind of [Draw]able: +pub fn iter <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + +/// Iterate over a collection of the same kind of [Draw]able: +pub fn iter_once <'a, S: Screen, D: 'a, U: Draw> ( + _iter: impl Iterator, _draw: impl Fn(D, usize)->U, +) -> impl Draw { + thunk(move|_to: &mut S|{ todo!() }) +} + +/// Iterate over a collection of various [Draw]ables: /// /// ``` /// use tengri::draw::{Origin::*, Split::*}; @@ -20,12 +34,6 @@ pub fn iter_dyn < thunk(move|_to: &mut S|{ todo!() }) } -pub fn iter <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( - _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, -) -> impl Draw { - thunk(move|_to: &mut S|{ todo!() }) -} - pub fn iter_north <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, ) -> impl Draw { @@ -61,3 +69,27 @@ pub fn iter_west <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( ) -> impl Draw { thunk(move|_to: &mut S|{ todo!() }) } + +pub fn row_south (south: S::Unit, height: S::Unit, content: impl Draw) + -> impl Draw +{ + y_push(south, h_exact(height, content)) +} + +pub fn row_north (north: S::Unit, height: S::Unit, content: impl Draw) + -> impl Draw +{ + y_pull(north, h_exact(height, content)) +} + +pub fn col_east (east: S::Unit, width: S::Unit, content: impl Draw) + -> impl Draw +{ + x_push(east, h_exact(width, content)) +} + +pub fn col_west (west: S::Unit, width: S::Unit, content: impl Draw) + -> impl Draw +{ + x_pull(west, h_exact(width, content)) +} diff --git a/src/space/size.rs b/src/space/size.rs index add71ae..7a275ac 100644 --- a/src/space/size.rs +++ b/src/space/size.rs @@ -2,8 +2,14 @@ use super::*; /// Uses [AtomicUsize] to measure size during\ /// rendering (which is normally read-only). -#[derive(Default, Debug)] -pub struct Size(AtomicUsize, AtomicUsize); +#[derive(Default, Debug, Clone)] +pub struct Size(Arc, Arc); + +impl PartialEq for Size { + fn eq (&self, _: &Self) -> bool { + todo!() + } +} impl X for Size { fn x (&self) -> u16 { 0 } diff --git a/src/text.rs b/src/text.rs index 9943d4b..7eb181e 100644 --- a/src/text.rs +++ b/src/text.rs @@ -19,7 +19,7 @@ pub(crate) use ::unicode_width::*; } } - impl Draw for &std::sync::Arc { + impl Draw for std::sync::Arc { fn draw (self, to: &mut Tui) -> Usually> { self.as_ref().draw(to) }