diff --git a/crates/tek_api/src/api_color.rs b/crates/tek_api/src/api_color.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/tek_api/src/api_name.rs b/crates/tek_api/src/api_name.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index 5761b696..23c35717 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -10,10 +10,7 @@ impl TryFrom<&Arc>> for ArrangerApp { tracks: vec![], metronome: false, transport: jack.read().unwrap().transport(), - clock: Arc::new(Clock::from(Instant::default())), jack: jack.clone(), - sequencer: SequencerView::from(&model.sequencer), - split: 20, selected: ArrangerSelection::Clip(0, 0), mode: ArrangerMode::Vertical(2), color: Color::Rgb(28, 35, 25).into(), @@ -82,10 +79,10 @@ impl InputToCommand> for ArrangerAppCommand { }, _ => Self::App(match view.focused() { Content(ArrangerViewFocus::Transport) => Transport( - TransportCommand::input_to_command(&view.app.sequencer.transport, input)? + TransportCommand::input_to_command(&view.app.transport, input)? ), Content(ArrangerViewFocus::PhraseEditor) => Editor( - PhraseEditorCommand::input_to_command(&view.app.sequencer.editor, input)? + PhraseEditorCommand::input_to_command(&view.app.editor, input)? ), Content(ArrangerViewFocus::PhrasePool) => match input.event() { key!(KeyCode::Char('e')) => EditPhrase( @@ -107,28 +104,28 @@ impl InputToCommand> for ArrangerAppCommand { _ => match input.event() { // FIXME: boundary conditions - key!(KeyCode::Up) => match view.selected { + key!(KeyCode::Up) => match view.app.selected { Select::Mix => return None, Select::Track(t) => return None, Select::Scene(s) => Select(Select::Scene(s - 1)), Select::Clip(t, s) => Select(Select::Clip(t, s - 1)), }, - key!(KeyCode::Down) => match view.selected { + key!(KeyCode::Down) => match view.app.selected { Select::Mix => Select(Select::Scene(0)), Select::Track(t) => Select(Select::Clip(t, 0)), Select::Scene(s) => Select(Select::Scene(s + 1)), Select::Clip(t, s) => Select(Select::Clip(t, s + 1)), }, - key!(KeyCode::Left) => match view.selected { + key!(KeyCode::Left) => match view.app.selected { Select::Mix => return None, Select::Track(t) => Select(Select::Track(t - 1)), Select::Scene(s) => return None, Select::Clip(t, s) => Select(Select::Clip(t - 1, s)), }, - key!(KeyCode::Right) => match view.selected { + key!(KeyCode::Right) => match view.app.selected { Select::Mix => return None, Select::Track(t) => Select(Select::Track(t + 1)), Select::Scene(s) => Select(Select::Clip(0, s)), @@ -145,42 +142,42 @@ impl InputToCommand> for ArrangerAppCommand { key!(KeyCode::Char('`')) => { todo!("toggle view mode") }, - key!(KeyCode::Char(',')) => match view.selected { + key!(KeyCode::Char(',')) => match view.app.selected { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t - 1)), Select::Scene(s) => Scene(Scene::Swap(s, s - 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, - key!(KeyCode::Char('.')) => match view.selected { + key!(KeyCode::Char('.')) => match view.app.selected { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t + 1)), Select::Scene(s) => Scene(Scene::Swap(s, s + 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, - key!(KeyCode::Char('<')) => match view.selected { + key!(KeyCode::Char('<')) => match view.app.selected { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t - 1)), Select::Scene(s) => Scene(Scene::Swap(s, s - 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, - key!(KeyCode::Char('>')) => match view.selected { + key!(KeyCode::Char('>')) => match view.app.selected { Select::Mix => Zoom(0), Select::Track(t) => Track(Track::Swap(t, t + 1)), Select::Scene(s) => Scene(Scene::Swap(s, s + 1)), Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, - key!(KeyCode::Enter) => match view.selected { + key!(KeyCode::Enter) => match view.app.selected { Select::Mix => return None, Select::Track(t) => return None, Select::Scene(s) => Scene(Scene::Play(s)), Select::Clip(t, s) => return None, }, - key!(KeyCode::Delete) => match view.selected { + key!(KeyCode::Delete) => match view.app.selected { Select::Mix => Clear, Select::Track(t) => Track(Track::Delete(t)), Select::Scene(s) => Scene(Scene::Delete(s)), @@ -189,12 +186,12 @@ impl InputToCommand> for ArrangerAppCommand { key!(KeyCode::Char('c')) => Clip(Clip::RandomColor), - key!(KeyCode::Char('s')) => match view.selected { + key!(KeyCode::Char('s')) => match view.app.selected { Select::Clip(t, s) => Clip(Clip::Set(t, s, None)), _ => return None, }, - key!(KeyCode::Char('g')) => match view.selected { + key!(KeyCode::Char('g')) => match view.app.selected { Select::Clip(t, s) => Clip(Clip::Get(t, s)), _ => return None, }, @@ -242,7 +239,7 @@ impl Command> for ArrangerViewCommand { Zoom(zoom) => { todo!(); }, Select(selected) => { state.selected = selected; Ok(None) }, EditPhrase(phrase) => { - state.sequencer.editor.phrase = phrase.clone(); + state.editor.phrase = phrase.clone(); state.focus(ArrangerViewFocus::PhraseEditor); state.focus_enter(); Ok(None) @@ -266,7 +263,6 @@ pub struct ArrangerView { tracks: Vec, scenes: Vec, name: Arc>, - sequencer: SequencerView, splits: [u16;2], selected: ArrangerSelection, mode: ArrangerMode, @@ -423,7 +419,7 @@ impl Content for ArrangerView { fn content (&self) -> impl Widget { Split::up( 1, - widget(&self.sequencer.transport), + widget(&self.transport), Split::down( self.split, lay!( @@ -450,9 +446,9 @@ impl Content for ArrangerView { .push_x(1), ), Split::right( - self.sequencer.split, - widget(&self.sequencer.phrases), - widget(&self.sequencer.editor), + self.split, + widget(&self.phrases), + widget(&self.editor), ) ) ) @@ -464,12 +460,12 @@ impl ArrangerView { /// Focus the editor with the current phrase pub fn show_phrase (&mut self) { - self.sequencer.editor.show(self.selected_phrase().as_ref()); + self.editor.show(self.selected_phrase().as_ref()); } pub fn activate (&mut self) { - let scenes = self.model.scenes(); - let tracks = self.model.tracks_mut(); + let scenes = self.scenes(); + let tracks = self.tracks_mut(); match self.selected { ArrangerSelection::Scene(s) => { for (t, track) in tracks.iter_mut().enumerate() { @@ -499,9 +495,9 @@ impl ArrangerView { pub fn is_last_row (&self) -> bool { let selected = self.selected; - (self.model.scenes().len() == 0 && (selected.is_mix() || selected.is_track())) || match selected { - ArrangerSelection::Scene(s) => s == self.model.scenes().len() - 1, - ArrangerSelection::Clip(_, s) => s == self.model.scenes().len() - 1, + (self.scenes().len() == 0 && (selected.is_mix() || selected.is_track())) || match selected { + ArrangerSelection::Scene(s) => s == self.scenes().len() - 1, + ArrangerSelection::Clip(_, s) => s == self.scenes().len() - 1, _ => false } } @@ -518,23 +514,23 @@ impl ArrangerView { self.color = ItemColor::random_dark() }, ArrangerSelection::Track(t) => { - self.model.tracks_mut()[t].color = ItemColor::random() + self.tracks_mut()[t].color = ItemColor::random() }, ArrangerSelection::Scene(s) => { - self.model.scenes_mut()[s].color = ItemColor::random() + self.scenes_mut()[s].color = ItemColor::random() }, ArrangerSelection::Clip(t, s) => { - if let Some(phrase) = &self.model.scenes_mut()[s].clips[t] { + if let Some(phrase) = &self.scenes_mut()[s].clips[t] { phrase.write().unwrap().color = ItemColorTriplet::random(); } } } } pub fn selected_scene (&self) -> Option<&ArrangerScene> { - self.selected.scene().map(|s|self.model.scenes().get(s)).flatten() + self.selected.scene().map(|s|self.scenes().get(s)).flatten() } pub fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> { - self.selected.scene().map(|s|self.model.scenes_mut().get_mut(s)).flatten() + self.selected.scene().map(|s|self.scenes_mut().get_mut(s)).flatten() } pub fn selected_phrase (&self) -> Option>> { self.selected_scene()?.clips.get(self.selected.track()?)?.clone() @@ -543,28 +539,28 @@ impl ArrangerView { impl Audio for ArrangerView { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - if self.model.process(client, scope) == Control::Quit { + if self.process(client, scope) == Control::Quit { return Control::Quit } // FIXME: one of these per playing track if let ArrangerSelection::Clip(t, s) = self.selected { - let phrase = self.model.scenes().get(s).map(|scene|scene.clips.get(t)); + let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t)); if let Some(Some(Some(phrase))) = phrase { - if let Some(track) = self.model.tracks().get(t) { + if let Some(track) = self.tracks().get(t) { 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.sequencer.transport.model.clock().current.pulse.get(); + let pulse = self.current().pulse.get(); let start = started_at.pulse.get(); let now = (pulse - start) % phrase.length as f64; - self.sequencer.editor.now.set(now); + self.editor.now.set(now); return Control::Continue } } } } } - self.sequencer.editor.now.set(0.); + self.editor.now.set(0.); return Control::Continue } } @@ -610,12 +606,12 @@ impl Audio for ArrangerView { ///// Focus the editor with the current phrase //pub fn edit_phrase (&mut self) { //if self.arrangement.selected.is_clip() && self.arrangement.phrase().is_none() { - //self.sequencer.phrases.append_new(None, Some(self.next_color().into())); + //self.phrases.append_new(None, Some(self.next_color().into())); //self.arrangement.phrase_put(); //} //self.show_phrase(); //self.focus(ArrangerViewFocus::PhraseEditor); - //self.sequencer.editor.entered = true; + //self.editor.entered = true; //} //pub fn next_color (&self) -> ItemColor { @@ -666,15 +662,15 @@ impl FocusGrid for ArrangerApp { let focused = self.focused(); if !self.entered { self.entered = focused == Content(Arranger); - self.app.sequencer.editor.entered = focused == Content(PhraseEditor); - self.app.sequencer.phrases.entered = focused == Content(PhrasePool); + self.app.editor.entered = focused == Content(PhraseEditor); + self.app.phrases.entered = focused == Content(PhrasePool); } } fn focus_exit (&mut self) { if self.entered { self.entered = false; - self.app.sequencer.editor.entered = false; - self.app.sequencer.phrases.entered = false; + self.app.editor.entered = false; + self.app.phrases.entered = false; } } fn entered (&self) -> Option { @@ -699,14 +695,14 @@ impl FocusGrid for ArrangerApp { use ArrangerViewFocus::*; let focused = self.focused(); self.app.focused = focused == Content(Arranger); - self.app.sequencer.transport.focused = focused == Content(Transport); - self.app.sequencer.phrases.focused = focused == Content(PhrasePool); - self.app.sequencer.editor.focused = focused == Content(PhraseEditor); + self.app.transport.focused = focused == Content(Transport); + self.app.phrases.focused = focused == Content(PhrasePool); + self.app.editor.focused = focused == Content(PhraseEditor); if let Some(mut status_bar) = self.status_bar { status_bar.update(&( self.focused(), self.app.selected, - self.app.sequencer.editor.entered + self.app.editor.entered )) } } @@ -990,9 +986,9 @@ pub fn arranger_content_vertical ( view: &ArrangerView, factor: usize ) -> impl Widget + use<'_> { - let clock = view.model.clock(); - let tracks = view.model.tracks(); - let scenes = view.model.scenes(); + let clock = view.clock(); + let tracks = view.tracks(); + let scenes = view.scenes(); let cols = track_widths(tracks); let rows = ArrangerScene::ppqs(scenes, factor); let bg = view.color; @@ -1030,14 +1026,13 @@ pub fn arranger_content_vertical ( let header = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{ // name and width of track let name = track.name.read().unwrap(); - let player = &track.player; let max_w = w.saturating_sub(1).min(name.len()).max(2); let name = format!("▎{}", &name[0..max_w]); let name = TuiStyle::bold(name, true); // beats elapsed - let elapsed = if let Some((_, Some(phrase))) = player.phrase.as_ref() { + let elapsed = if let Some((_, Some(phrase))) = track.phrase().as_ref() { let length = phrase.read().unwrap().length; - let elapsed = player.pulses_since_start().unwrap(); + let elapsed = track.pulses_since_start().unwrap(); let elapsed = clock.timebase().format_beats_1_short( (elapsed as usize % length) as f64 ); @@ -1046,7 +1041,7 @@ pub fn arranger_content_vertical ( String::from("▎") }; // beats until switchover - let until_next = player.next_phrase.as_ref().map(|(t, _)|{ + let until_next = track.next_phrase().as_ref().map(|(t, _)|{ let target = t.pulse.get(); let current = clock.current.pulse.get(); if target > current { @@ -1057,12 +1052,12 @@ pub fn arranger_content_vertical ( } }).unwrap_or(String::from("▎")); // name of active MIDI input - let input = format!("▎>{}", track.player.midi_inputs.get(0) + let input = format!("▎>{}", track.midi_inputs().get(0) .map(|port|port.short_name()) .transpose()? .unwrap_or("(none)".into())); // name of active MIDI output - let output = format!("▎<{}", track.player.midi_outputs.get(0) + let output = format!("▎<{}", track.midi_outputs().get(0) .map(|port|port.short_name()) .transpose()? .unwrap_or("(none)".into())); @@ -1095,7 +1090,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.player.phrase { + if let Some((_, Some(ref playing))) = track.phrase() { if *playing.read().unwrap() == *phrase.read().unwrap() { bg = color.light.rgb } @@ -1177,7 +1172,7 @@ pub fn arranger_content_horizontal ( view: &ArrangerView, ) -> impl Widget + use<'_> { let focused = view.focused; - let _tracks = view.model.tracks(); + let _tracks = view.tracks(); lay!( focused.then_some(Background(TuiTheme::border_bg())), row!( @@ -1333,7 +1328,7 @@ pub fn arranger_content_horizontal ( CustomWidget::new(|_|{todo!()}, |to: &mut TuiOutput|{ let [x, y, _, height] = to.area(); let mut x2 = 0; - Ok(for (scene_index, scene) in view.model.scenes().iter().enumerate() { + Ok(for (scene_index, scene) in view.scenes().iter().enumerate() { let active_scene = view.selected.scene() == Some(scene_index); let sep = Some(if active_scene { Style::default().yellow().not_dim() @@ -1376,7 +1371,7 @@ pub struct ArrangerScene { pub color: ItemColor, } -impl ArrangerSceneApi for ArrangerTrack { +impl ArrangerSceneApi for ArrangerScene { fn name (&self) -> &Arc> { &self.name } @@ -1453,5 +1448,11 @@ impl MidiInputApi for ArrangerTrack { impl MidiOutputApi for ArrangerTrack { } +impl ClockApi for ArrangerTrack { +} + +impl PlayheadApi for ArrangerTrack { +} + impl PlayerApi for ArrangerTrack { } diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index c3baf63d..8f564d58 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -4,15 +4,17 @@ impl TryFrom<&Arc>> for SequencerApp { type Error = Box; fn try_from (jack: &Arc>) -> Usually { let clock = Arc::new(Clock::from(Instant::default())); - Ok(Self::new(SequencerModel { + Ok(Self::new(SequencerView { phrases: vec![], - player: MIDIPlayer::new(jack, &clock, "preview")?, - transport: TransportModel { - metronome: false, - transport: jack.read().unwrap().transport(), - jack: jack.clone(), - clock: clock.clone() - }, + metronome: false, + transport: jack.read().unwrap().transport(), + jack: jack.clone(), + clock: Arc::new(Clock::from(Instant::default())), + focused: false, + focus: TransportViewFocus::PlayPause, + size: Measure::new(), + phrases: PhrasePoolView::from(&model.phrases()), + editor: PhraseEditor::new(), }.into(), None, None)) } } @@ -69,6 +71,26 @@ impl InputToCommand> for SequencerAppCommand { } } +impl Command> for SequencerAppCommand { + fn execute (self, state: &mut SequencerApp) -> Perhaps { + use AppViewCommand::*; + match self { + Focus(cmd) => delegate(cmd, Focus, state), + App(cmd) => delegate(cmd, App, state), + } + } +} + +impl Command> for SequencerViewCommand { + fn execute (self, state: &mut SequencerApp) -> Perhaps { + use SequencerViewCommand::*; + match self { + Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases), + Editor(cmd) => delegate(cmd, Editor, &mut state.editor), + Transport(cmd) => delegate(cmd, Transport, &mut state.transport) + } + } +} /// Root view for standalone `tek_sequencer`. pub struct SequencerView { @@ -129,18 +151,6 @@ pub enum SequencerStatusBar { PhraseEditor, } -impl From for SequencerView { - fn from (model: SequencerModel) -> Self { - Self { - split: 20, - transport: TransportView::from(&model.transport()), - phrases: PhrasePoolView::from(&model.phrases()), - editor: PhraseEditor::new(), - model, - } - } -} - impl Content for SequencerView { type Engine = Tui; fn content (&self) -> impl Widget { @@ -178,27 +188,6 @@ impl Content for SequencerStatusBar { } } -impl Command> for SequencerAppCommand { - fn execute (self, state: &mut SequencerApp) -> Perhaps { - use AppViewCommand::*; - match self { - Focus(cmd) => delegate(cmd, Focus, state), - App(cmd) => delegate(cmd, App, state), - } - } -} - -impl Command> for SequencerViewCommand { - fn execute (self, state: &mut SequencerApp) -> Perhaps { - use SequencerViewCommand::*; - match self { - Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases), - Editor(cmd) => delegate(cmd, Editor, &mut state.editor), - Transport(cmd) => delegate(cmd, Transport, &mut state.transport) - } - } -} - impl FocusGrid for SequencerApp { type Item = AppViewFocus; fn cursor (&self) -> (usize, usize) { @@ -241,13 +230,13 @@ impl FocusGrid for SequencerApp { } } -impl HasJack for SequencerView { +impl HasJack for SequencerView { fn jack (&self) -> &Arc> { self.transport.jack() } } -impl HasPhrases for SequencerView { +impl HasPhrases for SequencerView { fn phrases (&self) -> &Vec>> { &self.phrases } @@ -256,11 +245,23 @@ impl HasPhrases for SequencerView { } } -impl HasPlayer for SequencerView { - fn player (&self) -> &MIDIPlayer { - &self.player - } - fn player_mut (&mut self) -> &mut MIDIPlayer { - &mut self.player - } +impl HasMidiBuffer for SequencerView { +} + +impl HasPhrase for SequencerView { +} + +impl MidiInputApi for SequencerView { +} + +impl MidiOutputApi for SequencerView { +} + +impl ClockApi for SequencerView { +} + +impl PlayheadApi for SequencerView { +} + +impl PlayerApi for SequencerView { } diff --git a/crates/tek_tui/src/tui_transport.rs b/crates/tek_tui/src/tui_transport.rs index 8e9ba54e..4451c1a2 100644 --- a/crates/tek_tui/src/tui_transport.rs +++ b/crates/tek_tui/src/tui_transport.rs @@ -119,19 +119,18 @@ impl Command> for TransportAppCommand { impl Command> for TransportCommand { fn execute (self, state: &mut TransportApp) -> Perhaps { + use TransportCommand::{Clock, Playhead}; use ClockCommand::{SetBpm, SetQuant, SetSync}; - let clock = state.app.clock(); Ok(Some(match self { - SetBpm(bpm) => SetBpm(clock.timebase().bpm.set(bpm)), - SetQuant(quant) => SetQuant(clock.quant.set(quant)), - SetSync(sync) => SetSync(clock.sync.set(sync)), + Clock(SetBpm(bpm)) => Clock(SetBpm(state.timebase().bpm.set(bpm))), + Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))), + Clock(SetSync(sync)) => Clock(SetSync(state.sync().set(sync))), _ => return Ok(None) })) } } /// Stores and displays time-related info. -#[derive(Debug)] pub struct TransportView { _engine: PhantomData, jack: Arc>, @@ -154,21 +153,6 @@ pub struct TransportView { size: Measure, } -impl ClockApi for TransportView { - fn current (&self) -> &Instant { - &self.current - } - fn quant (&self) -> &Quantize { - &self.quant - } - fn sync (&self) -> &LaunchSync { - &self.sync - } -} - -impl PlayheadApi for TransportView { -} - /// Which item of the transport toolbar is focused #[derive(Clone, Copy, Debug, PartialEq)] pub enum TransportViewFocus { @@ -319,19 +303,28 @@ impl FocusGrid for TransportApp { } } -impl HasJack for TransportView { +impl HasJack for TransportView { fn jack (&self) -> &Arc> { &self.jack } } -impl HasClock for TransportView { - fn clock (&self) -> &Arc { - &self.clock +impl ClockApi for TransportView { + fn current (&self) -> &Instant { + &self.current + } + fn quant (&self) -> &Quantize { + &self.quant + } + fn sync (&self) -> &LaunchSync { + &self.sync } } -impl std::fmt::Debug for TransportView { +impl PlayheadApi for TransportView { +} + +impl std::fmt::Debug for TransportView { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), Error> { f.debug_struct("transport") .field("jack", &self.jack)