diff --git a/crates/tek_api/src/api_clock.rs b/crates/tek_api/src/api_clock.rs index 366d2c1f..13617b29 100644 --- a/crates/tek_api/src/api_clock.rs +++ b/crates/tek_api/src/api_clock.rs @@ -129,22 +129,6 @@ impl ClockModel { pub fn is_rolling (&self) -> bool { *self.playing.read().unwrap() == Some(TransportState::Rolling) } - //fn toggle_play (&self) -> Usually<()> { - //let playing = self.playing.read().unwrap().expect("1st sample has not been processed yet"); - //let playing = match playing { - //TransportState::Stopped => { - //self.transport.start()?; - //Some(TransportState::Starting) - //}, - //_ => { - //self.transport.stop()?; - //self.transport.locate(0)?; - //Some(TransportState::Stopped) - //}, - //}; - //*self.playing.write().unwrap() = playing; - //Ok(()) - //} } /// Hosts the JACK callback for updating the temporal pointer and playback status. diff --git a/crates/tek_api/src/api_jack.rs b/crates/tek_api/src/api_jack.rs index 9f5ff5ef..e378869f 100644 --- a/crates/tek_api/src/api_jack.rs +++ b/crates/tek_api/src/api_jack.rs @@ -3,3 +3,20 @@ use crate::*; pub trait JackApi { fn jack (&self) -> &Arc>; } + +pub trait HasMidiIns { + fn midi_ins (&self) -> &Vec>; + fn midi_ins_mut (&mut self) -> &mut Vec>; + fn has_midi_ins (&self) -> bool { + self.midi_ins().len() > 0 + } +} + +pub trait HasMidiOuts { + fn midi_outs (&self) -> &Vec>; + fn midi_outs_mut (&mut self) -> &mut Vec>; + fn midi_note (&mut self) -> &mut Vec; + fn has_midi_outs (&self) -> bool { + self.midi_outs().len() > 0 + } +} diff --git a/crates/tek_api/src/api_player.rs b/crates/tek_api/src/api_player.rs index 7c9029a1..2a9fff92 100644 --- a/crates/tek_api/src/api_player.rs +++ b/crates/tek_api/src/api_player.rs @@ -1,32 +1,19 @@ use crate::*; -pub trait HasPlayer: JackApi { +pub trait HasPlayer { fn player (&self) -> &impl MidiPlayerApi; fn player_mut (&mut self) -> &mut impl MidiPlayerApi; } -pub trait MidiPlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {} +pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {} -pub trait HasPhrase: HasClock { +pub trait HasPlayPhrase: HasClock { fn reset (&self) -> bool; fn reset_mut (&mut self) -> &mut bool; - - fn play_phrase (&self) - -> &Option<(Instant, Option>>)>; - fn play_phrase_mut (&mut self) - -> &mut Option<(Instant, Option>>)>; - - fn next_phrase (&self) - -> &Option<(Instant, Option>>)>; - fn next_phrase_mut (&mut self) - -> &mut Option<(Instant, Option>>)>; - fn enqueue_next (&mut self, phrase: Option<&Arc>>) { - let start = self.clock().next_launch_pulse() as f64; - let instant = Instant::from_pulse(&self.clock().timebase(), start); - let phrase = phrase.map(|p|p.clone()); - *self.next_phrase_mut() = Some((instant, phrase)); - *self.reset_mut() = true; - } + fn play_phrase (&self) -> &Option<(Instant, Option>>)>; + fn play_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)>; + fn next_phrase (&self) -> &Option<(Instant, Option>>)>; + fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)>; fn pulses_since_start (&self) -> Option { if let Some((started, Some(_))) = self.play_phrase().as_ref() { Some(self.clock().current.pulse.get() - started.pulse.get()) @@ -34,39 +21,24 @@ pub trait HasPhrase: HasClock { None } } + fn enqueue_next (&mut self, phrase: Option<&Arc>>) { + let start = self.clock().next_launch_pulse() as f64; + let instant = Instant::from_pulse(&self.clock().timebase(), start); + let phrase = phrase.map(|p|p.clone()); + *self.next_phrase_mut() = Some((instant, phrase)); + *self.reset_mut() = true; + } } -pub trait MidiInputApi: HasPhrase { - fn midi_ins (&self) -> &Vec>; - fn midi_ins_mut (&mut self) -> &mut Vec>; - fn has_midi_ins (&self) -> bool { - self.midi_ins().len() > 0 - } +pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { + fn notes_in (&self) -> &Arc>; + fn recording (&self) -> bool; fn recording_mut (&mut self) -> &mut bool; fn toggle_record (&mut self) { *self.recording_mut() = !self.recording(); } - - fn monitoring (&self) -> bool; - fn monitoring_mut (&mut self) -> &mut bool; - fn toggle_monitor (&mut self) { - *self.monitoring_mut() = !self.monitoring(); - } - - fn overdub (&self) -> bool; - fn overdub_mut (&mut self) -> &mut bool; - fn toggle_overdub (&mut self) { - *self.overdub_mut() = !self.overdub(); - } - - fn notes_in (&self) -> &Arc>; - - fn record ( - &mut self, - scope: &ProcessScope, - midi_buf: &mut Vec>>, - ) { + fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { let sample0 = scope.last_frame_time() as usize; // For highlighting keys and note repeat let notes_in = self.notes_in().clone(); @@ -107,11 +79,12 @@ pub trait MidiInputApi: HasPhrase { } } - fn monitor ( - &mut self, - scope: &ProcessScope, - midi_buf: &mut Vec>>, - ) { + fn monitoring (&self) -> bool; + fn monitoring_mut (&mut self) -> &mut bool; + fn toggle_monitor (&mut self) { + *self.monitoring_mut() = !self.monitoring(); + } + fn monitor (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { // For highlighting keys and note repeat let notes_in = self.notes_in().clone(); for input in self.midi_ins_mut().iter() { @@ -124,42 +97,33 @@ pub trait MidiInputApi: HasPhrase { } } + fn overdub (&self) -> bool; + fn overdub_mut (&mut self) -> &mut bool; + fn toggle_overdub (&mut self) { + *self.overdub_mut() = !self.overdub(); + } } -pub trait MidiOutputApi: HasPhrase { - fn midi_outs (&self) -> &Vec>; - - fn midi_outs_mut (&mut self) -> &mut Vec>; - - fn midi_note (&mut self) -> &mut Vec; +pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { fn notes_out (&self) -> &Arc>; - fn has_midi_outs (&self) -> bool { - self.midi_outs().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, - midi_buf: &mut Vec>>, - reset: bool + &mut self, scope: &ProcessScope, out_buf: &mut Vec>>, reset: bool ) { - for frame in &mut midi_buf[0..scope.n_frames() as usize] { + for frame in &mut out_buf[0..scope.n_frames() as usize] { frame.clear(); } if reset { - all_notes_off(midi_buf); + all_notes_off(out_buf); } } + /// Output notes from phrase to MIDI output ports. fn play ( - &mut self, - scope: &ProcessScope, - note_buffer: &mut Vec, - output_buffer: &mut Vec>> + &mut self, scope: &ProcessScope, note_buf: &mut Vec, out_buf: &mut Vec>> ) -> bool { let mut next = false; // Write MIDI events from currently playing phrase (if any) to MIDI output buffer @@ -207,15 +171,15 @@ pub trait MidiOutputApi: HasPhrase { // Output each MIDI event from phrase at appropriate frames of output buffer: for message in phrase.notes[pulse].iter() { // Clear output buffer for this MIDI event. - note_buffer.clear(); + note_buf.clear(); // TODO: support MIDI channels other than CH1. let channel = 0.into(); // Serialize MIDI event into message buffer. LiveEvent::Midi { channel, message: *message } - .write(note_buffer) + .write(note_buf) .unwrap(); // Append serialized message to output buffer. - output_buffer[sample].push(note_buffer.clone()); + out_buf[sample].push(note_buf.clone()); // Update the list of currently held notes. update_keys(&mut*notes, &message); } @@ -227,11 +191,9 @@ pub trait MidiOutputApi: HasPhrase { next } + /// Handle switchover from current to next playing phrase. fn switchover ( - &mut self, - scope: &ProcessScope, - note_buffer: &mut Vec, - output_buffer: &mut Vec>> + &mut self, scope: &ProcessScope, note_buf: &mut Vec, out_buf: &mut Vec>> ) { if self.clock().is_rolling() { let sample0 = scope.last_frame_time() as usize; @@ -251,19 +213,22 @@ pub trait MidiOutputApi: HasPhrase { } // TODO fill in remaining ticks of chunk from next phrase. // ?? just call self.play(scope) again, since enqueuement is off ??? - self.play(scope, note_buffer, output_buffer); + self.play(scope, note_buf, out_buf); // ?? or must it be with modified scope ?? // likely not because start time etc } } } - fn write (&mut self, scope: &ProcessScope, output_buffer: &Vec>>) { + /// Write a chunk of MIDI notes to the output buffer. + fn write ( + &mut self, scope: &ProcessScope, out_buf: &Vec>> + ) { let samples = scope.n_frames() as usize; for port in self.midi_outs_mut().iter_mut() { let writer = &mut port.writer(scope); for time in 0..samples { - for event in output_buffer[time].iter() { + for event in out_buf[time].iter() { writer.write(&RawMidi { time: time as u32, bytes: &event }) .expect(&format!("{event:?}")); } diff --git a/crates/tek_api/src/api_scene.rs b/crates/tek_api/src/api_scene.rs index c5356c1f..662569bc 100644 --- a/crates/tek_api/src/api_scene.rs +++ b/crates/tek_api/src/api_scene.rs @@ -78,7 +78,7 @@ pub trait ArrangerSceneApi: Sized { Some(clip) => tracks .get(track_index) .map(|track|{ - if let Some((_, Some(phrase))) = track.play_phrase() { + if let Some((_, Some(phrase))) = track.player().play_phrase() { *phrase.read().unwrap() == *clip.read().unwrap() } else { false diff --git a/crates/tek_api/src/api_track.rs b/crates/tek_api/src/api_track.rs index 60dd4a2c..43229a71 100644 --- a/crates/tek_api/src/api_track.rs +++ b/crates/tek_api/src/api_track.rs @@ -33,7 +33,7 @@ pub enum ArrangerTrackCommand { SetZoom(usize), } -pub trait ArrangerTrackApi: MidiPlayerApi + Send + Sync + Sized { +pub trait ArrangerTrackApi: HasPlayer + Send + Sync + Sized { /// Name of track fn name (&self) -> &Arc>; /// Preferred width of track column @@ -78,7 +78,7 @@ impl<'a, T: ArrangerTrackApi, H: HasTracks> Audio for TracksAudio<'a, T, H> { let note_buffer = &mut self.1; let output_buffer = &mut self.2; for track in model.tracks_mut().iter_mut() { - if PlayerAudio(track, note_buffer, output_buffer).process(client, scope) == Control::Quit { + if PlayerAudio(track.player_mut(), note_buffer, output_buffer).process(client, scope) == Control::Quit { return Control::Quit } } diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index 76001ba7..f5cbc884 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -91,79 +91,6 @@ impl TuiTheme { } } -macro_rules! impl_midi_player { - ($Struct:ident $(:: $field:ident)*) => { - impl HasPhrase for $Struct { - fn reset (&self) -> bool { - self$(.$field)*.reset - } - fn reset_mut (&mut self) -> &mut bool { - &mut self$(.$field)*.reset - } - fn play_phrase (&self) -> &Option<(Instant, Option>>)> { - &self$(.$field)*.play_phrase - } - fn play_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)> { - &mut self$(.$field)*.play_phrase - } - fn next_phrase (&self) -> &Option<(Instant, Option>>)> { - &self$(.$field)*.next_phrase - } - fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)> { - &mut self$(.$field)*.next_phrase - } - } - impl MidiInputApi for $Struct { - fn midi_ins (&self) -> &Vec> { - &self$(.$field)*.midi_ins - } - fn midi_ins_mut (&mut self) -> &mut Vec> { - &mut self$(.$field)*.midi_ins - } - fn recording (&self) -> bool { - self$(.$field)*.recording - } - fn recording_mut (&mut self) -> &mut bool { - &mut self$(.$field)*.recording - } - fn monitoring (&self) -> bool { - self$(.$field)*.monitoring - } - fn monitoring_mut (&mut self) -> &mut bool { - &mut self$(.$field)*.monitoring - } - fn overdub (&self) -> bool { - self$(.$field)*.overdub - } - fn overdub_mut (&mut self) -> &mut bool { - &mut self$(.$field)*.overdub - } - fn notes_in (&self) -> &Arc> { - &self$(.$field)*.notes_in - } - } - impl MidiOutputApi for $Struct { - fn midi_outs (&self) -> &Vec> { - &self$(.$field)*.midi_outs - } - fn midi_outs_mut (&mut self) -> &mut Vec> { - &mut self$(.$field)*.midi_outs - } - fn midi_note (&mut self) -> &mut Vec { - &mut self$(.$field)*.note_buf - } - fn notes_out (&self) -> &Arc> { - &self$(.$field)*.notes_in - } - } - impl MidiPlayerApi for $Struct {} - } -} - -impl_midi_player!(SequencerTui::player); -impl_midi_player!(ArrangerTrack::player); -impl_midi_player!(PhrasePlayerModel); - use std::fmt::{Debug, Formatter, Error}; type DebugResult = std::result::Result<(), Error>; diff --git a/crates/tek_tui/src/tui_control_arranger.rs b/crates/tek_tui/src/tui_control_arranger.rs index 86af7149..6176f48d 100644 --- a/crates/tek_tui/src/tui_control_arranger.rs +++ b/crates/tek_tui/src/tui_control_arranger.rs @@ -86,7 +86,7 @@ impl ArrangerControl for ArrangerTui { for (t, track) in self.tracks.iter_mut().enumerate() { let phrase = self.scenes[s].clips[t].clone(); if track.player.play_phrase.is_some() || phrase.is_some() { - track.enqueue_next(phrase.as_ref()); + track.player.enqueue_next(phrase.as_ref()); } } if self.clock().is_stopped() { @@ -94,7 +94,7 @@ impl ArrangerControl for ArrangerTui { } } else if let ArrangerSelection::Clip(t, s) = self.selected { let phrase = self.scenes()[s].clips[t].clone(); - self.tracks_mut()[t].enqueue_next(phrase.as_ref()); + self.tracks_mut()[t].player.enqueue_next(phrase.as_ref()); }; Ok(()) } diff --git a/crates/tek_tui/src/tui_model_arranger.rs b/crates/tek_tui/src/tui_model_arranger.rs index 79898e6b..33319bf9 100644 --- a/crates/tek_tui/src/tui_model_arranger.rs +++ b/crates/tek_tui/src/tui_model_arranger.rs @@ -94,6 +94,15 @@ pub struct ArrangerTrack { pub(crate) player: PhrasePlayerModel, } +impl HasPlayer for ArrangerTrack { + fn player (&self) -> &impl MidiPlayerApi { + &self.player + } + fn player_mut (&mut self) -> &mut impl MidiPlayerApi { + &mut self.player + } +} + impl ArrangerTrackApi for ArrangerTrack { /// Name of track fn name (&self) -> &Arc> { diff --git a/crates/tek_tui/src/tui_model_phrase_player.rs b/crates/tek_tui/src/tui_model_phrase_player.rs index 33a782de..75657a68 100644 --- a/crates/tek_tui/src/tui_model_phrase_player.rs +++ b/crates/tek_tui/src/tui_model_phrase_player.rs @@ -52,3 +52,77 @@ impl HasClock for PhrasePlayerModel { &self.clock } } + +impl HasPlayPhrase for PhrasePlayerModel { + fn reset (&self) -> bool { + self.reset + } + fn reset_mut (&mut self) -> &mut bool { + &mut self.reset + } + fn play_phrase (&self) -> &Option<(Instant, Option>>)> { + &self.play_phrase + } + fn play_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)> { + &mut self.play_phrase + } + fn next_phrase (&self) -> &Option<(Instant, Option>>)> { + &self.next_phrase + } + fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)> { + &mut self.next_phrase + } +} + +impl HasMidiIns for PhrasePlayerModel { + fn midi_ins (&self) -> &Vec> { + &self.midi_ins + } + fn midi_ins_mut (&mut self) -> &mut Vec> { + &mut self.midi_ins + } +} + +impl MidiRecordApi for PhrasePlayerModel { + fn recording (&self) -> bool { + self.recording + } + fn recording_mut (&mut self) -> &mut bool { + &mut self.recording + } + fn monitoring (&self) -> bool { + self.monitoring + } + fn monitoring_mut (&mut self) -> &mut bool { + &mut self.monitoring + } + fn overdub (&self) -> bool { + self.overdub + } + fn overdub_mut (&mut self) -> &mut bool { + &mut self.overdub + } + fn notes_in (&self) -> &Arc> { + &self.notes_in + } +} + +impl HasMidiOuts for PhrasePlayerModel { + 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 + } +} + +impl MidiPlaybackApi for PhrasePlayerModel { + fn notes_out (&self) -> &Arc> { + &self.notes_in + } +} + +impl MidiPlayerApi for PhrasePlayerModel {} diff --git a/crates/tek_tui/src/tui_view_arranger.rs b/crates/tek_tui/src/tui_view_arranger.rs index a60d26af..b4f0eb29 100644 --- a/crates/tek_tui/src/tui_view_arranger.rs +++ b/crates/tek_tui/src/tui_view_arranger.rs @@ -136,9 +136,9 @@ pub fn arranger_content_vertical ( let name = format!("▎{}", &name[0..max_w]); let name = TuiStyle::bold(name, true); // beats elapsed - let elapsed = if let Some((_, Some(phrase))) = track.play_phrase().as_ref() { + let elapsed = if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { let length = phrase.read().unwrap().length; - let elapsed = track.pulses_since_start().unwrap(); + let elapsed = track.player.pulses_since_start().unwrap(); let elapsed = timebase.format_beats_1_short( (elapsed as usize % length) as f64 ); @@ -147,7 +147,7 @@ pub fn arranger_content_vertical ( String::from("▎") }; // beats until switchover - let until_next = track.next_phrase().as_ref().map(|(t, _)|{ + let until_next = track.player.next_phrase().as_ref().map(|(t, _)|{ let target = t.pulse.get(); let current = current.pulse.get(); if target > current { @@ -158,12 +158,12 @@ pub fn arranger_content_vertical ( } }).unwrap_or(String::from("▎")); // name of active MIDI input - let input = format!("▎>{}", track.midi_ins().get(0) + let input = format!("▎>{}", track.player.midi_ins().get(0) .map(|port|port.short_name()) .transpose()? .unwrap_or("(none)".into())); // name of active MIDI output - let output = format!("▎<{}", track.midi_outs().get(0) + let output = format!("▎<{}", track.player.midi_outs().get(0) .map(|port|port.short_name()) .transpose()? .unwrap_or("(none)".into())); @@ -196,7 +196,7 @@ pub fn arranger_content_vertical ( let color = phrase.read().unwrap().color; add(&name.as_str()[0..max_w].push_x(1).fixed_x(w as u16))?; bg = color.dark.rgb; - if let Some((_, Some(ref playing))) = track.play_phrase() { + if let Some((_, Some(ref playing))) = track.player.play_phrase() { if *playing.read().unwrap() == *phrase.read().unwrap() { bg = color.light.rgb } diff --git a/crates/tek_tui/src/tui_view_phrase_selector.rs b/crates/tek_tui/src/tui_view_phrase_selector.rs index 1315ef23..d7becd6b 100644 --- a/crates/tek_tui/src/tui_view_phrase_selector.rs +++ b/crates/tek_tui/src/tui_view_phrase_selector.rs @@ -8,7 +8,7 @@ pub struct PhraseSelector<'a> { } impl<'a> PhraseSelector<'a> { - pub fn play_phrase ( + pub fn play_phrase ( state: &'a T, focused: bool, entered: bool, @@ -20,7 +20,7 @@ impl<'a> PhraseSelector<'a> { title: "Now:", } } - pub fn next_phrase ( + pub fn next_phrase ( state: &'a T, focused: bool, entered: bool,