mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
rudimentary input quantizer
This commit is contained in:
parent
665885f6ff
commit
f6a7cbf38e
8 changed files with 108 additions and 54 deletions
|
|
@ -18,6 +18,22 @@ handle!(App |self, e| {
|
||||||
});
|
});
|
||||||
|
|
||||||
const KEYMAP: &'static [KeyBinding<App>] = keymap!(App {
|
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| {
|
[Char('x'), NONE, "extend", "double the current clip", |app: &mut App| {
|
||||||
if let Some(phrase) = app.phrase_mut() {
|
if let Some(phrase) = app.phrase_mut() {
|
||||||
let mut notes = BTreeMap::new();
|
let mut notes = BTreeMap::new();
|
||||||
|
|
@ -29,7 +45,7 @@ const KEYMAP: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
}
|
}
|
||||||
Ok(true)
|
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.
|
// TODO: This toggles the loop flag for the clip under the cursor.
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
|
|
@ -49,19 +65,19 @@ const KEYMAP: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
// TODO: This moves the loop end to the next quant.
|
// TODO: This moves the loop end to the next quant.
|
||||||
Ok(true)
|
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()?;
|
app.toggle_play()?;
|
||||||
Ok(true)
|
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)?;
|
app.add_scene(None)?;
|
||||||
Ok(true)
|
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)?;
|
app.add_track(None)?;
|
||||||
Ok(true)
|
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 {
|
match app.section {
|
||||||
0 => {app.grid_mode = !app.grid_mode; Ok(true)},
|
0 => {app.grid_mode = !app.grid_mode; Ok(true)},
|
||||||
1 => {app.chain_mode = !app.chain_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)
|
_ => Ok(false)
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
[F(1), NONE, "toggle_help", "toggle help", |_: &mut App| {Ok(true)}],
|
[F(1), NONE, "help_toggle", "toggle help", |_: &mut App| {Ok(true)}],
|
||||||
[Char('r'), NONE, "toggle_record", "toggle recording", |app: &mut App| {
|
[Char('r'), NONE, "record_toggle", "toggle recording", |app: &mut App| {
|
||||||
app.track_mut().map(|t|t.1.toggle_record());
|
app.track_mut().map(|t|t.1.toggle_record());
|
||||||
Ok(true)
|
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());
|
app.track_mut().map(|t|t.1.toggle_overdub());
|
||||||
Ok(true)
|
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());
|
app.track_mut().map(|t|t.1.toggle_monitor());
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
|
|
@ -96,6 +112,9 @@ const KEYMAP: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
//s.sequencer_mut().map(|s|s.monitoring = !s.monitoring);
|
//s.sequencer_mut().map(|s|s.monitoring = !s.monitoring);
|
||||||
//Ok(true)
|
//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| {
|
[Up, NONE, "cursor_up", "move cursor up", |app: &mut App| {
|
||||||
if app.entered {
|
if app.entered {
|
||||||
match app.section {
|
match app.section {
|
||||||
|
|
@ -174,17 +193,12 @@ const KEYMAP: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
_ => Ok(false)
|
_ => Ok(false)
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
[Tab, NONE, "focus_next", "focus next area", focus_next],
|
[Char('.'), NONE, "cursor_inc", "increment value", increment],
|
||||||
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
|
[Char(','), NONE, "cursor_dec", "decrement value", decrement],
|
||||||
[Char('.'), NONE, "increment", "increment value", increment],
|
[Delete, CONTROL, "cursor_delete", "delete track", delete],
|
||||||
[Char(','), NONE, "decrement", "decrement value", decrement],
|
[Char('d'), CONTROL, "cursor_duplicate", "duplicate scene or track", duplicate],
|
||||||
[Delete, CONTROL, "delete", "delete track", delete],
|
[Enter, NONE, "cursor_activate", "activate item at cursor", enter],
|
||||||
[Char('d'), CONTROL, "duplicate", "duplicate scene or track", duplicate],
|
|
||||||
[Enter, NONE, "activate", "activate item at cursor", enter],
|
|
||||||
[Esc, NONE, "escape", "unfocus", escape],
|
|
||||||
//[Char('r'), CONTROL, "rename", "rename current element", rename],
|
//[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('a'), NONE, "note_add", "Add note", note_add],
|
||||||
// [Char('z'), NONE, "note_del", "Delete note", note_del],
|
// [Char('z'), NONE, "note_del", "Delete note", note_del],
|
||||||
// [CapsLock, NONE, "advance", "Toggle auto advance", nop],
|
// [CapsLock, NONE, "advance", "Toggle auto advance", nop],
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
14
src/main.rs
14
src/main.rs
|
|
@ -53,6 +53,7 @@ pub fn main () -> Usually<()> {
|
||||||
state.scene_cursor = 1;
|
state.scene_cursor = 1;
|
||||||
state.note_start = 12;
|
state.note_start = 12;
|
||||||
state.time_zoom = 12;
|
state.time_zoom = 12;
|
||||||
|
state.quant = 24;
|
||||||
|
|
||||||
let outputs: Vec<_> = ["Komplete.+:playback_FL", "Komplete.+:playback_FR"]
|
let outputs: Vec<_> = ["Komplete.+:playback_FL", "Komplete.+:playback_FR"]
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -228,6 +229,10 @@ pub struct App {
|
||||||
pub track_cursor: usize,
|
pub track_cursor: usize,
|
||||||
/// Collection of tracks
|
/// Collection of tracks
|
||||||
pub tracks: Vec<Track>,
|
pub tracks: Vec<Track>,
|
||||||
|
|
||||||
|
pub chunk_size: usize,
|
||||||
|
|
||||||
|
pub quant: usize,
|
||||||
}
|
}
|
||||||
process!(App |self, _client, scope| {
|
process!(App |self, _client, scope| {
|
||||||
let transport = self.transport.as_ref().unwrap().query().unwrap();
|
let transport = self.transport.as_ref().unwrap().query().unwrap();
|
||||||
|
|
@ -235,9 +240,9 @@ process!(App |self, _client, scope| {
|
||||||
if Some(transport.state) != self.playing {
|
if Some(transport.state) != self.playing {
|
||||||
panic = true;
|
panic = true;
|
||||||
}
|
}
|
||||||
self.playing = Some(transport.state);
|
self.playing = Some(transport.state);
|
||||||
self.playhead = transport.pos.frame() as usize;
|
self.playhead = transport.pos.frame() as usize;
|
||||||
let frames = scope.n_frames() as usize;
|
self.chunk_size = scope.n_frames() as usize;
|
||||||
let CycleTimes {
|
let CycleTimes {
|
||||||
current_frames,
|
current_frames,
|
||||||
current_usecs,
|
current_usecs,
|
||||||
|
|
@ -249,9 +254,10 @@ process!(App |self, _client, scope| {
|
||||||
self.midi_in.as_ref().unwrap().iter(scope),
|
self.midi_in.as_ref().unwrap().iter(scope),
|
||||||
&self.timebase,
|
&self.timebase,
|
||||||
self.playing,
|
self.playing,
|
||||||
|
self.quant,
|
||||||
panic,
|
panic,
|
||||||
&scope,
|
&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),
|
(current_usecs as usize, next_usecs.saturating_sub(current_usecs) as usize),
|
||||||
period_usecs as f64
|
period_usecs as f64
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ impl Phrase {
|
||||||
let end = timebase.usecs_frames((usec0 + usecs) as f64);
|
let end = timebase.usecs_frames((usec0 + usecs) as f64);
|
||||||
let repeat = timebase.pulses_frames(self.length as f64);
|
let repeat = timebase.pulses_frames(self.length as f64);
|
||||||
let ticks = timebase.frames_to_ticks(start, end, repeat);
|
let ticks = timebase.frames_to_ticks(start, end, repeat);
|
||||||
|
//panic!("{start} {end} {repeat} {ticks:?}");
|
||||||
for (time, tick) in ticks.iter() {
|
for (time, tick) in ticks.iter() {
|
||||||
if let Some(events) = self.notes.get(&(*tick as usize)) {
|
if let Some(events) = self.notes.get(&(*tick as usize)) {
|
||||||
for message in events.iter() {
|
for message in events.iter() {
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ impl Track {
|
||||||
input: MidiIter,
|
input: MidiIter,
|
||||||
timebase: &Arc<Timebase>,
|
timebase: &Arc<Timebase>,
|
||||||
playing: Option<TransportState>,
|
playing: Option<TransportState>,
|
||||||
|
quant: usize,
|
||||||
panic: bool,
|
panic: bool,
|
||||||
scope: &ProcessScope,
|
scope: &ProcessScope,
|
||||||
(frame0, frames): (usize, usize),
|
(frame0, frames): (usize, usize),
|
||||||
|
|
@ -158,6 +159,7 @@ impl Track {
|
||||||
if let Some(phrase) = phrase {
|
if let Some(phrase) = phrase {
|
||||||
let pulse = timebase.frames_pulses((frame0 + time) as f64) as usize;
|
let pulse = timebase.frames_pulses((frame0 + time) as f64) as usize;
|
||||||
let pulse = pulse % phrase.length;
|
let pulse = pulse % phrase.length;
|
||||||
|
let pulse = (pulse / quant) * quant;
|
||||||
let contains = phrase.notes.contains_key(&pulse);
|
let contains = phrase.notes.contains_key(&pulse);
|
||||||
if contains {
|
if contains {
|
||||||
phrase.notes.get_mut(&pulse).unwrap().push(message.clone());
|
phrase.notes.get_mut(&pulse).unwrap().push(message.clone());
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ render!(App |self, buf, area| {
|
||||||
record: self.track().map(|t|t.1.recording).unwrap_or(false),
|
record: self.track().map(|t|t.1.recording).unwrap_or(false),
|
||||||
overdub: self.track().map(|t|t.1.overdub).unwrap_or(false),
|
overdub: self.track().map(|t|t.1.overdub).unwrap_or(false),
|
||||||
frame: self.playhead,
|
frame: self.playhead,
|
||||||
|
quant: self.quant,
|
||||||
}.render(buf, area)?.height;
|
}.render(buf, area)?.height;
|
||||||
|
|
||||||
y = y + SceneGridView {
|
y = y + SceneGridView {
|
||||||
|
|
@ -54,10 +55,10 @@ render!(App |self, buf, area| {
|
||||||
let phrase = self.phrase();
|
let phrase = self.phrase();
|
||||||
|
|
||||||
let seq_area = SequencerView {
|
let seq_area = SequencerView {
|
||||||
|
phrase,
|
||||||
focused: self.section == 2,
|
focused: self.section == 2,
|
||||||
ppq: self.timebase.ppq() as usize,
|
ppq: self.timebase.ppq() as usize,
|
||||||
now: self.timebase.frames_pulses(self.playhead as f64) as usize,
|
now: self.timebase.frames_pulses(self.playhead as f64) as usize,
|
||||||
phrase: phrase,
|
|
||||||
time_cursor: self.time_cursor,
|
time_cursor: self.time_cursor,
|
||||||
time_start: self.time_start,
|
time_start: self.time_start,
|
||||||
time_zoom: self.time_zoom,
|
time_zoom: self.time_zoom,
|
||||||
|
|
|
||||||
|
|
@ -44,41 +44,23 @@ impl<'a> SequencerView<'a> {
|
||||||
Style::default().green().dim()
|
Style::default().green().dim()
|
||||||
});
|
});
|
||||||
let notes = &[];
|
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)?;
|
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 {
|
if let Some(phrase) = self.phrase {
|
||||||
self::horizontal::timer(buf, area, self.time_start, self.time_zoom, self.now % phrase.length);
|
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);
|
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.unwrap(), self.time_cursor, self.note_cursor);
|
||||||
self::horizontal::cursor(buf, area, style, self.time_cursor, self.note_cursor);
|
|
||||||
Ok(())
|
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 {
|
mod horizontal {
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ pub struct TransportView<'a> {
|
||||||
pub overdub: bool,
|
pub overdub: bool,
|
||||||
pub monitor: bool,
|
pub monitor: bool,
|
||||||
pub frame: usize,
|
pub frame: usize,
|
||||||
|
pub quant: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Render for TransportView<'a> {
|
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_rec(buf, x + 12, y, self.record);
|
||||||
draw_dub(buf, x + 19, y, self.overdub);
|
draw_dub(buf, x + 19, y, self.overdub);
|
||||||
draw_mon(buf, x + 26, y, self.monitor);
|
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);
|
draw_timer(buf, x + width - 1, y, &self.timebase, self.frame);
|
||||||
Ok(Rect { x, y, width, height: 1 })
|
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();
|
let style = Style::default().not_dim();
|
||||||
"BPM"
|
"BPM"
|
||||||
.blit(buf, x, y, Some(style));
|
.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()));
|
.blit(buf, x + 18, y, Some(style.bold()));
|
||||||
"QUANT"
|
"QUANT"
|
||||||
.blit(buf, x + 23, y, Some(style));
|
.blit(buf, x + 23, y, Some(style));
|
||||||
"1/16"
|
ppq_to_name(quant)
|
||||||
.blit(buf, x + 29, y, Some(style.bold()));
|
.blit(buf, x + 29, y, Some(style.bold()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue