diff --git a/src/control.rs b/src/control.rs index 5bfb4052..8ebba520 100644 --- a/src/control.rs +++ b/src/control.rs @@ -18,6 +18,22 @@ handle!(App |self, e| { }); const KEYMAP: &'static [KeyBinding] = keymap!(App { + [Char('+'), NONE, "quant_inc", "Zoom in", |app: &mut App| { + app.quant = next_note_length(app.quant); + Ok(true) + }], + [Char('_'), NONE, "quant_dec", "Zoom out", |app: &mut App| { + app.quant = prev_note_length(app.quant); + Ok(true) + }], + [Char('='), NONE, "zoom_in", "Zoom in", |app: &mut App| { + app.time_zoom = prev_note_length(app.time_zoom); + Ok(true) + }], + [Char('-'), NONE, "zoom_out", "Zoom out", |app: &mut App| { + app.time_zoom = next_note_length(app.time_zoom); + Ok(true) + }], [Char('x'), NONE, "extend", "double the current clip", |app: &mut App| { if let Some(phrase) = app.phrase_mut() { let mut notes = BTreeMap::new(); @@ -29,7 +45,7 @@ const KEYMAP: &'static [KeyBinding] = keymap!(App { } Ok(true) }], - [Char('l'), NONE, "toggle_loop", "toggle looping", |app: &mut App| { + [Char('l'), NONE, "loop_toggle", "toggle looping", |app: &mut App| { // TODO: This toggles the loop flag for the clip under the cursor. Ok(true) }], @@ -49,19 +65,19 @@ const KEYMAP: &'static [KeyBinding] = keymap!(App { // TODO: This moves the loop end to the next quant. Ok(true) }], - [Char(' '), NONE, "toggle_play", "play or pause", |app: &mut App| { + [Char(' '), NONE, "play_toggle", "play or pause", |app: &mut App| { app.toggle_play()?; Ok(true) }], - [Char('a'), CONTROL, "add_scene", "add a new scene", |app: &mut App| { + [Char('a'), CONTROL, "scene_add", "add a new scene", |app: &mut App| { app.add_scene(None)?; Ok(true) }], - [Char('t'), CONTROL, "add_track", "add a new track", |app: &mut App| { + [Char('t'), CONTROL, "track_add", "add a new track", |app: &mut App| { app.add_track(None)?; Ok(true) }], - [Char('`'), NONE, "switch_mode", "switch the display mode", |app: &mut App| { + [Char('`'), NONE, "mode_switch", "switch the display mode", |app: &mut App| { match app.section { 0 => {app.grid_mode = !app.grid_mode; Ok(true)}, 1 => {app.chain_mode = !app.chain_mode; Ok(true)}, @@ -69,16 +85,16 @@ const KEYMAP: &'static [KeyBinding] = keymap!(App { _ => Ok(false) } }], - [F(1), NONE, "toggle_help", "toggle help", |_: &mut App| {Ok(true)}], - [Char('r'), NONE, "toggle_record", "toggle recording", |app: &mut App| { + [F(1), NONE, "help_toggle", "toggle help", |_: &mut App| {Ok(true)}], + [Char('r'), NONE, "record_toggle", "toggle recording", |app: &mut App| { app.track_mut().map(|t|t.1.toggle_record()); Ok(true) }], - [Char('d'), NONE, "toggle_overdub", "toggle overdub", |app: &mut App| { + [Char('d'), NONE, "overdub_toggle", "toggle overdub", |app: &mut App| { app.track_mut().map(|t|t.1.toggle_overdub()); Ok(true) }], - [Char('m'), NONE, "toggle_monitor", "toggle input monitoring", |app: &mut App| { + [Char('m'), NONE, "monitor_toggle", "toggle input monitoring", |app: &mut App| { app.track_mut().map(|t|t.1.toggle_monitor()); Ok(true) }], @@ -96,6 +112,9 @@ const KEYMAP: &'static [KeyBinding] = keymap!(App { //s.sequencer_mut().map(|s|s.monitoring = !s.monitoring); //Ok(true) //} + [Tab, NONE, "focus_next", "focus next area", focus_next], + [Tab, SHIFT, "focus_prev", "focus previous area", focus_prev], + [Esc, NONE, "focus_out", "unfocus", escape], [Up, NONE, "cursor_up", "move cursor up", |app: &mut App| { if app.entered { match app.section { @@ -174,17 +193,12 @@ const KEYMAP: &'static [KeyBinding] = keymap!(App { _ => Ok(false) } }], - [Tab, NONE, "focus_next", "focus next area", focus_next], - [Tab, SHIFT, "focus_prev", "focus previous area", focus_prev], - [Char('.'), NONE, "increment", "increment value", increment], - [Char(','), NONE, "decrement", "decrement value", decrement], - [Delete, CONTROL, "delete", "delete track", delete], - [Char('d'), CONTROL, "duplicate", "duplicate scene or track", duplicate], - [Enter, NONE, "activate", "activate item at cursor", enter], - [Esc, NONE, "escape", "unfocus", escape], + [Char('.'), NONE, "cursor_inc", "increment value", increment], + [Char(','), NONE, "cursor_dec", "decrement value", decrement], + [Delete, CONTROL, "cursor_delete", "delete track", delete], + [Char('d'), CONTROL, "cursor_duplicate", "duplicate scene or track", duplicate], + [Enter, NONE, "cursor_activate", "activate item at cursor", enter], //[Char('r'), CONTROL, "rename", "rename current element", rename], -// [Char('='), NONE, "zoom_in", "Zoom in", zoom_in], -// [Char('-'), NONE, "zoom_out", "Zoom out", zoom_out], // [Char('a'), NONE, "note_add", "Add note", note_add], // [Char('z'), NONE, "note_del", "Delete note", note_del], // [CapsLock, NONE, "advance", "Toggle auto advance", nop], diff --git a/src/core/midi.rs b/src/core/midi.rs index 6caada72..d523bdc2 100644 --- a/src/core/midi.rs +++ b/src/core/midi.rs @@ -35,3 +35,51 @@ pub fn write_midi_output (writer: &mut ::jack::MidiWriter, output: &MIDIChunk, f } } } + +/// (ppq, name) +pub const NOTE_DURATIONS: [(usize, &str);16] = [ + (1, "1/384"), + (2, "1/192"), + (3, "1/128"), + (4, "1/96"), + (6, "1/64"), + (8, "1/48"), + (12, "1/32"), + (16, "1/24"), + (24, "1/16"), + (32, "1/12"), + (48, "1/8"), + (64, "1/6"), + (96, "1/4"), + (128, "1/3"), + (192, "1/2"), + (384, "1/1"), +]; + +pub fn ppq_to_name (ppq: usize) -> &'static str { + for (length, name) in &NOTE_DURATIONS { + if *length == ppq { + return name + } + } + "" +} + +pub fn prev_note_length (ppq: usize) -> usize { + for i in 1..=16 { + let length = NOTE_DURATIONS[16-i].0; + if length < ppq { + return length + } + } + ppq +} + +pub fn next_note_length (ppq: usize) -> usize { + for (length, _) in &NOTE_DURATIONS { + if *length > ppq { + return *length + } + } + ppq +} diff --git a/src/main.rs b/src/main.rs index 377cfd65..67ade2de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,6 +53,7 @@ pub fn main () -> Usually<()> { state.scene_cursor = 1; state.note_start = 12; state.time_zoom = 12; + state.quant = 24; let outputs: Vec<_> = ["Komplete.+:playback_FL", "Komplete.+:playback_FR"] .iter() @@ -228,6 +229,10 @@ pub struct App { pub track_cursor: usize, /// Collection of tracks pub tracks: Vec, + + pub chunk_size: usize, + + pub quant: usize, } process!(App |self, _client, scope| { let transport = self.transport.as_ref().unwrap().query().unwrap(); @@ -235,9 +240,9 @@ process!(App |self, _client, scope| { if Some(transport.state) != self.playing { panic = true; } - self.playing = Some(transport.state); - self.playhead = transport.pos.frame() as usize; - let frames = scope.n_frames() as usize; + self.playing = Some(transport.state); + self.playhead = transport.pos.frame() as usize; + self.chunk_size = scope.n_frames() as usize; let CycleTimes { current_frames, current_usecs, @@ -249,9 +254,10 @@ process!(App |self, _client, scope| { self.midi_in.as_ref().unwrap().iter(scope), &self.timebase, self.playing, + self.quant, panic, &scope, - (current_frames as usize, frames), + (current_frames as usize, self.chunk_size), (current_usecs as usize, next_usecs.saturating_sub(current_usecs) as usize), period_usecs as f64 ); diff --git a/src/model/phrase.rs b/src/model/phrase.rs index 7a224b4e..258e82b2 100644 --- a/src/model/phrase.rs +++ b/src/model/phrase.rs @@ -50,6 +50,7 @@ impl Phrase { let end = timebase.usecs_frames((usec0 + usecs) as f64); let repeat = timebase.pulses_frames(self.length as f64); let ticks = timebase.frames_to_ticks(start, end, repeat); + //panic!("{start} {end} {repeat} {ticks:?}"); for (time, tick) in ticks.iter() { if let Some(events) = self.notes.get(&(*tick as usize)) { for message in events.iter() { diff --git a/src/model/track.rs b/src/model/track.rs index e6447eeb..10a9ec6b 100644 --- a/src/model/track.rs +++ b/src/model/track.rs @@ -115,6 +115,7 @@ impl Track { input: MidiIter, timebase: &Arc, playing: Option, + quant: usize, panic: bool, scope: &ProcessScope, (frame0, frames): (usize, usize), @@ -158,6 +159,7 @@ impl Track { if let Some(phrase) = phrase { let pulse = timebase.frames_pulses((frame0 + time) as f64) as usize; let pulse = pulse % phrase.length; + let pulse = (pulse / quant) * quant; let contains = phrase.notes.contains_key(&pulse); if contains { phrase.notes.get_mut(&pulse).unwrap().push(message.clone()); diff --git a/src/view.rs b/src/view.rs index 260ee4be..577a2a96 100644 --- a/src/view.rs +++ b/src/view.rs @@ -25,6 +25,7 @@ render!(App |self, buf, area| { record: self.track().map(|t|t.1.recording).unwrap_or(false), overdub: self.track().map(|t|t.1.overdub).unwrap_or(false), frame: self.playhead, + quant: self.quant, }.render(buf, area)?.height; y = y + SceneGridView { @@ -54,10 +55,10 @@ render!(App |self, buf, area| { let phrase = self.phrase(); let seq_area = SequencerView { + phrase, focused: self.section == 2, ppq: self.timebase.ppq() as usize, now: self.timebase.frames_pulses(self.playhead as f64) as usize, - phrase: phrase, time_cursor: self.time_cursor, time_start: self.time_start, time_zoom: self.time_zoom, diff --git a/src/view/sequencer.rs b/src/view/sequencer.rs index fcb5769b..c5e106e9 100644 --- a/src/view/sequencer.rs +++ b/src/view/sequencer.rs @@ -44,41 +44,23 @@ impl<'a> SequencerView<'a> { Style::default().green().dim() }); let notes = &[]; - pulse_to_note_length(self.time_zoom) - .blit(buf, area.x, area.y, Some(Style::default().dim())); self::horizontal::keys(buf, area, self.note_start, notes)?; + let quant = ppq_to_name(self.time_zoom); + quant.blit( + buf, + area.x + area.width - 1 - quant.len() as u16, + area.y + area.height - 2, + style + ); if let Some(phrase) = self.phrase { self::horizontal::timer(buf, area, self.time_start, self.time_zoom, self.now % phrase.length); self::horizontal::lanes(buf, area, phrase, self.ppq, self.time_zoom, self.time_start, self.note_start); } - let style = style.unwrap_or_else(||{Style::default().green().not_dim()}); - self::horizontal::cursor(buf, area, style, self.time_cursor, self.note_cursor); + self::horizontal::cursor(buf, area, style.unwrap(), self.time_cursor, self.note_cursor); Ok(()) } } -fn pulse_to_note_length (time_z: usize) -> &'static str { - match time_z { - 1 => "1/384", - 2 => "1/192", - 3 => "1/128", - 4 => "1/96", - 6 => "1/64", - 8 => "1/48", - 12 => "1/32", - 16 => "1/24", - 24 => "1/16", - 32 => "1/12", - 48 => "1/8", - 64 => "1/6", - 96 => "1/4", - 128 => "1/3", - 192 => "1/2", - 384 => "1/1", - _ => "" - } -} - mod horizontal { use crate::core::*; use super::*; diff --git a/src/view/transport.rs b/src/view/transport.rs index 0634f2ec..8596b31c 100644 --- a/src/view/transport.rs +++ b/src/view/transport.rs @@ -7,6 +7,7 @@ pub struct TransportView<'a> { pub overdub: bool, pub monitor: bool, pub frame: usize, + pub quant: usize, } impl<'a> Render for TransportView<'a> { @@ -16,7 +17,7 @@ impl<'a> Render for TransportView<'a> { draw_rec(buf, x + 12, y, self.record); draw_dub(buf, x + 19, y, self.overdub); draw_mon(buf, x + 26, y, self.monitor); - draw_bpm(buf, x + 33, y, self.timebase.bpm() as usize); + draw_bpm(buf, x + 33, y, self.timebase.bpm() as usize, self.quant); draw_timer(buf, x + width - 1, y, &self.timebase, self.frame); Ok(Rect { x, y, width, height: 1 }) } @@ -77,7 +78,7 @@ pub fn draw_mon (buf: &mut Buffer, x: u16, y: u16, on: bool) { })) } -pub fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, bpm: usize) { +pub fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, bpm: usize, quant: usize) { let style = Style::default().not_dim(); "BPM" .blit(buf, x, y, Some(style)); @@ -89,7 +90,6 @@ pub fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, bpm: usize) { .blit(buf, x + 18, y, Some(style.bold())); "QUANT" .blit(buf, x + 23, y, Some(style)); - "1/16" + ppq_to_name(quant) .blit(buf, x + 29, y, Some(style.bold())); } -