diff --git a/jack/src/has_jack.rs b/jack/src/has_jack.rs index 42feb1de..fb8e16f5 100644 --- a/jack/src/has_jack.rs +++ b/jack/src/has_jack.rs @@ -1,24 +1,11 @@ use crate::*; - -pub trait HasJack { - fn jack (&self) -> &Arc>; -} - +/// Things that can provide a [JackClient] reference. +pub trait HasJack { fn jack (&self) -> &JackClient; } /// Implement [HasJack]. #[macro_export] macro_rules! has_jack { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasJack for $Struct $(<$($L),*$($T),*>)? { - fn jack (&$self) -> &Arc> { $cb } - } - }; -} - -/// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. -#[macro_export] macro_rules! from_jack { - (|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc>> for $Struct $(<$($L),*$($T),*>)? { - type Error = Box; - fn try_from ($jack: &Arc>) -> Usually { Ok($cb) } + fn jack (&$self) -> &JackClient { $cb } } }; } diff --git a/jack/src/jack_client.rs b/jack/src/jack_client.rs new file mode 100644 index 00000000..233f02a9 --- /dev/null +++ b/jack/src/jack_client.rs @@ -0,0 +1,150 @@ +use crate::*; +use self::JackClientState::*; + +/// Wraps [JackClientState] and through it [jack::Client]. +#[derive(Clone, Debug, Default)] +pub struct JackClient { + state: Arc> +} +impl JackClient { + pub fn new (name: &str) -> Usually { + let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; + Ok(Self { state: Arc::new(RwLock::new(Inactive(client))) }) + } + /// Return the internal [Client] handle that lets you call the JACK API. + pub fn inner (&self) -> Client { + self.state.read().unwrap().inner() + } + /// Activate a connection with an application. + /// + /// Consume a `JackClient::Inactive`, binding a process callback and returning a `JackClient::Active`. + /// + /// * [ ] TODO: Needs work. Strange ownership situation between the callback and the host object. + fn activate <'a: 'static> ( + &'a self, mut cb: impl FnMut(JackClient, &Client, &ProcessScope) -> Control + Send + 'a + ) -> Usually where Self: Send + Sync + 'a { + let client = self.inner(); + let state = Arc::new(RwLock::new(Activating)); + let event = Box::new(move|_|{/*TODO*/}) as Box; + let events = Notifications(event); + let frame = Box::new(move|c: &_, s: &_|cb(self.clone(), c, s)); + let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler<'a>); + *state.write().unwrap() = Active(client.activate_async(events, frames)?); + Ok(Self { state }) + } + /// Activate a connection with an application. + /// + /// * Wrap a [JackClient::Inactive] into [Arc>]. + /// * Pass it to the `init` callback + /// * This allows user code to connect to JACK + /// * While user code retains clone of the + /// [Arc>] that is + /// passed to `init`, the audio engine is running. + pub fn activate_with <'a: 'static, T> ( + &self, init: impl FnOnce(&JackClient)->Usually + ) -> Usually>> where T: Audio + 'a { + // Run init callback. Return value is target. Target must retain clone of `connection`. + let target = Arc::new(RwLock::new(init(&self)?)); + // Swap the `client` from the `JackClient::Inactive` + // for a `JackClient::Activating`. + let mut client = Activating; + std::mem::swap(&mut*self.state.write().unwrap(), &mut client); + // Replace the `JackClient::Activating` with a + // `JackClient::Active` wrapping the [AsyncClient] + // returned by the activation. + *self.state.write().unwrap() = Active(client.inner().activate_async( + // 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. + Notifications(Box::new(move|_|{/*TODO*/}) 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 `target`'s `process` callback, which in turn + // implements audio and MIDI input and output on a realtime basis. + ClosureProcessHandler::new(Box::new({ + let target = target.clone(); + move|c: &_, s: &_|if let Ok(mut target) = target.write() { + target.process(c, s) + } else { + Control::Quit + } + }) as BoxedAudioHandler), + )?); + Ok(target) + } + pub fn port_by_name (&self, name: &str) -> Option> { + self.inner().port_by_name(name) + } + pub fn register_port (&self, name: &str, spec: PS) -> Usually> { + Ok(self.inner().register_port(name, spec)?) + } +} +/// This is a connection which may be [Inactive], [Activating], or [Active]. +/// In the [Active] and [Inactive] states, [JackClientState::client] returns a +/// [jack::Client], which you can use to talk to the JACK API. +#[derive(Debug, Default)] enum JackClientState { + /// Unused + #[default] Inert, + /// Before activation. + Inactive(Client), + /// During activation. + Activating, + /// After activation. Must not be dropped for JACK thread to persist. + Active(DynamicAsyncClient<'static>), +} +impl JackClientState { + pub fn inner (&self) -> Client { + match self { + Inert => panic!("jack client not activated"), + Inactive(ref client) => unsafe { Client::from_raw(client.raw()) }, + Activating => panic!("jack client has not finished activation"), + Active(ref client) => unsafe { Client::from_raw(client.as_client().raw()) }, + } + } +} +/// This is a boxed realtime callback. +pub type BoxedAudioHandler<'j> = + Box Control + Send + 'j>; +/// This is the notification handler wrapper for a boxed realtime callback. +pub type DynamicAudioHandler<'j> = + ClosureProcessHandler<(), BoxedAudioHandler<'j>>; +/// This is a boxed [JackEvent] callback. +pub type BoxedJackEventHandler<'j> = + Box; +/// This is the notification handler wrapper for a boxed [JackEvent] callback. +pub type DynamicNotifications<'j> = + Notifications>; +/// This is a running JACK [AsyncClient] with maximum type erasure. +/// It has one [Box] containing a function that handles [JackEvent]s, +/// and another [Box] containing a function that handles realtime IO, +/// and that's all it knows about them. +pub type DynamicAsyncClient<'j> + = AsyncClient, DynamicAudioHandler<'j>>; + +impl RegisterPort for JackClient { + fn midi_in (&self, name: impl AsRef) -> Usually> { + Ok(self.inner().register_port(name.as_ref(), MidiIn::default())?) + } + fn midi_out (&self, name: impl AsRef) -> Usually> { + Ok(self.inner().register_port(name.as_ref(), MidiOut::default())?) + } + fn audio_in (&self, name: impl AsRef) -> Usually> { + Ok(self.inner().register_port(name.as_ref(), AudioIn::default())?) + } + fn audio_out (&self, name: impl AsRef) -> Usually> { + Ok(self.inner().register_port(name.as_ref(), AudioOut::default())?) + } +} +impl ConnectPort for JackClient { + fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { + self.inner().ports(re_name, re_type, flags) + } + fn port_by_name (&self, name: impl AsRef) -> Option> { + self.inner().port_by_name(name.as_ref()) + } + fn connect_ports (&self, source: &Port, target: &Port) + -> Usually<()> + { + Ok(self.inner().connect_ports(source, target)?) + } +} diff --git a/jack/src/jack_connect.rs b/jack/src/jack_connect.rs index 51fbd8b2..dcc4919a 100644 --- a/jack/src/jack_connect.rs +++ b/jack/src/jack_connect.rs @@ -1,136 +1,165 @@ use crate::*; - -/// This is a boxed realtime callback. -pub type BoxedAudioHandler = Box Control + Send>; - -/// This is the notification handler wrapper for a boxed realtime callback. -pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>; - -/// This is a boxed [JackEvent] callback. -pub type BoxedJackEventHandler = Box; - -/// This is the notification handler wrapper for a boxed [JackEvent] callback. -pub type DynamicNotifications = Notifications; - -/// This is a running JACK [AsyncClient] with maximum type erasure. -/// It has one [Box] containing a function that handles [JackEvent]s, -/// and another [Box] containing a function that handles realtime IO, -/// and that's all it knows about them. -pub type DynamicAsyncClient = AsyncClient; - -/// This is a connection which may be `Inactive`, `Activating`, or `Active`. -/// In the `Active` and `Inactive` states, its `client` method returns a -/// [Client] which you can use to talk to the JACK API. -#[derive(Debug, Default)] -pub enum JackConnection { - #[default] - Inert, - /// Before activation. - Inactive(Client), - /// During activation. - Activating, - /// After activation. Must not be dropped for JACK thread to persist. - Active(DynamicAsyncClient), -} - -impl From for Client { - fn from (jack: JackConnection) -> Self { - match jack { - JackConnection::Inactive(client) => client, - JackConnection::Inert => panic!("jack client not activated"), - JackConnection::Activating => panic!("jack client still activating"), - JackConnection::Active(_) => panic!("jack client already activated"), - } - } -} - -impl JackConnection { - pub fn new (name: &str) -> Usually { - let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; - Ok(Self::Inactive(client)) - } - /// Return the internal [Client] handle that lets you call the JACK API. - pub fn client (&self) -> &Client { - match self { - Self::Inert => panic!("jack client not activated"), - Self::Inactive(ref client) => client, - Self::Activating => panic!("jack client has not finished activation"), - Self::Active(ref client) => client.as_client(), - } - } - /// Activate a connection with an application. - /// - /// Consume a `JackConnection::Inactive`, - /// binding a process callback and - /// returning a `JackConnection::Active`. - /// - /// Needs work. Strange ownership situation between the callback - /// and the host object. - fn activate ( - self, - 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 events = Notifications(event); - let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&state, c, s)}); - let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler); - *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); - Ok(state) - } - /// Activate a connection with an application. - /// - /// * Wrap a [JackConnection::Inactive] into [Arc>]. - /// * Pass it to the `init` callback - /// * This allows user code to connect to JACK - /// * While user code retains clone of the - /// [Arc>] that is - /// passed to `init`, the audio engine is running. - pub fn activate_with ( - self, - init: impl FnOnce(&Arc>)->Usually - ) - -> Usually>> - { - // Wrap self for multiple ownership. - let connection = Arc::new(RwLock::new(self)); - // Run init callback. Return value is target. Target must retain clone of `connection`. - let target = Arc::new(RwLock::new(init(&connection)?)); - // Swap the `client` from the `JackConnection::Inactive` - // for a `JackConnection::Activating`. - let mut client = Self::Activating; - std::mem::swap(&mut*connection.write().unwrap(), &mut client); - // Replace the `JackConnection::Activating` with a - // `JackConnection::Active` wrapping the [AsyncClient] - // returned by the activation. - *connection.write().unwrap() = Self::Active(Client::from(client).activate_async( - // 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. - Notifications(Box::new(move|_|{/*TODO*/}) 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 `target`'s `process` callback, which in turn - // implements audio and MIDI input and output on a realtime basis. - ClosureProcessHandler::new(Box::new({ - let target = target.clone(); - move|c: &_, s: &_|if let Ok(mut target) = target.write() { - target.process(c, s) - } else { - Control::Quit +pub trait JackPortConnect { + fn jack (&self) -> &JackClient; + fn port (&self) -> &Port; + fn conn (&self) -> &[PortConnection]; + fn connect_to_matching (&mut self) -> Usually<()> { + use PortConnectionName::*; + use PortConnectionScope::*; + use PortConnectionStatus::*; + let jack = self.jack(); + for connect in self.conn().iter() { + let mut status = vec![]; + match &connect.name { + Exact(name) => for port in jack.ports(None, None, PortFlags::empty()).iter() { + if port.as_str() == &**name { + if let Some(port) = jack.port_by_name(port.as_str()) { + let port_status = Self::try_both_ways(jack, &port, &self.port()); + let name = port.name()?.into(); + status.push((port, name, port_status)); + if port_status == Connected { + break + } + } + } + }, + RegExp(re) => for port in jack.ports(Some(&re), None, PortFlags::empty()).iter() { + if let Some(port) = jack.port_by_name(port.as_str()) { + let port_status = Self::try_both_ways(jack, &port, &self.port()); + let name = port.name()?.into(); + status.push((port, name, port_status)); + if port_status == Connected && connect.scope == One { + break + } + } } - }) as BoxedAudioHandler), - )?); - Ok(target) + } + *connect.status.write().unwrap() = status + } + Ok(()) } - 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)?) + fn try_both_ways ( + jack: &impl ConnectPort, port_a: &Port, port_b: &Port + ) -> PortConnectionStatus where A: PortSpec, B: PortSpec { + if let Ok(_) = jack.connect_ports(port_a, port_b) { + PortConnectionStatus::Connected + } else if let Ok(_) = jack.connect_ports(port_b, port_a) { + PortConnectionStatus::Connected + } else { + PortConnectionStatus::Mismatch + } } } +#[derive(Clone, Debug)] +pub struct PortConnection { + pub name: PortConnectionName, + pub scope: PortConnectionScope, + pub status: Arc, Arc, PortConnectionStatus)>>>, +} +pub trait ConnectPort { + fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec; + fn port_by_name (&self, name: impl AsRef) -> Option>; + fn connect_ports (&self, source: &Port, target: &Port) + -> Usually<()>; + fn connect_midi_from (&self, input: &Port, ports: &[impl AsRef]) -> Usually<()> { + for port in ports.iter() { + let port = port.as_ref(); + if let Some(port) = self.port_by_name(port).as_ref() { + self.connect_ports(port, input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_midi_to (&self, output: &Port, ports: &[impl AsRef]) -> Usually<()> { + for port in ports.iter() { + let port = port.as_ref(); + if let Some(port) = self.port_by_name(port).as_ref() { + self.connect_ports(output, port)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_audio_from (&self, input: &Port, ports: &[impl AsRef]) -> Usually<()> { + for port in ports.iter() { + let port = port.as_ref(); + if let Some(port) = self.port_by_name(port).as_ref() { + self.connect_ports(port, input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_audio_to (&self, output: &Port, ports: &[impl AsRef]) -> Usually<()> { + for port in ports.iter() { + let port = port.as_ref(); + if let Some(port) = self.port_by_name(port).as_ref() { + self.connect_ports(output, port)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } +} +impl ConnectPort for Arc> { + fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { + self.read().unwrap().ports(re_name, re_type, flags) + } + fn port_by_name (&self, name: impl AsRef) -> Option> { + self.read().unwrap().port_by_name(name.as_ref()) + } + fn connect_ports (&self, source: &Port, target: &Port) + -> Usually<()> + { + Ok(self.read().unwrap().connect_ports(source, target)?) + } +} +impl PortConnection { + pub fn collect (exact: &[impl AsRef], re: &[impl AsRef], re_all: &[impl AsRef]) + -> Vec + { + let mut connections = vec![]; + for port in exact.iter() { connections.push(Self::exact(port)) } + for port in re.iter() { connections.push(Self::regexp(port)) } + for port in re_all.iter() { connections.push(Self::regexp_all(port)) } + connections + } + /// Connect to this exact port + pub fn exact (name: impl AsRef) -> Self { + let name = PortConnectionName::Exact(name.as_ref().into()); + Self { name, scope: PortConnectionScope::One, status: Arc::new(RwLock::new(vec![])) } + } + pub fn regexp (name: impl AsRef) -> Self { + let name = PortConnectionName::RegExp(name.as_ref().into()); + Self { name, scope: PortConnectionScope::One, status: Arc::new(RwLock::new(vec![])) } + } + pub fn regexp_all (name: impl AsRef) -> Self { + let name = PortConnectionName::RegExp(name.as_ref().into()); + Self { name, scope: PortConnectionScope::All, status: Arc::new(RwLock::new(vec![])) } + } + pub fn info (&self) -> Arc { + format!("{} {} {}", match self.scope { + PortConnectionScope::One => " ", + PortConnectionScope::All => "*", + }, match &self.name { + PortConnectionName::Exact(name) => format!("= {name}"), + PortConnectionName::RegExp(name) => format!("~ {name}"), + }, self.status.read().unwrap().len()).into() + } +} +#[derive(Clone, Debug, PartialEq)] +pub enum PortConnectionName { + /** Exact match */ + Exact(Arc), + /** Match regular expression */ + RegExp(Arc), +} +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PortConnectionScope { One, All } +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PortConnectionStatus { Missing, Disconnected, Connected, Mismatch, } diff --git a/jack/src/jack_port.rs b/jack/src/jack_port.rs index c37cef47..9a178aa0 100644 --- a/jack/src/jack_port.rs +++ b/jack/src/jack_port.rs @@ -1,279 +1,56 @@ use crate::*; -#[derive(Debug)] -pub struct JackPort { - /// Handle to JACK client, for receiving reconnect events. - pub jack: Arc>, - /// Port name - pub name: Arc, - /// Port handle. - pub port: Port, - /// List of ports to connect to. - pub connect: Vec -} -impl JackPort { - pub fn connect_to_matching (&mut self) -> Usually<()> { - use PortConnectionName::*; - use PortConnectionScope::*; - use PortConnectionStatus::*; - for connect in self.connect.iter_mut() { - let mut status = vec![]; - match &connect.name { - Exact(name) => for port in self.jack.ports(None, None, PortFlags::empty()).iter() { - if port.as_str() == &**name { - if let Some(port) = self.jack.port_by_name(port.as_str()) { - let port_status = Self::try_both_ways(&self.jack, &port, &self.port); - let name = port.name()?.into(); - status.push((port, name, port_status)); - if port_status == Connected { - break - } - } - } - }, - RegExp(re) => for port in self.jack.ports(Some(&re), None, PortFlags::empty()).iter() { - if let Some(port) = self.jack.port_by_name(port.as_str()) { - let port_status = Self::try_both_ways(&self.jack, &port, &self.port); - let name = port.name()?.into(); - status.push((port, name, port_status)); - if port_status == Connected && connect.scope == One { - break - } - } - } +macro_rules! impl_port { + ($Name:ident $Spec:ident |$jack:ident, $name:ident|$port:expr) => { + #[derive(Debug)] pub struct $Name { + /// Handle to JACK client, for receiving reconnect events. + pub jack: JackClient, + /// Port name + pub name: Arc, + /// Port handle. + pub port: Port<$Spec>, + /// List of ports to connect to. + pub conn: Vec + } + impl AsRef> for $Name { fn as_ref (&self) -> &Port<$Spec> { &self.port } } + impl $Name { + pub fn new ($jack: &JackClient, name: impl AsRef, connect: &[PortConnection]) + -> Usually + { + let $name = name.as_ref(); + let mut port = Self { + jack: $jack.clone(), + port: $port?, + name: $name.into(), + conn: connect.to_vec() + }; + port.connect_to_matching()?; + Ok(port) } - connect.status = status } - Ok(()) - } - fn try_both_ways ( - jack: &impl ConnectPort, port_a: &Port, port_b: &Port - ) - -> PortConnectionStatus - { - if let Ok(_) = jack.connect_ports(port_a, port_b) { - PortConnectionStatus::Connected - } else if let Ok(_) = jack.connect_ports(port_b, port_a) { - PortConnectionStatus::Connected - } else { - PortConnectionStatus::Mismatch + impl JackPortConnect<$Spec> for $Name { + fn jack (&self) -> &JackClient { &self.jack } + fn port (&self) -> &Port<$Spec> { &self.port } + fn conn (&self) -> &[PortConnection] { self.conn.as_slice() } } - } + }; } -#[derive(Clone, Debug, PartialEq)] -pub struct PortConnection { - pub name: PortConnectionName, - pub scope: PortConnectionScope, - pub status: Vec<(Port, Arc, PortConnectionStatus)>, -} -impl PortConnection { - pub fn collect (exact: &[impl AsRef], re: &[impl AsRef], re_all: &[impl AsRef]) - -> Vec - { - let mut connections = vec![]; - for port in exact.iter() { connections.push(Self::exact(port)) } - for port in re.iter() { connections.push(Self::regexp(port)) } - for port in re_all.iter() { connections.push(Self::regexp_all(port)) } - connections - } - /// Connect to this exact port - pub fn exact (name: impl AsRef) -> Self { - let name = PortConnectionName::Exact(name.as_ref().into()); - Self { name, scope: PortConnectionScope::One, status: vec![] } - } - pub fn regexp (name: impl AsRef) -> Self { - let name = PortConnectionName::RegExp(name.as_ref().into()); - Self { name, scope: PortConnectionScope::One, status: vec![] } - } - pub fn regexp_all (name: impl AsRef) -> Self { - let name = PortConnectionName::RegExp(name.as_ref().into()); - Self { name, scope: PortConnectionScope::All, status: vec![] } - } - pub fn info (&self) -> Arc { - format!("{} {} {}", match self.scope { - PortConnectionScope::One => " ", - PortConnectionScope::All => "*", - }, match &self.name { - PortConnectionName::Exact(name) => format!("= {name}"), - PortConnectionName::RegExp(name) => format!("~ {name}"), - }, self.status.len()).into() - } -} -#[derive(Clone, Debug, PartialEq)] -pub enum PortConnectionName { - /** Exact match */ - Exact(Arc), - /** Match regular expression */ - RegExp(Arc), -} -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum PortConnectionScope { One, All } -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum PortConnectionStatus { Missing, Disconnected, Connected, Mismatch, } +impl_port! { JackAudioIn AudioIn |jack, name|jack.audio_in(name) } +impl_port! { JackAudioOut AudioOut |jack, name|jack.audio_out(name) } +impl_port! { JackMidiIn MidiIn |jack, name|jack.midi_in(name) } +impl_port! { JackMidiOut MidiOut |jack, name|jack.midi_out(name) } -impl AsRef> for JackPort { - fn as_ref (&self) -> &Port { - &self.port - } -} -impl JackPort { - pub fn new ( - jack: &Arc>, name: impl AsRef, connect: &[PortConnection] - ) -> Usually { - let mut port = JackPort { - jack: jack.clone(), - port: jack.midi_in(name.as_ref())?, - name: name.as_ref().into(), - connect: connect.to_vec() - }; - port.connect_to_matching()?; - Ok(port) - } -} -impl JackPort { - pub fn new ( - jack: &Arc>, name: impl AsRef, connect: &[PortConnection] - ) -> Usually { - let mut port = Self { - jack: jack.clone(), - port: jack.midi_out(name.as_ref())?, - name: name.as_ref().into(), - connect: connect.to_vec() - }; - port.connect_to_matching()?; - Ok(port) - } -} -impl JackPort { - pub fn new ( - jack: &Arc>, name: impl AsRef, connect: &[PortConnection] - ) -> Usually { - let mut port = Self { - jack: jack.clone(), - port: jack.audio_in(name.as_ref())?, - name: name.as_ref().into(), - connect: connect.to_vec() - }; - port.connect_to_matching()?; - Ok(port) - } -} -impl JackPort { - pub fn new ( - jack: &Arc>, name: impl AsRef, connect: &[PortConnection] - ) -> Usually { - let mut port = Self { - jack: jack.clone(), - port: jack.audio_out(name.as_ref())?, - name: name.as_ref().into(), - connect: connect.to_vec() - }; - port.connect_to_matching()?; - Ok(port) - } -} - -pub trait ConnectPort { - fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec; - fn port_by_name (&self, name: impl AsRef) -> Option>; - fn connect_ports (&self, source: &Port, target: &Port) - -> Usually<()>; - fn connect_midi_from (&self, input: &Port, ports: &[impl AsRef]) -> Usually<()> { - for port in ports.iter() { - let port = port.as_ref(); - if let Some(port) = self.port_by_name(port).as_ref() { - self.connect_ports(port, input)?; - } else { - panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } - fn connect_midi_to (&self, output: &Port, ports: &[impl AsRef]) -> Usually<()> { - for port in ports.iter() { - let port = port.as_ref(); - if let Some(port) = self.port_by_name(port).as_ref() { - self.connect_ports(output, port)?; - } else { - panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } - fn connect_audio_from (&self, input: &Port, ports: &[impl AsRef]) -> Usually<()> { - for port in ports.iter() { - let port = port.as_ref(); - if let Some(port) = self.port_by_name(port).as_ref() { - self.connect_ports(port, input)?; - } else { - panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } - fn connect_audio_to (&self, output: &Port, ports: &[impl AsRef]) -> Usually<()> { - for port in ports.iter() { - let port = port.as_ref(); - if let Some(port) = self.port_by_name(port).as_ref() { - self.connect_ports(output, port)?; - } else { - panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } -} -impl ConnectPort for JackConnection { - fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { - self.client().ports(re_name, re_type, flags) - } - fn port_by_name (&self, name: impl AsRef) -> Option> { - self.client().port_by_name(name.as_ref()) - } - fn connect_ports (&self, source: &Port, target: &Port) - -> Usually<()> - { - Ok(self.client().connect_ports(source, target)?) - } -} -impl ConnectPort for Arc> { - fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { - self.read().unwrap().ports(re_name, re_type, flags) - } - fn port_by_name (&self, name: impl AsRef) -> Option> { - self.read().unwrap().port_by_name(name.as_ref()) - } - fn connect_ports (&self, source: &Port, target: &Port) - -> Usually<()> - { - Ok(self.read().unwrap().connect_ports(source, target)?) - } -} /// This is a utility trait for things that may register or connect [Port]s. /// It contains shorthand methods to this purpose. It's implemented for -/// `Arc>` for terse port registration in the -/// `init` callback of [JackClient::activate_with]. +/// `Arc>` for terse port registration in the +/// `init` callback of [jack::Client::activate_with]. pub trait RegisterPort { fn midi_in (&self, name: impl AsRef) -> Usually>; fn midi_out (&self, name: impl AsRef) -> Usually>; fn audio_in (&self, name: impl AsRef) -> Usually>; fn audio_out (&self, name: impl AsRef) -> Usually>; } -impl RegisterPort for JackConnection { - fn midi_in (&self, name: impl AsRef) -> Usually> { - Ok(self.client().register_port(name.as_ref(), MidiIn::default())?) - } - fn midi_out (&self, name: impl AsRef) -> Usually> { - Ok(self.client().register_port(name.as_ref(), MidiOut::default())?) - } - fn audio_in (&self, name: impl AsRef) -> Usually> { - Ok(self.client().register_port(name.as_ref(), AudioIn::default())?) - } - fn audio_out (&self, name: impl AsRef) -> Usually> { - Ok(self.client().register_port(name.as_ref(), AudioOut::default())?) - } -} impl RegisterPort for Arc> { fn midi_in (&self, name: impl AsRef) -> Usually> { self.read().unwrap().midi_in(name) diff --git a/jack/src/jack_sync.rs b/jack/src/jack_sync.rs new file mode 100644 index 00000000..0943dc55 --- /dev/null +++ b/jack/src/jack_sync.rs @@ -0,0 +1,20 @@ +use crate::*; +use jack::contrib::*; + +pub trait SyncToTransport { + fn client (&self) -> Client; + fn sync_lead (&self, enable: bool, cb: impl Fn(TimebaseInfo)->Position) -> Usually<()> { + if enable { + self.client().register_timebase_callback(false, cb)?; + } + Ok(()) + } + fn sync_follow (&self, enable: bool) -> Usually<()> { + // TODO: sync follow + Ok(()) + } +} + +impl SyncToTransport for JackClient { + fn client (&self) -> Client { self.inner() } +} diff --git a/jack/src/lib.rs b/jack/src/lib.rs index 4e81bca4..4c8d1766 100644 --- a/jack/src/lib.rs +++ b/jack/src/lib.rs @@ -1,8 +1,16 @@ +#![feature(type_alias_impl_trait)] + +mod has_jack; pub use self::has_jack::*; +mod jack_audio; pub use self::jack_audio::*; +mod jack_connect; pub use self::jack_connect::*; +mod jack_client; pub use self::jack_client::*; +mod jack_event; pub use self::jack_event::*; +mod jack_port; pub use self::jack_port::*; +mod jack_sync; pub use self::jack_sync::*; + pub(crate) use std::sync::{Arc, RwLock}; pub(crate) use std::collections::BTreeMap; - -pub use ::jack; -pub(crate) use ::jack::{ +pub use ::jack; pub(crate) use ::jack::{ contrib::ClosureProcessHandler, NotificationHandler, Client, AsyncClient, ClientOptions, ClientStatus, ProcessScope, Control, Frames, @@ -10,12 +18,6 @@ pub(crate) use ::jack::{ Unowned, MidiIn, MidiOut, AudioIn, AudioOut, }; -mod has_jack; pub use self::has_jack::*; -mod jack_audio; pub use self::jack_audio::*; -mod jack_connect; pub use self::jack_connect::*; -mod jack_event; pub use self::jack_event::*; -mod jack_port; pub use self::jack_port::*; - pub(crate) type Usually = Result>; //////////////////////////////////////////////////////////////////////////////////// diff --git a/midi/src/midi_in.rs b/midi/src/midi_in.rs index 2a9efb65..ff078d0c 100644 --- a/midi/src/midi_in.rs +++ b/midi/src/midi_in.rs @@ -2,8 +2,8 @@ use crate::*; /// Trait for thing that may receive MIDI. pub trait HasMidiIns { - fn midi_ins (&self) -> &Vec>; - fn midi_ins_mut (&mut self) -> &mut Vec>; + fn midi_ins (&self) -> &Vec; + fn midi_ins_mut (&mut self) -> &mut Vec; fn has_midi_ins (&self) -> bool { !self.midi_ins().is_empty() } diff --git a/midi/src/midi_out.rs b/midi/src/midi_out.rs index 3ac74eb5..b6e6fa65 100644 --- a/midi/src/midi_out.rs +++ b/midi/src/midi_out.rs @@ -2,8 +2,8 @@ use crate::*; /// Trait for thing that may output MIDI. pub trait HasMidiOuts { - fn midi_outs (&self) -> &Vec>; - fn midi_outs_mut (&mut self) -> &mut Vec>; + fn midi_outs (&self) -> &Vec; + fn midi_outs_mut (&mut self) -> &mut Vec; fn has_midi_outs (&self) -> bool { !self.midi_outs().is_empty() } diff --git a/midi/src/midi_player.rs b/midi/src/midi_player.rs index 70a03086..e91d4962 100644 --- a/midi/src/midi_player.rs +++ b/midi/src/midi_player.rs @@ -30,9 +30,9 @@ pub struct MidiPlayer { /// Send all notes off pub reset: bool, // TODO?: after Some(nframes) /// Record from MIDI ports to current sequence. - pub midi_ins: Vec>, + pub midi_ins: Vec, /// Play from current sequence to MIDI ports - pub midi_outs: Vec>, + pub midi_outs: Vec, /// Notes currently held at input pub notes_in: Arc>, /// Notes currently held at output @@ -62,7 +62,7 @@ impl Default for MidiPlayer { } impl MidiPlayer { pub fn new ( - jack: &Arc>, + jack: &JackClient, name: impl AsRef, clip: Option<&Arc>>, midi_from: &[PortConnection], @@ -71,8 +71,8 @@ impl MidiPlayer { let name = name.as_ref(); let clock = Clock::from(jack); Ok(Self { - midi_ins: vec![JackPort::::new(jack, format!("M/{name}"), midi_from)?,], - midi_outs: vec![JackPort::::new(jack, format!("{name}/M"), midi_to)?, ], + midi_ins: vec![JackMidiIn::new(jack, format!("M/{name}"), midi_from)?,], + midi_outs: vec![JackMidiOut::new(jack, format!("{name}/M"), midi_to)?, ], play_clip: Some((Moment::zero(&clock.timebase), clip.cloned())), clock, ..Default::default() @@ -89,18 +89,18 @@ impl std::fmt::Debug for MidiPlayer { } } from!(|clock: &Clock| MidiPlayer = Self { - clock: clock.clone(), - midi_ins: vec![], - midi_outs: vec![], - note_buf: vec![0;8], - reset: true, - recording: false, - monitoring: false, - overdub: false, - play_clip: None, - next_clip: None, - notes_in: RwLock::new([false;128]).into(), - notes_out: RwLock::new([false;128]).into(), + clock: clock.clone(), + midi_ins: vec![], + midi_outs: vec![], + note_buf: vec![0;8], + reset: true, + recording: false, + monitoring: false, + overdub: false, + play_clip: None, + next_clip: None, + notes_in: RwLock::new([false;128]).into(), + notes_out: RwLock::new([false;128]).into(), }); from!(|state: (&Clock, &Arc>)|MidiPlayer = { let (clock, clip) = state; @@ -110,12 +110,12 @@ from!(|state: (&Clock, &Arc>)|MidiPlayer = { }); has_clock!(|self: MidiPlayer|self.clock); impl HasMidiIns for MidiPlayer { - fn midi_ins (&self) -> &Vec> { &self.midi_ins } - fn midi_ins_mut (&mut self) -> &mut Vec> { &mut self.midi_ins } + fn midi_ins (&self) -> &Vec { &self.midi_ins } + fn midi_ins_mut (&mut self) -> &mut Vec { &mut self.midi_ins } } impl HasMidiOuts for MidiPlayer { - fn midi_outs (&self) -> &Vec> { &self.midi_outs } - fn midi_outs_mut (&mut self) -> &mut Vec> { &mut self.midi_outs } + fn midi_outs (&self) -> &Vec { &self.midi_outs } + fn midi_outs_mut (&mut self) -> &mut Vec { &mut self.midi_outs } fn midi_note (&mut self) -> &mut Vec { &mut self.note_buf } } /// Hosts the JACK callback for a single MIDI player diff --git a/plugin/src/plugin.rs b/plugin/src/plugin.rs index 29daa778..d077ecac 100644 --- a/plugin/src/plugin.rs +++ b/plugin/src/plugin.rs @@ -4,7 +4,7 @@ use crate::*; #[derive(Debug)] pub struct Plugin { /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Arc>, + pub jack: JackClient, pub name: Arc, pub path: Option>, pub plugin: Option, @@ -37,7 +37,7 @@ impl Debug for PluginKind { } impl Plugin { pub fn new_lv2 ( - jack: &Arc>, + jack: &JackClient, name: &str, path: &str, ) -> Usually { @@ -128,7 +128,7 @@ audio!(|self: PluginAudio, client, scope|{ impl Plugin { /// Create a plugin host device. pub fn new ( - jack: &Arc>, + jack: &JackClient, name: &str, ) -> Usually { Ok(Self { @@ -257,7 +257,7 @@ fn draw_header (state: &Plugin, to: &mut TuiOut, x: u16, y: u16, w: u16) { //} //}); -//from_atom!("plugin/lv2" => |jack: &Arc>, args| -> Plugin { +//from_atom!("plugin/lv2" => |jack: &JackClient, args| -> Plugin { //let mut name = String::new(); //let mut path = String::new(); //atom!(atom in args { diff --git a/sampler/src/sampler.rs b/sampler/src/sampler.rs index 24c4e53c..ee48c9c5 100644 --- a/sampler/src/sampler.rs +++ b/sampler/src/sampler.rs @@ -30,16 +30,16 @@ pub trait HasSampler { } /// The sampler device plays sounds in response to MIDI notes. #[derive(Debug)] pub struct Sampler { - pub jack: Arc>, + pub jack: JackClient, pub name: String, pub mapped: [Option>>;128], pub recording: Option<(usize, Arc>)>, pub unmapped: Vec>>, pub voices: Arc>>, - pub midi_in: Option>, - pub audio_ins: Vec>, + pub midi_in: Option, + pub audio_ins: Vec, pub input_meter: Vec, - pub audio_outs: Vec>, + pub audio_outs: Vec, pub buffer: Vec>, pub output_gain: f32 } @@ -89,7 +89,7 @@ impl Default for Sampler { } impl Sampler { pub fn new ( - jack: &Arc>, + jack: &JackClient, name: impl AsRef, midi_from: &[PortConnection], audio_from: &[&[PortConnection];2], @@ -97,14 +97,14 @@ impl Sampler { ) -> Usually { let name = name.as_ref(); Ok(Self { - midi_in: Some(JackPort::::new(jack, format!("M/{name}"), midi_from)?), + midi_in: Some(JackMidiIn::new(jack, format!("M/{name}"), midi_from)?), audio_ins: vec![ - JackPort::::new(jack, &format!("L/{name}"), audio_from[0])?, - JackPort::::new(jack, &format!("R/{name}"), audio_from[1])?, + JackAudioIn::new(jack, &format!("L/{name}"), audio_from[0])?, + JackAudioIn::new(jack, &format!("R/{name}"), audio_from[1])?, ], audio_outs: vec![ - JackPort::::new(jack, &format!("{name}/L"), audio_to[0])?, - JackPort::::new(jack, &format!("{name}/R"), audio_to[1])?, + JackAudioOut::new(jack, &format!("{name}/L"), audio_to[0])?, + JackAudioOut::new(jack, &format!("{name}/R"), audio_to[1])?, ], ..Default::default() }) @@ -349,7 +349,7 @@ impl Sampler { } /////////////////////////////////////////////////////////////////////////////////////////////////// type MidiSample = (Option, Arc>); -//from_atom!("sampler" => |jack: &Arc>, args| -> crate::Sampler { +//from_atom!("sampler" => |jack: &JackClient, args| -> crate::Sampler { //let mut name = String::new(); //let mut dir = String::new(); //let mut samples = BTreeMap::new(); @@ -377,7 +377,7 @@ type MidiSample = (Option, Arc>); //}); //Self::new(jack, &name) //}); -//from_atom!("sample" => |(_jack, dir): (&Arc>, &str), args| -> MidiSample { +//from_atom!("sample" => |(_jack, dir): (&JackClient, &str), args| -> MidiSample { //let mut name = String::new(); //let mut file = String::new(); //let mut midi = None; diff --git a/tek/src/cli.rs b/tek/src/cli.rs index 8c5b847d..402107a9 100644 --- a/tek/src/cli.rs +++ b/tek/src/cli.rs @@ -61,7 +61,7 @@ impl TekCli { pub fn run (&self) -> Usually<()> { let name = self.name.as_ref().map_or("tek", |x|x.as_str()); //let color = ItemPalette::random(); - let jack = JackConnection::new(name)?; + let jack = JackClient::new(name)?; let engine = Tui::new()?; let empty = &[] as &[&str]; let midi_froms = PortConnection::collect(&self.midi_from, &self.midi_from_re, empty); @@ -94,7 +94,7 @@ impl TekCli { } impl Tek { pub fn new_clock ( - jack: &Arc>, + jack: &JackClient, bpm: Option, sync_lead: bool, sync_follow: bool, midi_froms: &[PortConnection], midi_tos: &[PortConnection], ) -> Usually { @@ -103,8 +103,8 @@ impl Tek { jack: jack.clone(), color: ItemPalette::random(), clock: Clock::new(jack, bpm), - midi_ins: vec![JackPort::::new(jack, "GlobalI", midi_froms)?], - midi_outs: vec![JackPort::::new(jack, "GlobalO", midi_tos)?], + midi_ins: vec![JackMidiIn::new(jack, "GlobalI", midi_froms)?], + midi_outs: vec![JackMidiOut::new(jack, "GlobalO", midi_tos)?], keys: SourceIter(KEYS_APP), keys_clip: SourceIter(KEYS_CLIP), keys_track: SourceIter(KEYS_TRACK), @@ -112,12 +112,17 @@ impl Tek { keys_mix: SourceIter(KEYS_MIX), ..Default::default() }; - tek.sync_lead(sync_lead); - tek.sync_follow(sync_follow); + jack.sync_lead(sync_lead, |mut state|{ + let clock = tek.clock(); + clock.playhead.update_from_sample(state.position.frame() as f64); + state.position.bbt = Some(clock.bbt()); + state.position + }); + jack.sync_follow(sync_follow); Ok(tek) } pub fn new_sequencer ( - jack: &Arc>, + jack: &JackClient, bpm: Option, sync_lead: bool, sync_follow: bool, midi_froms: &[PortConnection], midi_tos: &[PortConnection], ) -> Usually { @@ -134,7 +139,7 @@ impl Tek { }) } pub fn new_groovebox ( - jack: &Arc>, + jack: &JackClient, bpm: Option, sync_lead: bool, sync_follow: bool, midi_froms: &[PortConnection], midi_tos: &[PortConnection], audio_froms: &[&[PortConnection];2], audio_tos: &[&[PortConnection];2], @@ -150,7 +155,7 @@ impl Tek { Ok(app) } pub fn new_arranger ( - jack: &Arc>, + jack: &JackClient, bpm: Option, sync_lead: bool, sync_follow: bool, midi_froms: &[PortConnection], midi_tos: &[PortConnection], audio_froms: &[&[PortConnection];2], audio_tos: &[&[PortConnection];2], diff --git a/tek/src/model.rs b/tek/src/model.rs index ed085597..750869fe 100644 --- a/tek/src/model.rs +++ b/tek/src/model.rs @@ -1,7 +1,7 @@ use crate::*; #[derive(Default, Debug)] pub struct Tek { /// Must not be dropped for the duration of the process - pub jack: Arc>, + pub jack: JackClient, /// Source of time pub clock: Clock, /// Theme @@ -11,10 +11,10 @@ use crate::*; pub player: Option, pub sampler: Option, pub midi_buf: Vec>>, - pub midi_ins: Vec>, - pub midi_outs: Vec>, - pub audio_ins: Vec>, - pub audio_outs: Vec>, + pub midi_ins: Vec, + pub midi_outs: Vec, + pub audio_ins: Vec, + pub audio_outs: Vec, pub note_buf: Vec, pub tracks: Vec, pub scenes: Vec, @@ -118,10 +118,10 @@ impl Tek { name, ..Default::default() }; - track.player.midi_ins.push(JackPort::::new( + track.player.midi_ins.push(JackMidiIn::new( &self.jack, &format!("{}I", &track.name), midi_from )?); - track.player.midi_outs.push(JackPort::::new( + track.player.midi_outs.push(JackMidiOut::new( &self.jack, &format!("{}O", &track.name), midi_to )?); self.tracks_mut().push(track); @@ -134,21 +134,6 @@ impl Tek { } Ok((index, &mut self.tracks_mut()[index])) } - pub fn sync_lead (&self, enable: bool) -> Usually<()> { - if enable { - self.jack.read().unwrap().client().register_timebase_callback(false, |mut state|{ - let clock = self.clock(); - clock.playhead.update_from_sample(state.position.frame() as f64); - state.position.bbt = Some(clock.bbt()); - state.position - })?; - } - Ok(()) - } - pub fn sync_follow (&self, enable: bool) -> Usually<()> { - // TODO: sync follow - Ok(()) - } fn clip (&self) -> Option>> { self.scene()?.clips.get(self.selected().track()?)?.clone() } @@ -254,9 +239,9 @@ pub trait HasSelection { /// Device chain pub devices: Vec>, /// Inputs of 1st device - pub audio_ins: Vec>, + pub audio_ins: Vec, /// Outputs of last device - pub audio_outs: Vec>, + pub audio_outs: Vec, } has_clock!(|self: Track|self.player.clock); has_player!(|self: Track|self.player); @@ -266,14 +251,14 @@ impl Track { fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } } } impl HasTracks for Tek { - fn midi_ins (&self) -> &Vec> { &self.midi_ins } - fn midi_outs (&self) -> &Vec> { &self.midi_outs } + fn midi_ins (&self) -> &Vec { &self.midi_ins } + fn midi_outs (&self) -> &Vec { &self.midi_outs } fn tracks (&self) -> &Vec { &self.tracks } fn tracks_mut (&mut self) -> &mut Vec { &mut self.tracks } } pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync { - fn midi_ins (&self) -> &Vec>; - fn midi_outs (&self) -> &Vec>; + fn midi_ins (&self) -> &Vec; + fn midi_outs (&self) -> &Vec; fn tracks (&self) -> &Vec; fn tracks_mut (&mut self) -> &mut Vec; fn track_longest (&self) -> usize { diff --git a/tek/src/view.rs b/tek/src/view.rs index 57b712eb..ac58428d 100644 --- a/tek/src/view.rs +++ b/tek/src/view.rs @@ -30,6 +30,8 @@ impl ViewMemo { scns: ViewMemo, String>, trks: ViewMemo, String>, stop: Arc, + edit: Arc, + last_color: Arc> } impl Default for ViewCache { fn default () -> Self { @@ -43,6 +45,8 @@ impl Default for ViewCache { scns: ViewMemo::new(None, String::with_capacity(16)), trks: ViewMemo::new(None, String::with_capacity(16)), stop: "⏹".into(), + edit: "edit".into(), + last_color: Arc::new(RwLock::new(ItemPalette::G[0])) } } } @@ -65,17 +69,16 @@ provide_num!(u16: |self: Tek| { ":sample-h" => if self.is_editing() { 0 } else { 5 }, ":samples-w" => if self.is_editing() { 4 } else { 11 }, ":samples-y" => if self.is_editing() { 1 } else { 0 }, - ":pool-w" => if self.is_editing() { 5 } else { - let w = self.size.w(); - if w > 60 { 20 } else if w > 40 { 15 } else { 10 } } }); +}); macro_rules! per_track { - (|$self:ident,$track:ident,$index:ident|$content:expr) => {{ + ($area:expr;|$self:ident,$track:ident,$index:ident|$content:expr) => {{ let tracks = ||$self.tracks_sizes($self.is_editing(), $self.editor_w()); Box::new(move||Align::x(Map::new(tracks, move|(_, $track, x1, x2), $index| { let width = (x2 - x1) as u16; let content = Fixed::y(1, $content); let styled = Tui::fg_bg($track.color.lightest.rgb, $track.color.base.rgb, content); - map_east(x1 as u16, width, Fixed::x(width, styled)) }))).into() }} } + Either(x2 >= $area, (), + map_east(x1 as u16, width, Fixed::x(width, styled))) }))).into() }} } macro_rules! io_header { ($self:ident, $key:expr, $label:expr, $count:expr, $content:expr) => { (move||{ @@ -204,13 +207,13 @@ impl Tek { " midi ins", self.midi_ins.len(), self.midi_ins().get(0).map( - move|input: &JackPort|Bsp::s( + move|input: &JackMidiIn|Bsp::s( Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(input.name.clone())))), - input.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, + input.conn().get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, connect.info())))))))); let rec = false; let mon = false; - let cells: ThunkBox<_> = per_track!(|self, track, _t|Bsp::s(Tui::bold(true, row!( + let cells: ThunkBox<_> = per_track!(self.size.w();|self, track, _t|Bsp::s(Tui::bold(true, row!( Tui::fg_bg(if rec { White } else { track.color.light.rgb }, track.color.dark.rgb, "Rcrd"), Tui::fg_bg(if rec { White } else { track.color.dark.rgb }, track.color.dark.rgb, "▐"), Tui::fg_bg(if mon { White } else { track.color.light.rgb }, track.color.dark.rgb, "Mntr"), @@ -226,13 +229,13 @@ impl Tek { let bg = Tui::g(64); let h = 1 + self.midi_outs.len() as u16; let header: ThunkBox<_> = io_header!(self, " O ", " midi outs", self.midi_outs.len(), self.midi_outs().get(0).map( - move|output: &JackPort|Bsp::s( + move|output: &JackMidiOut|Bsp::s( Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(output.name.clone())))), - output.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, + output.conn().get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, connect.info())))))))); let mute = false; let solo = false; - let cells: ThunkBox<_> = per_track!(|self, track, _t|Bsp::s(Tui::bold(true, row!( + let cells: ThunkBox<_> = per_track!(self.size.w();|self, track, _t|Bsp::s(Tui::bold(true, row!( Tui::fg_bg(if mute { White } else { track.color.light.rgb }, track.color.dark.rgb, "Mute"), Tui::fg_bg(if mute { White } else { track.color.dark.rgb }, track.color.dark.rgb, "▐"), Tui::fg_bg(if solo { White } else { track.color.light.rgb }, track.color.dark.rgb, "Solo"), @@ -247,7 +250,7 @@ impl Tek { let h = 1; let header: ThunkBox<_> = (move||Tui::bg(Tui::g(32), Fill::x(Align::w(self.view_track_add()))).boxed()).into(); - let cells: ThunkBox<_> = per_track!(|self, track, t|{ + let cells: ThunkBox<_> = per_track!(self.size.w();|self, track, t|{ let active = self.selected().track() == Some(t+1); let name = &track.name; let fg = track.color.lightest.rgb; @@ -277,110 +280,101 @@ impl Tek { self.fmtd.read().unwrap().scns.view.clone())) } fn view_scenes (&self) -> impl Content + use<'_> { - let header: ThunkBox<_> = (move||{ + let bstyle = Style::default().fg(Tui::g(0)); + let size_w = self.size.w(); + let size_h = self.size.h(); + let editing = self.is_editing(); + let tracks = move||self.tracks_sizes(editing, self.editor_w()); + let scenes = move||self.scenes_sizes(editing, 2, 15); + let selected_track = self.selected().track(); + let selected_scene = self.selected().scene(); + let border = move|h, cell|Push::y(1, Fixed::y(h, Outer(false, bstyle).enclose(cell))); + let header = move||{ let last_color = Arc::new(RwLock::new(ItemPalette::G[0])); - let iter = ||self.scenes_sizes(self.is_editing(), 2, 15); - Map::new(iter, move|(_, scene, y1, y2), i| { - let cell = phat_sel_3( + let iter = ||self.scenes_sizes(self.is_editing(), 2, 15); + let cell = { + let last_color = last_color.clone(); + move|i, color: &ItemPalette, name: Arc|phat_sel_3( self.selected().scene() == Some(i), - Tui::bold(true, Bsp::e("🭬", &scene.name)), - Tui::bold(true, Bsp::e("🭬", &scene.name)), + Tui::bold(true, Bsp::e("🭬", name.clone())), + Tui::bold(true, Bsp::e("🭬", name)), if i == 0 { Some(Reset) } else if self.selected().scene() == Some(i) { None } else { Some(last_color.read().unwrap().base.rgb) }, - if self.selected().scene() == Some(i+1) { scene.color.light } else { scene.color.base }.rgb, + if self.selected().scene() == Some(i+1) { color.light } else { color.base }.rgb, Some(Reset) - ); - let h = (1 + y2 - y1) as u16; + ) + }; + Map::new(iter, move|(_, scene, y1, y2), s| { + let cell = cell(s, &scene.color, scene.name.clone()); + let height = (1 + y2 - y1) as u16; *last_color.write().unwrap() = scene.color; - map_south(y1 as u16, h, Push::y(1, Fixed::y(h, - Outer(false, Style::default().fg(Tui::g(0))).enclose(cell)))) + map_south(y1 as u16, height, border(height, cell)) }).boxed() - }).into(); - let editing = self.is_editing(); - let tracks = move||self.tracks_sizes(editing, self.editor_w()); - let scenes = move||self.scenes_sizes(editing, 2, 15); - let selected_track = self.selected().track(); - let selected_scene = self.selected().scene(); - let border = |x|Outer(false, Style::default().fg(Tui::g(0))).enclose(x); - let d = 6 + self.midi_ins.len() + self.midi_outs.len(); - let cells: ThunkBox<_> = (move||Align::c(Map::new(tracks, { - let last_color = Arc::new(RwLock::new(ItemPalette::default())); - let area = self.size.w().saturating_sub(self.w_sidebar() as usize * 2); - move|(_, track, x1, x2), t| { + }; + let border = move|x|Outer(false, bstyle).enclose_bg(x); + let content: ThunkBox<_> = per_track!(size_w; |self, track, t|{ + let last_color = self.fmtd.read().unwrap().last_color.clone(); + let same_track = selected_track == Some(t+1); + border(Map::new(scenes, move|(_, scene, y1, y2), s|{ let last_color = last_color.clone(); - let same_track = selected_track == Some(t+1); - let w = (x2 - x1) as u16; - map_east(x1 as u16, w, border(Map::new(scenes, move|(_, scene, y1, y2), s|{ + Thunk::new(move||{ let last_color = last_color.clone(); - Either(x2 >= area, (), Thunk::new(move||{ - let last_color = last_color.clone(); - let mut fg = Tui::g(64); - let mut bg = ItemPalette::G[32]; - if let Some(clip) = &scene.clips[t] { - let clip = clip.read().unwrap(); - fg = clip.color.lightest.rgb; - bg = clip.color - }; - - // weird offsetting: - let selected = same_track && selected_scene == Some(s+1); - let neighbor = same_track && selected_scene == Some(s); - let active = editing && selected; - - //let top = if neighbor { None } else { Some(last_color.read().unwrap().base.rgb) }; - let top = if s == 0 { - Some(Reset) - } else if neighbor { - Some(last_color.read().unwrap().light.rgb) - } else { - Some(last_color.read().unwrap().base.rgb) - }; - let mid = if selected { bg.light } else { bg.base }.rgb; - let low = Some(Reset); - let h = (1 + y2 - y1) as u16; - *last_color.write().unwrap() = bg; - let tab = " Tab "; - let name = if active { - self.editor.as_ref() - .map(|e|e.clip().as_ref().map(|c|c.clone())) - .flatten() - .map(|c|c.read().unwrap().name.clone()) - .unwrap_or_else(||"".into()) - } else { - "edit".into() - }; - let label = move||{ - let clip = scene.clips[t].clone(); - let icon = " ⏹ "; - let name = clip.map(|c|c.read().unwrap().name.clone()); - Align::nw(Tui::fg(fg, Bsp::e(icon, Bsp::e(Tui::bold(true, name), " ")))) - }; - let area = self.size.h().saturating_sub(d);//self.w_sidebar() as usize * 2); - Either(y2 > area, (), map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Either::new(active, - Thunk::new(move||Bsp::a( - Fill::xy(Align::nw(button(tab, label()))), - &self.editor)), - Thunk::new(move||Bsp::a( - When::new(selected, Fill::y(Align::n(button(tab, "edit")))), - phat_sel_3( - selected, - Fill::xy(label()), - Fill::xy(label()), - top, mid, low - ) - )), - ))))) - - })) - }))).boxed() - } - })).boxed()).into(); - Outer(false, Style::default().fg(Tui::g(0))).enclose_bg({ - let w = self.w(); - let d = 6 + self.midi_ins.len() + self.midi_outs.len(); - let h = self.size.h().saturating_sub(d) as u16; - self.view_row(w, h, header, cells)}) + let mut fg = Tui::g(64); + let mut bg = ItemPalette::G[32]; + if let Some(clip) = &scene.clips[t] { + let clip = clip.read().unwrap(); + fg = clip.color.lightest.rgb; + bg = clip.color + }; + // weird offsetting: + let selected = same_track && selected_scene == Some(s+1); + let neighbor = same_track && selected_scene == Some(s); + let active = editing && selected; + let top = if s == 0 { + Some(Reset) + } else if neighbor { + Some(last_color.read().unwrap().light.rgb) + } else { + Some(last_color.read().unwrap().base.rgb) + }; + let mid = if selected { bg.light } else { bg.base }.rgb; + let low = Some(Reset); + let h = (1 + y2 - y1) as u16; + *last_color.write().unwrap() = bg; + let tab = " Tab "; + let name = if active { + self.editor.as_ref() + .map(|e|e.clip().as_ref().map(|c|c.clone())) + .flatten() + .map(|c|c.read().unwrap().name.clone()) + .unwrap_or_else(||"".into()) + } else { + self.fmtd.read().unwrap().edit.clone() + }; + let label = move||{ + let clip = scene.clips[t].clone(); + let icon = " ⏹ "; + let name = clip.map(|c|c.read().unwrap().name.clone()); + Align::nw(Tui::fg(fg, Bsp::e(icon, Bsp::e(Tui::bold(true, name), " "))))}; + let editor = Thunk::new(move||Bsp::a( + Fill::xy(Align::nw(button(tab, label()))), + &self.editor)); + let cell = Thunk::new(move||Bsp::a( + When::new(selected, Fill::y(Align::n(button(tab, "edit")))), + phat_sel_3(selected, Fill::xy(label()), Fill::xy(label()), top, mid, low))); + let cell = Either::new(active, editor, cell); + Either(y2 > size_h, (), map_south(y1 as u16, h, Push::y(1, Fixed::y(h, cell)))) + }) + })) + }); + let border = move|x|Outer(false, bstyle).enclose_bg(x); + border(self.view_row( + size_w as u16, + size_h as u16, + <_ as Into>>::into(header), + <_ as Into>>::into(content), + )) } fn w (&self) -> u16 { self.tracks_sizes(self.is_editing(), self.editor_w()) @@ -390,8 +384,9 @@ impl Tek { } fn w_sidebar (&self) -> u16 { let w = self.size.w(); - let w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - let w = if self.is_editing() { 8 } else { w }; + let w = if w > 80 { 20 } else if w > 70 { 18 } else if w > 60 { 16 } + else if w > 50 { 14 } else if w > 40 { 12 } else { 10 }; + let w = if self.is_editing() { w / 2 } else { w }; w } fn button <'a> ( diff --git a/tek/src/view_arranger.edn b/tek/src/view_arranger.edn index 109ddf7f..88300786 100644 --- a/tek/src/view_arranger.edn +++ b/tek/src/view_arranger.edn @@ -1,5 +1,5 @@ (bsp/a (fill/xy (align/n (max/y 1 :toolbar))) - (fill/x (align/c (bsp/a (fill/xy (align/e (fixed/x :pool-w :pool))) + (fill/x (align/c (bsp/a (fill/xy (align/e (fixed/x :sidebar-w :pool))) (bsp/a (fill/xy (align/s (bsp/s :scene-add (bsp/s :tracks (bsp/n :inputs :outputs))))) (fill/xy :scenes)))))) diff --git a/time/src/clock.rs b/time/src/clock.rs index eedded27..a669590e 100644 --- a/time/src/clock.rs +++ b/time/src/clock.rs @@ -81,11 +81,10 @@ pub struct Clock { /// Size of buffer in samples pub chunk: Arc, } -impl From<&Arc>> for Clock { - fn from (jack: &Arc>) -> Self { - let jack = jack.read().unwrap(); - let chunk = jack.client().buffer_size(); - let transport = jack.client().transport(); +impl From<&JackClient> for Clock { + fn from (jack: &JackClient) -> Self { + let chunk = jack.inner().buffer_size(); + let transport = jack.inner().transport(); let timebase = Arc::new(Timebase::default()); Self { quant: Arc::new(24.into()), @@ -114,7 +113,7 @@ impl std::fmt::Debug for Clock { } } impl Clock { - pub fn new (jack: &Arc>, bpm: Option) -> Self { + pub fn new (jack: &JackClient, bpm: Option) -> Self { let clock = Self::from(jack); if let Some(bpm) = bpm { clock.timebase.bpm.set(bpm);