mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
control transport values
This commit is contained in:
parent
33e5f47526
commit
45021bc77a
10 changed files with 168 additions and 86 deletions
|
|
@ -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<bool> {
|
||||
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<bool> {
|
|||
.map(|x|x.unwrap_or(false))
|
||||
}
|
||||
|
||||
pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = 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<App>] = 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<App>] = keymap!(App {
|
|||
Ok(true)
|
||||
}],
|
||||
});
|
||||
|
||||
fn focus_next (app: &mut App) -> Usually<bool> {
|
||||
app.section.next();
|
||||
app.transport.focused = app.section == AppSection::Transport;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn focus_prev (app: &mut App) -> Usually<bool> {
|
||||
app.section.prev();
|
||||
app.transport.focused = app.section == AppSection::Transport;
|
||||
Ok(true)
|
||||
}
|
||||
|
|
|
|||
32
src/control/focus.rs
Normal file
32
src/control/focus.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use crate::{core::*, model::{App, AppSection}};
|
||||
|
||||
pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = 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<bool> {
|
||||
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<bool> {
|
||||
app.section.prev();
|
||||
app.transport.focused = app.section == AppSection::Transport;
|
||||
app.transport.entered = app.entered;
|
||||
Ok(true)
|
||||
}
|
||||
40
src/control/transport.rs
Normal file
40
src/control/transport.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use crate::{core::*, model::{App, TransportFocus}};
|
||||
|
||||
pub const KEYMAP_TRANSPORT: &'static [KeyBinding<App>] = 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)
|
||||
}],
|
||||
});
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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::*};
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Timebase>,
|
||||
/// JACK transport handle.
|
||||
transport: Option<Transport>,
|
||||
/// Quantization factor
|
||||
pub quant: usize,
|
||||
/// Global sync quant
|
||||
pub sync: usize,
|
||||
/// Current transport state
|
||||
pub playing: Option<TransportState>,
|
||||
/// Current position according to transport
|
||||
|
|
@ -21,17 +48,20 @@ pub struct TransportToolbar {
|
|||
|
||||
impl TransportToolbar {
|
||||
pub fn new (transport: Option<Transport>) -> 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<()> {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()))?;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue