diff --git a/crates/tek_api/src/api_arranger.rs b/crates/tek_api/src/api_arranger.rs deleted file mode 100644 index f15139a5..00000000 --- a/crates/tek_api/src/api_arranger.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::*; - -#[derive(Clone, Debug)] -pub enum ArrangerCommand { - Clear, - Export, - Import, - StopAll, - Scene(ArrangerSceneCommand), - Track(ArrangerTrackCommand), - Clip(ArrangerClipCommand), -} - -pub trait ArrangerApi: HasJack + HasClock { - fn name (&self) -> &Arc>; - - fn tracks (&self) -> &Vec; - fn tracks_mut (&mut self) -> &mut Vec; - - fn scenes (&self) -> &Vec; - fn scenes_mut (&mut self) -> &mut Vec; - - fn track_default_name (&self) -> String { - format!("Track {}", self.tracks().len() + 1) - } - fn track_add ( - &mut self, name: Option<&str>, color: Option - ) -> Usually<&mut ArrangerTrack> { - let name = name.map_or_else(||self.track_default_name(), |x|x.to_string()); - let track = ArrangerTrack { - width: name.len() + 2, - color: color.unwrap_or_else(||ItemColor::random()), - player: MIDIPlayer::new(&self.jack(), &self.clock(), name.as_str())?, - name: Arc::new(name.into()), - }; - self.tracks_mut().push(track); - let index = self.tracks().len() - 1; - Ok(&mut self.tracks_mut()[index]) - } - fn track_del (&mut self, index: usize) { - self.tracks_mut().remove(index); - for scene in self.scenes_mut().iter_mut() { - scene.clips.remove(index); - } - } - fn scene_default_name (&self) -> String { - format!("Scene {}", self.scenes().len() + 1) - } - fn scene_add ( - &mut self, name: Option<&str>, color: Option - ) -> Usually<&mut ArrangerScene> { - let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string()); - let scene = ArrangerScene { - name: Arc::new(name.into()), - clips: vec![None;self.tracks().len()], - color: color.unwrap_or_else(||ItemColor::random()), - }; - self.scenes_mut().push(scene); - let index = self.scenes().len() - 1; - Ok(&mut self.scenes_mut()[index]) - } - fn scene_del (&mut self, index: usize) { - self.scenes_mut().remove(index); - } -} - -impl Command for ArrangerCommand { - fn execute (self, state: &mut ArrangerModel) -> Perhaps { - match self { - Self::Scene(command) => { return Ok(command.execute(state)?.map(Self::Scene)) }, - Self::Track(command) => { return Ok(command.execute(state)?.map(Self::Track)) }, - Self::Clip(command) => { return Ok(command.execute(state)?.map(Self::Clip)) }, - _ => todo!() - } - Ok(None) - } -} - -//impl Command for ArrangerSceneCommand { -//} - //Edit(phrase) => { state.state.phrase = phrase.clone() }, - //ToggleViewMode => { state.state.mode.to_next(); }, - //Delete => { state.state.delete(); }, - //Activate => { state.state.activate(); }, - //ZoomIn => { state.state.zoom_in(); }, - //ZoomOut => { state.state.zoom_out(); }, - //MoveBack => { state.state.move_back(); }, - //MoveForward => { state.state.move_forward(); }, - //RandomColor => { state.state.randomize_color(); }, - //Put => { state.state.phrase_put(); }, - //Get => { state.state.phrase_get(); }, - //AddScene => { state.state.scene_add(None, None)?; }, - //AddTrack => { state.state.track_add(None, None)?; }, - //ToggleLoop => { state.state.toggle_loop() }, - //pub fn zoom_in (&mut self) { - //if let ArrangerEditorMode::Vertical(factor) = self.mode { - //self.mode = ArrangerEditorMode::Vertical(factor + 1) - //} - //} - //pub fn zoom_out (&mut self) { - //if let ArrangerEditorMode::Vertical(factor) = self.mode { - //self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1)) - //} - //} - //pub fn move_back (&mut self) { - //match self.selected { - //ArrangerEditorFocus::Scene(s) => { - //if s > 0 { - //self.scenes.swap(s, s - 1); - //self.selected = ArrangerEditorFocus::Scene(s - 1); - //} - //}, - //ArrangerEditorFocus::Track(t) => { - //if t > 0 { - //self.tracks.swap(t, t - 1); - //self.selected = ArrangerEditorFocus::Track(t - 1); - //// FIXME: also swap clip order in scenes - //} - //}, - //_ => todo!("arrangement: move forward") - //} - //} - //pub fn move_forward (&mut self) { - //match self.selected { - //ArrangerEditorFocus::Scene(s) => { - //if s < self.scenes.len().saturating_sub(1) { - //self.scenes.swap(s, s + 1); - //self.selected = ArrangerEditorFocus::Scene(s + 1); - //} - //}, - //ArrangerEditorFocus::Track(t) => { - //if t < self.tracks.len().saturating_sub(1) { - //self.tracks.swap(t, t + 1); - //self.selected = ArrangerEditorFocus::Track(t + 1); - //// FIXME: also swap clip order in scenes - //} - //}, - //_ => todo!("arrangement: move forward") - //} - //} diff --git a/crates/tek_api/src/api_arranger_track.rs b/crates/tek_api/src/api_arranger_track.rs deleted file mode 100644 index 1759031c..00000000 --- a/crates/tek_api/src/api_arranger_track.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::*; - -#[derive(Clone, Debug)] -pub enum ArrangerTrackCommand { - Add, - Delete(usize), - RandomColor, - Stop, - Swap(usize, usize), - SetSize(usize), - SetZoom(usize), -} - -impl Command for ArrangerTrackCommand { - fn execute (self, state: &mut T) -> Perhaps { - match self { - Self::Delete(index) => { state.track_del(index); }, - _ => todo!() - } - Ok(None) - } -} - -pub trait ArrangerTrackApi: Sized { - /// Name of track - fn name (&self) -> Arc>; - /// Preferred width of track column - fn width (&self) -> usize; - /// Preferred width of track column - fn width_mut (&mut self) -> &mut usize; - /// Identifying color of track - fn color (&self) -> ItemColor; - /// The MIDI player for the track - fn player (&self) -> MIDIPlayer; - - fn longest_name (tracks: &[Self]) -> usize { - tracks.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max) - } - - const MIN_WIDTH: usize = 3; - - fn width_inc (&mut self) { - *self.width_mut() += 1; - } - - fn width_dec (&mut self) { - if self.width() > Self::MIN_WIDTH { - *self.width_mut() -= 1; - } - } -} diff --git a/crates/tek_api/src/api_arranger_clip.rs b/crates/tek_api/src/api_clip.rs similarity index 52% rename from crates/tek_api/src/api_arranger_clip.rs rename to crates/tek_api/src/api_clip.rs index 5d2b3655..fe8781ce 100644 --- a/crates/tek_api/src/api_arranger_clip.rs +++ b/crates/tek_api/src/api_clip.rs @@ -10,11 +10,11 @@ pub enum ArrangerClipCommand { RandomColor, } -impl Command for ArrangerClipCommand { - fn execute (self, state: &mut T) -> Perhaps { - match self { - _ => todo!() - } - Ok(None) - } -} +//impl Command for ArrangerClipCommand { + //fn execute (self, state: &mut T) -> Perhaps { + //match self { + //_ => todo!() + //} + //Ok(None) + //} +//} diff --git a/crates/tek_api/src/api_jack.rs b/crates/tek_api/src/api_jack.rs new file mode 100644 index 00000000..ce25e46a --- /dev/null +++ b/crates/tek_api/src/api_jack.rs @@ -0,0 +1,10 @@ +use crate::*; + +pub trait HasJack { + fn jack (&self) -> &impl JackApi; +} + +pub trait JackApi { + fn jack (&self) -> &Arc>; + fn transport (&self) -> &RwLock>; +} diff --git a/crates/tek_api/src/api_phrase.rs b/crates/tek_api/src/api_phrase.rs index 7210202a..c7b7e813 100644 --- a/crates/tek_api/src/api_phrase.rs +++ b/crates/tek_api/src/api_phrase.rs @@ -1,84 +1 @@ use crate::*; - -/// A MIDI sequence. -#[derive(Debug, Clone)] -pub struct Phrase { - pub uuid: uuid::Uuid, - /// Name of phrase - pub name: String, - /// Temporal resolution in pulses per quarter note - pub ppq: usize, - /// Length of phrase in pulses - pub length: usize, - /// Notes in phrase - pub notes: PhraseData, - /// Whether to loop the phrase or play it once - pub loop_on: bool, - /// Start of loop - pub loop_start: usize, - /// Length of loop - pub loop_length: usize, - /// All notes are displayed with minimum length - pub percussive: bool, - /// Identifying color of phrase - pub color: ItemColorTriplet, -} - -/// MIDI message structural -pub type PhraseData = Vec>; - -impl Phrase { - pub fn new ( - name: impl AsRef, - loop_on: bool, - length: usize, - notes: Option, - color: Option, - ) -> Self { - Self { - uuid: uuid::Uuid::new_v4(), - name: name.as_ref().to_string(), - ppq: PPQ, - length, - notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), - loop_on, - loop_start: 0, - loop_length: length, - percussive: true, - color: color.unwrap_or_else(ItemColorTriplet::random) - } - } - pub fn duplicate (&self) -> Self { - let mut clone = self.clone(); - clone.uuid = uuid::Uuid::new_v4(); - clone - } - pub fn toggle_loop (&mut self) { self.loop_on = !self.loop_on; } - pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { - if pulse >= self.length { panic!("extend phrase first") } - self.notes[pulse].push(message); - } - /// Check if a range `start..end` contains MIDI Note On `k` - pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool { - //panic!("{:?} {start} {end}", &self); - for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() { - for event in events.iter() { - if let MidiMessage::NoteOn {key,..} = event { if *key == k { return true } } - } - } - return false - } -} -impl Default for Phrase { - fn default () -> Self { - Self::new("(empty)", false, 0, None, Some(ItemColor::from(Color::Rgb(0, 0, 0)).into())) - } -} - -impl PartialEq for Phrase { - fn eq (&self, other: &Self) -> bool { - self.uuid == other.uuid - } -} - -impl Eq for Phrase {} diff --git a/crates/tek_api/src/api_playhead.rs b/crates/tek_api/src/api_playhead.rs index 0fcf84b2..46f3e9d4 100644 --- a/crates/tek_api/src/api_playhead.rs +++ b/crates/tek_api/src/api_playhead.rs @@ -35,18 +35,23 @@ impl Command for PlayheadCommand { pub trait PlayheadApi: ClockApi { - fn playing (&self) -> &Arc>; + fn transport (&self) -> &RwLock>; - fn transport (&self) -> Transport; + fn playing (&self) -> &RwLock>; + + /// Global sample and usec at which playback started + fn started (&self) -> &RwLock>; fn pulse (&self) -> f64 { self.current().pulse.get() } + fn next_launch_pulse (&self) -> usize { let sync = self.sync().get() as usize; let pulse = self.pulse() as usize; if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync } } + fn toggle_play (&self) -> Usually<()> { let playing = self.playing().read().unwrap().expect("1st sample has not been processed yet"); let playing = match playing { @@ -63,10 +68,12 @@ pub trait PlayheadApi: ClockApi { *self.playing().write().unwrap() = playing; Ok(()) } + fn is_stopped (&self) -> bool { *self.playing().read().unwrap() == Some(TransportState::Stopped) } + fn is_rolling (&self) -> bool { - *self.clock.playing.read().unwrap() == Some(TransportState::Rolling) + *self.playing().read().unwrap() == Some(TransportState::Rolling) } } diff --git a/crates/tek_api/src/api_arranger_scene.rs b/crates/tek_api/src/api_scene.rs similarity index 65% rename from crates/tek_api/src/api_arranger_scene.rs rename to crates/tek_api/src/api_scene.rs index de903db0..d0a7a95a 100644 --- a/crates/tek_api/src/api_arranger_scene.rs +++ b/crates/tek_api/src/api_scene.rs @@ -1,5 +1,30 @@ use crate::*; +pub trait HasScenes { + fn scenes (&self) -> &Vec; + fn scenes_mut (&mut self) -> &mut Vec; + + fn scene_default_name (&self) -> String { + format!("Scene {}", self.scenes().len() + 1) + } + fn scene_add (&mut self, name: Option<&str>, color: Option) + -> Usually<&mut S> + { + let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string()); + let scene = ArrangerScene { + name: Arc::new(name.into()), + clips: vec![None;self.tracks().len()], + color: color.unwrap_or_else(||ItemColor::random()), + }; + self.scenes_mut().push(scene); + let index = self.scenes().len() - 1; + Ok(&mut self.scenes_mut()[index]) + } + fn scene_del (&mut self, index: usize) { + self.scenes_mut().remove(index); + } +} + #[derive(Clone, Debug)] pub enum ArrangerSceneCommand { Add, @@ -11,15 +36,15 @@ pub enum ArrangerSceneCommand { SetZoom(usize), } -impl Command for ArrangerSceneCommand { - fn execute (self, state: &mut T) -> Perhaps { - match self { - Self::Delete(index) => { state.scene_del(index); }, - _ => todo!() - } - Ok(None) - } -} +//impl Command for ArrangerSceneCommand { + //fn execute (self, state: &mut T) -> Perhaps { + //match self { + //Self::Delete(index) => { state.scene_del(index); }, + //_ => todo!() + //} + //Ok(None) + //} +//} pub trait ArrangerSceneApi: Sized { fn name () -> Arc>; diff --git a/crates/tek_api/src/api_sequencer.rs b/crates/tek_api/src/api_sequencer.rs index aebf0cea..b7dc8550 100644 --- a/crates/tek_api/src/api_sequencer.rs +++ b/crates/tek_api/src/api_sequencer.rs @@ -24,25 +24,26 @@ pub trait HasMidiBuffer { } } -pub trait HasPhrase { +pub trait HasPhrase: PlayheadApi + HasClock + 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 switchover (&mut self, scope: &ProcessScope) { if self.is_rolling() { let sample0 = scope.last_frame_time() as usize; //let samples = scope.n_frames() as usize; - if let Some((start_at, phrase)) = &self.next_phrase { + if let Some((start_at, phrase)) = &self.next_phrase() { let start = start_at.sample.get() as usize; - let sample = self.clock.started.read().unwrap().unwrap().0; + let sample = self.clock().started.read().unwrap().unwrap().0; // If it's time to switch to the next phrase: if start <= sample0.saturating_sub(sample) { // Samples elapsed since phrase was supposed to start let skipped = sample0 - start; // Switch over to enqueued phrase - let started = Instant::from_sample(&self.clock.timebase(), start as f64); + let started = Instant::from_sample(&self.clock().timebase(), start as f64); self.phrase = Some((started, phrase.clone())); // Unset enqueuement (TODO: where to implement looping?) - self.next_phrase = None + *self.next_phrase_mut() = None } // TODO fill in remaining ticks of chunk from next phrase. // ?? just call self.play(scope) again, since enqueuement is off ??? @@ -53,16 +54,16 @@ pub trait HasPhrase { } } fn enqueue_next (&mut self, phrase: Option<&Arc>>) { - let start = self.clock.next_launch_pulse(); + let start = self.clock().next_launch_pulse(); self.next_phrase = Some(( - Instant::from_pulse(&self.clock.timebase(), start as f64), + Instant::from_pulse(&self.clock().timebase(), start as f64), phrase.map(|p|p.clone()) )); self.reset = 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()) + Some(self.clock().current.pulse.get() - started.pulse.get()) } else { None } diff --git a/crates/tek_api/src/api_track.rs b/crates/tek_api/src/api_track.rs new file mode 100644 index 00000000..aef03b9f --- /dev/null +++ b/crates/tek_api/src/api_track.rs @@ -0,0 +1,80 @@ +use crate::*; + +pub trait HasTracks { + fn tracks (&self) -> &Vec; + fn tracks_mut (&mut self) -> &mut Vec; + + fn track_default_name (&self) -> String { + format!("Track {}", self.tracks().len() + 1) + } + fn track_add (&mut self, name: Option<&str>, color: Option) + -> Usually<&mut T> + { + let name = name.map_or_else(||self.track_default_name(), |x|x.to_string()); + let track = ArrangerTrack { + width: name.len() + 2, + color: color.unwrap_or_else(||ItemColor::random()), + player: MIDIPlayer::new(&self.jack(), &self.clock(), name.as_str())?, + name: Arc::new(name.into()), + }; + self.tracks_mut().push(track); + let index = self.tracks().len() - 1; + Ok(&mut self.tracks_mut()[index]) + } + fn track_del (&mut self, index: usize) { + self.tracks_mut().remove(index); + for scene in self.scenes_mut().iter_mut() { + scene.clips.remove(index); + } + } +} + +#[derive(Clone, Debug)] +pub enum ArrangerTrackCommand { + Add, + Delete(usize), + RandomColor, + Stop, + Swap(usize, usize), + SetSize(usize), + SetZoom(usize), +} + +//impl Command for ArrangerTrackCommand { + //fn execute (self, state: &mut T) -> Perhaps { + //match self { + //Self::Delete(index) => { state.track_del(index); }, + //_ => todo!() + //} + //Ok(None) + //} +//} + +pub trait ArrangerTrackApi: Sized { + /// Name of track + fn name (&self) -> Arc>; + /// Preferred width of track column + fn width (&self) -> usize; + /// Preferred width of track column + fn width_mut (&mut self) -> &mut usize; + /// Identifying color of track + fn color (&self) -> ItemColor; + /// The MIDI player for the track + fn player (&self) -> MIDIPlayer; + + fn longest_name (tracks: &[Self]) -> usize { + tracks.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max) + } + + const MIN_WIDTH: usize = 3; + + fn width_inc (&mut self) { + *self.width_mut() += 1; + } + + fn width_dec (&mut self) { + if self.width() > Self::MIN_WIDTH { + *self.width_mut() -= 1; + } + } +} diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index e874f170..1751dd3d 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -11,17 +11,16 @@ pub(crate) use tek_core::jack::{ submod! { //api_jack - api_arranger - api_arranger_clip - api_arranger_scene - api_arranger_track + api_clip + api_scene + api_track api_clock - api_phrase + api_jack api_playhead api_pool api_sequencer //api_mixer - //api_mixer_track + //api_channel //api_plugin //api_plugin_kind //api_plugin_lv2 @@ -29,16 +28,75 @@ submod! { //api_sampler_sample //api_sampler_voice - model_arranger - model_arranger_scene - model_arranger_track + model_scene + model_track model_clock + model_phrase model_player model_pool model_sequencer model_transport } -pub trait HasJack { - fn jack (&self) -> &Arc>; -} +//impl Command for ArrangerSceneCommand { +//} + //Edit(phrase) => { state.state.phrase = phrase.clone() }, + //ToggleViewMode => { state.state.mode.to_next(); }, + //Delete => { state.state.delete(); }, + //Activate => { state.state.activate(); }, + //ZoomIn => { state.state.zoom_in(); }, + //ZoomOut => { state.state.zoom_out(); }, + //MoveBack => { state.state.move_back(); }, + //MoveForward => { state.state.move_forward(); }, + //RandomColor => { state.state.randomize_color(); }, + //Put => { state.state.phrase_put(); }, + //Get => { state.state.phrase_get(); }, + //AddScene => { state.state.scene_add(None, None)?; }, + //AddTrack => { state.state.track_add(None, None)?; }, + //ToggleLoop => { state.state.toggle_loop() }, + //pub fn zoom_in (&mut self) { + //if let ArrangerEditorMode::Vertical(factor) = self.mode { + //self.mode = ArrangerEditorMode::Vertical(factor + 1) + //} + //} + //pub fn zoom_out (&mut self) { + //if let ArrangerEditorMode::Vertical(factor) = self.mode { + //self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1)) + //} + //} + //pub fn move_back (&mut self) { + //match self.selected { + //ArrangerEditorFocus::Scene(s) => { + //if s > 0 { + //self.scenes.swap(s, s - 1); + //self.selected = ArrangerEditorFocus::Scene(s - 1); + //} + //}, + //ArrangerEditorFocus::Track(t) => { + //if t > 0 { + //self.tracks.swap(t, t - 1); + //self.selected = ArrangerEditorFocus::Track(t - 1); + //// FIXME: also swap clip order in scenes + //} + //}, + //_ => todo!("arrangement: move forward") + //} + //} + //pub fn move_forward (&mut self) { + //match self.selected { + //ArrangerEditorFocus::Scene(s) => { + //if s < self.scenes.len().saturating_sub(1) { + //self.scenes.swap(s, s + 1); + //self.selected = ArrangerEditorFocus::Scene(s + 1); + //} + //}, + //ArrangerEditorFocus::Track(t) => { + //if t < self.tracks.len().saturating_sub(1) { + //self.tracks.swap(t, t + 1); + //self.selected = ArrangerEditorFocus::Track(t + 1); + //// FIXME: also swap clip order in scenes + //} + //}, + //_ => todo!("arrangement: move forward") + //} + //} diff --git a/crates/tek_api/src/model_arranger.rs b/crates/tek_api/src/model_arranger.rs deleted file mode 100644 index 654cce54..00000000 --- a/crates/tek_api/src/model_arranger.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::*; - -#[derive(Debug)] -pub struct ArrangerModel { - /// State of the JACK transport. - transport: TransportModel, - /// Collection of phrases. - phrases: Vec>>, - /// Collection of tracks. - tracks: Vec, - /// Collection of scenes. - scenes: Vec, - /// Name of arranger - name: Arc>, -} - -impl HasJack for ArrangerModel { - fn jack (&self) -> &Arc> { - &self.transport.jack() - } -} - -impl HasClock for ArrangerModel { - fn clock (&self) -> &Arc { - &self.transport.clock() - } -} - -//impl TransportModelApi for ArrangerModel { - //fn transport (&self) -> &jack::Transport { - //&self.transport.transport() - //} - //fn metronome (&self) -> bool { - //self.transport.metronome() - //} -//} - -impl HasPhrases for ArrangerModel { - fn phrases (&self) -> &Vec>> { - &self.phrases - } - fn phrases_mut (&mut self) -> &mut Vec>> { - &mut self.phrases - } -} - -impl ArrangerApi for ArrangerModel { - fn name (&self) -> &Arc> { - &self.name - } - fn tracks (&self) -> &Vec { - &self.tracks - } - fn tracks_mut (&mut self) -> &mut Vec { - &mut self.tracks - } - fn scenes (&self) -> &Vec { - &self.scenes - } - fn scenes_mut (&mut self) -> &mut Vec { - &mut self.scenes - } -} diff --git a/crates/tek_api/src/model_clock.rs b/crates/tek_api/src/model_clock.rs index 52d06b36..aee60f95 100644 --- a/crates/tek_api/src/model_clock.rs +++ b/crates/tek_api/src/model_clock.rs @@ -28,6 +28,13 @@ impl ClockApi for Clock { } impl PlayheadApi for Clock { + fn playing (&self) -> &RwLock> { + &self.playing + } + /// Global sample and usec at which playback started + fn started (&self) -> &RwLock> { + &self.started + } } impl From for Clock { diff --git a/crates/tek_api/src/model_phrase.rs b/crates/tek_api/src/model_phrase.rs new file mode 100644 index 00000000..7210202a --- /dev/null +++ b/crates/tek_api/src/model_phrase.rs @@ -0,0 +1,84 @@ +use crate::*; + +/// A MIDI sequence. +#[derive(Debug, Clone)] +pub struct Phrase { + pub uuid: uuid::Uuid, + /// Name of phrase + pub name: String, + /// Temporal resolution in pulses per quarter note + pub ppq: usize, + /// Length of phrase in pulses + pub length: usize, + /// Notes in phrase + pub notes: PhraseData, + /// Whether to loop the phrase or play it once + pub loop_on: bool, + /// Start of loop + pub loop_start: usize, + /// Length of loop + pub loop_length: usize, + /// All notes are displayed with minimum length + pub percussive: bool, + /// Identifying color of phrase + pub color: ItemColorTriplet, +} + +/// MIDI message structural +pub type PhraseData = Vec>; + +impl Phrase { + pub fn new ( + name: impl AsRef, + loop_on: bool, + length: usize, + notes: Option, + color: Option, + ) -> Self { + Self { + uuid: uuid::Uuid::new_v4(), + name: name.as_ref().to_string(), + ppq: PPQ, + length, + notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), + loop_on, + loop_start: 0, + loop_length: length, + percussive: true, + color: color.unwrap_or_else(ItemColorTriplet::random) + } + } + pub fn duplicate (&self) -> Self { + let mut clone = self.clone(); + clone.uuid = uuid::Uuid::new_v4(); + clone + } + pub fn toggle_loop (&mut self) { self.loop_on = !self.loop_on; } + pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { + if pulse >= self.length { panic!("extend phrase first") } + self.notes[pulse].push(message); + } + /// Check if a range `start..end` contains MIDI Note On `k` + pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool { + //panic!("{:?} {start} {end}", &self); + for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() { + for event in events.iter() { + if let MidiMessage::NoteOn {key,..} = event { if *key == k { return true } } + } + } + return false + } +} +impl Default for Phrase { + fn default () -> Self { + Self::new("(empty)", false, 0, None, Some(ItemColor::from(Color::Rgb(0, 0, 0)).into())) + } +} + +impl PartialEq for Phrase { + fn eq (&self, other: &Self) -> bool { + self.uuid == other.uuid + } +} + +impl Eq for Phrase {} diff --git a/crates/tek_api/src/model_arranger_scene.rs b/crates/tek_api/src/model_scene.rs similarity index 100% rename from crates/tek_api/src/model_arranger_scene.rs rename to crates/tek_api/src/model_scene.rs diff --git a/crates/tek_api/src/model_arranger_track.rs b/crates/tek_api/src/model_track.rs similarity index 100% rename from crates/tek_api/src/model_arranger_track.rs rename to crates/tek_api/src/model_track.rs diff --git a/crates/tek_api/src/models.rs b/crates/tek_api/src/models.rs deleted file mode 100644 index c7b7e813..00000000 --- a/crates/tek_api/src/models.rs +++ /dev/null @@ -1 +0,0 @@ -use crate::*; diff --git a/crates/tek_api/src/todo_api_mixer_track.rs b/crates/tek_api/src/todo_api_channel.rs similarity index 100% rename from crates/tek_api/src/todo_api_mixer_track.rs rename to crates/tek_api/src/todo_api_channel.rs diff --git a/crates/tek_core/src/command.rs b/crates/tek_core/src/command.rs index f01caf82..480a42e8 100644 --- a/crates/tek_core/src/command.rs +++ b/crates/tek_core/src/command.rs @@ -6,8 +6,11 @@ pub enum NextPrev { Prev, } +pub trait Execute { + fn command (&mut self, command: T) -> Perhaps; +} + pub trait Command: Send + Sync + Sized { - fn translate (self, _: &S) -> Self { self } fn execute (self, state: &mut S) -> Perhaps; } pub fn delegate , S> ( diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index 7f4650e8..b8e3a89d 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -36,6 +36,9 @@ pub type ArrangerAppCommand = AppViewCommand; #[derive(Clone, Debug)] pub enum ArrangerViewCommand { + Scene(ArrangerSceneCommand), + Track(ArrangerTrackCommand), + Clip(ArrangerClipCommand), Edit(ArrangerCommand), Select(ArrangerSelection), Zoom(usize), @@ -217,6 +220,9 @@ impl Command> for ArrangerViewCommand { fn execute (self, state: &mut ArrangerApp) -> Perhaps { use ArrangerViewCommand::*; match self { + Scene(cmd) => { delegate(cmd, Scene, &mut state.app) }, + Track(cmd) => { delegate(cmd, Track, &mut state.app) }, + Clip(cmd) => { delegate(cmd, Clip, &mut state.app) }, Phrases(cmd) => { delegate(cmd, Phrases, &mut state.app) }, Editor(cmd) => { delegate(cmd, Editor, &mut state.app) }, Transport(cmd) => { delegate(cmd, Transport, &mut state.app) }, @@ -256,6 +262,45 @@ pub struct ArrangerView { size: Measure, } +impl HasJack for ArrangerView { + fn jack (&self) -> &Arc> { + &self.transport.jack() + } +} + +impl HasClock for ArrangerView { + fn clock (&self) -> &Arc { + &self.transport.clock() + } +} + +impl HasPhrases for ArrangerView { + fn phrases (&self) -> &Vec>> { + &self.phrases + } + fn phrases_mut (&mut self) -> &mut Vec>> { + &mut self.phrases + } +} + +impl HasTracks for ArrangerView { + fn tracks (&self) -> &Vec { + &self.tracks + } + fn tracks_mut (&mut self) -> &mut Vec { + &mut self.tracks + } +} + +impl HasScenes for ArrangerView { + fn scenes (&self) -> &Vec { + &self.scenes + } + fn scenes_mut (&mut self) -> &mut Vec { + &mut self.scenes + } +} + /// Display mode of arranger #[derive(Clone, PartialEq)] pub enum ArrangerMode {