diff --git a/src/device/launcher.rs b/src/device/launcher.rs index dcb3f321..2a217ae4 100644 --- a/src/device/launcher.rs +++ b/src/device/launcher.rs @@ -12,54 +12,14 @@ impl Launcher { } } -pub fn process ( - client: &Client, - scope: &ProcessScope -) -> Control { +pub fn process (_: &mut Launcher, _: &Client, _: &ProcessScope) -> Control { Control::Continue } -pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually { +pub fn render (_: &Launcher, _: &mut Buffer, _: Rect) -> Usually { Ok(Rect::default()) } -pub fn handle (state: &mut Launcher, event: &Event) -> Result<(), Box> { +pub fn handle (_: &mut Launcher, _: &AppEvent) -> Result<(), Box> { Ok(()) } - //let mut x = areas[1].x; - //for (index, track) in [ - //"Track 1", - //"Track 2", - //"Track 3", - //"Track 4", - //"Track 5", - //"Bus 1", - //"Bus 2", - //"Mix", - //].iter().enumerate() { - //buffer.set_string( - //x + 10 * (index + 1) as u16, areas[1].y, - //"┬", Style::default().not_bold().dim() - //); - //buffer.set_string( - //x + 10 * (index + 1) as u16, areas[1].y + areas[1].height - 1, - //"┴", Style::default().not_bold().dim() - //); - //for y in areas[1].y+1..areas[1].y+areas[1].height - 1 { - //buffer.set_string( - //x + 10 * (index + 1) as u16, y, - //"│", Style::default().not_bold().gray().dim() - //); - //} - //for y in areas[1].y+2..areas[1].y+areas[1].height - 1 { - //buffer.set_string( - //x + 10 * index as u16 + 1, y, - //"--------", Style::default().not_bold().gray().dim() - //); - //} - //buffer.set_string( - //x + 10 * index as u16 + 1, areas[1].y + 1, - //track, Style::default().bold().not_dim() - //); - //} - diff --git a/src/device/looper.rs b/src/device/looper.rs index 6c2c243c..c26cf3ca 100644 --- a/src/device/looper.rs +++ b/src/device/looper.rs @@ -12,28 +12,15 @@ impl Looper { } } -pub fn process ( - state: &mut Looper, - client: &Client, - scope: &ProcessScope -) -> Control { +pub fn process (_: &mut Looper, _: &Client, _: &ProcessScope) -> Control { Control::Continue } -pub fn render (state: &Looper, buf: &mut Buffer, area: Rect) - -> Usually -{ - //let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row); - //stdout - //.queue(move_to(0, 0))?.queue(Print(" Name Input Length Route"))? - //.queue(move_to(0, 1))?.queue(PrintStyledContent(" Metronome [ ] ████ Track 1".bold()))? - //.queue(move_to(0, 2))?.queue(PrintStyledContent(" Loop 1 [ ] ████ Track 1".bold()))? - //.queue(move_to(0, 3))?.queue(PrintStyledContent(" Loop 2 [ ] ████████ Track 2".bold()))? - //.queue(move_to(0, 4))?.queue(PrintStyledContent(" Loop 3 [ ] ████████ Track 3".bold()))?; +pub fn render (_: &Looper, _: &mut Buffer, _: Rect) -> Usually { Ok(Rect::default()) } -pub fn handle (state: &mut Looper, event: &AppEvent) -> Result<(), Box> { +pub fn handle (_: &mut Looper, _: &AppEvent) -> Result<(), Box> { Ok(()) } diff --git a/src/device/mixer.rs b/src/device/mixer.rs index 7ed3c2f2..b3285b38 100644 --- a/src/device/mixer.rs +++ b/src/device/mixer.rs @@ -29,9 +29,9 @@ impl Mixer { } pub fn process ( - mixer: &mut Mixer, - client: &Client, - scope: &ProcessScope + _: &mut Mixer, + _: &Client, + _: &ProcessScope ) -> Control { Control::Continue } diff --git a/src/device/plugin.rs b/src/device/plugin.rs index 9c116fbc..21e427cb 100644 --- a/src/device/plugin.rs +++ b/src/device/plugin.rs @@ -12,7 +12,7 @@ impl Plugin { } } -pub fn process (state: &mut Plugin, client: &Client, scope: &ProcessScope) -> Control { +pub fn process (_: &mut Plugin, _: &Client, _: &ProcessScope) -> Control { Control::Continue } @@ -31,6 +31,6 @@ pub fn render (state: &Plugin, buf: &mut Buffer, Rect { x, y, width, height }: R Ok(Rect { x, y, width: 40, height: 7 }) } -pub fn handle (state: &mut Plugin, event: &AppEvent) -> Result<(), Box> { +pub fn handle (_: &mut Plugin, _: &AppEvent) -> Result<(), Box> { Ok(()) } diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index 76aeb426..6c4bc4cb 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -5,37 +5,47 @@ type Sequence = std::collections::BTreeMap>; pub struct Sequencer { name: String, - mode: SequencerView, - note_axis: (u16, u16), - note_cursor: u16, - time_axis: (u16, u16), - time_cursor: u16, - rate: Hz, - tempo: Tempo, transport: ::jack::Transport, - input_port: Port, - input_connect: Vec, - output_port: Port, - output_connect: Vec, - /// Play sequence to output. - playing: bool, - /// Write input to sequence. - recording: bool, - /// Don't delete when recording. - overdub: bool, - /// Red keys on piano roll. - notes_on: Vec, + /// Samples per second + rate: Hz, + /// Beats per minute + tempo: Tempo, /// MIDI resolution (a.k.a. PPQ) ticks_per_beat: u64, /// Sequencer resolution, e.g. 16 steps per beat. steps_per_beat: u64, /// Steps in sequence, e.g. 64 16ths = 4 beat loop. steps: u64, + input_port: Port, + input_connect: Vec, + /// Play input through output. + monitoring: bool, + /// Red keys on piano roll. + notes_on: Vec, + /// Write input to sequence. + recording: bool, /// Map: tick -> MIDI events at tick sequence: Sequence, + /// Don't delete when recording. + overdub: bool, + /// Play sequence to output. + playing: bool, + output_port: Port, + output_connect: Vec, + + /// Display mode + mode: SequencerView, + /// Range of notes to display + note_axis: (u16, u16), + /// Position of cursor within note range + note_cursor: u16, + /// Range of time steps to display + time_axis: (u16, u16), + /// Position of cursor within time range + time_cursor: u16, } -#[derive(Debug)] +#[derive(Debug, Clone)] enum SequencerView { Tiny, Compact, @@ -55,19 +65,20 @@ impl Sequencer { time_cursor: 0, rate: Hz(client.sample_rate() as u32), tempo: Tempo(120000), - transport: client.transport(), - input_port: client.register_port("in", MidiIn::default())?, - input_connect: vec!["nanoKEY Studio * (capture): *".into()], - output_port: client.register_port("out", MidiOut::default())?, - output_connect: vec![], - sequence: std::collections::BTreeMap::new(), - playing: true, - recording: true, - overdub: true, - notes_on: vec![false;128], ticks_per_beat: 96, steps_per_beat: 8, steps: 64, + transport: client.transport(), + input_port: client.register_port("in", MidiIn::default())?, + input_connect: vec!["nanoKEY Studio * (capture): *".into()], + monitoring: true, + notes_on: vec![false;128], + playing: true, + recording: true, + sequence: std::collections::BTreeMap::new(), + overdub: true, + output_port: client.register_port("out", MidiOut::default())?, + output_connect: vec![], }).activate(client) } @@ -101,47 +112,6 @@ impl Sequencer { #[inline] fn tps (&self) -> f64 { self.bps() * self.tpb() } - /// Ticks per loop яснота - #[inline] fn tpl (&self) -> f64 { - self.tpb() * self.steps as f64 / self.steps_per_beat as f64 - } - - /// Length of sequence in beat notes and remainder. - #[inline] fn beats (&self) -> (u64, u64) { - (self.steps / self.steps_per_beat, self.steps % self.steps_per_beat) - } - /// Length of sequence in ticks, rounded down. - #[inline] fn ticks (&self) -> u64 { - self.steps * self.ticks_per_beat / self.steps_per_beat - } - /// Length of sequence in frames. - #[inline] fn frames (&self) -> u64 { - self.steps * self.usec_per_step().0 - } - /// Ticks per step, rounded down. - #[inline] fn ticks_per_step (&self) -> u64 { - self.ticks_per_beat / self.steps_per_beat - } - /// Microseconds per step for current tempo. - #[inline] fn usec_per_step (&self) -> Usec { - self.tempo.usec_per_step(self.steps_per_beat as u64) - } - /// Microseconds per tick for current tempo. - #[inline] fn usec_per_tick (&self) -> Usec { - Usec(self.tempo.usec_per_beat().0 / self.ticks_per_beat) - } - /// Convert frame to microsecond for current sample rate. - #[inline] fn frame_to_usec (&self, frame: u32) -> Usec { - Frame(frame).to_usec(&self.rate) - } - /// Convert frame to tick for current sample rate, tempo, and PPQ. - #[inline] fn frame_to_tick (&self, frame: Frames) -> u32 { - (self.frame_to_usec(frame).0 / self.usec_per_tick().0) as u32 - } - /// Convert tick to usec for current sample rate, tempo, and PPQ. - #[inline] fn tick_to_usec (&self, tick: u32) -> u32 { - (tick as u64 * self.usec_per_tick().0) as u32 - } fn frames_to_ticks (&self, start: u64, end: u64) -> Vec<(u64, u64)> { let fpl = self.fpl() as u64; @@ -254,9 +224,7 @@ fn process_out (s: &mut Sequencer, scope: &ProcessScope) { } } -fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) - -> Usually -{ +fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, width, .. } = area; let (time0, time1) = s.time_axis; let (note0, note1) = s.note_axis; @@ -289,7 +257,7 @@ fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) x, y, width: header.width.max(piano.width), - height: header.height + piano.height + 1 + height: header.height + piano.height + 3 })) } @@ -398,7 +366,7 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu } } if beat % s.steps == step as u64 { - buf.set_string(x + 39 - 2, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow()); + buf.set_string(x + 4, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow()); for key in note0..note1 { let color = if s.notes_on[key as usize] { Style::default().red() @@ -408,13 +376,24 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu } } } + let height = (time1-time0)/2; + buf.set_string(x + 2, y + height + 1, format!( + "Q 1/{} | N {} ({}-{}) | T {} ({}-{})", + 4 * s.steps_per_beat, + s.note_axis.0 + s.note_cursor, + s.note_axis.0, + s.note_axis.1 - 1, + s.time_axis.0 + s.time_cursor + 1, + s.time_axis.0 + 1, + s.time_axis.1, + ), Style::default().dim()); buf.set_string( x + 5 + s.note_cursor, y + s.time_cursor / 2, if s.time_cursor % 2 == 0 { "▀" } else { "▄" }, Style::default() ); - Ok(Rect { x, y, width: area.width, height: (time1-time0)/2 }) + Ok(Rect { x, y, width: area.width, height }) } fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool { @@ -462,50 +441,55 @@ fn draw_horizontal (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually Result<(), Box> { - match event { - AppEvent::Input(Event::Key(event)) => { - for (code, _, _, command) in COMMANDS.iter() { - if *code == event.code { - command(s); - break - } - } - }, - _ => {} - }; - Ok(()) + handle_keymap(COMMANDS, s, event) } -const COMMANDS: [(KeyCode, &'static str, &'static str, &'static dyn Fn(&mut Sequencer));18] = [ - (KeyCode::Up, "cursor_up", "move cursor up", &cursor_up), - (KeyCode::Down, "cursor_down", "move cursor down", &cursor_down), - (KeyCode::Left, "cursor_left", "move cursor left", &cursor_left), - (KeyCode::Right, "cursor_right", "move cursor right", &cursor_right), - (KeyCode::Char(']'), "cursor_inc", "increase note duration", &cursor_inc), - (KeyCode::Char('['), "cursor_dec", "decrease note duration", &cursor_dec), - (KeyCode::Char('`'), "mode_next", "next view mode", &mode_next), - (KeyCode::Tab, "mode_prev", "previous view mode", &mode_prev), - (KeyCode::Char('+'), "zoom_in", "Zoom in", &nop), - (KeyCode::Char('-'), "zoom_out", "Zoom out", &nop), - (KeyCode::Char('a'), "note_add", "Add note", ¬e_add), - (KeyCode::Char('d'), "note_del", "Delete note", ¬e_del), - (KeyCode::CapsLock, "advance", "Toggle auto advance", &nop), - (KeyCode::Char('w'), "rest", "Advance by note duration", &nop), - (KeyCode::Char('r'), "record", "Toggle recodring", &toggle_record), - (KeyCode::Char('o'), "overdub", "Toggle overdub", &toggle_overdub), - (KeyCode::Char('p'), "play", "Toggle play/pause", &toggle_play), - (KeyCode::Char('s'), "stop", "Stop and rewind", &nop), -]; +pub const COMMANDS: &'static [KeyBinding] = keymap!(Sequencer { + [Up, NONE, "cursor_up", "move cursor up", cursor_up], + [Down, NONE, "cursor_down", "move cursor down", cursor_down], + [Left, NONE, "cursor_left", "move cursor left", cursor_left], + [Right, NONE, "cursor_right", "move cursor right", cursor_right], + [Char(']'), NONE, "cursor_inc", "increase note duration", cursor_duration_inc], + [Char('['), NONE, "cursor_dec", "decrease note duration", cursor_duration_dec], + [Char('`'), NONE, "mode_next", "Next view mode", mode_next], + [Char('+'), NONE, "zoom_in", "Zoom in", nop], + [Char('-'), NONE, "zoom_out", "Zoom out", nop], + [Char('a'), NONE, "note_add", "Add note", note_add], + [Char('d'), NONE, "note_del", "Delete note", note_del], + [CapsLock, NONE, "advance", "Toggle auto advance", nop], + [Char('w'), NONE, "rest", "Advance by note duration", nop], + [Char('r'), NONE, "record", "Toggle recodring", toggle_record], + [Char('o'), NONE, "overdub", "Toggle overdub", toggle_overdub], + [Char('p'), NONE, "play", "Toggle play/pause", toggle_play], + [Char('s'), NONE, "stop", "Stop and rewind", stop_and_rewind], + [Char('q'), NONE, "quantize_next", "Next quantize value", quantize_next], + [Char('Q'), SHIFT, "quantize_prev", "Previous quantize value", quantize_prev], + [Char('n'), NONE, "note_axis", "Focus note axis", nop], + [Char('t'), NONE, "time_axis", "Focus time axis", nop], + [Char('v'), NONE, "variations", "Focus variation selector", nop], + [Char('s'), SHIFT, "sync", "Focus sync selector", nop] +}); fn nop (_: &mut Sequencer) { } @@ -532,21 +516,33 @@ fn note_add (s: &mut Sequencer) { } fn note_del (_: &mut Sequencer) { } -fn cursor_up (s: &mut Sequencer) { +fn time_cursor_inc (s: &mut Sequencer) { let time = s.time_axis.1 - s.time_axis.0; + s.time_cursor = ((time + s.time_cursor) + 1) % time +} +fn time_cursor_dec (s: &mut Sequencer) { + let time = s.time_axis.1 - s.time_axis.0; + s.time_cursor = ((time + s.time_cursor) - 1) % time +} +fn note_cursor_inc (s: &mut Sequencer) { let note = s.note_axis.1 - s.note_axis.0; + s.note_cursor = ((note + s.note_cursor) + 1) % note +} +fn note_cursor_dec (s: &mut Sequencer) { + let note = s.note_axis.1 - s.note_axis.0; + s.note_cursor = ((note + s.note_cursor) - 1) % note +} +fn cursor_up (s: &mut Sequencer) { match s.mode { - SequencerView::Vertical => { s.time_cursor = ((time + s.time_cursor) - 1) % time }, - SequencerView::Horizontal => { s.note_cursor = ((note + s.note_cursor) - 1) % note }, + SequencerView::Vertical => time_cursor_dec(s), + SequencerView::Horizontal => note_cursor_dec(s), _ => unimplemented!() } } fn cursor_down (s: &mut Sequencer) { - let time = s.time_axis.1 - s.time_axis.0; - let note = s.note_axis.1 - s.note_axis.0; match s.mode { - SequencerView::Vertical => { s.time_cursor = ((time + s.time_cursor) + 1) % time }, - SequencerView::Horizontal => { s.note_cursor = ((note + s.note_cursor) + 1) % note }, + SequencerView::Vertical => time_cursor_inc(s), + SequencerView::Horizontal => note_cursor_inc(s), _ => unimplemented!() } } @@ -554,8 +550,8 @@ fn cursor_left (s: &mut Sequencer) { let time = s.time_axis.1 - s.time_axis.0; let note = s.note_axis.1 - s.note_axis.0; match s.mode { - SequencerView::Vertical => { s.note_cursor = ((note + s.note_cursor) - 1) % note }, - SequencerView::Horizontal => { s.time_cursor = ((time + s.time_cursor) - 1) % time }, + SequencerView::Vertical => note_cursor_dec(s), + SequencerView::Horizontal => time_cursor_dec(s), _ => unimplemented!() } } @@ -563,22 +559,31 @@ fn cursor_right (s: &mut Sequencer) { let time = s.time_axis.1 - s.time_axis.0; let note = s.note_axis.1 - s.note_axis.0; match s.mode { - SequencerView::Vertical => { s.note_cursor = ((note + s.note_cursor) + 1) % note }, - SequencerView::Horizontal => { s.time_cursor = ((time + s.time_cursor) + 1) % time }, + SequencerView::Vertical => note_cursor_inc(s), + SequencerView::Horizontal => time_cursor_inc(s), _ => unimplemented!() } } -fn cursor_inc (s: &mut Sequencer) { +fn cursor_duration_inc (s: &mut Sequencer) { //s.cursor.2 = s.cursor.2 + 1 } -fn cursor_dec (s: &mut Sequencer) { +fn cursor_duration_dec (s: &mut Sequencer) { //if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 } } fn mode_next (s: &mut Sequencer) { - s.mode = SequencerView::Horizontal + s.mode = s.mode.next() } -fn mode_prev (s: &mut Sequencer) { - s.mode = SequencerView::Vertical +impl SequencerView { + fn next (&self) -> Self { + match self { + Self::Horizontal => Self::Vertical, + Self::Vertical => Self::Horizontal, + _ => self.clone() + } + } +} +fn stop_and_rewind (s: &mut Sequencer) { + s.playing = false } fn toggle_play (s: &mut Sequencer) { s.playing = !s.playing @@ -589,6 +594,16 @@ fn toggle_record (s: &mut Sequencer) { fn toggle_overdub (s: &mut Sequencer) { s.overdub = !s.overdub } +fn quantize_next (s: &mut Sequencer) { + if s.steps_per_beat < 64 { + s.steps_per_beat = s.steps_per_beat * 2 + } +} +fn quantize_prev (s: &mut Sequencer) { + if s.steps_per_beat > 1 { + s.steps_per_beat = s.steps_per_beat / 2 + } +} #[cfg(test)] mod test { use super::*; diff --git a/src/layout.rs b/src/layout.rs index cdeb5b9b..599fecaa 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -46,9 +46,7 @@ impl Device for Rows { self.focused = true; self.items[self.focus].handle(&AppEvent::Blur)?; }, - _ => { - println!("{event:?}"); - } + _ => {} }, _ => {} } @@ -94,9 +92,42 @@ impl Columns { impl Device for Columns { fn handle (&mut self, event: &AppEvent) -> Usually<()> { - if self.focused { - self.items[self.focus].handle(event) + if !self.focused { + match event { + AppEvent::Input(Event::Key(KeyEvent { code: KeyCode::Esc, .. })) => { + self.focused = true; + self.items[self.focus].handle(&AppEvent::Blur)?; + Ok(()) + }, + _ => self.items[self.focus].handle(event) + } } else { + match event { + AppEvent::Input(event) => match event { + Event::Key(KeyEvent { code: KeyCode::Left, .. }) => { + if self.focus == 0 { + self.focus = self.items.len(); + } + self.focus = self.focus - 1; + }, + Event::Key(KeyEvent { code: KeyCode::Right, .. }) => { + self.focus = self.focus + 1; + if self.focus >= self.items.len() { + self.focus = 0; + } + }, + Event::Key(KeyEvent { code: KeyCode::Enter, .. }) => { + self.focused = false; + self.items[self.focus].handle(&AppEvent::Focus)?; + }, + Event::Key(KeyEvent { code: KeyCode::Esc, .. }) => { + self.focused = true; + self.items[self.focus].handle(&AppEvent::Blur)?; + }, + _ => {} + }, + _ => {} + } Ok(()) } } @@ -111,7 +142,7 @@ impl Device for Columns { height: area.height })?; if self.focused && i == self.focus { - draw_box_styled(buf, result, Some(Style::default().yellow())); + draw_box_styled(buf, result, Some(Style::default().green())); } w = w + result.width; h = h.max(result.height); diff --git a/src/main.rs b/src/main.rs index 98b0c59a..e695746a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,8 +13,8 @@ pub mod config; pub mod layout; pub mod time; -use crate::device::{Sequencer, Transport}; -use crate::layout::Rows; +use crate::device::*; +use crate::layout::*; fn main () -> Result<(), Box> { let _cli = cli::Cli::parse(); @@ -33,8 +33,12 @@ fn main () -> Result<(), Box> { //])?), //])), //Box::new(Mixer::new("Mixer#000")?), - Box::new(Sequencer::new("Melody#000")?), Box::new(Transport::new("Transport")?), + Box::new(Columns::new(true, vec![ + Box::new(Sequencer::new("Melody#000")?), + Box::new(Sequencer::new("Melody#001")?), + Box::new(Sequencer::new("Rhythm#000")?), + ])), //Box::new(Sequencer::new("Rhythm#000")?), ])) } diff --git a/src/prelude.rs b/src/prelude.rs index 5b75df8d..4dac77b8 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -85,3 +85,40 @@ pub type BoxedNotificationHandler = Box; pub type BoxedProcessHandler = Box Control + Send>; pub type Jack = AsyncClient>; + +pub type KeyBinding = ( + KeyCode, KeyModifiers, &'static str, &'static str, &'static dyn Fn(&mut T) +); + +#[macro_export] macro_rules! key { + ($k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: ident) => { + (KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f) + }; +} + +#[macro_export] macro_rules! keymap { + ($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: ident]),* }) => { + &[ + $((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as &'static dyn Fn(&mut $T))),* + ] as &'static [KeyBinding<$T>] + } +} + +pub use crate::{key, keymap}; + +pub fn handle_keymap ( + commands: &[KeyBinding], state: &mut T, event: &AppEvent +) -> Result<(), Box> { + match event { + AppEvent::Input(Event::Key(event)) => { + for (code, modifiers, _, _, command) in commands.iter() { + if *code == event.code && modifiers.bits() == event.modifiers.bits() { + command(state); + break + } + } + }, + _ => {} + }; + Ok(()) +}