diff --git a/crates/tek_sequencer/src/arranger_cmd.rs b/crates/tek_sequencer/src/arranger_cmd.rs new file mode 100644 index 00000000..ce001bb1 --- /dev/null +++ b/crates/tek_sequencer/src/arranger_cmd.rs @@ -0,0 +1,95 @@ +use crate::*; +/// Handle top-level events in standalone arranger. +impl Handle for Arranger { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + if !match self.focused() { + ArrangerFocus::Transport => self.transport.handle(from)?, + ArrangerFocus::PhrasePool => self.phrases.handle(from)?, + ArrangerFocus::PhraseEditor => self.editor.handle(from)?, + ArrangerFocus::Arrangement => { + let mut handle_phrase = ||{ + let result = self.phrases.handle(from); + self.arrangement.phrase_put(); + result + }; + match from.event() { + key!(KeyCode::Char('a')) => handle_phrase()?, + key!(KeyCode::Char('c')) => handle_phrase()?, + key!(KeyCode::Char('i')) => handle_phrase()?, + key!(KeyCode::Char('d')) => handle_phrase()?, + _ => self.arrangement.handle(from)? + } + }, + }.unwrap_or(false) { + match from.event() { + // Tab navigation + key!(KeyCode::Tab) => { self.focus_next(); }, + key!(Shift-KeyCode::Tab) => { self.focus_prev(); }, + key!(KeyCode::BackTab) => { self.focus_prev(); }, + key!(Shift-KeyCode::BackTab) => { self.focus_prev(); }, + // Directional navigation + key!(KeyCode::Up) => { self.focus_up(); }, + key!(KeyCode::Down) => { self.focus_down(); }, + key!(KeyCode::Left) => { self.focus_left(); }, + key!(KeyCode::Right) => { self.focus_right(); }, + // Put selected phrase at position + key!(KeyCode::Char('s')) => { self.arrangement.phrase_put(); }, + // Global play/pause binding + key!(KeyCode::Char(' ')) => match self.transport { + Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; }, + None => { return Ok(None) } + }, + _ => {} + } + }; + Ok(Some(true)) + } +} +impl Handle for Arrangement { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char('`')) => { self.mode.to_next(); }, + key!(KeyCode::Delete) => { self.delete(); }, + key!(KeyCode::Char('.')) => { self.increment(); }, + key!(KeyCode::Char(',')) => { self.decrement(); }, + // TODO: next/prev scene + key!(KeyCode::Enter) => { self.activate(); }, + key!(Ctrl-KeyCode::Char('a')) => { self.scene_add(None, None)?; }, + key!(Ctrl-KeyCode::Char('t')) => { self.track_add(None, None)?; }, + key!(KeyCode::Char('n')) => { todo!("rename selected"); }, + key!(KeyCode::Char('l')) => if let Some(phrase) = self.phrase() { + phrase.write().unwrap().toggle_loop() + }, + key!(KeyCode::Up) => { + match self.mode { + ArrangementViewMode::Horizontal => self.track_prev(), + _ => self.scene_prev(), + }; + self.show_phrase(); + }, + key!(KeyCode::Down) => { + match self.mode { + ArrangementViewMode::Horizontal => self.track_next(), + _ => self.scene_next(), + }; + self.show_phrase(); + }, + key!(KeyCode::Left) => { + match self.mode { + ArrangementViewMode::Horizontal => self.scene_prev(), + _ => self.track_prev(), + }; + self.show_phrase(); + }, + key!(KeyCode::Right) => { + match self.mode { + ArrangementViewMode::Horizontal => self.scene_next(), + _ => self.track_next(), + }; + self.show_phrase(); + }, + _ => return Ok(None) + } + Ok(Some(true)) + } +} diff --git a/crates/tek_sequencer/src/arranger_tui.rs b/crates/tek_sequencer/src/arranger_tui.rs index 8ee5ccf6..9ba43add 100644 --- a/crates/tek_sequencer/src/arranger_tui.rs +++ b/crates/tek_sequencer/src/arranger_tui.rs @@ -25,52 +25,6 @@ impl Content for Arranger { }) } } -/// Handle top-level events in standalone arranger. -impl Handle for Arranger { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - if !match self.focused() { - ArrangerFocus::Transport => self.transport.handle(from)?, - ArrangerFocus::PhrasePool => self.phrases.handle(from)?, - ArrangerFocus::PhraseEditor => self.editor.handle(from)?, - ArrangerFocus::Arrangement => { - let mut handle_phrase = ||{ - let result = self.phrases.handle(from); - self.arrangement.phrase_put(); - result - }; - match from.event() { - key!(KeyCode::Char('a')) => handle_phrase()?, - key!(KeyCode::Char('c')) => handle_phrase()?, - key!(KeyCode::Char('i')) => handle_phrase()?, - key!(KeyCode::Char('d')) => handle_phrase()?, - _ => self.arrangement.handle(from)? - } - }, - }.unwrap_or(false) { - match from.event() { - // Tab navigation - key!(KeyCode::Tab) => { self.focus_next(); }, - key!(Shift-KeyCode::Tab) => { self.focus_prev(); }, - key!(KeyCode::BackTab) => { self.focus_prev(); }, - key!(Shift-KeyCode::BackTab) => { self.focus_prev(); }, - // Directional navigation - key!(KeyCode::Up) => { self.focus_up(); }, - key!(KeyCode::Down) => { self.focus_down(); }, - key!(KeyCode::Left) => { self.focus_left(); }, - key!(KeyCode::Right) => { self.focus_right(); }, - // Put selected phrase at position - key!(KeyCode::Char('s')) => { self.arrangement.phrase_put(); }, - // Global play/pause binding - key!(KeyCode::Char(' ')) => match self.transport { - Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; }, - None => { return Ok(None) } - }, - _ => {} - } - }; - Ok(Some(true)) - } -} impl Arranger { pub fn rename_selected (&mut self) { let Arrangement { selected, ref name, ref tracks, ref scenes, .. } = self.arrangement; @@ -92,54 +46,6 @@ impl Arranger { //}; } } -impl Handle for Arrangement { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char('`')) => { self.mode.to_next(); }, - key!(KeyCode::Delete) => { self.delete(); }, - key!(KeyCode::Char('.')) => { self.increment(); }, - key!(KeyCode::Char(',')) => { self.decrement(); }, - // TODO: next/prev scene - key!(KeyCode::Enter) => { self.activate(); }, - key!(Ctrl-KeyCode::Char('a')) => { self.scene_add(None, None)?; }, - key!(Ctrl-KeyCode::Char('t')) => { self.track_add(None, None)?; }, - key!(KeyCode::Char('n')) => { todo!("rename selected"); }, - key!(KeyCode::Char('l')) => if let Some(phrase) = self.phrase() { - phrase.write().unwrap().toggle_loop() - }, - key!(KeyCode::Up) => { - match self.mode { - ArrangementViewMode::Horizontal => self.track_prev(), - _ => self.scene_prev(), - }; - self.show_phrase(); - }, - key!(KeyCode::Down) => { - match self.mode { - ArrangementViewMode::Horizontal => self.track_next(), - _ => self.scene_next(), - }; - self.show_phrase(); - }, - key!(KeyCode::Left) => { - match self.mode { - ArrangementViewMode::Horizontal => self.scene_prev(), - _ => self.track_prev(), - }; - self.show_phrase(); - }, - key!(KeyCode::Right) => { - match self.mode { - ArrangementViewMode::Horizontal => self.scene_next(), - _ => self.track_next(), - }; - self.show_phrase(); - }, - _ => return Ok(None) - } - Ok(Some(true)) - } -} impl Content for Arrangement { type Engine = Tui; fn content (&self) -> impl Widget { diff --git a/crates/tek_sequencer/src/lib.rs b/crates/tek_sequencer/src/lib.rs index 30b8dbc3..cd8e15fe 100644 --- a/crates/tek_sequencer/src/lib.rs +++ b/crates/tek_sequencer/src/lib.rs @@ -10,9 +10,9 @@ pub(crate) use palette::{*, convert::*, okhsl::*}; use rand::distributions::uniform::UniformSampler; submod! { - arranger arranger_tui - sequencer sequencer_tui - transport transport_tui + arranger arranger_cmd arranger_tui + sequencer sequencer_cmd sequencer_tui + transport transport_cmd transport_tui } pub const PPQ: usize = 96; @@ -39,8 +39,8 @@ tui_style!(STYLE_VALUE = pub fn random_okhsl () -> Okhsl { let mut rng = thread_rng(); UniformOkhsl::new( - Okhsl::new(-180.0, 0.05, 0.1), - Okhsl::new( 180.0, 0.9, 0.5), + Okhsl::new(-180.0, 0.01, 0.05), + Okhsl::new( 180.0, 0.9, 0.5), ).sample(&mut rng) } diff --git a/crates/tek_sequencer/src/sequencer_cmd.rs b/crates/tek_sequencer/src/sequencer_cmd.rs new file mode 100644 index 00000000..7bea9896 --- /dev/null +++ b/crates/tek_sequencer/src/sequencer_cmd.rs @@ -0,0 +1,118 @@ +use crate::*; +/// Handle top-level events in standalone arranger. +impl Handle for Sequencer { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + if !match self.focused() { + SequencerFocus::Transport => self.transport.handle(from)?, + SequencerFocus::PhrasePool => self.phrases.handle(from)?, + SequencerFocus::PhraseEditor => self.editor.handle(from)? + }.unwrap_or(false) { + match from.event() { + // Tab navigation + key!(KeyCode::Tab) => { self.focus_next(); }, + key!(Shift-KeyCode::Tab) => { self.focus_prev(); }, + key!(KeyCode::BackTab) => { self.focus_prev(); }, + key!(Shift-KeyCode::BackTab) => { self.focus_prev(); }, + // Directional navigation + key!(KeyCode::Up) => { self.focus_up(); }, + key!(KeyCode::Down) => { self.focus_down(); }, + key!(KeyCode::Left) => { self.focus_left(); }, + key!(KeyCode::Right) => { self.focus_right(); }, + // Global play/pause binding + key!(KeyCode::Char(' ')) => match self.transport { + Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; }, + None => { return Ok(None) } + }, + _ => {} + } + }; + Ok(Some(true)) + } +} +impl Handle for PhrasePool { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match self.mode { + Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) => { + let mut phrase = self.phrases[phrase].write().unwrap(); + match from.event() { + key!(KeyCode::Backspace) => { phrase.name.pop(); }, + key!(KeyCode::Char(c)) => { phrase.name.push(*c); }, + key!(Shift-KeyCode::Char(c)) => { phrase.name.push(*c); }, + key!(KeyCode::Esc) => { phrase.name = old_name.clone(); self.mode = None; }, + key!(KeyCode::Enter) => { self.mode = None; }, + _ => return Ok(None) + } + }, + None => match from.event() { + key!(KeyCode::Up) => self.phrase = if self.phrase > 0 { + self.phrase - 1 + } else { + self.phrases.len() - 1 + }, + key!(KeyCode::Down) => { + self.phrase = (self.phrase + 1) % self.phrases.len() + }, + key!(KeyCode::Char('a')) => { // append new + let mut phrase = Phrase::default(); + phrase.name = String::from("(no name)"); + phrase.color = random_color(); + self.phrases.push(Arc::new(RwLock::new(phrase))); + self.phrase = self.phrases.len() - 1; + }, + key!(KeyCode::Char('i')) => { // insert new + let mut phrase = Phrase::default(); + phrase.name = String::from("(no name)"); + phrase.color = random_color(); + self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); + self.phrase += 1; + }, + key!(KeyCode::Char('d')) => { // insert duplicate + let mut phrase = (*self.phrases[self.phrase].read().unwrap()).clone(); + phrase.color = random_color_near(phrase.color, 0.2); + self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); + self.phrase += 1; + }, + key!(KeyCode::Char('c')) => { // change color + let mut phrase = self.phrases[self.phrase].write().unwrap(); + phrase.color = random_color(); + }, + key!(KeyCode::Char('n')) => { // change name + let phrase = self.phrases[self.phrase].read().unwrap(); + self.mode = Some(PhrasePoolMode::Rename(self.phrase, phrase.name.clone())); + }, + _ => return Ok(None), + } + } + + return Ok(Some(true)) + } +} +impl Handle for PhraseEditor { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char('`')) => { + self.mode = !self.mode; + }, + key!(KeyCode::Up) => match self.entered { + true => { self.note_axis.point_dec(); }, + false => { self.note_axis.start_dec(); }, + }, + key!(KeyCode::Down) => match self.entered { + true => { self.note_axis.point_inc(); }, + false => { self.note_axis.start_inc(); }, + }, + key!(KeyCode::Left) => match self.entered { + true => { self.time_axis.point_dec(); }, + false => { self.time_axis.start_dec(); }, + }, + key!(KeyCode::Right) => match self.entered { + true => { self.time_axis.point_inc(); }, + false => { self.time_axis.start_inc(); }, + }, + _ => { + return Ok(None) + } + } + return Ok(Some(true)) + } +} diff --git a/crates/tek_sequencer/src/sequencer_tui.rs b/crates/tek_sequencer/src/sequencer_tui.rs index bb83ca4a..0d28accc 100644 --- a/crates/tek_sequencer/src/sequencer_tui.rs +++ b/crates/tek_sequencer/src/sequencer_tui.rs @@ -10,36 +10,6 @@ impl Content for Sequencer { }) } } -/// Handle top-level events in standalone arranger. -impl Handle for Sequencer { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - if !match self.focused() { - SequencerFocus::Transport => self.transport.handle(from)?, - SequencerFocus::PhrasePool => self.phrases.handle(from)?, - SequencerFocus::PhraseEditor => self.editor.handle(from)? - }.unwrap_or(false) { - match from.event() { - // Tab navigation - key!(KeyCode::Tab) => { self.focus_next(); }, - key!(Shift-KeyCode::Tab) => { self.focus_prev(); }, - key!(KeyCode::BackTab) => { self.focus_prev(); }, - key!(Shift-KeyCode::BackTab) => { self.focus_prev(); }, - // Directional navigation - key!(KeyCode::Up) => { self.focus_up(); }, - key!(KeyCode::Down) => { self.focus_down(); }, - key!(KeyCode::Left) => { self.focus_left(); }, - key!(KeyCode::Right) => { self.focus_right(); }, - // Global play/pause binding - key!(KeyCode::Char(' ')) => match self.transport { - Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; }, - None => { return Ok(None) } - }, - _ => {} - } - }; - Ok(Some(true)) - } -} // TODO: Display phrases always in order of appearance impl Content for PhrasePool { type Engine = Tui; @@ -82,64 +52,6 @@ impl Content for PhrasePool { }).push_x(1)) } } -impl Handle for PhrasePool { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match self.mode { - Some(PhrasePoolMode::Rename(phrase, ref mut old_name)) => { - let mut phrase = self.phrases[phrase].write().unwrap(); - match from.event() { - key!(KeyCode::Backspace) => { phrase.name.pop(); }, - key!(KeyCode::Char(c)) => { phrase.name.push(*c); }, - key!(Shift-KeyCode::Char(c)) => { phrase.name.push(*c); }, - key!(KeyCode::Esc) => { phrase.name = old_name.clone(); self.mode = None; }, - key!(KeyCode::Enter) => { self.mode = None; }, - _ => return Ok(None) - } - }, - None => match from.event() { - key!(KeyCode::Up) => self.phrase = if self.phrase > 0 { - self.phrase - 1 - } else { - self.phrases.len() - 1 - }, - key!(KeyCode::Down) => { - self.phrase = (self.phrase + 1) % self.phrases.len() - }, - key!(KeyCode::Char('a')) => { // append new - let mut phrase = Phrase::default(); - phrase.name = String::from("(no name)"); - phrase.color = random_color(); - self.phrases.push(Arc::new(RwLock::new(phrase))); - self.phrase = self.phrases.len() - 1; - }, - key!(KeyCode::Char('i')) => { // insert new - let mut phrase = Phrase::default(); - phrase.name = String::from("(no name)"); - phrase.color = random_color(); - self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); - self.phrase += 1; - }, - key!(KeyCode::Char('d')) => { // insert duplicate - let mut phrase = (*self.phrases[self.phrase].read().unwrap()).clone(); - phrase.color = random_color_near(phrase.color, 0.2); - self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); - self.phrase += 1; - }, - key!(KeyCode::Char('c')) => { // change color - let mut phrase = self.phrases[self.phrase].write().unwrap(); - phrase.color = random_color(); - }, - key!(KeyCode::Char('n')) => { // change name - let phrase = self.phrases[self.phrase].read().unwrap(); - self.mode = Some(PhrasePoolMode::Rename(self.phrase, phrase.name.clone())); - }, - _ => return Ok(None), - } - } - - return Ok(Some(true)) - } -} impl Content for PhraseEditor { type Engine = Tui; fn content (&self) -> impl Widget { @@ -247,35 +159,6 @@ impl Content for PhraseEditor { }).push_x(1)) } } -impl Handle for PhraseEditor { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char('`')) => { - self.mode = !self.mode; - }, - key!(KeyCode::Up) => match self.entered { - true => { self.note_axis.point_dec(); }, - false => { self.note_axis.start_dec(); }, - }, - key!(KeyCode::Down) => match self.entered { - true => { self.note_axis.point_inc(); }, - false => { self.note_axis.start_inc(); }, - }, - key!(KeyCode::Left) => match self.entered { - true => { self.time_axis.point_dec(); }, - false => { self.time_axis.start_dec(); }, - }, - key!(KeyCode::Right) => match self.entered { - true => { self.time_axis.point_inc(); }, - false => { self.time_axis.start_inc(); }, - }, - _ => { - return Ok(None) - } - } - return Ok(Some(true)) - } -} impl PhraseEditor { const H_KEYS_OFFSET: usize = 5; /// Select which pattern to display. This pre-renders it to the buffer at full resolution. diff --git a/crates/tek_sequencer/src/transport_cmd.rs b/crates/tek_sequencer/src/transport_cmd.rs new file mode 100644 index 00000000..69a99282 --- /dev/null +++ b/crates/tek_sequencer/src/transport_cmd.rs @@ -0,0 +1,52 @@ +use crate::*; +impl Handle for TransportToolbar { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Left) => { self.focus.prev(); }, + key!(KeyCode::Right) => { self.focus.next(); }, + _ => match self.focus { + TransportToolbarFocus::PlayPause => return self.handle_play_pause(from), + TransportToolbarFocus::Bpm => return self.handle_bpm(from), + TransportToolbarFocus::Quant => return self.handle_quant(from), + TransportToolbarFocus::Sync => return self.handle_sync(from), + TransportToolbarFocus::Clock => {/*todo*/}, + } + } + Ok(Some(true)) + } +} +impl TransportToolbar { + fn handle_play_pause (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Enter) => self.toggle_play().map(|_|())?, + _ => return Ok(None) + } + Ok(Some(true)) + } + fn handle_bpm (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char(',')) => { self.bpm -= 1.0; }, + key!(KeyCode::Char('.')) => { self.bpm += 1.0; }, + key!(KeyCode::Char('<')) => { self.bpm -= 0.001; }, + key!(KeyCode::Char('>')) => { self.bpm += 0.001; }, + _ => return Ok(None) + } + Ok(Some(true)) + } + fn handle_quant (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char(',')) => { self.quant = prev_note_length(self.quant); }, + key!(KeyCode::Char('.')) => { self.quant = next_note_length(self.quant); }, + _ => return Ok(None) + } + return Ok(Some(true)) + } + fn handle_sync (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char(',')) => { self.sync = prev_note_length(self.sync); }, + key!(KeyCode::Char('.')) => { self.sync = next_note_length(self.sync); }, + _ => return Ok(None) + } + return Ok(Some(true)) + } +} diff --git a/crates/tek_sequencer/src/transport_tui.rs b/crates/tek_sequencer/src/transport_tui.rs index b88cc301..7e7e318f 100644 --- a/crates/tek_sequencer/src/transport_tui.rs +++ b/crates/tek_sequencer/src/transport_tui.rs @@ -1,55 +1,4 @@ use crate::*; -impl TransportToolbar { - fn handle_play_pause (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Enter) => self.toggle_play().map(|_|())?, - _ => return Ok(None) - } - Ok(Some(true)) - } - fn handle_bpm (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char(',')) => { self.bpm -= 1.0; }, - key!(KeyCode::Char('.')) => { self.bpm += 1.0; }, - key!(KeyCode::Char('<')) => { self.bpm -= 0.001; }, - key!(KeyCode::Char('>')) => { self.bpm += 0.001; }, - _ => return Ok(None) - } - Ok(Some(true)) - } - fn handle_quant (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char(',')) => { self.quant = prev_note_length(self.quant); }, - key!(KeyCode::Char('.')) => { self.quant = next_note_length(self.quant); }, - _ => return Ok(None) - } - return Ok(Some(true)) - } - fn handle_sync (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char(',')) => { self.sync = prev_note_length(self.sync); }, - key!(KeyCode::Char('.')) => { self.sync = next_note_length(self.sync); }, - _ => return Ok(None) - } - return Ok(Some(true)) - } -} -impl Handle for TransportToolbar { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Left) => { self.focus.prev(); }, - key!(KeyCode::Right) => { self.focus.next(); }, - _ => match self.focus { - TransportToolbarFocus::PlayPause => return self.handle_play_pause(from), - TransportToolbarFocus::Bpm => return self.handle_bpm(from), - TransportToolbarFocus::Quant => return self.handle_quant(from), - TransportToolbarFocus::Sync => return self.handle_sync(from), - TransportToolbarFocus::Clock => {/*todo*/}, - } - } - Ok(Some(true)) - } -} impl Content for TransportToolbar { type Engine = Tui; fn content (&self) -> impl Widget {