diff --git a/crates/tek_core/src/tui.rs b/crates/tek_core/src/tui.rs index 7bedad32..c8f888c5 100644 --- a/crates/tek_core/src/tui.rs +++ b/crates/tek_core/src/tui.rs @@ -367,6 +367,18 @@ impl Widget for &str { Ok(to.blit(&self, x, y, None)) } } +impl Widget for String { + type Engine = Tui; + fn layout (&self, _: [u16;2]) -> Perhaps<[u16;2]> { + // TODO: line breaks + Ok(Some([self.chars().count() as u16, 1])) + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + let [x, y, ..] = to.area(); + //let [w, h] = self.layout(to.area().wh())?.unwrap(); + Ok(to.blit(&self, x, y, None)) + } +} impl> Widget for DebugOverlay { type Engine = Tui; diff --git a/crates/tek_sequencer/src/main_arranger.rs b/crates/tek_sequencer/src/main_arranger.rs index af95d08b..86c2dd86 100644 --- a/crates/tek_sequencer/src/main_arranger.rs +++ b/crates/tek_sequencer/src/main_arranger.rs @@ -7,19 +7,19 @@ pub fn main () -> Usually<()> { ArrangerCli::parse().run() } pub struct ArrangerCli { /// Name of JACK client #[arg(short, long)] - name: Option, + name: Option, /// Pulses per quarter note (arruencer resolution; default: 96) #[arg(short, long)] - ppq: Option, + ppq: Option, /// Whether to include a transport toolbar (default: true) #[arg(short, long, default_value_t = true)] transport: bool, /// Number of tracks #[arg(short = 'x', long, default_value_t = 8)] - tracks: usize, + tracks: usize, /// Number of scenes #[arg(short, long, default_value_t = 8)] - scenes: usize, + scenes: usize, } impl ArrangerCli { /// Run the arranger TUI from CLI arguments. @@ -183,10 +183,6 @@ impl Content for SequencerProxy { fn content (&self) -> impl Widget { "" } } impl Focusable for SequencerProxy { - fn is_focused (&self) -> bool { - self.1 - } - fn set_focused (&mut self, focus: bool) { - self.1 = focus - } + fn is_focused (&self) -> bool { self.1 } + fn set_focused (&mut self, focus: bool) { self.1 = focus } } diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index 5e44d06f..bc8fb20e 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -36,12 +36,12 @@ impl Arranger { match self.selected { ArrangerFocus::Scene(s) => { for (track_index, track) in self.tracks.iter_mut().enumerate() { - track.sequence = self.scenes[s].clips[track_index]; + track.playing_phrase = self.scenes[s].clips[track_index]; track.reset = true; } }, ArrangerFocus::Clip(t, s) => { - self.tracks[t].sequence = self.scenes[s].clips[t]; + self.tracks[t].playing_phrase = self.scenes[s].clips[t]; self.tracks[t].reset = true; }, _ => {} @@ -433,7 +433,7 @@ impl Scene { .all(|(track_index, phrase_index)|match phrase_index { Some(i) => tracks .get(track_index) - .map(|track|track.sequence == Some(*i)) + .map(|track|track.playing_phrase == Some(*i)) .unwrap_or(false), None => true }) @@ -444,71 +444,80 @@ impl Scene { /// Phrase editor. pub struct Sequencer { - pub name: Arc>, - pub mode: bool, - pub focused: bool, - pub entered: bool, - pub phrase: Option>>, - pub transport: Option>>>, - pub buffer: BigBuffer, - pub keys: Buffer, + pub name: Arc>, + pub mode: bool, + pub focused: bool, + pub entered: bool, + pub phrase: Option>>, + pub transport: Option>>>, + /// The full piano roll is rendered to this buffer + pub buffer: BigBuffer, + /// The full piano keys is rendered to this buffer + pub keys: Buffer, /// Highlight input keys - pub keys_in: [bool; 128], + pub keys_in: [bool; 128], /// Highlight output keys - pub keys_out: [bool; 128], - pub now: usize, - pub ppq: usize, - pub note_axis: FixedAxis, - pub time_axis: ScaledAxis, + pub keys_out: [bool; 128], + /// Current point in playing phrase + pub now: usize, + /// Temporal resolution (default 96) + pub ppq: usize, + /// Scroll/room in pitch axis + pub note_axis: FixedAxis, + /// Scroll/room in time axis + pub time_axis: ScaledAxis, /// Play input through output. - pub monitoring: bool, + pub monitoring: bool, /// Write input to sequence. - pub recording: bool, + pub recording: bool, /// Overdub input to sequence. - pub overdub: bool, + pub overdub: bool, /// Map: tick -> MIDI events at tick - pub phrases: Vec>>, - /// Phrase selector - pub sequence: Option, + pub phrases: Vec>>, + /// Phrase currently being played + pub playing_phrase: Option, + /// Phrase currently being viewed + pub viewing_phrase: Option, /// Output from current sequence. - pub midi_out: Option>, + pub midi_out: Option>, /// MIDI output buffer - midi_out_buf: Vec>>, + midi_out_buf: Vec>>, /// Send all notes off - pub reset: bool, // TODO?: after Some(nframes) + pub reset: bool, // TODO?: after Some(nframes) /// Highlight keys on piano roll. - pub notes_in: [bool;128], + pub notes_in: [bool;128], /// Highlight keys on piano roll. - pub notes_out: [bool;128], + pub notes_out: [bool;128], } impl Sequencer { pub fn new (name: &str) -> Self { Self { - name: Arc::new(RwLock::new(name.into())), - monitoring: false, - recording: false, - overdub: true, - phrases: vec![], - sequence: None, - midi_out: None, - midi_out_buf: vec![Vec::with_capacity(16);16384], - reset: true, - notes_in: [false;128], - notes_out: [false;128], - buffer: Default::default(), - keys: keys_vert(), - entered: false, - focused: false, - mode: false, - keys_in: [false;128], - keys_out: [false;128], - phrase: None, - now: 0, - ppq: 96, - transport: None, - note_axis: FixedAxis { start: 12, point: Some(36) }, - time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) }, + name: Arc::new(RwLock::new(name.into())), + monitoring: false, + recording: false, + overdub: true, + phrases: vec![], + viewing_phrase: None, + playing_phrase: None, + midi_out: None, + midi_out_buf: vec![Vec::with_capacity(16);16384], + reset: true, + notes_in: [false;128], + notes_out: [false;128], + buffer: Default::default(), + keys: keys_vert(), + entered: false, + focused: false, + mode: false, + keys_in: [false;128], + keys_out: [false;128], + phrase: None, + now: 0, + ppq: 96, + transport: None, + note_axis: FixedAxis { start: 12, point: Some(36) }, + time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) }, } } pub fn toggle_monitor (&mut self) { @@ -549,7 +558,7 @@ impl Sequencer { if let ( Some(TransportState::Rolling), Some((start_frame, _)), Some(phrase) ) = ( - playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id)) + playing, started, self.playing_phrase.and_then(|id|self.phrases.get_mut(id)) ) { phrase.read().map(|phrase|{ if self.midi_out.is_some() { diff --git a/crates/tek_sequencer/src/sequencer_tui.rs b/crates/tek_sequencer/src/sequencer_tui.rs index 293fefd0..241fe463 100644 --- a/crates/tek_sequencer/src/sequencer_tui.rs +++ b/crates/tek_sequencer/src/sequencer_tui.rs @@ -180,7 +180,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { let name = name.read().unwrap(); let name = format!("{clip:02} {}", name); add(&name.as_str().push_x(1))?; - if (track as &Sequencer<_>).sequence == Some(*clip) { + if (track as &Sequencer<_>).playing_phrase == Some(*clip) { color = COLOR_PLAYING } else { color = COLOR_BG1 @@ -473,10 +473,6 @@ impl<'a> Content for HorizontalArranger<'a, Tui> { todo!() }, |_: &mut TuiOutput|{ todo!() - }), - // gain - CustomWidget::new(|_|{ - todo!() //let Self(tracks) = self; //let mut area = to.area(); //let off = Some(Style::default().dim()); @@ -496,6 +492,10 @@ impl<'a> Content for HorizontalArranger<'a, Tui> { //} //area.width = 4; //Ok(Some(area)) + }), + // gain + CustomWidget::new(|_|{ + todo!() }, |_: &mut TuiOutput|{ todo!() //let Self(tracks) = self; @@ -772,12 +772,12 @@ impl Content for Sequencer { let length = phrase.length; let looped = phrase.looped; add(&"")?; - add(&col! {"Length: ", format!("{length}").as_str(),})?; + add(&col!("Length: ", format!("{length}").as_str()))?; add(&"")?; - add(&col! { "Loop [ ]", "From: ", " 1.1.1", "Length: ", " 1.0.0", })?; + add(&col!("Loop [ ]", "From: ", " 1.1.1", "Length: ", " 1.0.0"))?; add(&"")?; - add(&col! { "Notes: ", "C#0-C#9 ", "[ /2 ]", "[ x2 ]" - , "[ Rev ]", "[ Inv ]", "[ Dup ]" })?; + add(&col!("Notes: ", "C#0-C#9 ", "[ /2 ]", "[ x2 ]", + "[ Rev ]", "[ Inv ]", "[ Dup ]"))?; } Ok(()) }).min_x(10); @@ -785,13 +785,12 @@ impl Content for Sequencer { // keys CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{ if to.area().h() < 2 { return Ok(()) } - to.buffer_update(to.area().set_w(5).shrink_y(2), &|cell, x, y|{ + Ok(to.buffer_update(to.area().set_w(5).shrink_y(2), &|cell, x, y|{ let y = y + self.note_axis.start as u16; if x < self.keys.area.width && y < self.keys.area.height { *cell = self.keys.get(x, y).clone() } - }); - Ok(()) + })) }).fill_y(), // playhead CustomWidget::new(|_|Ok(Some([32,2])), |to: &mut TuiOutput|{ @@ -817,7 +816,7 @@ impl Content for Sequencer { let offset = Sequencer::H_KEYS_OFFSET as u16; if to.area().h() < 2 || to.area().w() < offset { return Ok(()) } let area = to.area().push_x(offset).shrink_x(offset).shrink_y(2); - to.buffer_update(area, &move |cell, x, y|{ + Ok(to.buffer_update(area, &move |cell, x, y|{ cell.set_bg(Color::Rgb(20, 20, 20)); let src_x = ((x as usize + self.time_axis.start) * self.time_axis.scale) as usize; let src_y = (y as usize + self.note_axis.start) as usize; @@ -825,8 +824,7 @@ impl Content for Sequencer { let src = self.buffer.get(src_x, self.buffer.height - src_y); src.map(|src|{ cell.set_symbol(src.symbol()); cell.set_fg(src.fg); }); } - }); - Ok(()) + })) }).fill_x(), // note cursor CustomWidget::new(|_|Ok(Some([1,1])), |to: &mut TuiOutput|{ @@ -839,14 +837,16 @@ impl Content for Sequencer { } Ok(()) }), - //zoom + // zoom CustomWidget::new(|_|Ok(Some([10,1])), |to: &mut TuiOutput|{ let [x, y, w, h] = to.area.xywh(); let quant = ppq_to_name(self.time_axis.scale); - let x = x + w - 1 - quant.len() as u16; - let y = y + h - 2; - to.blit(&quant, x, y, self.style_focus()); - Ok(()) + Ok(to.blit( + &quant, + x + w - 1 - quant.len() as u16, + y + h - 2, + self.style_focus() + )) }), ); row!(toolbar, content).fill_x() @@ -910,45 +910,30 @@ const NTH_OCTAVE: [&'static str;11] = [ impl Handle for Sequencer { fn handle (&mut self, from: &TuiInput) -> Perhaps { match from.event() { - // NONE, "seq_cursor_up", "move cursor up", |sequencer: &mut Sequencer| { - key!(KeyCode::Up) => { - match self.entered { - true => { self.note_axis.point_dec(); }, - false => { self.note_axis.start_dec(); }, - } - Ok(Some(true)) - }, - // NONE, "seq_cursor_down", "move cursor down", |self: &mut Sequencer| { - key!(KeyCode::Down) => { - match self.entered { - true => { self.note_axis.point_inc(); }, - false => { self.note_axis.start_inc(); }, - } - Ok(Some(true)) - }, - // NONE, "seq_cursor_left", "move cursor up", |self: &mut Sequencer| { - key!(KeyCode::Left) => { - match self.entered { - true => { self.time_axis.point_dec(); }, - false => { self.time_axis.start_dec(); }, - } - Ok(Some(true)) - }, - // NONE, "seq_cursor_right", "move cursor up", |self: &mut Sequencer| { - key!(KeyCode::Right) => { - match self.entered { - true => { self.time_axis.point_inc(); }, - false => { self.time_axis.start_inc(); }, - } - Ok(Some(true)) - }, - // NONE, "seq_mode_switch", "switch the display mode", |self: &mut Sequencer| { key!(KeyCode::Char('`')) => { self.mode = !self.mode; - Ok(Some(true)) }, - _ => Ok(None) + 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)) } } @@ -1013,9 +998,7 @@ impl Handle for TransportToolbar { impl Content for TransportToolbar { type Engine = Tui; fn content (&self) -> impl Widget { - row! { - - // play/pause + row!( self.focus.wrap(self.focused, TransportToolbarFocus::PlayPause, &Styled( match self.playing { Some(TransportState::Stopped) => Some(GRAY_DIM.bold()), @@ -1030,37 +1013,26 @@ impl Content for TransportToolbar { _ => unreachable!(), } ).min_xy(11, 2).push_x(1)), - - // bpm self.focus.wrap(self.focused, TransportToolbarFocus::Bpm, &Outset::X(1u16, col! { - "BPM", - format!("{}.{:03}", self.bpm as usize, (self.bpm * 1000.0) % 1000.0).as_str() + "BPM", format!("{}.{:03}", self.bpm as usize, (self.bpm * 1000.0) % 1000.0) })), - - // quant self.focus.wrap(self.focused, TransportToolbarFocus::Quant, &Outset::X(1u16, col! { "QUANT", ppq_to_name(self.quant as usize) })), - - // sync self.focus.wrap(self.focused, TransportToolbarFocus::Sync, &Outset::X(1u16, col! { - "SYNC", ppq_to_name(self.sync as usize) + "SYNC", ppq_to_name(self.sync as usize) })), - - // clock self.focus.wrap(self.focused, TransportToolbarFocus::Clock, &{ let Self { frame: _frame, pulse, ppq, usecs, .. } = self; let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1); let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); let (minutes, seconds) = (seconds / 60, seconds % 60); - Outset::X(1u16, col! { - format!("{bars}.{beats}.{pulses:02}").as_str(), - format!("{minutes}:{seconds:02}:{msecs:03}").as_str(), - }) + let time1 = format!("{bars}.{beats}.{pulses:02}"); + let time2 = format!("{minutes}:{seconds:02}:{msecs:03}"); + col!(time1.as_str(), time2.as_str()).outset_x(1) }), - - }.fill_x().bg(Color::Rgb(40, 50, 30)) + ).fill_x().bg(Color::Rgb(40, 50, 30)) } }