diff --git a/crates/tek_api/src/api_sequencer.rs b/crates/tek_api/src/api_player.rs similarity index 90% rename from crates/tek_api/src/api_sequencer.rs rename to crates/tek_api/src/api_player.rs index 481d8249..4df0773c 100644 --- a/crates/tek_api/src/api_sequencer.rs +++ b/crates/tek_api/src/api_player.rs @@ -1,10 +1,12 @@ use crate::*; pub trait HasPlayer: HasJack { - fn player (&self) -> &MIDIPlayer; - fn player_mut (&mut self) -> &mut MIDIPlayer; + fn player (&self) -> &impl PlayerApi; + fn player_mut (&mut self) -> &mut impl PlayerApi; } +pub trait PlayerApi: MidiInputApi + MidiOutputApi {} + pub trait HasMidiBuffer { fn midi_buffer (&self) -> &mut Vec>>; @@ -25,9 +27,16 @@ pub trait HasMidiBuffer { } pub trait HasPhrase: ClockApi + PlayheadApi + HasMidiBuffer { - fn phrase (&self) -> &Option<(Instant, Arc>)>; - fn next_phrase (&self) -> &Option<(Instant, Arc>)>; - fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Arc>)>; + fn phrase (&self) + -> &Option<(Instant, Arc>)>; + fn phrase_mut (&self) + -> &mut Option<(Instant, Arc>)>; + + fn next_phrase (&self) + -> &Option<(Instant, Arc>)>; + fn next_phrase_mut (&mut self) + -> &mut Option<(Instant, Arc>)>; + fn switchover (&mut self, scope: &ProcessScope) { if self.is_rolling() { let sample0 = scope.last_frame_time() as usize; @@ -41,7 +50,7 @@ pub trait HasPhrase: ClockApi + PlayheadApi + HasMidiBuffer { let skipped = sample0 - start; // Switch over to enqueued phrase let started = Instant::from_sample(&self.timebase(), start as f64); - self.phrase = Some((started, phrase.clone())); + *self.phrase_mut() = Some((started, phrase.clone())); // Unset enqueuement (TODO: where to implement looping?) *self.next_phrase_mut() = None } @@ -55,22 +64,22 @@ pub trait HasPhrase: ClockApi + PlayheadApi + HasMidiBuffer { } fn enqueue_next (&mut self, phrase: Option<&Arc>>) { let start = self.next_launch_pulse(); - self.next_phrase = Some(( + *self.next_phrase_mut() = Some(( Instant::from_pulse(&self.timebase(), start as f64), phrase.map(|p|p.clone()) )); - self.reset = true; + *self.reset_mut() = true; } fn pulses_since_start (&self) -> Option { - if let Some((started, Some(_))) = self.phrase.as_ref() { - Some(self.clock().current.pulse.get() - started.pulse.get()) + if let Some((started, Some(_))) = self.phrase().as_ref() { + Some(self.current().pulse.get() - started.pulse.get()) } else { None } } } -pub trait MidiInputApi: ClockApi + PlayheadApi + HasMidiBuffer + HasPhrase { +pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase { fn midi_ins (&self) -> &Vec>; fn midi_ins_mut (&self) -> &mut Vec>; fn has_midi_ins (&self) -> bool { @@ -147,7 +156,7 @@ pub trait MidiInputApi: ClockApi + PlayheadApi + HasMidiBuffer + HasPhrase { } -pub trait MidiOutputApi: ClockApi + PlayheadApi + HasMidiBuffer + HasPhrase { +pub trait MidiOutputApi: PlayheadApi + HasMidiBuffer + HasPhrase { fn midi_outs (&self) -> &Vec>; fn midi_outs_mut (&self) -> &mut Vec>; @@ -172,12 +181,12 @@ pub trait MidiOutputApi: ClockApi + PlayheadApi + HasMidiBuffer + HasPhrase { // First sample to populate. Greater than 0 means that the first // pulse of the phrase falls somewhere in the middle of the chunk. let sample = started.sample.get() as usize; - let sample = sample + self.clock().started.read().unwrap().unwrap().0; + let sample = sample + self.started().read().unwrap().unwrap().0; let sample = sample0.saturating_sub(sample); // Iterator that emits sample (index into output buffer at which to write MIDI event) // paired with pulse (index into phrase from which to take the MIDI event) for each // sample of the output buffer that corresponds to a MIDI pulse. - let pulses = self.clock().timebase().pulses_between_samples(sample, sample + samples); + let pulses = self.timebase().pulses_between_samples(sample, sample + samples); // Notes active during current chunk. let notes = &mut self.notes_out().write().unwrap(); for (sample, pulse) in pulses { diff --git a/crates/tek_api/src/api_playhead.rs b/crates/tek_api/src/api_playhead.rs index 6ab948bf..90d64724 100644 --- a/crates/tek_api/src/api_playhead.rs +++ b/crates/tek_api/src/api_playhead.rs @@ -34,11 +34,9 @@ impl Command for PlayheadCommand { } pub trait PlayheadApi: ClockApi { - fn transport (&self) -> &jack::Transport; - + /// Playback state fn playing (&self) -> &RwLock>; - /// Global sample and usec at which playback started fn started (&self) -> &RwLock>; diff --git a/crates/tek_api/src/api_scene.rs b/crates/tek_api/src/api_scene.rs index 70f2ddaa..8d15c6f3 100644 --- a/crates/tek_api/src/api_scene.rs +++ b/crates/tek_api/src/api_scene.rs @@ -66,12 +66,12 @@ pub trait ArrangerSceneApi: Sized { /// Returns true if all phrases in the scene are /// currently playing on the given collection of tracks. - fn is_playing (&self, tracks: &[ArrangerTrack]) -> bool { + fn is_playing (&self, tracks: &[T]) -> bool { self.clips().iter().any(|clip|clip.is_some()) && self.clips().iter().enumerate() .all(|(track_index, clip)|match clip { Some(clip) => tracks .get(track_index) - .map(|track|if let Some((_, Some(phrase))) = &track.player.phrase { + .map(|track|if let Some((_, Some(phrase))) = track.player().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 46e763f9..b35ec58b 100644 --- a/crates/tek_api/src/api_track.rs +++ b/crates/tek_api/src/api_track.rs @@ -41,7 +41,7 @@ pub trait ArrangerTrackApi: Sized { /// Identifying color of track fn color (&self) -> ItemColor; /// The MIDI player for the track - fn player (&self) -> MIDIPlayer; + fn player (&self) -> &impl PlayerApi; fn longest_name (tracks: &[Self]) -> usize { tracks.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max) diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index 9aa58e6b..d2cd8094 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -10,15 +10,14 @@ pub(crate) use tek_core::jack::{ submod! { //api_jack - api_clip api_scene api_track api_clock api_jack + api_player api_playhead api_pool - api_sequencer //api_mixer //api_channel //api_plugin @@ -28,11 +27,11 @@ submod! { //api_sampler_sample //api_sampler_voice - model_scene - model_track + //model_scene + //model_track //model_clock model_phrase - model_player + //model_player model_pool } diff --git a/crates/tek_api/src/model_player.rs b/crates/tek_api/src/model_player.rs index 92479f0b..4e906eb2 100644 --- a/crates/tek_api/src/model_player.rs +++ b/crates/tek_api/src/model_player.rs @@ -5,7 +5,7 @@ pub struct MIDIPlayer { /// Global timebase pub clock: Arc, /// Start time and phrase being played - pub phrase: Option<(Instant, Option>>)>, + pub play_phrase: Option<(Instant, Option>>)>, /// Start time and next phrase pub next_phrase: Option<(Instant, Option>>)>, /// Play input through output. diff --git a/crates/tek_api/src/model_scene.rs b/crates/tek_api/src/model_scene.rs index 7c0d8fb4..9b969a38 100644 --- a/crates/tek_api/src/model_scene.rs +++ b/crates/tek_api/src/model_scene.rs @@ -1,15 +1,5 @@ use crate::*; -#[derive(Default, Debug, Clone)] -pub struct ArrangerScene { - /// Name of scene - pub name: Arc>, - /// Clips in scene, one per track - pub clips: Vec>>>, - /// Identifying color of scene - pub color: ItemColor, -} - impl ArrangerScene { //TODO diff --git a/crates/tek_api/src/model_track.rs b/crates/tek_api/src/model_track.rs index eda31bc5..c7b7e813 100644 --- a/crates/tek_api/src/model_track.rs +++ b/crates/tek_api/src/model_track.rs @@ -1,13 +1 @@ use crate::*; - -#[derive(Debug)] -pub struct ArrangerTrack { - /// Name of track - pub name: Arc>, - /// Preferred width of track column - pub width: usize, - /// Identifying color of track - pub color: ItemColor, - /// The MIDI player for the track - pub player: MIDIPlayer -} diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index ce071fd8..2d7cc6bf 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -244,11 +244,15 @@ impl Command> for ArrangerViewCommand { /// Root view for standalone `tek_arranger` pub struct ArrangerView { jack: Arc>, - clock: Arc, + playing: RwLock>, + started: RwLock>, + current: Instant, + quant: Quantize, + sync: LaunchSync, transport: jack::Transport, metronome: bool, - transport: TransportModel, phrases: Vec>>, + phrase: usize, tracks: Vec, scenes: Vec, name: Arc>, @@ -257,6 +261,7 @@ pub struct ArrangerView { selected: ArrangerSelection, mode: ArrangerMode, color: ItemColor, + editor: PhraseEditor, focused: bool, entered: bool, size: Measure, @@ -833,14 +838,14 @@ impl Content for ArrangerStatusBar { type Engine = Tui; fn content (&self) -> impl Widget { let label = match self { - Self::Transport => "TRANSPORT", + Self::Transport => "TRANSPORT", Self::ArrangerMix => "PROJECT", Self::ArrangerTrack => "TRACK", Self::ArrangerScene => "SCENE", Self::ArrangerClip => "CLIP", - Self::PhrasePool => "SEQ LIST", - Self::PhraseView => "VIEW SEQ", - Self::PhraseEdit => "EDIT SEQ", + Self::PhrasePool => "SEQ LIST", + Self::PhraseView => "VIEW SEQ", + Self::PhraseEdit => "EDIT SEQ", }; let status_bar_bg = TuiTheme::status_bar_bg(); let mode_bg = TuiTheme::mode_bg(); @@ -1362,3 +1367,49 @@ pub fn arranger_content_horizontal ( ) ) } + +#[derive(Default, Debug, Clone)] +pub struct ArrangerScene { + /// Name of scene + pub name: Arc>, + /// Clips in scene, one per track + pub clips: Vec>>>, + /// Identifying color of scene + pub color: ItemColor, +} + +#[derive(Debug)] +pub struct ArrangerTrack { + /// Name of track + pub name: Arc>, + /// Preferred width of track column + pub width: usize, + /// Identifying color of track + pub color: ItemColor, + /// The MIDI player for the track + pub player: MIDIPlayer + /// Start time and phrase being played + play_phrase: Option<(Instant, Option>>)>, + /// Start time and next phrase + next_phrase: Option<(Instant, Option>>)>, + /// Play input through output. + monitoring: bool, + /// Write input to sequence. + recording: bool, + /// Overdub input to sequence. + overdub: bool, + /// Send all notes off + reset: bool, // TODO?: after Some(nframes) + /// Record from MIDI ports to current sequence. + midi_inputs: Vec>, + /// Play from current sequence to MIDI ports + midi_outputs: Vec>, + /// MIDI output buffer + midi_note: Vec, + /// MIDI output buffer + midi_chunk: Vec>>, + /// Notes currently held at input + notes_in: Arc>, + /// Notes currently held at output + notes_out: Arc>, +} diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index c305ce9d..8d76f3b8 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -72,15 +72,43 @@ impl InputToCommand> for SequencerAppCommand { /// Root view for standalone `tek_sequencer`. pub struct SequencerView { - jack: Arc>, - clock: Arc, - transport: jack::Transport, - metronome: bool, - phrases: Vec>>, - player: MIDIPlayer, - phrases: PhrasePoolView, - editor: PhraseEditor, - split: u16, + jack: Arc>, + playing: RwLock>, + started: RwLock>, + current: Instant, + quant: Quantize, + sync: LaunchSync, + clock: Arc, + transport: jack::Transport, + metronome: bool, + phrases: Vec>>, + view_phrase: usize, + editor: PhraseEditor, + split: u16, + /// Start time and phrase being played + play_phrase: Option<(Instant, Option>>)>, + /// Start time and next phrase + next_phrase: Option<(Instant, Option>>)>, + /// Play input through output. + monitoring: bool, + /// Write input to sequence. + recording: bool, + /// Overdub input to sequence. + overdub: bool, + /// Send all notes off + reset: bool, // TODO?: after Some(nframes) + /// Record from MIDI ports to current sequence. + midi_inputs: Vec>, + /// Play from current sequence to MIDI ports + midi_outputs: Vec>, + /// MIDI output buffer + midi_note: Vec, + /// MIDI output buffer + midi_chunk: Vec>>, + /// Notes currently held at input + notes_in: Arc>, + /// Notes currently held at output + notes_out: Arc>, } /// Sections in the sequencer app that may be focused diff --git a/crates/tek_tui/src/tui_transport.rs b/crates/tek_tui/src/tui_transport.rs index dfcc1ef6..a66a8229 100644 --- a/crates/tek_tui/src/tui_transport.rs +++ b/crates/tek_tui/src/tui_transport.rs @@ -129,8 +129,16 @@ impl Command> for TransportCommand { pub struct TransportView { _engine: PhantomData, jack: Arc>, - /// Current sample rate, tempo, and PPQ. - clock: Arc, + /// Playback state + playing: RwLock>, + /// Global sample and usec at which playback started + started: RwLock>, + /// Current moment in time + current: Instant, + /// Note quantization factor + quant: Quantize, + /// Launch quantization factor + sync: LaunchSync, /// JACK transport handle. transport: jack::Transport, /// Enable metronome?