From e13569df9313d5aae007e08b89412376ca33258e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 29 Jun 2024 15:29:49 +0300 Subject: [PATCH] sequencer time indicator --- src/device/launcher/grid.rs | 15 ++++++++++++- src/device/launcher/mod.rs | 36 ++++++++++++++++++++++-------- src/device/sequencer/horizontal.rs | 24 ++++++++++---------- src/device/sequencer/mod.rs | 31 ++++++++++++------------- src/main.rs | 2 +- 5 files changed, 68 insertions(+), 40 deletions(-) diff --git a/src/device/launcher/grid.rs b/src/device/launcher/grid.rs index 65ba4a02..a65697a7 100644 --- a/src/device/launcher/grid.rs +++ b/src/device/launcher/grid.rs @@ -17,6 +17,13 @@ impl<'a> LauncherGridView<'a> { self.separator_h(2, false); self.separator_h((self.state.cursor.1 * 2) as u16, true); self.separator_h(((self.state.cursor.1 + 1) * 2) as u16, true); + draw_box_styled(self.buf, self.area, Some( + if self.focused { + Style::default().green().dim() + } else { + Style::default().dim() + } + )); let columns = self.column_names(); let (mut x, y) = (self.area.x, self.area.y); @@ -47,7 +54,6 @@ impl<'a> LauncherGridView<'a> { } "+Add track…".blit(self.buf, x + 2, y + 1, Some(Style::default().dim())); - draw_box_styled(self.buf, self.area, Some(self.highlight(self.focused))); Ok(self.area) } @@ -135,7 +141,14 @@ impl<'a> LauncherGridView<'a> { } y2 = y2 + 1; } + let hi = (track + 1 == self.state.cursor.0) && + (self.state.scenes.len() + 1 == self.state.cursor.1); " + Add clip".blit(self.buf, x, y + y2, Some(Style::default().dim())); + if hi { + draw_box_styled( + self.buf, Rect { x: x - 2, y: y + y2 - 1, width: 16, height: 3 }, Some(Style::default().green()) + ); + } } fn highlight (&self, highlight: bool) -> Style { diff --git a/src/device/launcher/mod.rs b/src/device/launcher/mod.rs index 25ea143b..3c795b54 100644 --- a/src/device/launcher/mod.rs +++ b/src/device/launcher/mod.rs @@ -183,10 +183,10 @@ pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually //separator.blit(buf, x, y + 22, Some(Style::default().dim())); //separator.blit(buf, x, y + 41, Some(Style::default().dim())); let mut y = y + 1; - y = y + LauncherGridView - ::new(state, buf, Rect { x, y, width, height: 22 }, state.view.is_tracks()) - .draw()?.height + 1; - y = y + draw_section_sequencer(state, buf, Rect { x, y, width, height: 28 })?.height + 1; + y = y + LauncherGridView::new( + state, buf, Rect { x, y, width, height: 22 }, state.view.is_tracks() + ).draw()?.height; + y = y + draw_section_sequencer(state, buf, Rect { x, y, width, height: 28 })?.height; y = y + draw_section_chains(state, buf, Rect { x, y, width, height: 21 })?.height; if state.show_help { let style = Some(Style::default().bold().white().not_dim().on_black().italic()); @@ -221,34 +221,52 @@ fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Us let Rect { x, y, width, height } = area; let style = Some(Style::default().green().dim()); let view = &state.view; - match state.view { + match view { LauncherView::Sequencer => { draw_box_styled(buf, area, style); }, _ => {}, }; if let Some(track) = state.tracks.get(state.col().saturating_sub(1)) { - let state = track.sequencer.state(); + let frame = state.position; + let timebase = &state.timebase; + let tick = (frame as f64 / timebase.frames_per_tick()) as usize; + let state = track.sequencer.state(); + let zoom = state.resolution; + + let step = state.phrase().map(|_|tick / zoom); + crate::device::sequencer::horizontal::timer(buf, x+5, y, + step.unwrap_or(0) / 4, + state.steps * zoom, + state.time_axis.0, + state.time_axis.1 + ); + let keys_area = Rect { x, y: y + 1, width, height }; - crate::device::sequencer::horizontal::keys(buf, keys_area, state.note_axis.1)?; + crate::device::sequencer::horizontal::keys(buf, keys_area, + state.note_axis.1 + )?; + if let Some(phrase) = state.phrase() { crate::device::sequencer::horizontal::lanes(buf, x, y + 1, &phrase, state.timebase.ppq() as u32, - state.resolution as u32, + state.resolution as u32, state.time_axis.0 as u32, state.time_axis.1 as u32, state.note_axis.0 as u32, state.note_axis.1 as u32, ); } + let cursor_style = match view { LauncherView::Sequencer => Style::default().green().not_dim(), _ => Style::default().green().dim(), }; crate::device::sequencer::horizontal::cursor(buf, x, y + 1, cursor_style, state.time_cursor, - state.note_cursor); + state.note_cursor + ); } Ok(area) } diff --git a/src/device/sequencer/horizontal.rs b/src/device/sequencer/horizontal.rs index 470c3d88..726cc8e0 100644 --- a/src/device/sequencer/horizontal.rs +++ b/src/device/sequencer/horizontal.rs @@ -10,7 +10,7 @@ pub fn draw ( area.x = area.x + 13; let Rect { x, y, width, .. } = area; keys(buf, area, s.note_axis.1)?; - timer(s, buf, x, y, beat); + timer(buf, x + 6, y - 1, beat, s.steps, s.time_axis.0, s.time_axis.1); let height = 32.max(s.note_axis.1 - s.note_axis.0) / 2; if let Some(phrase) = s.phrase() { lanes(buf, x, y, @@ -25,7 +25,8 @@ pub fn draw ( } cursor(buf, x, y, Style::default().green().not_dim(), s.time_cursor, - s.note_cursor); + s.note_cursor + ); footer(s, buf, x, y, width, height); Ok(Rect { x: x - 13, @@ -35,10 +36,9 @@ pub fn draw ( }) } -pub fn timer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, beat: usize) { - let (time0, time1) = s.time_axis; +pub fn timer (buf: &mut Buffer, x: u16, y: u16, beat: usize, steps: usize, time0: u16, time1: u16) { for step in time0..time1 { - buf.set_string(x + 6 + step, y - 1, &"-", if beat % s.steps == step as usize { + buf.set_string(x + step, y, &"-", if beat % steps == step as usize { Style::default().yellow().bold().not_dim() } else { Style::default() @@ -77,16 +77,16 @@ pub fn lanes ( note1: u32, ) { let bg = Style::default(); - let bw = bg.dim(); - let wh = bg.white(); - //let (time0, time1) = s.time_axis; - //let (note0, note1) = s.note_axis; - //let resolution = s.resolution; + let (bw, wh) = (bg.dim(), bg.white()); for step in time0..time1 { let x = x as u32 + 5 + step; let (a, b) = ((step + 0) * ppq / time_zoom, (step + 1) * ppq / time_zoom,); - if step % time_zoom == 0 { - format!("{}", step + 1).blit(buf, x as u16, y - 1, None); + if step % 4 == 0 { + "|".blit(buf, x as u16, y - 1, Some(Style::default().dim())); + } + if step % (time_zoom * 4) == 0 { + format!("{}", step / time_zoom / 4 + 1) + .blit(buf, x as u16, y - 1, Some(Style::default().bold().not_dim())); } let h = (note1-note0)/2; for k in 0..h { diff --git a/src/device/sequencer/mod.rs b/src/device/sequencer/mod.rs index c882e9dc..cfa305cb 100644 --- a/src/device/sequencer/mod.rs +++ b/src/device/sequencer/mod.rs @@ -24,37 +24,34 @@ impl Phrase { } pub struct Sequencer { - pub name: String, + pub name: String, /// JACK transport handle. - transport: ::jack::Transport, + pub transport: ::jack::Transport, /// JACK MIDI input port that will be created. - pub midi_in: Port, + pub midi_in: Port, /// JACK MIDI output port that will be created. - pub midi_out: Port, - + pub midi_out: Port, /// Holds info about tempo - pub timebase: Arc, + pub timebase: Arc, /// Steps in sequence, e.g. 64 16ths = 4 beat loop. /// FIXME: play start / end / loop in ppm - steps: usize, + pub steps: usize, /// Phrase selector - sequence: usize, + pub sequence: usize, /// Map: tick -> MIDI events at tick - pub sequences: Vec, + pub sequences: Vec, /// Red keys on piano roll. - notes_on: Vec, - + pub notes_on: Vec, /// Play sequence to output. - pub playing: TransportState, + pub playing: TransportState, /// Play input through output. - pub monitoring: bool, + pub monitoring: bool, /// Write input to sequence. - pub recording: bool, + pub recording: bool, /// Don't delete when recording. - pub overdub: bool, - + pub overdub: bool, /// Display mode - mode: SequencerView, + pub mode: SequencerView, /// Range of notes to display pub note_axis: (u16, u16), /// Position of cursor within note range diff --git a/src/main.rs b/src/main.rs index f7398f99..d6ebd364 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,7 @@ fn main () -> Result<(), Box> { Track::new("Kick", &timebase, Some(vec![ //Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(), + Sampler::new("Sampler")?.boxed(), ]), Some(vec![ Phrase::new("HelloKick", ppq * 4, Some(BTreeMap::from([ ( ppq * 0, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ), @@ -58,7 +59,6 @@ fn main () -> Result<(), Box> { ]))) ]))?, - //Sampler::new("Sampler")?.boxed(), //Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(), //Plugin::lv2("Bass/Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(), //Plugin::lv2("Pads/Odin2", "file:///home/user/.lv2/Odin2.lv2", &[1, 0, 0, 2])?.boxed(),