From 638298ad322415598d9c9243cf6e9a365bc9c76f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 15 Nov 2024 22:06:52 +0100 Subject: [PATCH] wip: refactor pt.42 (84e) lotta todo! --- crates/tek_api/src/api_clock.rs | 11 +- crates/tek_api/src/api_phrase.rs | 2 +- crates/tek_api/src/api_player.rs | 2 +- crates/tek_api/src/api_playhead.rs | 19 +- crates/tek_tui/src/lib.rs | 2 - crates/tek_tui/src/tui_arranger.rs | 155 +++++++++++--- crates/tek_tui/src/tui_pool.rs | 287 +++++++++++++++++++++++--- crates/tek_tui/src/tui_pool_length.rs | 177 ---------------- crates/tek_tui/src/tui_pool_rename.rs | 66 ------ crates/tek_tui/src/tui_sequencer.rs | 88 +++++++- crates/tek_tui/src/tui_transport.rs | 62 +++--- 11 files changed, 536 insertions(+), 335 deletions(-) delete mode 100644 crates/tek_tui/src/tui_pool_length.rs delete mode 100644 crates/tek_tui/src/tui_pool_rename.rs diff --git a/crates/tek_api/src/api_clock.rs b/crates/tek_api/src/api_clock.rs index 0ed1279c..1bd979e9 100644 --- a/crates/tek_api/src/api_clock.rs +++ b/crates/tek_api/src/api_clock.rs @@ -19,16 +19,13 @@ impl Command for ClockCommand { } pub trait ClockApi: Send + Sync { - /// Current moment in time - fn current (&self) -> &Instant; + /// Temporal resolution in all units + fn timebase (&self) -> &Arc; /// Note quantization factor - fn quant (&self) -> &Quantize; + fn quant (&self) -> &Quantize; /// Launch quantization factor - fn sync (&self) -> &LaunchSync; + fn sync (&self) -> &LaunchSync; - fn timebase (&self) -> &Arc { - &self.current().timebase - } fn sr (&self) -> &SampleRate { &self.timebase().sr } diff --git a/crates/tek_api/src/api_phrase.rs b/crates/tek_api/src/api_phrase.rs index da312755..a105ec3c 100644 --- a/crates/tek_api/src/api_phrase.rs +++ b/crates/tek_api/src/api_phrase.rs @@ -5,7 +5,7 @@ pub trait HasPhrases { fn phrases_mut (&mut self) -> &mut Vec>>; } -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug)] pub enum PhrasePoolCommand { Add(usize), Delete(usize), diff --git a/crates/tek_api/src/api_player.rs b/crates/tek_api/src/api_player.rs index aaa92f85..1169b6e6 100644 --- a/crates/tek_api/src/api_player.rs +++ b/crates/tek_api/src/api_player.rs @@ -27,7 +27,7 @@ pub trait HasMidiBuffer { } } -pub trait HasPhrase: ClockApi + PlayheadApi + HasMidiBuffer { +pub trait HasPhrase: PlayheadApi + HasMidiBuffer { fn phrase (&self) -> &Option<(Instant, Option>>)>; fn phrase_mut (&self) diff --git a/crates/tek_api/src/api_playhead.rs b/crates/tek_api/src/api_playhead.rs index 90ea73e3..b7023667 100644 --- a/crates/tek_api/src/api_playhead.rs +++ b/crates/tek_api/src/api_playhead.rs @@ -34,12 +34,24 @@ impl Command for PlayheadCommand { } pub trait PlayheadApi: ClockApi { + /// Current moment in time + fn current (&self) -> &Instant; + /// Handle to JACK transport fn transport (&self) -> &jack::Transport; /// Playback state fn playing (&self) -> &RwLock>; /// Global sample and usec at which playback started fn started (&self) -> &RwLock>; + fn is_stopped (&self) -> bool { + *self.playing().read().unwrap() == Some(TransportState::Stopped) + } + + fn is_rolling (&self) -> bool { + *self.playing().read().unwrap() == Some(TransportState::Rolling) + } + + /// Current pulse fn pulse (&self) -> f64 { self.current().pulse.get() } @@ -67,13 +79,6 @@ pub trait PlayheadApi: ClockApi { Ok(()) } - fn is_stopped (&self) -> bool { - *self.playing().read().unwrap() == Some(TransportState::Stopped) - } - - fn is_rolling (&self) -> bool { - *self.playing().read().unwrap() == Some(TransportState::Rolling) - } } /// Hosts the JACK callback for updating the temporal pointer and playback status. diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index a9166c08..27af547b 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -21,8 +21,6 @@ submod! { //tui_plugin_vst2 //tui_plugin_vst3 tui_pool - tui_pool_length - tui_pool_rename //tui_sampler // TODO //tui_sampler_cmd tui_sequencer diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index 23c35717..dcbeb0ff 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -28,7 +28,7 @@ pub type ArrangerApp = AppView< ArrangerStatusBar >; -impl Audio for ArrangerApp { +impl Audio for ArrangerApp { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { ArrangerRefAudio(self.app).process(client, scope) } @@ -78,9 +78,19 @@ impl InputToCommand> for ArrangerAppCommand { Self::App(Playhead(PlayheadCommand::Play(None))) }, _ => Self::App(match view.focused() { - Content(ArrangerViewFocus::Transport) => Transport( - TransportCommand::input_to_command(&view.app.transport, input)? - ), + Content(ArrangerViewFocus::Transport) => { + match TransportCommand::input_to_command(&view.app.transport, input)? { + Focus(command) => { + todo!() + }, + App(Clock(command)) => { + todo!() + }, + App(Playhead(command)) => { + todo!() + }, + } + }, Content(ArrangerViewFocus::PhraseEditor) => Editor( PhraseEditorCommand::input_to_command(&view.app.editor, input)? ), @@ -280,8 +290,8 @@ impl HasJack for ArrangerView { } impl ClockApi for ArrangerView { - fn current (&self) -> &Instant { - &self.current + fn timebase (&self) -> &Arc { + &self.current.timebase } fn quant (&self) -> &Quantize { &self.quant @@ -291,6 +301,21 @@ impl ClockApi for ArrangerView { } } +impl PlayheadApi for ArrangerView { + fn current (&self) -> &Instant { + &self.current + } + fn transport (&self) -> &jack::Transport { + &self.transport + } + fn playing (&self) -> &RwLock> { + &self.playing + } + fn started (&self) -> &RwLock> { + &self.started + } +} + impl HasPhrases for ArrangerView { fn phrases (&self) -> &Vec>> { &self.phrases @@ -986,16 +1011,17 @@ pub fn arranger_content_vertical ( view: &ArrangerView, factor: usize ) -> impl Widget + use<'_> { - 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; - let clip_bg = TuiTheme::border_bg(); - let sep_fg = TuiTheme::separator_fg(false); - let header_h = 3u16;//5u16; - let scenes_w = 3 + ArrangerScene::longest_name(scenes) as u16; // x of 1st track + let timebase = view.timebase(); + let current = view.current(); + let tracks = view.tracks(); + let scenes = view.scenes(); + let cols = track_widths(tracks); + let rows = ArrangerScene::ppqs(scenes, factor); + let bg = view.color; + let clip_bg = TuiTheme::border_bg(); + let sep_fg = TuiTheme::separator_fg(false); + let header_h = 3u16;//5u16; + let scenes_w = 3 + ArrangerScene::longest_name(scenes) as u16; // x of 1st track let arrangement = Layers::new(move |add|{ let rows: &[(usize, usize)] = rows.as_ref(); let cols: &[(usize, usize)] = cols.as_ref(); @@ -1033,7 +1059,7 @@ pub fn arranger_content_vertical ( let elapsed = if let Some((_, Some(phrase))) = track.phrase().as_ref() { let length = phrase.read().unwrap().length; let elapsed = track.pulses_since_start().unwrap(); - let elapsed = clock.timebase().format_beats_1_short( + let elapsed = timebase.format_beats_1_short( (elapsed as usize % length) as f64 ); format!("▎+{elapsed:>}") @@ -1043,21 +1069,21 @@ pub fn arranger_content_vertical ( // beats until switchover let until_next = track.next_phrase().as_ref().map(|(t, _)|{ let target = t.pulse.get(); - let current = clock.current.pulse.get(); + let current = current.pulse.get(); if target > current { let remaining = target - current; - format!("▎-{:>}", clock.timebase().format_beats_0_short(remaining)) + format!("▎-{:>}", timebase.format_beats_0_short(remaining)) } else { String::new() } }).unwrap_or(String::from("▎")); // name of active MIDI input - let input = format!("▎>{}", track.midi_inputs().get(0) + let input = format!("▎>{}", track.midi_ins().get(0) .map(|port|port.short_name()) .transpose()? .unwrap_or("(none)".into())); // name of active MIDI output - let output = format!("▎<{}", track.midi_outputs().get(0) + let output = format!("▎<{}", track.midi_outs().get(0) .map(|port|port.short_name()) .transpose()? .unwrap_or("(none)".into())); @@ -1437,22 +1463,105 @@ impl ArrangerTrackApi for ArrangerTrack { } impl HasMidiBuffer for ArrangerTrack { + fn midi_buffer (&self) -> &Vec>> { + todo!() + } + fn midi_buffer_mut (&self) -> &mut Vec>> { + todo!() + } + fn reset (&self) -> bool { + todo!() + } + fn reset_mut (&mut self) -> &mut bool { + todo!() + } } impl HasPhrase for ArrangerTrack { + fn phrase (&self) -> &Option<(Instant, Option>>)> { + todo!() + } + fn phrase_mut (&self) -> &mut Option<(Instant, Option>>)> { + todo!() + } + fn next_phrase (&self) -> &Option<(Instant, Option>>)> { + todo!() + } + fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)> { + todo!() + } } impl MidiInputApi for ArrangerTrack { + fn midi_ins(&self) -> &Vec> { + todo!() + } + fn midi_ins_mut(&self) -> &mut Vec> { + todo!() + } + fn recording(&self) -> bool { + todo!() + } + fn recording_mut(&mut self) -> &mut bool { + todo!() + } + fn monitoring(&self) -> bool { + todo!() + } + fn monitoring_mut(&mut self) -> &mut bool { + todo!() + } + fn overdub(&self) -> bool { + todo!() + } + fn overdub_mut(&mut self) -> &mut bool { + todo!() + } + fn notes_in(&self) -> &Arc> { + todo!() + } } impl MidiOutputApi for ArrangerTrack { + fn midi_outs (&self) -> &Vec> { + todo!() + } + fn midi_outs_mut (&mut self) -> &mut Vec> { + todo!() + } + fn midi_note (&mut self) -> &mut Vec { + todo!() + } + fn notes_out (&self) -> &Arc> { + todo!() + } } impl ClockApi for ArrangerTrack { + fn timebase (&self) -> &Arc { + todo!() + } + fn quant (&self) -> &Quantize { + todo!() + } + fn sync (&self) -> &LaunchSync { + todo!() + } } impl PlayheadApi for ArrangerTrack { + fn current(&self) -> &Instant { + todo!() + } + fn transport(&self) -> &Transport { + todo!() + } + fn playing(&self) -> &RwLock> { + todo!() + } + fn started(&self) -> &RwLock> { + todo!() + } } -impl PlayerApi for ArrangerTrack { -} +impl PlayerApi for ArrangerTrack {} diff --git a/crates/tek_tui/src/tui_pool.rs b/crates/tek_tui/src/tui_pool.rs index 7c8f8e90..3e6120a0 100644 --- a/crates/tek_tui/src/tui_pool.rs +++ b/crates/tek_tui/src/tui_pool.rs @@ -3,7 +3,7 @@ use crate::*; pub struct PhrasePoolView { _engine: PhantomData, /// Collection of phrases - pub model: PhrasePoolModel, + pub phrases: Vec>>, /// Selected phrase pub phrase: usize, /// Scroll offset @@ -25,7 +25,7 @@ pub enum PhrasePoolMode { } impl PhrasePoolView { - pub fn new (model: PhrasePoolModel) -> Self { + pub fn new (phrases: Vec>>) -> Self { Self { _engine: Default::default(), scroll: 0, @@ -33,14 +33,14 @@ impl PhrasePoolView { mode: None, focused: false, entered: false, - model, + phrases, } } pub fn len (&self) -> usize { - self.model.phrases.len() + self.phrases.len() } pub fn phrase (&self) -> &Arc> { - &self.model.phrases[self.phrase] + &self.phrases[self.phrase] } pub fn index_before (&self, index: usize) -> usize { index.overflowing_sub(1).0.min(self.len() - 1) @@ -49,8 +49,8 @@ impl PhrasePoolView { (index + 1) % self.len() } pub fn index_of (&self, phrase: &Phrase) -> Option { - for i in 0..self.model.phrases.len() { - if *self.model.phrases[i].read().unwrap() == *phrase { return Some(i) } + for i in 0..self.phrases.len() { + if *self.phrases[i].read().unwrap() == *phrase { return Some(i) } } return None } @@ -61,33 +61,33 @@ impl PhrasePoolView { } pub fn delete_selected (&mut self) { if self.phrase > 0 { - self.model.phrases.remove(self.phrase); - self.phrase = self.phrase.min(self.model.phrases.len().saturating_sub(1)); + self.phrases.remove(self.phrase); + self.phrase = self.phrase.min(self.phrases.len().saturating_sub(1)); } } pub fn append_new (&mut self, name: Option<&str>, color: Option) { - self.model.phrases.push(Self::new_phrase(name, color)); - self.phrase = self.model.phrases.len() - 1; + self.phrases.push(Self::new_phrase(name, color)); + self.phrase = self.phrases.len() - 1; } pub fn insert_new (&mut self, name: Option<&str>, color: Option) { - self.model.phrases.insert(self.phrase + 1, Self::new_phrase(name, color)); + self.phrases.insert(self.phrase + 1, Self::new_phrase(name, color)); self.phrase += 1; } pub fn insert_dup (&mut self) { - let mut phrase = self.model.phrases[self.phrase].read().unwrap().duplicate(); + let mut phrase = self.phrases[self.phrase].read().unwrap().duplicate(); phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25); - self.model.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); + self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); self.phrase += 1; } pub fn move_up (&mut self) { if self.phrase > 1 { - self.model.phrases.swap(self.phrase - 1, self.phrase); + self.phrases.swap(self.phrase - 1, self.phrase); self.phrase -= 1; } } pub fn move_down (&mut self) { - if self.phrase < self.model.phrases.len().saturating_sub(1) { - self.model.phrases.swap(self.phrase + 1, self.phrase); + if self.phrase < self.phrases.len().saturating_sub(1) { + self.phrases.swap(self.phrase + 1, self.phrase); self.phrase += 1; } } @@ -97,9 +97,9 @@ impl PhrasePoolView { impl Content for PhrasePoolView { type Engine = Tui; fn content (&self) -> impl Widget { - let Self { focused, model, mode, .. } = self; + let Self { focused, phrases, mode, .. } = self; let content = col!( - (i, phrase) in model.phrases.iter().enumerate() => Layers::new(|add|{ + (i, phrase) in phrases.iter().enumerate() => Layers::new(|add|{ let Phrase { ref name, color, length, .. } = *phrase.read().unwrap(); let mut length = PhraseLength::new(length, None); if let Some(PhrasePoolMode::Length(phrase, new_length, focus)) = mode { @@ -124,7 +124,7 @@ impl Content for PhrasePoolView { let content = content.fill_xy().bg(Color::Rgb(28, 35, 25)).border(border); let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)}; let upper_left = format!("[{}] Phrases", if self.entered {"■"} else {" "}); - let upper_right = format!("({})", model.phrases.len()); + let upper_right = format!("({})", phrases.len()); lay!( content, TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw().fill_xy(), @@ -187,13 +187,13 @@ impl Command> for PhrasePoolViewCommand { view.phrase = phrase }, Self::Edit(command) => { - return Ok(command.execute(&mut view.model)?.map(Self::Edit)) + return Ok(command.execute(&mut view)?.map(Self::Edit)) } Self::Rename(command) => match command { Rename::Begin => { view.mode = Some(PhrasePoolMode::Rename( view.phrase, - view.model.phrases[view.phrase].read().unwrap().name.clone() + view.phrases[view.phrase].read().unwrap().name.clone() )) }, _ => { @@ -204,7 +204,7 @@ impl Command> for PhrasePoolViewCommand { Length::Begin => { view.mode = Some(PhrasePoolMode::Length( view.phrase, - view.model.phrases[view.phrase].read().unwrap().length, + view.phrases[view.phrase].read().unwrap().length, PhraseLengthFocus::Bar )) }, @@ -216,3 +216,244 @@ impl Command> for PhrasePoolViewCommand { Ok(None) } } + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum PhraseLengthCommand { + Begin, + Next, + Prev, + Inc, + Dec, + Set(usize), + Cancel, +} + +impl InputToCommand> for PhraseLengthCommand { + fn input_to_command (view: &PhrasePoolView, from: &TuiInput) -> Option { + if let Some(PhrasePoolMode::Length(_, length, _)) = view.mode { + Some(match from.event() { + key!(KeyCode::Up) => Self::Inc, + key!(KeyCode::Down) => Self::Dec, + key!(KeyCode::Right) => Self::Next, + key!(KeyCode::Left) => Self::Prev, + key!(KeyCode::Enter) => Self::Set(length), + key!(KeyCode::Esc) => Self::Cancel, + _ => return None + }) + } else { + unreachable!() + } + } +} + +impl Command> for PhraseLengthCommand { + fn execute (self, view: &mut PhrasePoolView) -> Perhaps { + use PhraseLengthFocus::*; + use PhraseLengthCommand::*; + if let Some(PhrasePoolMode::Length(phrase, ref mut length, ref mut focus)) = view.mode { + match self { + Self::Cancel => { + view.mode = None; + }, + Self::Prev => { + focus.prev() + }, + Self::Next => { + focus.next() + }, + Self::Inc => match focus { + Bar => { *length += 4 * PPQ }, + Beat => { *length += PPQ }, + Tick => { *length += 1 }, + }, + Self::Dec => match focus { + Bar => { *length = length.saturating_sub(4 * PPQ) }, + Beat => { *length = length.saturating_sub(PPQ) }, + Tick => { *length = length.saturating_sub(1) }, + }, + Self::Set(length) => { + let mut phrase = view.phrases[phrase].write().unwrap(); + let old_length = phrase.length; + phrase.length = length; + view.mode = None; + return Ok(Some(Self::Set(old_length))) + }, + _ => unreachable!() + } + Ok(None) + } else if self == Begin { + view.mode = Some(PhrasePoolMode::Length( + view.phrase, + view.phrases[view.phrase].read().unwrap().length, + PhraseLengthFocus::Bar + )); + Ok(None) + } else { + unreachable!() + } + } +} + +/// Displays and edits phrase length. +pub struct PhraseLength { + _engine: PhantomData, + /// Pulses per beat (quaver) + pub ppq: usize, + /// Beats per bar + pub bpb: usize, + /// Length of phrase in pulses + pub pulses: usize, + /// Selected subdivision + pub focus: Option, +} + +impl PhraseLength { + pub fn new (pulses: usize, focus: Option) -> Self { + Self { _engine: Default::default(), ppq: PPQ, bpb: 4, pulses, focus } + } + pub fn bars (&self) -> usize { + self.pulses / (self.bpb * self.ppq) + } + pub fn beats (&self) -> usize { + (self.pulses % (self.bpb * self.ppq)) / self.ppq + } + pub fn ticks (&self) -> usize { + self.pulses % self.ppq + } + pub fn bars_string (&self) -> String { + format!("{}", self.bars()) + } + pub fn beats_string (&self) -> String { + format!("{}", self.beats()) + } + pub fn ticks_string (&self) -> String { + format!("{:>02}", self.ticks()) + } +} + +impl Content for PhraseLength { + type Engine = Tui; + fn content (&self) -> impl Widget { + Layers::new(move|add|{ + match self.focus { + None => add(&row!( + " ", self.bars_string(), + ".", self.beats_string(), + ".", self.ticks_string(), + " " + )), + Some(PhraseLengthFocus::Bar) => add(&row!( + "[", self.bars_string(), + "]", self.beats_string(), + ".", self.ticks_string(), + " " + )), + Some(PhraseLengthFocus::Beat) => add(&row!( + " ", self.bars_string(), + "[", self.beats_string(), + "]", self.ticks_string(), + " " + )), + Some(PhraseLengthFocus::Tick) => add(&row!( + " ", self.bars_string(), + ".", self.beats_string(), + "[", self.ticks_string(), + "]" + )), + } + }) + } +} + +/// Focused field of `PhraseLength` +#[derive(Copy, Clone)] +pub enum PhraseLengthFocus { + /// Editing the number of bars + Bar, + /// Editing the number of beats + Beat, + /// Editing the number of ticks + Tick, +} + +impl PhraseLengthFocus { + pub fn next (&mut self) { + *self = match self { + Self::Bar => Self::Beat, + Self::Beat => Self::Tick, + Self::Tick => Self::Bar, + } + } + pub fn prev (&mut self) { + *self = match self { + Self::Bar => Self::Tick, + Self::Beat => Self::Bar, + Self::Tick => Self::Beat, + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum PhraseRenameCommand { + Begin, + Set(String), + Confirm, + Cancel, +} + +impl InputToCommand> for PhraseRenameCommand { + fn input_to_command (view: &PhrasePoolView, from: &TuiInput) -> Option { + if let Some(PhrasePoolMode::Rename(_, ref old_name)) = view.mode { + Some(match from.event() { + key!(KeyCode::Char(c)) => { + let mut new_name = old_name.clone(); + new_name.push(*c); + Self::Set(new_name) + }, + key!(KeyCode::Backspace) => { + let mut new_name = old_name.clone(); + new_name.pop(); + Self::Set(new_name) + }, + key!(KeyCode::Enter) => Self::Confirm, + key!(KeyCode::Esc) => Self::Cancel, + _ => return None + }) + } else { + unreachable!() + } + } +} + +impl Command> for PhraseRenameCommand { + fn execute (self, view: &mut PhrasePoolView) -> Perhaps { + use PhraseRenameCommand::*; + if let Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) = view.mode { + match self { + Set(s) => { + view.phrases[phrase].write().unwrap().name = s.into(); + return Ok(Some(Self::Set(old_name.clone()))) + }, + Confirm => { + let old_name = old_name.clone(); + view.mode = None; + return Ok(Some(Self::Set(old_name))) + }, + Cancel => { + let mut phrase = view.phrases[phrase].write().unwrap(); + phrase.name = old_name.clone(); + }, + _ => unreachable!() + }; + Ok(None) + } else if self == Begin { + view.mode = Some(PhrasePoolMode::Rename( + view.phrase, + view.phrases[view.phrase].read().unwrap().name.clone() + )); + Ok(None) + } else { + unreachable!() + } + } +} diff --git a/crates/tek_tui/src/tui_pool_length.rs b/crates/tek_tui/src/tui_pool_length.rs deleted file mode 100644 index 969017ea..00000000 --- a/crates/tek_tui/src/tui_pool_length.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::*; - -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum PhraseLengthCommand { - Begin, - Next, - Prev, - Inc, - Dec, - Set(usize), - Cancel, -} - -impl InputToCommand> for PhraseLengthCommand { - fn input_to_command (view: &PhrasePoolView, from: &TuiInput) -> Option { - if let Some(PhrasePoolMode::Length(_, length, _)) = view.mode { - Some(match from.event() { - key!(KeyCode::Up) => Self::Inc, - key!(KeyCode::Down) => Self::Dec, - key!(KeyCode::Right) => Self::Next, - key!(KeyCode::Left) => Self::Prev, - key!(KeyCode::Enter) => Self::Set(length), - key!(KeyCode::Esc) => Self::Cancel, - _ => return None - }) - } else { - unreachable!() - } - } -} - -impl Command> for PhraseLengthCommand { - fn execute (self, view: &mut PhrasePoolView) -> Perhaps { - use PhraseLengthFocus::*; - use PhraseLengthCommand::*; - if let Some(PhrasePoolMode::Length(phrase, ref mut length, ref mut focus)) = view.mode { - match self { - Self::Cancel => { - view.mode = None; - }, - Self::Prev => { - focus.prev() - }, - Self::Next => { - focus.next() - }, - Self::Inc => match focus { - Bar => { *length += 4 * PPQ }, - Beat => { *length += PPQ }, - Tick => { *length += 1 }, - }, - Self::Dec => match focus { - Bar => { *length = length.saturating_sub(4 * PPQ) }, - Beat => { *length = length.saturating_sub(PPQ) }, - Tick => { *length = length.saturating_sub(1) }, - }, - Self::Set(length) => { - let mut phrase = view.model.phrases[phrase].write().unwrap(); - let old_length = phrase.length; - phrase.length = length; - view.mode = None; - return Ok(Some(Self::Set(old_length))) - }, - _ => unreachable!() - } - Ok(None) - } else if self == Begin { - view.mode = Some(PhrasePoolMode::Length( - view.phrase, - view.model.phrases[view.phrase].read().unwrap().length, - PhraseLengthFocus::Bar - )); - Ok(None) - } else { - unreachable!() - } - } -} - -/// Displays and edits phrase length. -pub struct PhraseLength { - _engine: PhantomData, - /// Pulses per beat (quaver) - pub ppq: usize, - /// Beats per bar - pub bpb: usize, - /// Length of phrase in pulses - pub pulses: usize, - /// Selected subdivision - pub focus: Option, -} - -impl PhraseLength { - pub fn new (pulses: usize, focus: Option) -> Self { - Self { _engine: Default::default(), ppq: PPQ, bpb: 4, pulses, focus } - } - pub fn bars (&self) -> usize { - self.pulses / (self.bpb * self.ppq) - } - pub fn beats (&self) -> usize { - (self.pulses % (self.bpb * self.ppq)) / self.ppq - } - pub fn ticks (&self) -> usize { - self.pulses % self.ppq - } - pub fn bars_string (&self) -> String { - format!("{}", self.bars()) - } - pub fn beats_string (&self) -> String { - format!("{}", self.beats()) - } - pub fn ticks_string (&self) -> String { - format!("{:>02}", self.ticks()) - } -} - -impl Content for PhraseLength { - type Engine = Tui; - fn content (&self) -> impl Widget { - Layers::new(move|add|{ - match self.focus { - None => add(&row!( - " ", self.bars_string(), - ".", self.beats_string(), - ".", self.ticks_string(), - " " - )), - Some(PhraseLengthFocus::Bar) => add(&row!( - "[", self.bars_string(), - "]", self.beats_string(), - ".", self.ticks_string(), - " " - )), - Some(PhraseLengthFocus::Beat) => add(&row!( - " ", self.bars_string(), - "[", self.beats_string(), - "]", self.ticks_string(), - " " - )), - Some(PhraseLengthFocus::Tick) => add(&row!( - " ", self.bars_string(), - ".", self.beats_string(), - "[", self.ticks_string(), - "]" - )), - } - }) - } -} - -/// Focused field of `PhraseLength` -#[derive(Copy, Clone)] -pub enum PhraseLengthFocus { - /// Editing the number of bars - Bar, - /// Editing the number of beats - Beat, - /// Editing the number of ticks - Tick, -} - -impl PhraseLengthFocus { - pub fn next (&mut self) { - *self = match self { - Self::Bar => Self::Beat, - Self::Beat => Self::Tick, - Self::Tick => Self::Bar, - } - } - pub fn prev (&mut self) { - *self = match self { - Self::Bar => Self::Tick, - Self::Beat => Self::Bar, - Self::Tick => Self::Beat, - } - } -} diff --git a/crates/tek_tui/src/tui_pool_rename.rs b/crates/tek_tui/src/tui_pool_rename.rs deleted file mode 100644 index 4711eb1d..00000000 --- a/crates/tek_tui/src/tui_pool_rename.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::*; - -#[derive(Clone, Debug, PartialEq)] -pub enum PhraseRenameCommand { - Begin, - Set(String), - Confirm, - Cancel, -} - -impl InputToCommand> for PhraseRenameCommand { - fn input_to_command (view: &PhrasePoolView, from: &TuiInput) -> Option { - if let Some(PhrasePoolMode::Rename(_, ref old_name)) = view.mode { - Some(match from.event() { - key!(KeyCode::Char(c)) => { - let mut new_name = old_name.clone(); - new_name.push(*c); - Self::Set(new_name) - }, - key!(KeyCode::Backspace) => { - let mut new_name = old_name.clone(); - new_name.pop(); - Self::Set(new_name) - }, - key!(KeyCode::Enter) => Self::Confirm, - key!(KeyCode::Esc) => Self::Cancel, - _ => return None - }) - } else { - unreachable!() - } - } -} - -impl Command> for PhraseRenameCommand { - fn execute (self, view: &mut PhrasePoolView) -> Perhaps { - use PhraseRenameCommand::*; - if let Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) = view.mode { - match self { - Set(s) => { - view.model.phrases[phrase].write().unwrap().name = s.into(); - return Ok(Some(Self::Set(old_name.clone()))) - }, - Confirm => { - let old_name = old_name.clone(); - view.mode = None; - return Ok(Some(Self::Set(old_name))) - }, - Cancel => { - let mut phrase = view.model.phrases[phrase].write().unwrap(); - phrase.name = old_name.clone(); - }, - _ => unreachable!() - }; - Ok(None) - } else if self == Begin { - view.mode = Some(PhrasePoolMode::Rename( - view.phrase, - view.model.phrases[view.phrase].read().unwrap().name.clone() - )); - Ok(None) - } else { - unreachable!() - } - } -} diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index 8f564d58..972e9945 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -9,7 +9,6 @@ impl TryFrom<&Arc>> for SequencerApp { 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(), @@ -246,22 +245,105 @@ impl HasPhrases for SequencerView { } impl HasMidiBuffer for SequencerView { + fn midi_buffer (&self) -> &Vec>> { + todo!() + } + fn midi_buffer_mut (&self) -> &mut Vec>> { + todo!() + } + fn reset (&self) -> bool { + todo!() + } + fn reset_mut (&mut self) -> &mut bool { + todo!() + } } impl HasPhrase for SequencerView { + fn phrase (&self) -> &Option<(Instant, Option>>)> { + todo!() + } + fn phrase_mut (&self) -> &mut Option<(Instant, Option>>)> { + todo!() + } + fn next_phrase (&self) -> &Option<(Instant, Option>>)> { + todo!() + } + fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option>>)> { + todo!() + } } impl MidiInputApi for SequencerView { + fn midi_ins(&self) -> &Vec> { + todo!() + } + fn midi_ins_mut(&self) -> &mut Vec> { + todo!() + } + fn recording(&self) -> bool { + todo!() + } + fn recording_mut(&mut self) -> &mut bool { + todo!() + } + fn monitoring(&self) -> bool { + todo!() + } + fn monitoring_mut(&mut self) -> &mut bool { + todo!() + } + fn overdub(&self) -> bool { + todo!() + } + fn overdub_mut(&mut self) -> &mut bool { + todo!() + } + fn notes_in(&self) -> &Arc> { + todo!() + } } impl MidiOutputApi for SequencerView { + fn midi_outs (&self) -> &Vec> { + todo!() + } + fn midi_outs_mut (&mut self) -> &mut Vec> { + todo!() + } + fn midi_note (&mut self) -> &mut Vec { + todo!() + } + fn notes_out (&self) -> &Arc> { + todo!() + } } impl ClockApi for SequencerView { + fn timebase (&self) -> &Arc { + todo!() + } + fn quant (&self) -> &Quantize { + todo!() + } + fn sync (&self) -> &LaunchSync { + todo!() + } } impl PlayheadApi for SequencerView { + fn current(&self) -> &Instant { + todo!() + } + fn transport(&self) -> &Transport { + todo!() + } + fn playing(&self) -> &RwLock> { + todo!() + } + fn started(&self) -> &RwLock> { + todo!() + } } -impl PlayerApi 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 4451c1a2..f0c6fbf4 100644 --- a/crates/tek_tui/src/tui_transport.rs +++ b/crates/tek_tui/src/tui_transport.rs @@ -8,7 +8,6 @@ impl TryFrom<&Arc>> for TransportApp { 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(), @@ -57,39 +56,40 @@ impl InputToCommand> for TransportCommand { use KeyCode::Char; use AppViewFocus::Content; use ClockCommand::{SetBpm, SetQuant, SetSync}; - use TransportViewFocus::{Bpm, Quant, Sync, PlayPause, Clock}; - let clock = app.app.clock(); + use TransportViewFocus as Focus; + use TransportCommand::{Clock, Playhead}; + let timebase = app.app.timebase(); Some(match input.event() { key!(Char('.')) => match app.focused() { - Content(Bpm) => SetBpm(clock.timebase().bpm.get() + 1.0), - Content(Quant) => SetQuant(next_note_length(clock.quant.get()as usize)as f64), - Content(Sync) => SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), - Content(PlayPause) => {todo!()}, - Content(Clock) => {todo!()}, + Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() + 1.0)), + Content(Focus::Quant) => Clock(SetQuant(next_note_length(app.app.quant().get()as usize)as f64)), + Content(Focus::Sync) => Clock(SetSync(next_note_length(app.app.sync().get()as usize)as f64+1.)), + Content(Focus::PlayPause) => Playhead(todo!()), + Content(Focus::Clock) => Playhead(todo!()), _ => {todo!()} }, key!(KeyCode::Char(',')) => match app.focused() { - Content(Bpm) => SetBpm(clock.timebase().bpm.get() - 1.0), - Content(Quant) => SetQuant(prev_note_length(clock.quant.get()as usize)as f64), - Content(Sync) => SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), - Content(PlayPause) => {todo!()}, - Content(Clock) => {todo!()} + Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() - 1.0)), + Content(Focus::Quant) => Clock(SetQuant(prev_note_length(app.app.quant().get()as usize)as f64)), + Content(Focus::Sync) => Clock(SetSync(prev_note_length(app.app.sync().get()as usize)as f64+1.)), + Content(Focus::PlayPause) => Playhead(todo!()), + Content(Focus::Clock) => Playhead(todo!()), _ => {todo!()} }, key!(KeyCode::Char('>')) => match app.focused() { - Content(Bpm) => SetBpm(clock.timebase().bpm.get() + 0.001), - Content(Quant) => SetQuant(next_note_length(clock.quant.get()as usize)as f64), - Content(Sync) => SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), - Content(PlayPause) => {todo!()}, - Content(Clock) => {todo!()} + Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() + 0.001)), + Content(Focus::Quant) => Clock(SetQuant(next_note_length(app.app.quant().get()as usize)as f64)), + Content(Focus::Sync) => Clock(SetSync(next_note_length(app.app.sync().get()as usize)as f64+1.)), + Content(Focus::PlayPause) => Playhead(todo!()), + Content(Focus::Clock) => Playhead(todo!()), _ => {todo!()} }, key!(KeyCode::Char('<')) => match app.focused() { - Content(Bpm) => SetBpm(clock.timebase().bpm.get() - 0.001), - Content(Quant) => SetQuant(prev_note_length(clock.quant.get()as usize)as f64), - Content(Sync) => SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), - Content(PlayPause) => {todo!()}, - Content(Clock) => {todo!()} + Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() - 0.001)), + Content(Focus::Quant) => Clock(SetQuant(prev_note_length(app.app.quant().get()as usize)as f64)), + Content(Focus::Sync) => Clock(SetSync(prev_note_length(app.app.sync().get()as usize)as f64+1.)), + Content(Focus::PlayPause) => Playhead(todo!()), + Content(Focus::Clock) => Playhead(todo!()), _ => {todo!()} }, _ => return None @@ -310,8 +310,8 @@ impl HasJack for TransportView { } impl ClockApi for TransportView { - fn current (&self) -> &Instant { - &self.current + fn timebase (&self) -> &Arc { + &self.current.timebase } fn quant (&self) -> &Quantize { &self.quant @@ -322,6 +322,18 @@ impl ClockApi for TransportView { } impl PlayheadApi for TransportView { + fn current (&self) -> &Instant { + &self.current + } + fn transport (&self) -> &jack::Transport { + &self.transport + } + fn playing (&self) -> &RwLock> { + &self.playing + } + fn started (&self) -> &RwLock> { + &self.started + } } impl std::fmt::Debug for TransportView {