rudimentary input quantizer

This commit is contained in:
🪞👃🪞 2024-07-05 15:48:28 +03:00
parent 665885f6ff
commit f6a7cbf38e
8 changed files with 108 additions and 54 deletions

View file

@ -18,6 +18,22 @@ handle!(App |self, e| {
});
const KEYMAP: &'static [KeyBinding<App>] = 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<App>] = 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<App>] = 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<App>] = 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<App>] = 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<App>] = 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],

View file

@ -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
}

View file

@ -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<Track>,
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
);

View file

@ -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() {

View file

@ -115,6 +115,7 @@ impl Track {
input: MidiIter,
timebase: &Arc<Timebase>,
playing: Option<TransportState>,
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());

View file

@ -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,

View file

@ -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::*;

View file

@ -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()));
}