diff --git a/Cargo.lock b/Cargo.lock index 696dee05..06345338 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2701,6 +2701,7 @@ dependencies = [ name = "tek_snd" version = "0.1.0" dependencies = [ + "livi", "tek_api", "tek_core", ] @@ -2714,6 +2715,7 @@ dependencies = [ "symphonia", "tek_api", "tek_core", + "tek_snd", "vst", "wavers", "winit", diff --git a/crates/tek_api/src/sequencer.rs b/crates/tek_api/src/sequencer.rs index 3d87fd5c..091436bd 100644 --- a/crates/tek_api/src/sequencer.rs +++ b/crates/tek_api/src/sequencer.rs @@ -61,18 +61,18 @@ impl MIDIPlayer { ], }) } - fn is_rolling (&self) -> bool { + pub fn is_rolling (&self) -> bool { *self.clock.playing.read().unwrap() == Some(TransportState::Rolling) } - fn has_midi_inputs (&self) -> bool { + pub fn has_midi_inputs (&self) -> bool { self.midi_inputs.len() > 0 } - fn has_midi_outputs (&self) -> bool { + pub fn has_midi_outputs (&self) -> bool { self.midi_outputs.len() > 0 } /// Clear the section of the output buffer that we will be using, /// emitting "all notes off" at start of buffer if requested. - fn clear (&mut self, scope: &ProcessScope, force_reset: bool) { + pub fn clear (&mut self, scope: &ProcessScope, force_reset: bool) { for frame in &mut self.midi_chunk[0..scope.n_frames() as usize] { frame.clear(); } @@ -80,7 +80,7 @@ impl MIDIPlayer { all_notes_off(&mut self.midi_chunk); self.reset = false; } } - fn play (&mut self, scope: &ProcessScope) -> bool { + pub fn play (&mut self, scope: &ProcessScope) -> bool { let mut next = false; // Write MIDI events from currently playing phrase (if any) to MIDI output buffer if self.is_rolling() { @@ -138,7 +138,7 @@ impl MIDIPlayer { } next } - fn switchover (&mut self, scope: &ProcessScope) { + pub fn switchover (&mut self, scope: &ProcessScope) { if self.is_rolling() { let sample0 = scope.last_frame_time() as usize; //let samples = scope.n_frames() as usize; @@ -163,7 +163,7 @@ impl MIDIPlayer { } } } - fn record (&mut self, scope: &ProcessScope) { + pub fn record (&mut self, scope: &ProcessScope) { let sample0 = scope.last_frame_time() as usize; if let (true, Some((started, phrase))) = (self.is_rolling(), &self.phrase) { let start = started.sample.get() as usize; @@ -199,7 +199,7 @@ impl MIDIPlayer { // TODO switch to next phrase and record into it } } - fn monitor (&mut self, scope: &ProcessScope) { + pub fn monitor (&mut self, scope: &ProcessScope) { let mut notes_in = self.notes_in.write().unwrap(); for input in self.midi_inputs.iter() { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { @@ -210,7 +210,7 @@ impl MIDIPlayer { } } } - fn write (&mut self, scope: &ProcessScope) { + pub fn write (&mut self, scope: &ProcessScope) { let samples = scope.n_frames() as usize; for port in self.midi_outputs.iter_mut() { let writer = &mut port.writer(scope); diff --git a/crates/tek_snd/Cargo.toml b/crates/tek_snd/Cargo.toml index 64634afe..343c3219 100644 --- a/crates/tek_snd/Cargo.toml +++ b/crates/tek_snd/Cargo.toml @@ -6,3 +6,4 @@ version = "0.1.0" [dependencies] tek_core = { path = "../tek_core" } tek_api = { path = "../tek_api" } +livi = "0.7.4" diff --git a/crates/tek_snd/src/lib.rs b/crates/tek_snd/src/lib.rs index 0ee522f6..95009e6e 100644 --- a/crates/tek_snd/src/lib.rs +++ b/crates/tek_snd/src/lib.rs @@ -5,5 +5,8 @@ pub(crate) use tek_core::midly::{*, live::LiveEvent, num::u7}; submod! { snd_arrange snd_mixer + snd_plugin snd_sampler + snd_sequencer + snd_transport } diff --git a/crates/tek_snd/src/snd_arrange.rs b/crates/tek_snd/src/snd_arrange.rs index cb8322e1..ced88161 100644 --- a/crates/tek_snd/src/snd_arrange.rs +++ b/crates/tek_snd/src/snd_arrange.rs @@ -13,7 +13,7 @@ impl From<&Arc>> for ArrangementAudio { 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 { + if MIDIPlayerAudio::from(&mut track.player).process(client, scope) == Control::Quit { return Control::Quit } } diff --git a/crates/tek_snd/src/snd_plugin.rs b/crates/tek_snd/src/snd_plugin.rs index 26565a94..e003173b 100644 --- a/crates/tek_snd/src/snd_plugin.rs +++ b/crates/tek_snd/src/snd_plugin.rs @@ -1,18 +1,17 @@ use crate::*; -pub struct PluginAudio { - model: Arc> -} +pub struct PluginAudio(Arc>); impl From<&Arc>> for PluginAudio { fn from (model: &Arc>) -> Self { - Self { model: model.clone() } + Self(model.clone()) } } impl Audio for PluginAudio { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - match self.plugin.as_mut() { + let state = &mut*self.0.write().unwrap(); + match state.plugin.as_mut() { Some(PluginKind::LV2(LV2Plugin { features, ref mut instance, @@ -21,7 +20,7 @@ impl Audio for PluginAudio { })) => { let urid = features.midi_urid(); input_buffer.clear(); - for port in self.midi_ins.iter() { + for port in state.midi_ins.iter() { let mut atom = ::livi::event::LV2AtomSequence::new( &features, scope.n_frames() as usize @@ -39,7 +38,7 @@ impl Audio for PluginAudio { input_buffer.push(atom); } let mut outputs = vec![]; - for _ in self.midi_outs.iter() { + for _ in state.midi_outs.iter() { outputs.push(::livi::event::LV2AtomSequence::new( &features, scope.n_frames() as usize @@ -48,8 +47,8 @@ impl Audio for PluginAudio { 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))); + .with_audio_inputs(state.audio_ins.iter().map(|o|o.as_slice(scope))) + .with_audio_outputs(state.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))); unsafe { instance.run(scope.n_frames() as usize, ports).unwrap() }; diff --git a/crates/tek_snd/src/snd_sequencer.rs b/crates/tek_snd/src/snd_sequencer.rs index f1918ce2..b9ce8e15 100644 --- a/crates/tek_snd/src/snd_sequencer.rs +++ b/crates/tek_snd/src/snd_sequencer.rs @@ -1,51 +1,50 @@ use crate::*; -pub struct SequencerAudio { - transport: Arc>, - player: Arc>, -} +pub struct SequencerAppAudio<'a>(&'a mut Transport, &'a mut MIDIPlayer); /// JACK process callback for sequencer app -impl Audio for SequencerAudio { +impl<'a> Audio for SequencerAppAudio<'a> { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - self.transport.write().unwrap().process(client, scope); - self.player.write().unwrap().process(client, scope); + if TransportAudio::from(&mut*self.0).process(client, scope) == Control::Quit { + return Control::Quit + } + if MIDIPlayerAudio::from(&mut*self.1).process(client, scope) == Control::Quit { + return Control::Quit + } Control::Continue } } -pub struct MIDIPlayerAudio { - model: Arc> -} +pub struct MIDIPlayerAudio<'a>(&'a mut MIDIPlayer); -impl From<&Arc>> for MIDIPlayerAudio { - fn from (model: &Arc>) -> Self { - Self { model: model.clone() } +impl<'a> From<&'a mut MIDIPlayer> for MIDIPlayerAudio<'a> { + fn from (model: &'a mut MIDIPlayer) -> Self { + Self(model) } } /// JACK process callback for a sequencer's phrase player/recorder. -impl Audio for MIDIPlayer { +impl<'a> Audio for MIDIPlayerAudio<'a> { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let has_midi_outputs = self.has_midi_outputs(); - let has_midi_inputs = self.has_midi_inputs(); + let has_midi_outputs = self.0.has_midi_outputs(); + let has_midi_inputs = self.0.has_midi_inputs(); // Clear output buffer(s) - self.clear(scope, false); + self.0.clear(scope, false); // Write chunk of phrase to output, handle switchover - if self.play(scope) { - self.switchover(scope); + if self.0.play(scope) { + self.0.switchover(scope); } if has_midi_inputs { - if self.recording || self.monitoring { + if self.0.recording || self.0.monitoring { // Record and/or monitor input - self.record(scope) - } else if has_midi_outputs && self.monitoring { + self.0.record(scope) + } else if has_midi_outputs && self.0.monitoring { // Monitor input to output - self.monitor(scope) + self.0.monitor(scope) } } // Write to output port(s) - self.write(scope); + self.0.write(scope); Control::Continue } } diff --git a/crates/tek_snd/src/snd_transport.rs b/crates/tek_snd/src/snd_transport.rs index bfc12823..e25ec578 100644 --- a/crates/tek_snd/src/snd_transport.rs +++ b/crates/tek_snd/src/snd_transport.rs @@ -1,24 +1,23 @@ use crate::*; -pub struct TransportAudio { - model: Transport -} +pub struct TransportAudio<'a>(&'a mut Transport); -impl From<&Arc>> for TransportAudio { - fn from (model: &Arc>) -> Self { - Self { model: model.clone() } +impl<'a> From<&'a mut Transport> for TransportAudio<'a> { + fn from (model: &'a mut Transport) -> Self { + Self(model) } } -impl Audio for Transport { +impl<'a> Audio for TransportAudio<'a> { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let times = scope.cycle_times().unwrap(); + let state = &mut self.0; + 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(); + let transport = state.transport.query().unwrap(); + state.clock.current.sample.set(transport.pos.frame() as f64); + let mut playing = state.clock.playing.write().unwrap(); + let mut started = state.clock.started.write().unwrap(); if *playing != Some(transport.state) { match transport.state { TransportState::Rolling => { @@ -34,7 +33,7 @@ impl Audio for Transport { if *playing == Some(TransportState::Stopped) { *started = None; } - self.clock.current.update_from_usec(match *started { + state.clock.current.update_from_usec(match *started { Some((_, usecs)) => current_usecs as f64 - usecs as f64, None => 0. }); diff --git a/crates/tek_tui/Cargo.toml b/crates/tek_tui/Cargo.toml index a4c6cd05..9e38b995 100644 --- a/crates/tek_tui/Cargo.toml +++ b/crates/tek_tui/Cargo.toml @@ -6,6 +6,7 @@ version = "0.1.0" [dependencies] tek_core = { path = "../tek_core" } tek_api = { path = "../tek_api" } +tek_snd = { path = "../tek_snd" } livi = "0.7.4" suil-rs = { path = "../suil" } diff --git a/crates/tek_tui/src/tui_app.rs b/crates/tek_tui/src/tui_app.rs index 386e4424..6eb2c4b3 100644 --- a/crates/tek_tui/src/tui_app.rs +++ b/crates/tek_tui/src/tui_app.rs @@ -55,15 +55,19 @@ where { type Engine = Tui; fn content (&self) -> impl Widget { + let menus = self.menu_bar.as_ref().map_or_else( + ||&[] as &[Menu<_, _, _>], + |m|m.menus.as_slice() + ); Split::down( if self.menu_bar.is_some() { 1 } else { 0 }, - row!(menu in self.menu_bar.menus.iter() => { + row!(menu in menus.iter() => { row!(" ", menu.title.as_str(), " ") }), Split::up( if self.status_bar.is_some() { 1 } else { 0 }, widget(&self.status_bar), - self.ui + widget(&self.ui) ) ) } diff --git a/crates/tek_tui/src/tui_phrase.rs b/crates/tek_tui/src/tui_phrase.rs index 98b9d786..abf2b8e8 100644 --- a/crates/tek_tui/src/tui_phrase.rs +++ b/crates/tek_tui/src/tui_phrase.rs @@ -336,10 +336,18 @@ pub(crate) fn keys_vert () -> Buffer { cell.set_fg(Color::White); cell.set_bg(Color::White); }, - 2 => if y % 6 == 0 { cell.set_char('C'); }, - 3 => if y % 6 == 0 { cell.set_symbol(NTH_OCTAVE[(y / 6) as usize]); }, + 2 => if y % 6 == 0 { + cell.set_char('C'); + }, + 3 => if y % 6 == 0 { + cell.set_symbol(NTH_OCTAVE[(y / 6) as usize]); + }, _ => {} } }); buffer } + +const NTH_OCTAVE: [&'static str; 11] = [ + "-2", "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8", +]; diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index b07b2b17..d4c63d15 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -1,7 +1,8 @@ use crate::*; use std::cmp::PartialEq; -/// Root level object for standalone `tek_sequencer` +/// Root level object for standalone `tek_sequencer`. +/// Also embeddable, in which case the `player` is used for preview. pub struct SequencerView { /// Controls the JACK transport. pub transport: TransportView,