diff --git a/src/control.rs b/src/control.rs index cc4585cd..dbe4e744 100644 --- a/src/control.rs +++ b/src/control.rs @@ -1,6 +1,6 @@ use crate::{core::*, handle, App, AppSection}; -pubmod!{ arranger chain mixer plugin sampler sequencer } +pubmod!{ arranger chain focus mixer plugin sampler sequencer transport } handle!{ App |self, e| { @@ -15,10 +15,10 @@ handle!{ Ok(if self.entered { handle_focused(self, e)? || handle_keymap(self, e, KEYMAP)? - || handle_keymap(self, e, KEYMAP_FOCUS)? + || handle_keymap(self, e, crate::control::focus::KEYMAP_FOCUS)? } else { handle_keymap(self, e, KEYMAP)? - || handle_keymap(self, e, KEYMAP_FOCUS)? + || handle_keymap(self, e, crate::control::focus::KEYMAP_FOCUS)? || handle_focused(self, e)? }) } @@ -27,13 +27,14 @@ handle!{ fn handle_focused (state: &mut App, e: &AppEvent) -> Usually { match state.section { AppSection::Transport => - Ok(false), + handle_keymap(state, e, crate::control::transport::KEYMAP_TRANSPORT), AppSection::Arranger => handle_keymap(state, e, crate::control::arranger::KEYMAP_ARRANGER), AppSection::Sequencer => handle_keymap(state, e, crate::control::sequencer::KEYMAP_SEQUENCER), AppSection::Chain => Ok(if state.entered { - handle_device(state, e)? || handle_keymap(state, e, crate::control::chain::KEYMAP_CHAIN)? + handle_device(state, e)? || + handle_keymap(state, e, crate::control::chain::KEYMAP_CHAIN)? } else { handle_keymap(state, e, crate::control::chain::KEYMAP_CHAIN)? || handle_device(state, e)? }) @@ -48,32 +49,7 @@ fn handle_device (state: &mut App, e: &AppEvent) -> Usually { .map(|x|x.unwrap_or(false)) } -pub const KEYMAP_FOCUS: &'static [KeyBinding] = keymap!(App { - [Char(';'), NONE, "command", "open command palette", |app: &mut App| { - app.modal = Some(Box::new(crate::view::HelpModal::new())); - Ok(true) - }], - [Tab, NONE, "focus_next", "focus next area", focus_next], - [Tab, SHIFT, "focus_prev", "focus previous area", focus_prev], - [Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{ - app.entered = false; - Ok(true) - }], - [Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{ - app.entered = true; - Ok(true) - }], -}); - pub const KEYMAP: &'static [KeyBinding] = keymap!(App { - [Up, NONE, "focus_prev", "focus previous area", |app: &mut App|match app.track_cursor { - 0 => {app.section = AppSection::Arranger;Ok(true)}, - _ => focus_prev(app) - }], - [Down, NONE, "focus_next", "focus next area", |app: &mut App|match app.track_cursor { - 0 => {app.section = AppSection::Chain;Ok(true)}, - _ => focus_next(app) - }], [Char(' '), NONE, "play_toggle", "play or pause", |app: &mut App| { app.transport.toggle_play()?; Ok(true) @@ -144,15 +120,3 @@ pub const KEYMAP: &'static [KeyBinding] = keymap!(App { Ok(true) }], }); - -fn focus_next (app: &mut App) -> Usually { - app.section.next(); - app.transport.focused = app.section == AppSection::Transport; - Ok(true) -} - -fn focus_prev (app: &mut App) -> Usually { - app.section.prev(); - app.transport.focused = app.section == AppSection::Transport; - Ok(true) -} diff --git a/src/control/focus.rs b/src/control/focus.rs new file mode 100644 index 00000000..bab26405 --- /dev/null +++ b/src/control/focus.rs @@ -0,0 +1,32 @@ +use crate::{core::*, model::{App, AppSection}}; + +pub const KEYMAP_FOCUS: &'static [KeyBinding] = keymap!(App { + [Char(';'), NONE, "command", "open command palette", |app: &mut App| { + app.modal = Some(Box::new(crate::view::HelpModal::new())); + Ok(true) + }], + [Tab, NONE, "focus_next", "focus next area", focus_next], + [Tab, SHIFT, "focus_prev", "focus previous area", focus_prev], + [Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{ + app.entered = false; + Ok(true) + }], + [Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{ + app.entered = true; + Ok(true) + }], +}); + +pub fn focus_next (app: &mut App) -> Usually { + app.section.next(); + app.transport.focused = app.section == AppSection::Transport; + app.transport.entered = app.entered; + Ok(true) +} + +pub fn focus_prev (app: &mut App) -> Usually { + app.section.prev(); + app.transport.focused = app.section == AppSection::Transport; + app.transport.entered = app.entered; + Ok(true) +} diff --git a/src/control/transport.rs b/src/control/transport.rs new file mode 100644 index 00000000..2fa7dbe8 --- /dev/null +++ b/src/control/transport.rs @@ -0,0 +1,40 @@ +use crate::{core::*, model::{App, TransportFocus}}; + +pub const KEYMAP_TRANSPORT: &'static [KeyBinding] = keymap!(App { + [Left, NONE, "transport_prev", "select previous control", |app: &mut App| Ok({ + app.transport.selected.prev(); + true + })], + [Right, NONE, "transport_next", "select next control", |app: &mut App| Ok({ + app.transport.selected.next(); + true + })], + [Char('.'), NONE, "transport_increment", "increment value at cursor", |app: &mut App| { + match app.transport.selected { + TransportFocus::BPM => { + app.transport.timebase.bpm.fetch_add(1.0, Ordering::Relaxed); + }, + TransportFocus::Quant => { + app.transport.quant = next_note_length(app.transport.quant) + }, + TransportFocus::Sync => { + app.transport.sync = next_note_length(app.transport.sync) + }, + }; + Ok(true) + }], + [Char(','), NONE, "transport_decrement", "decrement value at cursor", |app: &mut App| { + match app.transport.selected { + TransportFocus::BPM => { + app.transport.timebase.bpm.fetch_sub(1.0, Ordering::Relaxed); + }, + TransportFocus::Quant => { + app.transport.quant = prev_note_length(app.transport.quant); + }, + TransportFocus::Sync => { + app.transport.sync = prev_note_length(app.transport.sync); + }, + }; + Ok(true) + }], +}); diff --git a/src/core/midi.rs b/src/core/midi.rs index 1b393112..daa0177d 100644 --- a/src/core/midi.rs +++ b/src/core/midi.rs @@ -34,23 +34,33 @@ 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 const NOTE_DURATIONS: [(usize, &str);26] = [ + (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"), + (256, "2/3"), + (384, "1/1"), + (512, "4/3"), + (576, "3/2"), + (768, "2/1"), + (1152, "3/1"), + (1536, "4/1"), + (2304, "6/1"), + (3072, "8/1"), + (3456, "9/1"), + (6144, "16/1"), ]; pub fn ppq_to_name (ppq: usize) -> &'static str { diff --git a/src/core/time.rs b/src/core/time.rs index 8d62fdc3..8d42bc19 100644 --- a/src/core/time.rs +++ b/src/core/time.rs @@ -4,11 +4,11 @@ use atomic_float::AtomicF64; /// Keeps track of global time units. pub struct Timebase { /// Frames per second - rate: AtomicF64, + pub rate: AtomicF64, /// Beats per minute - bpm: AtomicF64, + pub bpm: AtomicF64, /// Ticks per beat - ppq: AtomicF64, + pub ppq: AtomicF64, } impl Default for Timebase { fn default () -> Self { diff --git a/src/model.rs b/src/model.rs index 6d756cb9..18d29618 100644 --- a/src/model.rs +++ b/src/model.rs @@ -13,7 +13,7 @@ pub use self::track::Track; pub use self::sampler::{Sampler, Sample, read_sample_data}; pub use self::mixer::Mixer; pub use self::plugin::{Plugin, PluginKind, lv2::LV2Plugin}; -pub use self::transport::TransportToolbar; +pub use self::transport::{TransportToolbar, TransportFocus}; use crate::{core::*, view::*}; diff --git a/src/model/transport.rs b/src/model/transport.rs index 271f0a1b..5f18181b 100644 --- a/src/model/transport.rs +++ b/src/model/transport.rs @@ -1,16 +1,43 @@ use crate::core::*; +#[derive(PartialEq)] +pub enum TransportFocus { + BPM, + Quant, + Sync, +} + +impl TransportFocus { + pub fn prev (&mut self) { + *self = match self { + Self::BPM => Self::Sync, + Self::Quant => Self::BPM, + Self::Sync => Self::Quant, + } + } + pub fn next (&mut self) { + *self = match self { + Self::BPM => Self::Quant, + Self::Quant => Self::Sync, + Self::Sync => Self::BPM, + } + } +} + pub struct TransportToolbar { pub metronome: bool, pub mode: bool, pub focused: bool, pub entered: bool, + pub selected: TransportFocus, /// Current sample rate, tempo, and PPQ. pub timebase: Arc, /// JACK transport handle. transport: Option, /// Quantization factor pub quant: usize, + /// Global sync quant + pub sync: usize, /// Current transport state pub playing: Option, /// Current position according to transport @@ -21,17 +48,20 @@ pub struct TransportToolbar { impl TransportToolbar { pub fn new (transport: Option) -> Self { + let timebase = Arc::new(Timebase::default()); Self { - transport, + selected: TransportFocus::BPM, metronome: false, mode: false, focused: false, entered: false, - timebase: Arc::new(Timebase::default()), playhead: 0, playing: Some(TransportState::Stopped), started: None, quant: 24, + sync: timebase.ppq() as usize * 4, + transport, + timebase, } } pub fn toggle_play (&mut self) -> Usually<()> { diff --git a/src/view/arranger.rs b/src/view/arranger.rs index 316bfabb..8fcdbdf0 100644 --- a/src/view/arranger.rs +++ b/src/view/arranger.rs @@ -130,7 +130,7 @@ impl<'a> ArrangerView<'a> { let mut width = 0; for y in 0..area.height { if y == 0 { - "MIX".blit(buf, area.x + 1, area.y + y, style1)?; + //"MIX".blit(buf, area.x + 1, area.y + y, style1)?; } else if y % 2 == 1 { let index = y as usize / 2; if let Some(track) = self.tracks.get(index) { @@ -156,7 +156,7 @@ impl<'a> ArrangerView<'a> { area.x = area.x + 1; for y in 0..area.height { if y == 0 { - " MON ".blit(buf, area.x, area.y + y, style2)?; + //" MON ".blit(buf, area.x, area.y + y, style2)?; } else if y % 2 == 1 { let index = y as usize / 2; if let Some(track) = self.tracks.get(index) { @@ -179,7 +179,7 @@ impl<'a> ArrangerView<'a> { area.x = area.x + 1; for y in 0..area.height { if y == 0 { - " REC ".blit(buf, area.x, area.y + y, style2)?; + //" REC ".blit(buf, area.x, area.y + y, style2)?; } else if y % 2 == 1 { let index = y as usize / 2; if let Some(track) = self.tracks.get(index) { @@ -202,7 +202,7 @@ impl<'a> ArrangerView<'a> { area.x = area.x + 1; for y in 0..area.height { if y == 0 { - " OVR ".blit(buf, area.x, area.y + y, style2)?; + //" OVR ".blit(buf, area.x, area.y + y, style2)?; } else if y % 2 == 1 { let index = y as usize / 2; if let Some(track) = self.tracks.get(index) { @@ -225,7 +225,7 @@ impl<'a> ArrangerView<'a> { area.x = area.x + 1; for y in 0..area.height { if y == 0 { - " DEL ".blit(buf, area.x, area.y + y, style2)?; + //" DEL ".blit(buf, area.x, area.y + y, style2)?; } else if y % 2 == 1 { let index = y as usize / 2; if let Some(_) = self.tracks.get(index) { @@ -244,7 +244,7 @@ impl<'a> ArrangerView<'a> { area.x = area.x + 1; for y in 0..area.height { if y == 0 { - " GAIN ".blit(buf, area.x, area.y + y, style2)?; + //" GAIN ".blit(buf, area.x, area.y + y, style2)?; } else if y % 2 == 1 { let index = y as usize / 2; if let Some(_) = self.tracks.get(index) { diff --git a/src/view/help.rs b/src/view/help.rs index a3d0799e..6f17160d 100644 --- a/src/view/help.rs +++ b/src/view/help.rs @@ -48,11 +48,11 @@ render!(HelpModal |self, buf, area|{ let y = y + 1; for i in 0..height-3 { let y = y + i; - if let Some(command) = crate::control::KEYMAP_FOCUS.get(i as usize) { + if let Some(command) = crate::control::focus::KEYMAP_FOCUS.get(i as usize) { format!("{:?}", command.0).blit(buf, x, y, Some(Style::default().white().bold()))?; command.2.blit(buf, x + 11, y, Some(Style::default().white().bold()))?; command.3.blit(buf, x + 26, y, Some(Style::default().white().dim()))?; - } else if let Some(command) = crate::control::KEYMAP.get((i as usize) - crate::control::KEYMAP_FOCUS.len()) { + } else if let Some(command) = crate::control::KEYMAP.get((i as usize) - crate::control::focus::KEYMAP_FOCUS.len()) { format!("{:?}", command.0).blit(buf, x, y, Some(Style::default().white().bold()))?; command.2.blit(buf, x + 11, y, Some(Style::default().white().bold()))?; command.3.blit(buf, x + 26, y, Some(Style::default().white().dim()))?; diff --git a/src/view/transport.rs b/src/view/transport.rs index 14729a4f..502a329b 100644 --- a/src/view/transport.rs +++ b/src/view/transport.rs @@ -11,9 +11,9 @@ render!(TransportToolbar |self, buf, area| { let bpm = self.bpm(); let pulse = self.pulse(); let usecs = self.usecs(); - let Self { quant, focused, entered, .. } = self; + let Self { quant, sync, focused, entered, .. } = self; fill_bg(buf, area, Nord::bg_lo(*focused, *entered)); - let area = Split::right([ + Split::right([ // Play/Pause button &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ @@ -38,21 +38,33 @@ render!(TransportToolbar |self, buf, area| { &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ "BPM".blit(buf, x, y, Some(not_dim))?; let width = format!("{}.{:03}", bpm, bpm % 1).blit(buf, x, y + 1, Some(not_dim_bold))?.width; - Ok(Rect { x, y, width: (width + 2).max(10), height: 2 }) + let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; + if self.focused && self.entered && self.selected == TransportFocus::BPM { + Corners(Style::default().green().not_dim()).draw(buf, Rect { x: area.x - 1, ..area })?; + } + Ok(area) }, // Quantization &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ "QUANT".blit(buf, x, y, Some(not_dim))?; let width = ppq_to_name(*quant).blit(buf, x, y + 1, Some(not_dim_bold))?.width; - Ok(Rect { x, y, width: (width + 2).max(10), height: 2 }) + let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; + if self.focused && self.entered && self.selected == TransportFocus::Quant { + Corners(Style::default().green().not_dim()).draw(buf, Rect { x: area.x - 1, ..area })?; + } + Ok(area) }, // Clip launch sync &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ "SYNC".blit(buf, x, y, Some(not_dim))?; - let width = "4/4".blit(buf, x, y + 1, Some(not_dim_bold))?.width; - Ok(Rect { x, y, width: (width + 2).max(10), height: 2 }) + let width = ppq_to_name(*sync).blit(buf, x, y + 1, Some(not_dim_bold))?.width; + let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; + if self.focused && self.entered && self.selected == TransportFocus::Sync { + Corners(Style::default().green().not_dim()).draw(buf, Rect { x: area.x - 1, ..area })?; + } + Ok(area) }, // Clock @@ -65,13 +77,7 @@ render!(TransportToolbar |self, buf, area| { timer.blit(buf, x + width - timer.len() as u16 - 1, y, Some(not_dim)) } - ]).render(buf, area)?; - - Ok(if *focused && *entered { - Corners(Style::default().green().not_dim()).draw(buf, area)? - } else { - area - }) + ]).render(buf, area) }); // Record button/indicator