diff --git a/Cargo.lock b/Cargo.lock index d347c250..696dee05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2697,6 +2697,14 @@ dependencies = [ "toml", ] +[[package]] +name = "tek_snd" +version = "0.1.0" +dependencies = [ + "tek_api", + "tek_core", +] + [[package]] name = "tek_tui" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a4b7de76..476c1519 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "crates/tek_core", "crates/tek_api", - "crates/tek_cli", - "crates/tek_tui" + "crates/tek_snd", + "crates/tek_tui", + "crates/tek_cli" ] diff --git a/crates/tek_api/src/api_jack.rs b/crates/tek_api/src/api_jack.rs deleted file mode 100644 index 82b859b5..00000000 --- a/crates/tek_api/src/api_jack.rs +++ /dev/null @@ -1,519 +0,0 @@ -use crate::*; -use tek_core::jack::{*, Transport as JackTransport}; - -/// Trait for things that have a JACK process callback. -pub trait Audio: Send + Sync { - fn process(&mut self, _: &Client, _: &ProcessScope) -> Control { - Control::Continue - } - fn callback( - state: &Arc>, client: &Client, scope: &ProcessScope - ) -> Control where Self: Sized { - if let Ok(mut state) = state.write() { - state.process(client, scope) - } else { - Control::Quit - } - } -} - -/// A UI component that may be associated with a JACK client by the `Jack` factory. -pub trait AudioComponent: Component + Audio { - /// Perform type erasure for collecting heterogeneous devices. - fn boxed(self) -> Box> - where - Self: Sized + 'static, - { - Box::new(self) - } -} - -/// All things that implement the required traits can be treated as `AudioComponent`. -impl + Audio> AudioComponent for W {} - -/// Trait for things that wrap a JACK client. -pub trait AudioEngine { - fn activate ( - self, - process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static - ) -> Usually>> where Self: Send + Sync + 'static; - fn client (&self) -> &Client; - fn transport (&self) -> JackTransport { - self.client().transport() - } - fn port_by_name (&self, name: &str) -> Option> { - self.client().port_by_name(name) - } - fn register_port (&self, name: &str, spec: PS) -> Usually> { - Ok(self.client().register_port(name, spec)?) - } - fn thread_init (&self, _: &Client) {} - unsafe fn shutdown (&mut self, status: ClientStatus, reason: &str) {} - fn freewheel (&mut self, _: &Client, enabled: bool) {} - fn client_registration (&mut self, _: &Client, name: &str, reg: bool) {} - fn port_registration (&mut self, _: &Client, id: PortId, reg: bool) {} - fn ports_connected (&mut self, _: &Client, a: PortId, b: PortId, are: bool) {} - fn sample_rate (&mut self, _: &Client, frames: Frames) -> Control { - Control::Continue - } - fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { - Control::Continue - } - fn graph_reorder (&mut self, _: &Client) -> Control { - Control::Continue - } - fn xrun (&mut self, _: &Client) -> Control { - Control::Continue - } -} - -/// Wraps [Client] or [DynamicAsyncClient] in place. -#[derive(Debug)] -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>)->Usually - ) - -> 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 { - JackClient::Inactive(client) => client, - JackClient::Activating => panic!("jack client still activating"), - JackClient::Active(_) => panic!("jack client already activated"), - } - } -} - -impl AudioEngine for JackClient { - fn client(&self) -> &Client { - match self { - Self::Inactive(ref client) => client, - Self::Activating => panic!("jack client has not finished activation"), - Self::Active(ref client) => client.as_client(), - } - } - fn activate( - self, - mut 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 = contrib::ClosureProcessHandler::new(frame as BoxedAudioHandler); - *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); - Ok(state) - } -} - -#[derive(Debug)] -/// Event enum for JACK events. -pub enum JackEvent { - ThreadInit, - Shutdown(ClientStatus, String), - Freewheel(bool), - SampleRate(Frames), - ClientRegistration(String, bool), - PortRegistration(PortId, bool), - PortRename(PortId, String, String), - PortsConnected(PortId, PortId, bool), - GraphReorder, - XRun, -} - -/// Trait for things that may expose JACK ports. -pub trait Ports { - fn audio_ins(&self) -> Usually>> { - Ok(vec![]) - } - fn audio_outs(&self) -> Usually>> { - Ok(vec![]) - } - fn midi_ins(&self) -> Usually>> { - Ok(vec![]) - } - fn midi_outs(&self) -> Usually>> { - Ok(vec![]) - } -} - -fn register_ports( - client: &Client, - names: Vec, - spec: T, -) -> Usually>> { - names - .into_iter() - .try_fold(BTreeMap::new(), |mut ports, name| { - let port = client.register_port(&name, spec)?; - ports.insert(name, port); - Ok(ports) - }) -} - -fn query_ports(client: &Client, names: Vec) -> BTreeMap> { - names.into_iter().fold(BTreeMap::new(), |mut ports, name| { - let port = client.port_by_name(&name).unwrap(); - ports.insert(name, port); - ports - }) -} - -/// Notification handler used by the [Jack] factory -/// when constructing [JackDevice]s. -pub type DynamicNotifications = Notifications>; - -/// Generic notification handler that emits [JackEvent] -pub struct Notifications(pub T); - -impl NotificationHandler for Notifications { - 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 - } -} - -///// A [AudioComponent] bound to a JACK client and a set of ports. -//pub struct JackDevice { - ///// The active JACK client of this device. - //pub client: DynamicAsyncClient, - ///// The device state, encapsulated for sharing between threads. - //pub state: Arc>>>, - ///// Unowned copies of the device's JACK ports, for connecting to the device. - ///// The "real" readable/writable `Port`s are owned by the `state`. - //pub ports: UnownedJackPorts, -//} - -//impl std::fmt::Debug for JackDevice { - //fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - //f.debug_struct("JackDevice") - //.field("ports", &self.ports) - //.finish() - //} -//} - -//impl Widget for JackDevice { - //type Engine = E; - //fn layout(&self, to: E::Size) -> Perhaps { - //self.state.read().unwrap().layout(to) - //} - //fn render(&self, to: &mut E::Output) -> Usually<()> { - //self.state.read().unwrap().render(to) - //} -//} - -//impl Handle for JackDevice { - //fn handle(&mut self, from: &E::Input) -> Perhaps { - //self.state.write().unwrap().handle(from) - //} -//} - -//impl Ports for JackDevice { - //fn audio_ins(&self) -> Usually>> { - //Ok(self.ports.audio_ins.values().collect()) - //} - //fn audio_outs(&self) -> Usually>> { - //Ok(self.ports.audio_outs.values().collect()) - //} - //fn midi_ins(&self) -> Usually>> { - //Ok(self.ports.midi_ins.values().collect()) - //} - //fn midi_outs(&self) -> Usually>> { - //Ok(self.ports.midi_outs.values().collect()) - //} -//} - -//impl JackDevice { - ///// Returns a locked mutex of the state's contents. - //pub fn state(&self) -> LockResult>>> { - //self.state.read() - //} - ///// Returns a locked mutex of the state's contents. - //pub fn state_mut(&self) -> LockResult>>> { - //self.state.write() - //} - //pub fn connect_midi_in(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(port, self.midi_ins()?[index])?) - //} - //pub fn connect_midi_out(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(self.midi_outs()?[index], port)?) - //} - //pub fn connect_audio_in(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(port, self.audio_ins()?[index])?) - //} - //pub fn connect_audio_out(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(self.audio_outs()?[index], port)?) - //} -//} - -///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut]. -//#[derive(Default, Debug)] -//pub struct JackPorts { - //pub audio_ins: BTreeMap>, - //pub midi_ins: BTreeMap>, - //pub audio_outs: BTreeMap>, - //pub midi_outs: BTreeMap>, -//} - -///// Collection of JACK ports as [Unowned]. -//#[derive(Default, Debug)] -//pub struct UnownedJackPorts { - //pub audio_ins: BTreeMap>, - //pub midi_ins: BTreeMap>, - //pub audio_outs: BTreeMap>, - //pub midi_outs: BTreeMap>, -//} - -//impl JackPorts { - //pub fn clone_unowned(&self) -> UnownedJackPorts { - //let mut unowned = UnownedJackPorts::default(); - //for (name, port) in self.midi_ins.iter() { - //unowned.midi_ins.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.midi_outs.iter() { - //unowned.midi_outs.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.audio_ins.iter() { - //unowned.audio_ins.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.audio_outs.iter() { - //unowned - //.audio_outs - //.insert(name.clone(), port.clone_unowned()); - //} - //unowned - //} -//} - -///// Implement the `Ports` trait. -//#[macro_export] -//macro_rules! ports { - //($T:ty $({ $(audio: { - //$(ins: |$ai_arg:ident|$ai_impl:expr,)? - //$(outs: |$ao_arg:ident|$ao_impl:expr,)? - //})? $(midi: { - //$(ins: |$mi_arg:ident|$mi_impl:expr,)? - //$(outs: |$mo_arg:ident|$mo_impl:expr,)? - //})?})?) => { - //impl Ports for $T {$( - //$( - //$(fn audio_ins <'a> (&'a self) -> Usually>> { - //let cb = |$ai_arg:&'a Self|$ai_impl; - //cb(self) - //})? - //)? - //$( - //$(fn audio_outs <'a> (&'a self) -> Usually>> { - //let cb = (|$ao_arg:&'a Self|$ao_impl); - //cb(self) - //})? - //)? - //)? $( - //$( - //$(fn midi_ins <'a> (&'a self) -> Usually>> { - //let cb = (|$mi_arg:&'a Self|$mi_impl); - //cb(self) - //})? - //)? - //$( - //$(fn midi_outs <'a> (&'a self) -> Usually>> { - //let cb = (|$mo_arg:&'a Self|$mo_impl); - //cb(self) - //})? - //)? - //)?} - //}; -//} - -///// `JackDevice` factory. Creates JACK `Client`s, performs port registration -///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. -//pub struct Jack { - //pub client: Client, - //pub midi_ins: Vec, - //pub audio_ins: Vec, - //pub midi_outs: Vec, - //pub audio_outs: Vec, -//} - -//impl Jack { - //pub fn new(name: &str) -> Usually { - //Ok(Self { - //midi_ins: vec![], - //audio_ins: vec![], - //midi_outs: vec![], - //audio_outs: vec![], - //client: Client::new(name, ClientOptions::NO_START_SERVER)?.0, - //}) - //} - //pub fn run<'a: 'static, D, E>( - //self, - //state: impl FnOnce(JackPorts) -> Box, - //) -> Usually> - //where - //D: AudioComponent + Sized + 'static, - //E: Engine + 'static, - //{ - //let owned_ports = JackPorts { - //audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?, - //audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?, - //midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?, - //midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?, - //}; - //let midi_outs = owned_ports - //.midi_outs - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let midi_ins = owned_ports - //.midi_ins - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let audio_outs = owned_ports - //.audio_outs - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let audio_ins = owned_ports - //.audio_ins - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let state = Arc::new(RwLock::new(state(owned_ports) as Box>)); - //let client = self.client.activate_async( - //Notifications(Box::new({ - //let _state = state.clone(); - //move |_event| { - //// FIXME: this deadlocks - ////state.lock().unwrap().handle(&event).unwrap(); - //} - //}) as Box), - //contrib::ClosureProcessHandler::new(Box::new({ - //let state = state.clone(); - //move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s) - //}) as BoxedAudioHandler), - //)?; - //Ok(JackDevice { - //ports: UnownedJackPorts { - //audio_ins: query_ports(&client.as_client(), audio_ins), - //audio_outs: query_ports(&client.as_client(), audio_outs), - //midi_ins: query_ports(&client.as_client(), midi_ins), - //midi_outs: query_ports(&client.as_client(), midi_outs), - //}, - //client, - //state, - //}) - //} - //pub fn audio_in(mut self, name: &str) -> Self { - //self.audio_ins.push(name.to_string()); - //self - //} - //pub fn audio_out(mut self, name: &str) -> Self { - //self.audio_outs.push(name.to_string()); - //self - //} - //pub fn midi_in(mut self, name: &str) -> Self { - //self.midi_ins.push(name.to_string()); - //self - //} - //pub fn midi_out(mut self, name: &str) -> Self { - //self.midi_outs.push(name.to_string()); - //self - //} -//} diff --git a/crates/tek_api/src/arrange.rs b/crates/tek_api/src/arrange.rs index 6179c5c0..4a338a17 100644 --- a/crates/tek_api/src/arrange.rs +++ b/crates/tek_api/src/arrange.rs @@ -38,23 +38,6 @@ pub struct ArrangementScene { pub color: ItemColor, } -impl Audio for Arrangement { - #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - for track in self.tracks.iter_mut() { - if track.process(client, scope) == Control::Quit { - return Control::Quit - } - } - Control::Continue - } -} - -impl Audio for ArrangementTrack { - #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - self.player.process(client, scope) - } -} - impl Arrangement { pub fn is_stopped (&self) -> bool { *self.clock.playing.read().unwrap() == Some(TransportState::Stopped) diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index 14ab00db..0db69c44 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -9,7 +9,7 @@ pub(crate) use tek_core::jack::{ }; submod! { - api_jack + //api_jack arrange arrange_cmd diff --git a/crates/tek_api/src/mixer.rs b/crates/tek_api/src/mixer.rs index 7f05914e..4d8cc6bc 100644 --- a/crates/tek_api/src/mixer.rs +++ b/crates/tek_api/src/mixer.rs @@ -9,9 +9,3 @@ pub struct Mixer { pub selected_track: usize, pub selected_column: usize, } - -impl Audio for Mixer { - fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { - Control::Continue - } -} diff --git a/crates/tek_api/src/mixer_track.rs b/crates/tek_api/src/mixer_track.rs index 84c27945..513a6934 100644 --- a/crates/tek_api/src/mixer_track.rs +++ b/crates/tek_api/src/mixer_track.rs @@ -72,7 +72,7 @@ impl MixerTrack { } } -pub trait MixerTrackDevice: Audio + Debug { +pub trait MixerTrackDevice: Debug + Send + Sync { fn boxed (self) -> Box where Self: Sized + 'static { Box::new(self) } diff --git a/crates/tek_api/src/plugin.rs b/crates/tek_api/src/plugin.rs index 01846d91..22334c85 100644 --- a/crates/tek_api/src/plugin.rs +++ b/crates/tek_api/src/plugin.rs @@ -53,57 +53,3 @@ impl Plugin { //Ok(jack) //} } - -impl Audio for Plugin { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - match self.plugin.as_mut() { - Some(PluginKind::LV2(LV2Plugin { - features, - ref mut instance, - ref mut input_buffer, - .. - })) => { - let urid = features.midi_urid(); - input_buffer.clear(); - for port in self.midi_ins.iter() { - let mut atom = ::livi::event::LV2AtomSequence::new( - &features, - scope.n_frames() as usize - ); - for event in port.iter(scope) { - match event.bytes.len() { - 3 => atom.push_midi_event::<3>( - event.time as i64, - urid, - &event.bytes[0..3] - ).unwrap(), - _ => {} - } - } - input_buffer.push(atom); - } - let mut outputs = vec![]; - for _ in self.midi_outs.iter() { - outputs.push(::livi::event::LV2AtomSequence::new( - &features, - scope.n_frames() as usize - )); - } - let ports = ::livi::EmptyPortConnections::new() - .with_atom_sequence_inputs(input_buffer.iter()) - .with_atom_sequence_outputs(outputs.iter_mut()) - .with_audio_inputs( - self.audio_ins.iter().map(|o|o.as_slice(scope)) - ) - .with_audio_outputs( - self.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope)) - ); - unsafe { - instance.run(scope.n_frames() as usize, ports).unwrap() - }; - }, - _ => {} - } - Control::Continue - } -} diff --git a/crates/tek_api/src/plugin_lv2.rs b/crates/tek_api/src/plugin_lv2.rs index a8b79776..b47c159b 100644 --- a/crates/tek_api/src/plugin_lv2.rs +++ b/crates/tek_api/src/plugin_lv2.rs @@ -59,9 +59,3 @@ impl LV2Plugin { Plugin::new_lv2(jack, &name, &path) } } - -impl Audio for LV2Plugin { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - Control::Continue - } -} diff --git a/crates/tek_api/src/sampler.rs b/crates/tek_api/src/sampler.rs index d44470a0..11358035 100644 --- a/crates/tek_api/src/sampler.rs +++ b/crates/tek_api/src/sampler.rs @@ -14,16 +14,6 @@ pub struct Sampler { pub output_gain: f32 } -impl Audio for Sampler { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - self.process_midi_in(scope); - self.clear_output_buffer(); - self.process_audio_out(scope); - self.write_output_buffer(scope); - Control::Continue - } -} - impl Sampler { pub fn from_edn <'e> (jack: &Arc>, args: &[Edn<'e>]) -> Usually { let mut name = String::new(); @@ -63,52 +53,4 @@ impl Sampler { output_gain: 0. }) } - /// Create [Voice]s from [Sample]s in response to MIDI input. - pub fn process_midi_in (&mut self, scope: &ProcessScope) { - for RawMidi { time, bytes } in self.midi_in.iter(scope) { - if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { - if let MidiMessage::NoteOn { ref key, ref vel } = message { - if let Some(sample) = self.mapped.get(key) { - self.voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); - } - } - } - } - } - /// Zero the output buffer. - pub fn clear_output_buffer (&mut self) { - for buffer in self.buffer.iter_mut() { - buffer.fill(0.0); - } - } - /// Mix all currently playing samples into the output. - pub fn process_audio_out (&mut self, scope: &ProcessScope) { - let channel_count = self.buffer.len(); - self.voices.write().unwrap().retain_mut(|voice|{ - for index in 0..scope.n_frames() as usize { - if let Some(frame) = voice.next() { - for (channel, sample) in frame.iter().enumerate() { - // Averaging mixer: - //self.buffer[channel % channel_count][index] = ( - //(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0 - //); - self.buffer[channel % channel_count][index] += - sample * self.output_gain; - } - } else { - return false - } - } - return true - }); - } - /// Write output buffer to output ports. - pub fn write_output_buffer (&mut self, scope: &ProcessScope) { - for (i, port) in self.audio_outs.iter_mut().enumerate() { - let buffer = &self.buffer[i]; - for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { - *value = *buffer.get(i).unwrap_or(&0.0); - } - } - } } diff --git a/crates/tek_api/src/sequencer.rs b/crates/tek_api/src/sequencer.rs index e74371e8..3d87fd5c 100644 --- a/crates/tek_api/src/sequencer.rs +++ b/crates/tek_api/src/sequencer.rs @@ -33,32 +33,6 @@ pub struct MIDIPlayer { pub notes_out: Arc>, } -/// JACK process callback for a sequencer's phrase player/recorder. -impl Audio for MIDIPlayer { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let has_midi_outputs = self.has_midi_outputs(); - let has_midi_inputs = self.has_midi_inputs(); - // Clear output buffer(s) - self.clear(scope, false); - // Write chunk of phrase to output, handle switchover - if self.play(scope) { - self.switchover(scope); - } - if has_midi_inputs { - if self.recording || self.monitoring { - // Record and/or monitor input - self.record(scope) - } else if has_midi_outputs && self.monitoring { - // Monitor input to output - self.monitor(scope) - } - } - // Write to output port(s) - self.write(scope); - Control::Continue - } -} - /// Methods used primarily by the process callback impl MIDIPlayer { pub fn new ( diff --git a/crates/tek_api/src/transport.rs b/crates/tek_api/src/transport.rs index 080c3ab4..17daa765 100644 --- a/crates/tek_api/src/transport.rs +++ b/crates/tek_api/src/transport.rs @@ -21,38 +21,6 @@ impl Debug for Transport { } } -impl Audio for Transport { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - 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.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(); - if *playing != Some(transport.state) { - match transport.state { - TransportState::Rolling => { - *started = Some((current_frames as usize, current_usecs as usize)) - }, - TransportState::Stopped => { - *started = None - }, - _ => {} - } - }; - *playing = Some(transport.state); - if *playing == Some(TransportState::Stopped) { - *started = None; - } - self.clock.current.update_from_usec(match *started { - Some((_, usecs)) => current_usecs as f64 - usecs as f64, - None => 0. - }); - Control::Continue - } -} - impl Transport { pub fn toggle_play (&mut self) -> Usually<()> { let playing = self.clock.playing.read().unwrap().expect("1st sample has not been processed yet"); diff --git a/crates/tek_core/src/audio.rs b/crates/tek_core/src/audio.rs index 073926af..fac1f602 100644 --- a/crates/tek_core/src/audio.rs +++ b/crates/tek_core/src/audio.rs @@ -1,2 +1,534 @@ use crate::*; use jack::*; + +/// Trait for things that have a JACK process callback. +pub trait Audio: Send + Sync { + fn process(&mut self, _: &Client, _: &ProcessScope) -> Control { + Control::Continue + } + fn callback( + state: &Arc>, client: &Client, scope: &ProcessScope + ) -> Control where Self: Sized { + if let Ok(mut state) = state.write() { + state.process(client, scope) + } else { + Control::Quit + } + } +} + +/// A UI component that may be associated with a JACK client by the `Jack` factory. +pub trait AudioComponent: Component + Audio { + /// Perform type erasure for collecting heterogeneous devices. + fn boxed(self) -> Box> + where + Self: Sized + 'static, + { + Box::new(self) + } +} + +/// All things that implement the required traits can be treated as `AudioComponent`. +impl + Audio> AudioComponent for W {} + +/// Trait for things that wrap a JACK client. +pub trait AudioEngine { + + fn transport (&self) -> Transport { + self.client().transport() + } + + fn port_by_name (&self, name: &str) -> Option> { + self.client().port_by_name(name) + } + + fn register_port (&self, name: &str, spec: PS) -> Usually> { + Ok(self.client().register_port(name, spec)?) + } + + fn client (&self) -> &Client; + + fn activate ( + self, + process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static + ) -> Usually>> where Self: Send + Sync + 'static; + + fn thread_init (&self, _: &Client) {} + + unsafe fn shutdown (&mut self, status: ClientStatus, reason: &str) {} + + fn freewheel (&mut self, _: &Client, enabled: bool) {} + + fn client_registration (&mut self, _: &Client, name: &str, reg: bool) {} + + fn port_registration (&mut self, _: &Client, id: PortId, reg: bool) {} + + fn ports_connected (&mut self, _: &Client, a: PortId, b: PortId, are: bool) {} + + fn sample_rate (&mut self, _: &Client, frames: Frames) -> Control { + Control::Continue + } + + fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { + Control::Continue + } + + fn graph_reorder (&mut self, _: &Client) -> Control { + Control::Continue + } + + fn xrun (&mut self, _: &Client) -> Control { + Control::Continue + } +} + +/// Wraps [Client] or [DynamicAsyncClient] in place. +#[derive(Debug)] +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>)->Usually + ) + -> 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 { + JackClient::Inactive(client) => client, + JackClient::Activating => panic!("jack client still activating"), + JackClient::Active(_) => panic!("jack client already activated"), + } + } +} + +impl AudioEngine for JackClient { + fn client(&self) -> &Client { + match self { + Self::Inactive(ref client) => client, + Self::Activating => panic!("jack client has not finished activation"), + Self::Active(ref client) => client.as_client(), + } + } + fn activate( + self, + mut 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 = contrib::ClosureProcessHandler::new(frame as BoxedAudioHandler); + *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); + Ok(state) + } +} + +#[derive(Debug)] +/// Event enum for JACK events. +pub enum JackEvent { + ThreadInit, + Shutdown(ClientStatus, String), + Freewheel(bool), + SampleRate(Frames), + ClientRegistration(String, bool), + PortRegistration(PortId, bool), + PortRename(PortId, String, String), + PortsConnected(PortId, PortId, bool), + GraphReorder, + XRun, +} + +/// Trait for things that may expose JACK ports. +pub trait Ports { + fn audio_ins(&self) -> Usually>> { + Ok(vec![]) + } + fn audio_outs(&self) -> Usually>> { + Ok(vec![]) + } + fn midi_ins(&self) -> Usually>> { + Ok(vec![]) + } + fn midi_outs(&self) -> Usually>> { + Ok(vec![]) + } +} + +fn register_ports( + client: &Client, + names: Vec, + spec: T, +) -> Usually>> { + names + .into_iter() + .try_fold(BTreeMap::new(), |mut ports, name| { + let port = client.register_port(&name, spec)?; + ports.insert(name, port); + Ok(ports) + }) +} + +fn query_ports(client: &Client, names: Vec) -> BTreeMap> { + names.into_iter().fold(BTreeMap::new(), |mut ports, name| { + let port = client.port_by_name(&name).unwrap(); + ports.insert(name, port); + ports + }) +} + +/// Notification handler used by the [Jack] factory +/// when constructing [JackDevice]s. +pub type DynamicNotifications = Notifications>; + +/// Generic notification handler that emits [JackEvent] +pub struct Notifications(pub T); + +impl NotificationHandler for Notifications { + 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 + } +} + +///// A [AudioComponent] bound to a JACK client and a set of ports. +//pub struct JackDevice { + ///// The active JACK client of this device. + //pub client: DynamicAsyncClient, + ///// The device state, encapsulated for sharing between threads. + //pub state: Arc>>>, + ///// Unowned copies of the device's JACK ports, for connecting to the device. + ///// The "real" readable/writable `Port`s are owned by the `state`. + //pub ports: UnownedJackPorts, +//} + +//impl std::fmt::Debug for JackDevice { + //fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + //f.debug_struct("JackDevice") + //.field("ports", &self.ports) + //.finish() + //} +//} + +//impl Widget for JackDevice { + //type Engine = E; + //fn layout(&self, to: E::Size) -> Perhaps { + //self.state.read().unwrap().layout(to) + //} + //fn render(&self, to: &mut E::Output) -> Usually<()> { + //self.state.read().unwrap().render(to) + //} +//} + +//impl Handle for JackDevice { + //fn handle(&mut self, from: &E::Input) -> Perhaps { + //self.state.write().unwrap().handle(from) + //} +//} + +//impl Ports for JackDevice { + //fn audio_ins(&self) -> Usually>> { + //Ok(self.ports.audio_ins.values().collect()) + //} + //fn audio_outs(&self) -> Usually>> { + //Ok(self.ports.audio_outs.values().collect()) + //} + //fn midi_ins(&self) -> Usually>> { + //Ok(self.ports.midi_ins.values().collect()) + //} + //fn midi_outs(&self) -> Usually>> { + //Ok(self.ports.midi_outs.values().collect()) + //} +//} + +//impl JackDevice { + ///// Returns a locked mutex of the state's contents. + //pub fn state(&self) -> LockResult>>> { + //self.state.read() + //} + ///// Returns a locked mutex of the state's contents. + //pub fn state_mut(&self) -> LockResult>>> { + //self.state.write() + //} + //pub fn connect_midi_in(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(port, self.midi_ins()?[index])?) + //} + //pub fn connect_midi_out(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(self.midi_outs()?[index], port)?) + //} + //pub fn connect_audio_in(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(port, self.audio_ins()?[index])?) + //} + //pub fn connect_audio_out(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(self.audio_outs()?[index], port)?) + //} +//} + +///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut]. +//#[derive(Default, Debug)] +//pub struct JackPorts { + //pub audio_ins: BTreeMap>, + //pub midi_ins: BTreeMap>, + //pub audio_outs: BTreeMap>, + //pub midi_outs: BTreeMap>, +//} + +///// Collection of JACK ports as [Unowned]. +//#[derive(Default, Debug)] +//pub struct UnownedJackPorts { + //pub audio_ins: BTreeMap>, + //pub midi_ins: BTreeMap>, + //pub audio_outs: BTreeMap>, + //pub midi_outs: BTreeMap>, +//} + +//impl JackPorts { + //pub fn clone_unowned(&self) -> UnownedJackPorts { + //let mut unowned = UnownedJackPorts::default(); + //for (name, port) in self.midi_ins.iter() { + //unowned.midi_ins.insert(name.clone(), port.clone_unowned()); + //} + //for (name, port) in self.midi_outs.iter() { + //unowned.midi_outs.insert(name.clone(), port.clone_unowned()); + //} + //for (name, port) in self.audio_ins.iter() { + //unowned.audio_ins.insert(name.clone(), port.clone_unowned()); + //} + //for (name, port) in self.audio_outs.iter() { + //unowned + //.audio_outs + //.insert(name.clone(), port.clone_unowned()); + //} + //unowned + //} +//} + +///// Implement the `Ports` trait. +//#[macro_export] +//macro_rules! ports { + //($T:ty $({ $(audio: { + //$(ins: |$ai_arg:ident|$ai_impl:expr,)? + //$(outs: |$ao_arg:ident|$ao_impl:expr,)? + //})? $(midi: { + //$(ins: |$mi_arg:ident|$mi_impl:expr,)? + //$(outs: |$mo_arg:ident|$mo_impl:expr,)? + //})?})?) => { + //impl Ports for $T {$( + //$( + //$(fn audio_ins <'a> (&'a self) -> Usually>> { + //let cb = |$ai_arg:&'a Self|$ai_impl; + //cb(self) + //})? + //)? + //$( + //$(fn audio_outs <'a> (&'a self) -> Usually>> { + //let cb = (|$ao_arg:&'a Self|$ao_impl); + //cb(self) + //})? + //)? + //)? $( + //$( + //$(fn midi_ins <'a> (&'a self) -> Usually>> { + //let cb = (|$mi_arg:&'a Self|$mi_impl); + //cb(self) + //})? + //)? + //$( + //$(fn midi_outs <'a> (&'a self) -> Usually>> { + //let cb = (|$mo_arg:&'a Self|$mo_impl); + //cb(self) + //})? + //)? + //)?} + //}; +//} + +///// `JackDevice` factory. Creates JACK `Client`s, performs port registration +///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. +//pub struct Jack { + //pub client: Client, + //pub midi_ins: Vec, + //pub audio_ins: Vec, + //pub midi_outs: Vec, + //pub audio_outs: Vec, +//} + +//impl Jack { + //pub fn new(name: &str) -> Usually { + //Ok(Self { + //midi_ins: vec![], + //audio_ins: vec![], + //midi_outs: vec![], + //audio_outs: vec![], + //client: Client::new(name, ClientOptions::NO_START_SERVER)?.0, + //}) + //} + //pub fn run<'a: 'static, D, E>( + //self, + //state: impl FnOnce(JackPorts) -> Box, + //) -> Usually> + //where + //D: AudioComponent + Sized + 'static, + //E: Engine + 'static, + //{ + //let owned_ports = JackPorts { + //audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?, + //audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?, + //midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?, + //midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?, + //}; + //let midi_outs = owned_ports + //.midi_outs + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let midi_ins = owned_ports + //.midi_ins + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let audio_outs = owned_ports + //.audio_outs + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let audio_ins = owned_ports + //.audio_ins + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let state = Arc::new(RwLock::new(state(owned_ports) as Box>)); + //let client = self.client.activate_async( + //Notifications(Box::new({ + //let _state = state.clone(); + //move |_event| { + //// FIXME: this deadlocks + ////state.lock().unwrap().handle(&event).unwrap(); + //} + //}) as Box), + //contrib::ClosureProcessHandler::new(Box::new({ + //let state = state.clone(); + //move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s) + //}) as BoxedAudioHandler), + //)?; + //Ok(JackDevice { + //ports: UnownedJackPorts { + //audio_ins: query_ports(&client.as_client(), audio_ins), + //audio_outs: query_ports(&client.as_client(), audio_outs), + //midi_ins: query_ports(&client.as_client(), midi_ins), + //midi_outs: query_ports(&client.as_client(), midi_outs), + //}, + //client, + //state, + //}) + //} + //pub fn audio_in(mut self, name: &str) -> Self { + //self.audio_ins.push(name.to_string()); + //self + //} + //pub fn audio_out(mut self, name: &str) -> Self { + //self.audio_outs.push(name.to_string()); + //self + //} + //pub fn midi_in(mut self, name: &str) -> Self { + //self.midi_ins.push(name.to_string()); + //self + //} + //pub fn midi_out(mut self, name: &str) -> Self { + //self.midi_outs.push(name.to_string()); + //self + //} +//} diff --git a/crates/tek_core/src/command.rs b/crates/tek_core/src/command.rs index 233f17ea..f01caf82 100644 --- a/crates/tek_core/src/command.rs +++ b/crates/tek_core/src/command.rs @@ -6,7 +6,7 @@ pub enum NextPrev { Prev, } -pub trait Command: Sized { +pub trait Command: Send + Sync + Sized { fn translate (self, _: &S) -> Self { self } fn execute (self, state: &mut S) -> Perhaps; } diff --git a/crates/tek_core/src/focus.rs b/crates/tek_core/src/focus.rs index 95da3d64..e4cbf21e 100644 --- a/crates/tek_core/src/focus.rs +++ b/crates/tek_core/src/focus.rs @@ -1,5 +1,34 @@ use crate::*; +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum FocusCommand { + Next, + Prev, + Up, + Down, + Left, + Right, + Enter, + Exit +} + +impl Command for FocusCommand { + fn execute (self, state: &mut F) -> Perhaps { + use FocusCommand::*; + match self { + Next => { state.focus_next(); }, + Prev => { state.focus_prev(); }, + Up => { state.focus_up(); }, + Down => { state.focus_down(); }, + Left => { state.focus_left(); }, + Right => { state.focus_right(); }, + Enter => { state.focus_enter(); }, + Exit => { state.focus_exit(); }, + } + Ok(None) + } +} + pub trait FocusGrid { type Item: Copy + PartialEq; fn layout (&self) -> &[&[Self::Item]]; @@ -85,32 +114,3 @@ pub trait FocusGrid { self.update_focus(); } } - -#[derive(Copy, Clone, PartialEq)] -pub enum FocusCommand { - Next, - Prev, - Up, - Down, - Left, - Right, - Enter, - Exit -} - -impl Command for FocusCommand { - fn execute (self, state: &mut F) -> Perhaps { - use FocusCommand::*; - match self { - Next => { state.focus_next(); }, - Prev => { state.focus_prev(); }, - Up => { state.focus_up(); }, - Down => { state.focus_down(); }, - Left => { state.focus_left(); }, - Right => { state.focus_right(); }, - Enter => { state.focus_enter(); }, - Exit => { state.focus_exit(); }, - } - Ok(None) - } -} diff --git a/crates/tek_core/src/lib.rs b/crates/tek_core/src/lib.rs index b872a5e5..6a8bc071 100644 --- a/crates/tek_core/src/lib.rs +++ b/crates/tek_core/src/lib.rs @@ -36,7 +36,7 @@ use std::fmt::{Debug, Display}; } submod! { - //audio + audio color command edn diff --git a/crates/tek_snd/Cargo.toml b/crates/tek_snd/Cargo.toml new file mode 100644 index 00000000..64634afe --- /dev/null +++ b/crates/tek_snd/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "tek_snd" +edition = "2021" +version = "0.1.0" + +[dependencies] +tek_core = { path = "../tek_core" } +tek_api = { path = "../tek_api" } diff --git a/crates/tek_snd/src/lib.rs b/crates/tek_snd/src/lib.rs new file mode 100644 index 00000000..0ee522f6 --- /dev/null +++ b/crates/tek_snd/src/lib.rs @@ -0,0 +1,9 @@ +pub use tek_core::{*, jack::{*, Transport as JackTransport}}; +pub use tek_api::{*, Transport}; +pub(crate) use tek_core::midly::{*, live::LiveEvent, num::u7}; + +submod! { + snd_arrange + snd_mixer + snd_sampler +} diff --git a/crates/tek_snd/src/snd_arrange.rs b/crates/tek_snd/src/snd_arrange.rs new file mode 100644 index 00000000..cb8322e1 --- /dev/null +++ b/crates/tek_snd/src/snd_arrange.rs @@ -0,0 +1,22 @@ +use crate::*; + +pub struct ArrangementAudio { + model: Arc> +} + +impl From<&Arc>> for ArrangementAudio { + fn from (model: &Arc>) -> Self { + Self { model: model.clone() } + } +} + +impl Audio for ArrangementAudio { + #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + for track in self.model.write().unwrap().tracks.iter_mut() { + if track.player.process(client, scope) == Control::Quit { + return Control::Quit + } + } + Control::Continue + } +} diff --git a/crates/tek_snd/src/snd_mixer.rs b/crates/tek_snd/src/snd_mixer.rs new file mode 100644 index 00000000..e799ace9 --- /dev/null +++ b/crates/tek_snd/src/snd_mixer.rs @@ -0,0 +1,17 @@ +use crate::*; + +pub struct MixerAudio { + model: Arc> +} + +impl From<&Arc>> for MixerAudio { + fn from (model: &Arc>) -> Self { + Self { model: model.clone() } + } +} + +impl Audio for MixerAudio { + fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { + Control::Continue + } +} diff --git a/crates/tek_snd/src/snd_plugin.rs b/crates/tek_snd/src/snd_plugin.rs new file mode 100644 index 00000000..cd02b402 --- /dev/null +++ b/crates/tek_snd/src/snd_plugin.rs @@ -0,0 +1,65 @@ +use crate::*; + +pub struct PluginAudio { + model: Arc> +} + +impl From<&Arc>> for PluginAudio { + fn from (model: &Arc>) -> Self { + Self { model: model.clone() } + } +} + +impl Audio for PluginAudio { + fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + match self.plugin.as_mut() { + Some(PluginKind::LV2(LV2Plugin { + features, + ref mut instance, + ref mut input_buffer, + .. + })) => { + let urid = features.midi_urid(); + input_buffer.clear(); + for port in self.midi_ins.iter() { + let mut atom = ::livi::event::LV2AtomSequence::new( + &features, + scope.n_frames() as usize + ); + for event in port.iter(scope) { + match event.bytes.len() { + 3 => atom.push_midi_event::<3>( + event.time as i64, + urid, + &event.bytes[0..3] + ).unwrap(), + _ => {} + } + } + input_buffer.push(atom); + } + let mut outputs = vec![]; + for _ in self.midi_outs.iter() { + outputs.push(::livi::event::LV2AtomSequence::new( + &features, + scope.n_frames() as usize + )); + } + let ports = ::livi::EmptyPortConnections::new() + .with_atom_sequence_inputs(input_buffer.iter()) + .with_atom_sequence_outputs(outputs.iter_mut()) + .with_audio_inputs( + self.audio_ins.iter().map(|o|o.as_slice(scope)) + ) + .with_audio_outputs( + self.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope)) + ); + unsafe { + instance.run(scope.n_frames() as usize, ports).unwrap() + }; + }, + _ => {} + } + Control::Continue + } +} diff --git a/crates/tek_snd/src/snd_sampler.rs b/crates/tek_snd/src/snd_sampler.rs new file mode 100644 index 00000000..44f6ece6 --- /dev/null +++ b/crates/tek_snd/src/snd_sampler.rs @@ -0,0 +1,79 @@ +use crate::*; + +pub struct SamplerAudio { + model: Arc> +} + +impl From<&Arc>> for SamplerAudio { + fn from (model: &Arc>) -> Self { + Self { model: model.clone() } + } +} + +impl Audio for SamplerAudio { + #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + self.process_midi_in(scope); + self.clear_output_buffer(); + self.process_audio_out(scope); + self.write_output_buffer(scope); + Control::Continue + } +} + +impl SamplerAudio { + + /// Create [Voice]s from [Sample]s in response to MIDI input. + pub fn process_midi_in (&mut self, scope: &ProcessScope) { + let Sampler { midi_in, mapped, voices, .. } = &*self.model.read().unwrap(); + for RawMidi { time, bytes } in midi_in.iter(scope) { + if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { + if let MidiMessage::NoteOn { ref key, ref vel } = message { + if let Some(sample) = mapped.get(key) { + voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); + } + } + } + } + } + + /// Zero the output buffer. + pub fn clear_output_buffer (&mut self) { + for buffer in self.model.write().unwrap().buffer.iter_mut() { + buffer.fill(0.0); + } + } + + /// Mix all currently playing samples into the output. + pub fn process_audio_out (&mut self, scope: &ProcessScope) { + let Sampler { ref mut buffer, voices, output_gain, .. } = &mut*self.model.write().unwrap(); + let channel_count = buffer.len(); + voices.write().unwrap().retain_mut(|voice|{ + for index in 0..scope.n_frames() as usize { + if let Some(frame) = voice.next() { + for (channel, sample) in frame.iter().enumerate() { + // Averaging mixer: + //self.buffer[channel % channel_count][index] = ( + //(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0 + //); + buffer[channel % channel_count][index] += sample * *output_gain; + } + } else { + return false + } + } + return true + }); + } + + /// Write output buffer to output ports. + pub fn write_output_buffer (&mut self, scope: &ProcessScope) { + let Sampler { ref mut audio_outs, buffer, .. } = &mut*self.model.write().unwrap(); + for (i, port) in audio_outs.iter_mut().enumerate() { + let buffer = &buffer[i]; + for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { + *value = *buffer.get(i).unwrap_or(&0.0); + } + } + } + +} diff --git a/crates/tek_snd/src/snd_sequencer.rs b/crates/tek_snd/src/snd_sequencer.rs new file mode 100644 index 00000000..a7668ff9 --- /dev/null +++ b/crates/tek_snd/src/snd_sequencer.rs @@ -0,0 +1,37 @@ +use crate::*; + +pub struct MIDIPlayerAudio { + model: Arc> +} + +impl From<&Arc>> for MIDIPlayerAudio { + fn from (model: &Arc>) -> Self { + Self { model: model.clone() } + } +} + +/// JACK process callback for a sequencer's phrase player/recorder. +impl Audio for MIDIPlayer { + fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + let has_midi_outputs = self.has_midi_outputs(); + let has_midi_inputs = self.has_midi_inputs(); + // Clear output buffer(s) + self.clear(scope, false); + // Write chunk of phrase to output, handle switchover + if self.play(scope) { + self.switchover(scope); + } + if has_midi_inputs { + if self.recording || self.monitoring { + // Record and/or monitor input + self.record(scope) + } else if has_midi_outputs && self.monitoring { + // Monitor input to output + self.monitor(scope) + } + } + // Write to output port(s) + self.write(scope); + Control::Continue + } +} diff --git a/crates/tek_snd/src/snd_transport.rs b/crates/tek_snd/src/snd_transport.rs new file mode 100644 index 00000000..bfc12823 --- /dev/null +++ b/crates/tek_snd/src/snd_transport.rs @@ -0,0 +1,43 @@ +use crate::*; + +pub struct TransportAudio { + model: Transport +} + +impl From<&Arc>> for TransportAudio { + fn from (model: &Arc>) -> Self { + Self { model: model.clone() } + } +} + +impl Audio for Transport { + fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + 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.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(); + if *playing != Some(transport.state) { + match transport.state { + TransportState::Rolling => { + *started = Some((current_frames as usize, current_usecs as usize)) + }, + TransportState::Stopped => { + *started = None + }, + _ => {} + } + }; + *playing = Some(transport.state); + if *playing == Some(TransportState::Stopped) { + *started = None; + } + self.clock.current.update_from_usec(match *started { + Some((_, usecs)) => current_usecs as f64 - usecs as f64, + None => 0. + }); + Control::Continue + } +} diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index 269f7d5d..1047a4be 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -11,6 +11,7 @@ pub(crate) use std::fs::read_dir; submod! { tui_app + tui_app_foc tui_arrangement tui_arrangement_cmd diff --git a/crates/tek_tui/src/tui_app.rs b/crates/tek_tui/src/tui_app.rs index 1d6fda4a..386e4424 100644 --- a/crates/tek_tui/src/tui_app.rs +++ b/crates/tek_tui/src/tui_app.rs @@ -1,26 +1,28 @@ use crate::*; -pub struct App +pub struct AppContainer where + T: Send + Sync, E: Engine, C: Command, U: From>> + Widget + Handle, A: From>> + Audio, S: From>> + StatusBar { - cursor: (usize, usize), - entered: bool, - menu_bar: Option>, - status_bar: Option, - history: Vec, - size: Measure, - ui: U, - audio: A, - model: Arc>, + pub cursor: (usize, usize), + pub entered: bool, + pub menu_bar: Option>, + pub status_bar: Option, + pub history: Vec, + pub size: Measure, + pub ui: U, + pub audio: A, + pub model: Arc>, } -impl From for App +impl From for AppContainer where + T: Send + Sync, E: Engine, C: Command, U: From>> + Widget + Handle, @@ -42,3 +44,27 @@ where } } } + +impl Content for AppContainer +where + T: Send + Sync, + C: Command, + U: From>> + Widget + Handle, + A: From>> + Audio, + S: From>> + StatusBar +{ + type Engine = Tui; + fn content (&self) -> impl Widget { + Split::down( + if self.menu_bar.is_some() { 1 } else { 0 }, + row!(menu in self.menu_bar.menus.iter() => { + row!(" ", menu.title.as_str(), " ") + }), + Split::up( + if self.status_bar.is_some() { 1 } else { 0 }, + widget(&self.status_bar), + self.ui + ) + ) + } +} diff --git a/crates/tek_tui/src/tui_app_foc.rs b/crates/tek_tui/src/tui_app_foc.rs new file mode 100644 index 00000000..eba5e4e1 --- /dev/null +++ b/crates/tek_tui/src/tui_app_foc.rs @@ -0,0 +1,59 @@ +use crate::*; + +#[derive(Debug, Copy, Clone)] +pub enum AppContainerCommand { + Focus(FocusCommand), + App(T) +} + +#[derive(Debug, Copy, Clone)] +pub enum AppContainerFocus { + Menu, + Content(F), +} + +impl FocusGrid for AppContainer +where + T: Send + Sync, + C: Command, + U: From>> + Widget + Handle + FocusGrid, + A: From>> + Audio, + S: From>> + StatusBar +{ + type Item = AppContainerFocus<::Item>; + fn cursor (&self) -> (usize, usize) { + self.cursor + } + fn cursor_mut (&mut self) -> &mut (usize, usize) { + &mut self.cursor + } + fn focus_enter (&mut self) { + let focused = self.focused(); + if !self.entered { + self.entered = true; + // TODO + } + } + fn focus_exit (&mut self) { + if self.entered { + self.entered = false; + // TODO + } + } + fn entered (&self) -> Option { + if self.entered { + Some(self.focused()) + } else { + None + } + } + fn layout (&self) -> &[&[Self::Item]] { + &[ + &[AppContainerFocus::Menu], + &[AppContainerFocus::Content], + ] + } + fn update_focus (&mut self) { + // TODO + } +} diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index da2048da..1949d3f4 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -27,7 +27,7 @@ impl Audio for ArrangerView { if let Some((ref started_at, Some(ref playing))) = track.player.phrase { let phrase = phrase.read().unwrap(); if *playing.read().unwrap() == *phrase { - let pulse = self.clock.current.pulse.get(); + let pulse = self.sequencer.transport.state.clock.current.pulse.get(); let start = started_at.pulse.get(); let now = (pulse - start) % phrase.length as f64; self.sequencer.editor.now.set(now); @@ -38,7 +38,7 @@ impl Audio for ArrangerView { } } self.sequencer.editor.now.set(0.); - self.state.process(client, scope) + return Control::Continue } }