mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
wip: p.42, e=22, more intermediary trait layers
This commit is contained in:
parent
638298ad32
commit
dbf6e353b7
15 changed files with 937 additions and 688 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum ClockCommand {
|
pub enum ClockCommand {
|
||||||
SetBpm(f64),
|
SetBpm(f64),
|
||||||
SetQuant(f64),
|
SetQuant(f64),
|
||||||
|
|
|
||||||
|
|
@ -7,27 +7,10 @@ pub trait HasPlayer: HasJack {
|
||||||
|
|
||||||
pub trait PlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {}
|
pub trait PlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {}
|
||||||
|
|
||||||
pub trait HasMidiBuffer {
|
pub trait HasPhrase: PlayheadApi {
|
||||||
fn midi_buffer (&self) -> &Vec<Vec<Vec<u8>>>;
|
|
||||||
fn midi_buffer_mut (&self) -> &mut Vec<Vec<Vec<u8>>>;
|
|
||||||
|
|
||||||
fn reset (&self) -> bool;
|
fn reset (&self) -> bool;
|
||||||
fn reset_mut (&mut self) -> &mut bool;
|
fn reset_mut (&mut self) -> &mut bool;
|
||||||
|
|
||||||
/// Clear the section of the output buffer that we will be using,
|
|
||||||
/// emitting "all notes off" at start of buffer if requested.
|
|
||||||
fn clear (&mut self, scope: &ProcessScope, force_reset: bool) {
|
|
||||||
for frame in &mut self.midi_buffer_mut()[0..scope.n_frames() as usize] {
|
|
||||||
frame.clear();
|
|
||||||
}
|
|
||||||
if self.reset() || force_reset {
|
|
||||||
all_notes_off(&mut self.midi_buffer_mut());
|
|
||||||
*self.reset_mut() = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasPhrase: PlayheadApi + HasMidiBuffer {
|
|
||||||
fn phrase (&self)
|
fn phrase (&self)
|
||||||
-> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
-> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
||||||
fn phrase_mut (&self)
|
fn phrase_mut (&self)
|
||||||
|
|
@ -54,7 +37,7 @@ pub trait HasPhrase: PlayheadApi + HasMidiBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
|
pub trait MidiInputApi: PlayheadApi + HasPhrase {
|
||||||
fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
|
fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
|
||||||
fn midi_ins_mut (&self) -> &mut Vec<Port<MidiIn>>;
|
fn midi_ins_mut (&self) -> &mut Vec<Port<MidiIn>>;
|
||||||
fn has_midi_ins (&self) -> bool {
|
fn has_midi_ins (&self) -> bool {
|
||||||
|
|
@ -80,7 +63,11 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
|
||||||
|
|
||||||
fn notes_in (&self) -> &Arc<RwLock<[bool;128]>>;
|
fn notes_in (&self) -> &Arc<RwLock<[bool;128]>>;
|
||||||
|
|
||||||
fn record (&mut self, scope: &ProcessScope) {
|
fn record (
|
||||||
|
&mut self,
|
||||||
|
scope: &ProcessScope,
|
||||||
|
midi_buf: &mut Vec<Vec<Vec<u8>>>,
|
||||||
|
) {
|
||||||
let sample0 = scope.last_frame_time() as usize;
|
let sample0 = scope.last_frame_time() as usize;
|
||||||
if let (true, Some((started, phrase))) = (self.is_rolling(), self.phrase()) {
|
if let (true, Some((started, phrase))) = (self.is_rolling(), self.phrase()) {
|
||||||
let start = started.sample.get() as usize;
|
let start = started.sample.get() as usize;
|
||||||
|
|
@ -92,7 +79,7 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
|
||||||
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
|
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
|
||||||
if let LiveEvent::Midi { message, .. } = event {
|
if let LiveEvent::Midi { message, .. } = event {
|
||||||
if self.monitoring() {
|
if self.monitoring() {
|
||||||
self.midi_buffer_mut()[sample].push(bytes.to_vec())
|
midi_buf[sample].push(bytes.to_vec())
|
||||||
}
|
}
|
||||||
if self.recording() {
|
if self.recording() {
|
||||||
if let Some(phrase) = phrase {
|
if let Some(phrase) = phrase {
|
||||||
|
|
@ -117,12 +104,16 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn monitor (&mut self, scope: &ProcessScope) {
|
fn monitor (
|
||||||
|
&mut self,
|
||||||
|
scope: &ProcessScope,
|
||||||
|
midi_buf: &mut Vec<Vec<Vec<u8>>>,
|
||||||
|
) {
|
||||||
let mut notes_in = self.notes_in().write().unwrap();
|
let mut notes_in = self.notes_in().write().unwrap();
|
||||||
for input in self.midi_ins_mut().iter() {
|
for input in self.midi_ins_mut().iter() {
|
||||||
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
|
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
|
||||||
if let LiveEvent::Midi { message, .. } = event {
|
if let LiveEvent::Midi { message, .. } = event {
|
||||||
self.midi_buffer_mut()[sample].push(bytes.to_vec());
|
midi_buf[sample].push(bytes.to_vec());
|
||||||
update_keys(&mut notes_in, &message);
|
update_keys(&mut notes_in, &message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +122,7 @@ pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MidiOutputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
|
pub trait MidiOutputApi: PlayheadApi + HasPhrase {
|
||||||
fn midi_outs (&self) -> &Vec<Port<MidiOut>>;
|
fn midi_outs (&self) -> &Vec<Port<MidiOut>>;
|
||||||
|
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
|
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
|
||||||
|
|
@ -144,6 +135,22 @@ pub trait MidiOutputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
|
||||||
self.midi_outs().len() > 0
|
self.midi_outs().len() > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear the section of the output buffer that we will be using,
|
||||||
|
/// emitting "all notes off" at start of buffer if requested.
|
||||||
|
fn clear (
|
||||||
|
&mut self,
|
||||||
|
scope: &ProcessScope,
|
||||||
|
midi_buf: &mut Vec<Vec<Vec<u8>>>,
|
||||||
|
reset: bool
|
||||||
|
) {
|
||||||
|
for frame in &mut midi_buf[0..scope.n_frames() as usize] {
|
||||||
|
frame.clear();
|
||||||
|
}
|
||||||
|
if reset {
|
||||||
|
all_notes_off(midi_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn play (
|
fn play (
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &ProcessScope,
|
scope: &ProcessScope,
|
||||||
|
|
@ -298,26 +305,26 @@ pub struct PlayerAudio<'a, T: PlayerApi>(
|
||||||
/// JACK process callback for a sequencer's phrase player/recorder.
|
/// JACK process callback for a sequencer's phrase player/recorder.
|
||||||
impl<'a, T: PlayerApi> Audio for PlayerAudio<'a, T> {
|
impl<'a, T: PlayerApi> Audio for PlayerAudio<'a, T> {
|
||||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
let model = &mut self.0;
|
let model = &mut self.0;
|
||||||
let note_buffer = &mut self.1;
|
let note_buf = &mut self.1;
|
||||||
let output_buffer = &mut self.2;
|
let midi_buf = &mut self.2;
|
||||||
// Clear output buffer(s)
|
// Clear output buffer(s)
|
||||||
model.clear(scope, false);
|
model.clear(scope, midi_buf, false);
|
||||||
// Write chunk of phrase to output, handle switchover
|
// Write chunk of phrase to output, handle switchover
|
||||||
if model.play(scope, note_buffer, output_buffer) {
|
if model.play(scope, note_buf, midi_buf) {
|
||||||
model.switchover(scope, note_buffer, output_buffer);
|
model.switchover(scope, note_buf, midi_buf);
|
||||||
}
|
}
|
||||||
if model.has_midi_ins() {
|
if model.has_midi_ins() {
|
||||||
if model.recording() || model.monitoring() {
|
if model.recording() || model.monitoring() {
|
||||||
// Record and/or monitor input
|
// Record and/or monitor input
|
||||||
model.record(scope)
|
model.record(scope, midi_buf)
|
||||||
} else if model.has_midi_outs() && model.monitoring() {
|
} else if model.has_midi_outs() && model.monitoring() {
|
||||||
// Monitor input to output
|
// Monitor input to output
|
||||||
model.monitor(scope)
|
model.monitor(scope, midi_buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Write to output port(s)
|
// Write to output port(s)
|
||||||
model.write(scope, output_buffer);
|
model.write(scope, midi_buf);
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum PlayheadCommand {
|
pub enum PlayheadCommand {
|
||||||
Play(Option<usize>),
|
Play(Option<usize>),
|
||||||
Pause(Option<usize>),
|
Pause(Option<usize>),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,18 @@ use crate::*;
|
||||||
pub trait HasTracks<T: ArrangerTrackApi>: Send + Sync {
|
pub trait HasTracks<T: ArrangerTrackApi>: Send + Sync {
|
||||||
fn tracks (&self) -> &Vec<T>;
|
fn tracks (&self) -> &Vec<T>;
|
||||||
fn tracks_mut (&mut self) -> &mut Vec<T>;
|
fn tracks_mut (&mut self) -> &mut Vec<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ArrangerTrackApi> HasTracks<T> for Vec<T> {
|
||||||
|
fn tracks (&self) -> &Vec<T> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn tracks_mut (&mut self) -> &mut Vec<T> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ArrangerTracksApi<T: ArrangerTrackApi>: HasTracks<T> {
|
||||||
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)-> Usually<&mut T>;
|
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)-> Usually<&mut T>;
|
||||||
fn track_del (&mut self, index: usize);
|
fn track_del (&mut self, index: usize);
|
||||||
fn track_default_name (&self) -> String {
|
fn track_default_name (&self) -> String {
|
||||||
|
|
@ -21,16 +33,6 @@ pub enum ArrangerTrackCommand {
|
||||||
SetZoom(usize),
|
SetZoom(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
//impl<T: ArrangerApi> Command<T> for ArrangerTrackCommand {
|
|
||||||
//fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
||||||
//match self {
|
|
||||||
//Self::Delete(index) => { state.track_del(index); },
|
|
||||||
//_ => todo!()
|
|
||||||
//}
|
|
||||||
//Ok(None)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
pub trait ArrangerTrackApi: PlayerApi + Send + Sync + Sized {
|
pub trait ArrangerTrackApi: PlayerApi + Send + Sync + Sized {
|
||||||
/// Name of track
|
/// Name of track
|
||||||
fn name (&self) -> &Arc<RwLock<String>>;
|
fn name (&self) -> &Arc<RwLock<String>>;
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub enum FocusCommand {
|
||||||
Exit
|
Exit
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: FocusGrid> Command<F> for FocusCommand {
|
impl<F: FocusOrder + FocusGrid + FocusEnter> Command<F> for FocusCommand {
|
||||||
fn execute (self, state: &mut F) -> Perhaps<FocusCommand> {
|
fn execute (self, state: &mut F) -> Perhaps<FocusCommand> {
|
||||||
use FocusCommand::*;
|
use FocusCommand::*;
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -24,93 +24,124 @@ impl<F: FocusGrid> Command<F> for FocusCommand {
|
||||||
Right => { state.focus_right(); },
|
Right => { state.focus_right(); },
|
||||||
Enter => { state.focus_enter(); },
|
Enter => { state.focus_enter(); },
|
||||||
Exit => { state.focus_exit(); },
|
Exit => { state.focus_exit(); },
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FocusGrid {
|
pub trait HasFocus {
|
||||||
|
type Item: Copy + PartialEq + Debug;
|
||||||
|
fn focused (&self) -> Self::Item;
|
||||||
|
fn focus (&mut self, target: Self::Item);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FocusOrder {
|
||||||
|
fn focus_next (&mut self);
|
||||||
|
fn focus_prev (&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FocusEnter {
|
||||||
type Item: Copy + PartialEq + Debug;
|
type Item: Copy + PartialEq + Debug;
|
||||||
fn layout (&self) -> &[&[Self::Item]];
|
|
||||||
fn cursor (&self) -> (usize, usize);
|
|
||||||
fn cursor_mut (&mut self) -> &mut (usize, usize);
|
|
||||||
fn update_focus (&mut self) {}
|
|
||||||
fn focus_enter (&mut self) {}
|
fn focus_enter (&mut self) {}
|
||||||
fn focus_exit (&mut self) {}
|
fn focus_exit (&mut self) {}
|
||||||
fn entered (&self) -> Option<Self::Item>;
|
fn focus_entered (&self) -> Option<Self::Item>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FocusGrid {
|
||||||
|
type Item: Copy + PartialEq + Debug;
|
||||||
|
fn focus_layout (&self) -> &[&[Self::Item]];
|
||||||
|
fn focus_cursor (&self) -> (usize, usize);
|
||||||
|
fn focus_cursor_mut (&mut self) -> &mut (usize, usize);
|
||||||
|
fn focus_update (&mut self) {}
|
||||||
|
fn focus_up (&mut self) {
|
||||||
|
let layout = self.focus_layout();
|
||||||
|
let (x, y) = self.focus_cursor();
|
||||||
|
let next_y = if y == 0 { layout.len().saturating_sub(1) } else { y - 1 };
|
||||||
|
let next_x = if layout[y].len() == layout[next_y].len() { x } else {
|
||||||
|
((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize
|
||||||
|
};
|
||||||
|
*self.focus_cursor_mut() = (next_x, next_y);
|
||||||
|
self.focus_update();
|
||||||
|
}
|
||||||
|
fn focus_down (&mut self) {
|
||||||
|
let layout = self.focus_layout();
|
||||||
|
let (x, y) = self.focus_cursor();
|
||||||
|
let next_y = if y >= layout.len().saturating_sub(1) { 0 } else { y + 1 };
|
||||||
|
let next_x = if layout[y].len() == layout[next_y].len() { x } else {
|
||||||
|
((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize
|
||||||
|
};
|
||||||
|
*self.focus_cursor_mut() = (next_x, next_y);
|
||||||
|
self.focus_update();
|
||||||
|
}
|
||||||
|
fn focus_left (&mut self) {
|
||||||
|
let layout = self.focus_layout();
|
||||||
|
let (x, y) = self.focus_cursor();
|
||||||
|
let next_x = if x == 0 { layout[y].len().saturating_sub(1) } else { x - 1 };
|
||||||
|
*self.focus_cursor_mut() = (next_x, y);
|
||||||
|
self.focus_update();
|
||||||
|
}
|
||||||
|
fn focus_right (&mut self) {
|
||||||
|
let layout = self.focus_layout();
|
||||||
|
let (x, y) = self.focus_cursor();
|
||||||
|
let next_x = if x >= layout[y].len().saturating_sub(1) { 0 } else { x + 1 };
|
||||||
|
*self.focus_cursor_mut() = (next_x, y);
|
||||||
|
self.focus_update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> HasFocus for U
|
||||||
|
where
|
||||||
|
T: Copy + PartialEq + Debug,
|
||||||
|
U: FocusGrid<Item = T> + FocusOrder,
|
||||||
|
{
|
||||||
|
type Item = T;
|
||||||
fn focused (&self) -> Self::Item {
|
fn focused (&self) -> Self::Item {
|
||||||
let (x, y) = self.cursor();
|
let (x, y) = self.focus_cursor();
|
||||||
self.layout()[y][x]
|
self.focus_layout()[y][x]
|
||||||
}
|
}
|
||||||
fn focus (&mut self, target: Self::Item) {
|
fn focus (&mut self, target: Self::Item) {
|
||||||
while self.focused() != target {
|
while self.focused() != target {
|
||||||
self.focus_next()
|
self.focus_next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn focus_up (&mut self) {
|
}
|
||||||
let layout = self.layout();
|
|
||||||
let (x, y) = self.cursor();
|
impl<T, U> FocusOrder for U
|
||||||
let next_y = if y == 0 { layout.len().saturating_sub(1) } else { y - 1 };
|
where
|
||||||
let next_x = if layout[y].len() == layout[next_y].len() { x } else {
|
T: Copy + PartialEq + Debug,
|
||||||
((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize
|
U: HasFocus<Item = T> + FocusGrid<Item = T> + FocusEnter
|
||||||
};
|
{
|
||||||
*self.cursor_mut() = (next_x, next_y);
|
|
||||||
self.update_focus();
|
|
||||||
}
|
|
||||||
fn focus_down (&mut self) {
|
|
||||||
let layout = self.layout();
|
|
||||||
let (x, y) = self.cursor();
|
|
||||||
let next_y = if y >= layout.len().saturating_sub(1) { 0 } else { y + 1 };
|
|
||||||
let next_x = if layout[y].len() == layout[next_y].len() { x } else {
|
|
||||||
((x as f32 / layout[y].len() as f32) * layout[next_y].len() as f32) as usize
|
|
||||||
};
|
|
||||||
*self.cursor_mut() = (next_x, next_y);
|
|
||||||
self.update_focus();
|
|
||||||
}
|
|
||||||
fn focus_left (&mut self) {
|
|
||||||
let layout = self.layout();
|
|
||||||
let (x, y) = self.cursor();
|
|
||||||
let next_x = if x == 0 { layout[y].len().saturating_sub(1) } else { x - 1 };
|
|
||||||
*self.cursor_mut() = (next_x, y);
|
|
||||||
self.update_focus();
|
|
||||||
}
|
|
||||||
fn focus_right (&mut self) {
|
|
||||||
let layout = self.layout();
|
|
||||||
let (x, y) = self.cursor();
|
|
||||||
let next_x = if x >= layout[y].len().saturating_sub(1) { 0 } else { x + 1 };
|
|
||||||
*self.cursor_mut() = (next_x, y);
|
|
||||||
self.update_focus();
|
|
||||||
}
|
|
||||||
fn focus_next (&mut self) {
|
fn focus_next (&mut self) {
|
||||||
let current = self.focused();
|
let current = self.focused();
|
||||||
let (x, y) = self.cursor();
|
let (x, y) = self.focus_cursor();
|
||||||
if x < self.layout()[y].len().saturating_sub(1) {
|
if x < self.focus_layout()[y].len().saturating_sub(1) {
|
||||||
self.focus_right();
|
self.focus_right();
|
||||||
} else {
|
} else {
|
||||||
self.focus_down();
|
self.focus_down();
|
||||||
self.cursor_mut().0 = 0;
|
self.focus_cursor_mut().0 = 0;
|
||||||
}
|
}
|
||||||
if self.focused() == current { // FIXME: prevent infinite loop
|
if self.focused() == current { // FIXME: prevent infinite loop
|
||||||
self.focus_next()
|
self.focus_next()
|
||||||
}
|
}
|
||||||
self.focus_exit();
|
self.focus_exit();
|
||||||
self.update_focus();
|
self.focus_update();
|
||||||
}
|
}
|
||||||
fn focus_prev (&mut self) {
|
fn focus_prev (&mut self) {
|
||||||
let current = self.focused();
|
let current = self.focused();
|
||||||
let (x, _) = self.cursor();
|
let (x, _) = self.focus_cursor();
|
||||||
if x > 0 {
|
if x > 0 {
|
||||||
self.focus_left();
|
self.focus_left();
|
||||||
} else {
|
} else {
|
||||||
self.focus_up();
|
self.focus_up();
|
||||||
let (_, y) = self.cursor();
|
let (_, y) = self.focus_cursor();
|
||||||
let next_x = self.layout()[y].len().saturating_sub(1);
|
let next_x = self.focus_layout()[y].len().saturating_sub(1);
|
||||||
self.cursor_mut().0 = next_x;
|
self.focus_cursor_mut().0 = next_x;
|
||||||
}
|
}
|
||||||
if self.focused() == current { // FIXME: prevent infinite loop
|
if self.focused() == current { // FIXME: prevent infinite loop
|
||||||
self.focus_prev()
|
self.focus_prev()
|
||||||
}
|
}
|
||||||
self.focus_exit();
|
self.focus_exit();
|
||||||
self.update_focus();
|
self.focus_update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -883,6 +883,7 @@ impl<E: Engine> Measure<E> {
|
||||||
pub fn set_h (&self, h: impl Into<usize>) { self.2.store(h.into(), Ordering::Relaxed) }
|
pub fn set_h (&self, h: impl Into<usize>) { self.2.store(h.into(), Ordering::Relaxed) }
|
||||||
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) { self.set_w(w); self.set_h(h); }
|
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) { self.set_w(w); self.set_h(h); }
|
||||||
pub fn new () -> Self { Self(PhantomData::default(), 0.into(), 0.into()) }
|
pub fn new () -> Self { Self(PhantomData::default(), 0.into(), 0.into()) }
|
||||||
|
pub fn format (&self) -> String { format!("{}x{}", self.w(), self.h()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> Widget for Measure<E> {
|
impl<E: Engine> Widget for Measure<E> {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ use std::fmt::Debug;
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
tui_arranger
|
tui_arranger
|
||||||
|
tui_arranger_track
|
||||||
|
tui_arranger_scene
|
||||||
//tui_mixer // TODO
|
//tui_mixer // TODO
|
||||||
tui_phrase
|
tui_phrase
|
||||||
//tui_plugin // TODO
|
//tui_plugin // TODO
|
||||||
|
|
@ -24,9 +26,11 @@ submod! {
|
||||||
//tui_sampler // TODO
|
//tui_sampler // TODO
|
||||||
//tui_sampler_cmd
|
//tui_sampler_cmd
|
||||||
tui_sequencer
|
tui_sequencer
|
||||||
|
tui_sequencer_cmd
|
||||||
tui_status
|
tui_status
|
||||||
tui_theme
|
tui_theme
|
||||||
tui_transport
|
tui_transport
|
||||||
|
tui_transport_cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppView<E, A, C, S>
|
pub struct AppView<E, A, C, S>
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,25 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerApp<Tui> {
|
||||||
Ok(Self::new(ArrangerView {
|
Ok(Self::new(ArrangerView {
|
||||||
name: Arc::new(RwLock::new(String::new())),
|
name: Arc::new(RwLock::new(String::new())),
|
||||||
phrases: vec![],
|
phrases: vec![],
|
||||||
|
phrase: 0,
|
||||||
scenes: vec![],
|
scenes: vec![],
|
||||||
tracks: vec![],
|
tracks: vec![],
|
||||||
metronome: false,
|
metronome: false,
|
||||||
|
playing: None.into(),
|
||||||
|
started: None.into(),
|
||||||
transport: jack.read().unwrap().transport(),
|
transport: jack.read().unwrap().transport(),
|
||||||
|
current: Instant::default(),
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
selected: ArrangerSelection::Clip(0, 0),
|
selected: ArrangerSelection::Clip(0, 0),
|
||||||
mode: ArrangerMode::Vertical(2),
|
mode: ArrangerMode::Vertical(2),
|
||||||
color: Color::Rgb(28, 35, 25).into(),
|
color: Color::Rgb(28, 35, 25).into(),
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
focused: false,
|
|
||||||
entered: false,
|
entered: false,
|
||||||
|
quant: Default::default(),
|
||||||
|
sync: Default::default(),
|
||||||
|
splits: [20, 20],
|
||||||
|
note_buf: vec![],
|
||||||
|
midi_buf: vec![],
|
||||||
}.into(), None, None))
|
}.into(), None, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -28,9 +36,14 @@ pub type ArrangerApp<E: Engine> = AppView<
|
||||||
ArrangerStatusBar
|
ArrangerStatusBar
|
||||||
>;
|
>;
|
||||||
|
|
||||||
impl<E: Engine> Audio for ArrangerApp<E> {
|
impl Audio for ArrangerApp<Tui> {
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
ArrangerRefAudio(self.app).process(client, scope)
|
TracksAudio(
|
||||||
|
&mut self.app.tracks,
|
||||||
|
&mut self.app.note_buf,
|
||||||
|
&mut self.app.midi_buf,
|
||||||
|
Default::default(),
|
||||||
|
).process(client, scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,39 +91,35 @@ impl InputToCommand<Tui, ArrangerApp<Tui>> for ArrangerAppCommand {
|
||||||
Self::App(Playhead(PlayheadCommand::Play(None)))
|
Self::App(Playhead(PlayheadCommand::Play(None)))
|
||||||
},
|
},
|
||||||
_ => Self::App(match view.focused() {
|
_ => Self::App(match view.focused() {
|
||||||
Content(ArrangerViewFocus::Transport) => {
|
Content(ArrangerFocus::Transport) => {
|
||||||
match TransportCommand::input_to_command(&view.app.transport, input)? {
|
use TransportCommand::{Clock, Playhead};
|
||||||
Focus(command) => {
|
match TransportCommand::input_to_command(view, input)? {
|
||||||
|
Clock(command) => {
|
||||||
todo!()
|
todo!()
|
||||||
},
|
},
|
||||||
App(Clock(command)) => {
|
Playhead(command) => {
|
||||||
todo!()
|
|
||||||
},
|
|
||||||
App(Playhead(command)) => {
|
|
||||||
todo!()
|
todo!()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Content(ArrangerViewFocus::PhraseEditor) => Editor(
|
Content(ArrangerFocus::PhraseEditor) => Editor(
|
||||||
PhraseEditorCommand::input_to_command(&view.app.editor, input)?
|
PhraseEditorCommand::input_to_command(&view.app.editor, input)?
|
||||||
),
|
),
|
||||||
Content(ArrangerViewFocus::PhrasePool) => match input.event() {
|
Content(ArrangerFocus::PhrasePool) => match input.event() {
|
||||||
key!(KeyCode::Char('e')) => EditPhrase(
|
key!(KeyCode::Char('e')) => EditPhrase(
|
||||||
Some(view.app.phrases.phrase().clone())
|
Some(view.app.phrase().clone())
|
||||||
),
|
),
|
||||||
_ => Phrases(
|
_ => Phrases(
|
||||||
PhrasePoolViewCommand::input_to_command(&view.app.phrases, input)?
|
PhrasePoolViewCommand::input_to_command(view, input)?
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
Content(ArrangerViewFocus::Arranger) => {
|
Content(ArrangerFocus::Arranger) => {
|
||||||
use ArrangerSelection as Select;
|
use ArrangerSelection as Select;
|
||||||
use ArrangerTrackCommand as Track;
|
use ArrangerTrackCommand as Track;
|
||||||
use ArrangerClipCommand as Clip;
|
use ArrangerClipCommand as Clip;
|
||||||
use ArrangerSceneCommand as Scene;
|
use ArrangerSceneCommand as Scene;
|
||||||
match input.event() {
|
match input.event() {
|
||||||
key!(KeyCode::Char('e')) => EditPhrase(
|
key!(KeyCode::Char('e')) => EditPhrase(view.phrase()),
|
||||||
view.selected_phrase()
|
|
||||||
),
|
|
||||||
_ => match input.event() {
|
_ => match input.event() {
|
||||||
// FIXME: boundary conditions
|
// FIXME: boundary conditions
|
||||||
|
|
||||||
|
|
@ -250,7 +259,7 @@ impl Command<ArrangerApp<Tui>> for ArrangerViewCommand {
|
||||||
Select(selected) => { state.selected = selected; Ok(None) },
|
Select(selected) => { state.selected = selected; Ok(None) },
|
||||||
EditPhrase(phrase) => {
|
EditPhrase(phrase) => {
|
||||||
state.editor.phrase = phrase.clone();
|
state.editor.phrase = phrase.clone();
|
||||||
state.focus(ArrangerViewFocus::PhraseEditor);
|
state.focus(ArrangerFocus::PhraseEditor);
|
||||||
state.focus_enter();
|
state.focus_enter();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
@ -277,10 +286,10 @@ pub struct ArrangerView<E: Engine> {
|
||||||
selected: ArrangerSelection,
|
selected: ArrangerSelection,
|
||||||
mode: ArrangerMode,
|
mode: ArrangerMode,
|
||||||
color: ItemColor,
|
color: ItemColor,
|
||||||
editor: PhraseEditor<E>,
|
|
||||||
focused: bool,
|
|
||||||
entered: bool,
|
entered: bool,
|
||||||
size: Measure<E>,
|
size: Measure<E>,
|
||||||
|
note_buf: Vec<u8>,
|
||||||
|
midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasJack for ArrangerView<Tui> {
|
impl HasJack for ArrangerView<Tui> {
|
||||||
|
|
@ -332,15 +341,27 @@ impl HasTracks<ArrangerTrack> for ArrangerView<Tui> {
|
||||||
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> {
|
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> {
|
||||||
&mut self.tracks
|
&mut self.tracks
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrangerTracksApi<ArrangerTrack> for ArrangerView<Tui> {
|
||||||
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
||||||
-> Usually<&mut ArrangerTrack>
|
-> Usually<&mut ArrangerTrack>
|
||||||
{
|
{
|
||||||
let name = name.map_or_else(||self.track_default_name(), |x|x.to_string());
|
let name = name.map_or_else(||self.track_default_name(), |x|x.to_string());
|
||||||
let track = ArrangerTrack {
|
let track = ArrangerTrack {
|
||||||
name: Arc::new(name.into()),
|
width: name.len() + 2,
|
||||||
color: color.unwrap_or_else(||ItemColor::random()),
|
name: Arc::new(name.into()),
|
||||||
width: name.len() + 2,
|
color: color.unwrap_or_else(||ItemColor::random()),
|
||||||
//player: MIDIPlayer::new(&self.jack(), &self.clock(), name.as_str())?,
|
midi_ins: vec![],
|
||||||
|
midi_outs: vec![],
|
||||||
|
reset: true,
|
||||||
|
recording: false,
|
||||||
|
monitoring: false,
|
||||||
|
overdub: false,
|
||||||
|
play_phrase: None,
|
||||||
|
next_phrase: None,
|
||||||
|
notes_in: RwLock::new([false;128]).into(),
|
||||||
|
notes_out: RwLock::new([false;128]).into(),
|
||||||
};
|
};
|
||||||
self.tracks_mut().push(track);
|
self.tracks_mut().push(track);
|
||||||
let index = self.tracks().len() - 1;
|
let index = self.tracks().len() - 1;
|
||||||
|
|
@ -387,7 +408,7 @@ pub enum ArrangerMode {
|
||||||
|
|
||||||
/// Sections in the arranger app that may be focused
|
/// Sections in the arranger app that may be focused
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum ArrangerViewFocus {
|
pub enum ArrangerFocus {
|
||||||
/// The transport (toolbar) is focused
|
/// The transport (toolbar) is focused
|
||||||
Transport,
|
Transport,
|
||||||
/// The arrangement (grid) is focused
|
/// The arrangement (grid) is focused
|
||||||
|
|
@ -444,9 +465,9 @@ impl Content for ArrangerView<Tui> {
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
Split::up(
|
Split::up(
|
||||||
1,
|
1,
|
||||||
widget(&self.transport),
|
widget(&TransportRef(self)),
|
||||||
Split::down(
|
Split::down(
|
||||||
self.split,
|
self.splits[0],
|
||||||
lay!(
|
lay!(
|
||||||
Layers::new(move |add|{
|
Layers::new(move |add|{
|
||||||
match self.mode {
|
match self.mode {
|
||||||
|
|
@ -471,9 +492,9 @@ impl Content for ArrangerView<Tui> {
|
||||||
.push_x(1),
|
.push_x(1),
|
||||||
),
|
),
|
||||||
Split::right(
|
Split::right(
|
||||||
self.split,
|
self.splits[1],
|
||||||
widget(&self.phrases),
|
widget(&self.phrases),
|
||||||
widget(&self.editor),
|
widget(&PhraseEditorRef(self)),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -481,7 +502,7 @@ impl Content for ArrangerView<Tui> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// General methods for arranger
|
/// General methods for arranger
|
||||||
impl<E: Engine> ArrangerView<E> {
|
impl ArrangerView<Tui> {
|
||||||
|
|
||||||
/// Focus the editor with the current phrase
|
/// Focus the editor with the current phrase
|
||||||
pub fn show_phrase (&mut self) {
|
pub fn show_phrase (&mut self) {
|
||||||
|
|
@ -562,6 +583,30 @@ impl<E: Engine> ArrangerView<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TransportViewState for ArrangerView<Tui> {
|
||||||
|
fn focus (&self) -> TransportViewFocus {
|
||||||
|
self.focus
|
||||||
|
}
|
||||||
|
fn focused (&self) -> bool {
|
||||||
|
self.focused
|
||||||
|
}
|
||||||
|
fn transport_state (&self) -> Option<TransportState> {
|
||||||
|
*self.playing().read().unwrap()
|
||||||
|
}
|
||||||
|
fn bpm_value (&self) -> f64 {
|
||||||
|
self.bpm().get()
|
||||||
|
}
|
||||||
|
fn sync_value (&self) -> f64 {
|
||||||
|
self.sync().get()
|
||||||
|
}
|
||||||
|
fn format_beat (&self) -> String {
|
||||||
|
self.current().format_beat()
|
||||||
|
}
|
||||||
|
fn format_msu (&self) -> String {
|
||||||
|
self.current().usec.format_msu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<E: Engine> Audio for ArrangerView<E> {
|
impl<E: Engine> Audio for ArrangerView<E> {
|
||||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
if self.process(client, scope) == Control::Quit {
|
if self.process(client, scope) == Control::Quit {
|
||||||
|
|
@ -635,7 +680,7 @@ impl<E: Engine> Audio for ArrangerView<E> {
|
||||||
//self.arrangement.phrase_put();
|
//self.arrangement.phrase_put();
|
||||||
//}
|
//}
|
||||||
//self.show_phrase();
|
//self.show_phrase();
|
||||||
//self.focus(ArrangerViewFocus::PhraseEditor);
|
//self.focus(ArrangerFocus::PhraseEditor);
|
||||||
//self.editor.entered = true;
|
//self.editor.entered = true;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
|
@ -672,18 +717,10 @@ impl<E: Engine> Audio for ArrangerView<E> {
|
||||||
//self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
//self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
||||||
//}
|
//}
|
||||||
|
|
||||||
/// Focus layout of arranger app
|
impl FocusEnter for ArrangerApp<Tui> {
|
||||||
impl FocusGrid for ArrangerApp<Tui> {
|
|
||||||
type Item = AppViewFocus<ArrangerViewFocus>;
|
|
||||||
fn cursor (&self) -> (usize, usize) {
|
|
||||||
self.cursor
|
|
||||||
}
|
|
||||||
fn cursor_mut (&mut self) -> &mut (usize, usize) {
|
|
||||||
&mut self.cursor
|
|
||||||
}
|
|
||||||
fn focus_enter (&mut self) {
|
fn focus_enter (&mut self) {
|
||||||
use AppViewFocus::*;
|
use AppViewFocus::*;
|
||||||
use ArrangerViewFocus::*;
|
use ArrangerFocus::*;
|
||||||
let focused = self.focused();
|
let focused = self.focused();
|
||||||
if !self.entered {
|
if !self.entered {
|
||||||
self.entered = focused == Content(Arranger);
|
self.entered = focused == Content(Arranger);
|
||||||
|
|
@ -698,16 +735,27 @@ impl FocusGrid for ArrangerApp<Tui> {
|
||||||
self.app.phrases.entered = false;
|
self.app.phrases.entered = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn entered (&self) -> Option<Self::Item> {
|
fn focus_entered (&self) -> Option<Self::Item> {
|
||||||
if self.entered {
|
if self.entered {
|
||||||
Some(self.focused())
|
Some(self.focused())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn layout (&self) -> &[&[Self::Item]] {
|
}
|
||||||
|
|
||||||
|
/// Focus layout of arranger app
|
||||||
|
impl FocusGrid for ArrangerApp<Tui> {
|
||||||
|
type Item = AppViewFocus<ArrangerFocus>;
|
||||||
|
fn focus_cursor (&self) -> (usize, usize) {
|
||||||
|
self.cursor
|
||||||
|
}
|
||||||
|
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
||||||
|
&mut self.cursor
|
||||||
|
}
|
||||||
|
fn focus_layout (&self) -> &[&[Self::Item]] {
|
||||||
use AppViewFocus::*;
|
use AppViewFocus::*;
|
||||||
use ArrangerViewFocus::*;
|
use ArrangerFocus::*;
|
||||||
&[
|
&[
|
||||||
&[Menu, Menu ],
|
&[Menu, Menu ],
|
||||||
&[Content(Transport), Content(Transport) ],
|
&[Content(Transport), Content(Transport) ],
|
||||||
|
|
@ -715,19 +763,15 @@ impl FocusGrid for ArrangerApp<Tui> {
|
||||||
&[Content(PhrasePool), Content(PhraseEditor)],
|
&[Content(PhrasePool), Content(PhraseEditor)],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
fn update_focus (&mut self) {
|
fn focus_update (&mut self) {
|
||||||
use AppViewFocus::*;
|
use AppViewFocus::*;
|
||||||
use ArrangerViewFocus::*;
|
use ArrangerFocus::*;
|
||||||
let focused = self.focused();
|
let focused = self.focused();
|
||||||
self.app.focused = focused == Content(Arranger);
|
|
||||||
self.app.transport.focused = focused == Content(Transport);
|
|
||||||
self.app.phrases.focused = focused == Content(PhrasePool);
|
|
||||||
self.app.editor.focused = focused == Content(PhraseEditor);
|
|
||||||
if let Some(mut status_bar) = self.status_bar {
|
if let Some(mut status_bar) = self.status_bar {
|
||||||
status_bar.update(&(
|
status_bar.update(&(
|
||||||
self.focused(),
|
self.focused(),
|
||||||
self.app.selected,
|
self.app.selected,
|
||||||
self.app.editor.entered
|
focused == Content(PhraseEditor) && self.entered
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -827,7 +871,7 @@ impl ArrangerSelection {
|
||||||
|
|
||||||
impl StatusBar for ArrangerStatusBar {
|
impl StatusBar for ArrangerStatusBar {
|
||||||
|
|
||||||
type State = (AppViewFocus<ArrangerViewFocus>, ArrangerSelection, bool);
|
type State = (AppViewFocus<ArrangerFocus>, ArrangerSelection, bool);
|
||||||
|
|
||||||
fn hotkey_fg () -> Color where Self: Sized {
|
fn hotkey_fg () -> Color where Self: Sized {
|
||||||
TuiTheme::hotkey_fg()
|
TuiTheme::hotkey_fg()
|
||||||
|
|
@ -836,15 +880,15 @@ impl StatusBar for ArrangerStatusBar {
|
||||||
use AppViewFocus::*;
|
use AppViewFocus::*;
|
||||||
if let Content(focused) = focused {
|
if let Content(focused) = focused {
|
||||||
*self = match focused {
|
*self = match focused {
|
||||||
ArrangerViewFocus::Transport => ArrangerStatusBar::Transport,
|
ArrangerFocus::Transport => ArrangerStatusBar::Transport,
|
||||||
ArrangerViewFocus::Arranger => match selected {
|
ArrangerFocus::Arranger => match selected {
|
||||||
ArrangerSelection::Mix => ArrangerStatusBar::ArrangerMix,
|
ArrangerSelection::Mix => ArrangerStatusBar::ArrangerMix,
|
||||||
ArrangerSelection::Track(_) => ArrangerStatusBar::ArrangerTrack,
|
ArrangerSelection::Track(_) => ArrangerStatusBar::ArrangerTrack,
|
||||||
ArrangerSelection::Scene(_) => ArrangerStatusBar::ArrangerScene,
|
ArrangerSelection::Scene(_) => ArrangerStatusBar::ArrangerScene,
|
||||||
ArrangerSelection::Clip(_, _) => ArrangerStatusBar::ArrangerClip,
|
ArrangerSelection::Clip(_, _) => ArrangerStatusBar::ArrangerClip,
|
||||||
},
|
},
|
||||||
ArrangerViewFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
|
ArrangerFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
|
||||||
ArrangerViewFocus::PhraseEditor => match entered {
|
ArrangerFocus::PhraseEditor => match entered {
|
||||||
true => ArrangerStatusBar::PhraseEdit,
|
true => ArrangerStatusBar::PhraseEdit,
|
||||||
false => ArrangerStatusBar::PhraseView,
|
false => ArrangerStatusBar::PhraseView,
|
||||||
},
|
},
|
||||||
|
|
@ -1386,182 +1430,3 @@ pub fn arranger_content_horizontal (
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
|
||||||
pub struct ArrangerScene {
|
|
||||||
/// Name of scene
|
|
||||||
pub name: Arc<RwLock<String>>,
|
|
||||||
/// Clips in scene, one per track
|
|
||||||
pub clips: Vec<Option<Arc<RwLock<Phrase>>>>,
|
|
||||||
/// Identifying color of scene
|
|
||||||
pub color: ItemColor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArrangerSceneApi for ArrangerScene {
|
|
||||||
fn name (&self) -> &Arc<RwLock<String>> {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
fn clips (&self) -> &Vec<Option<Arc<RwLock<Phrase>>>> {
|
|
||||||
&self.clips
|
|
||||||
}
|
|
||||||
fn color (&self) -> ItemColor {
|
|
||||||
self.color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ArrangerTrack {
|
|
||||||
/// Name of track
|
|
||||||
pub name: Arc<RwLock<String>>,
|
|
||||||
/// Preferred width of track column
|
|
||||||
pub width: usize,
|
|
||||||
/// Identifying color of track
|
|
||||||
pub color: ItemColor,
|
|
||||||
/// Start time and phrase being played
|
|
||||||
play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
|
||||||
/// Start time and next phrase
|
|
||||||
next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
|
||||||
/// Play input through output.
|
|
||||||
monitoring: bool,
|
|
||||||
/// Write input to sequence.
|
|
||||||
recording: bool,
|
|
||||||
/// Overdub input to sequence.
|
|
||||||
overdub: bool,
|
|
||||||
/// Send all notes off
|
|
||||||
reset: bool, // TODO?: after Some(nframes)
|
|
||||||
/// Record from MIDI ports to current sequence.
|
|
||||||
midi_inputs: Vec<Port<MidiIn>>,
|
|
||||||
/// Play from current sequence to MIDI ports
|
|
||||||
midi_outputs: Vec<Port<MidiOut>>,
|
|
||||||
/// MIDI output buffer
|
|
||||||
midi_note: Vec<u8>,
|
|
||||||
/// MIDI output buffer
|
|
||||||
midi_chunk: Vec<Vec<Vec<u8>>>,
|
|
||||||
/// Notes currently held at input
|
|
||||||
notes_in: Arc<RwLock<[bool; 128]>>,
|
|
||||||
/// Notes currently held at output
|
|
||||||
notes_out: Arc<RwLock<[bool; 128]>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArrangerTrackApi for ArrangerTrack {
|
|
||||||
/// Name of track
|
|
||||||
fn name (&self) -> &Arc<RwLock<String>> {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
/// Preferred width of track column
|
|
||||||
fn width (&self) -> usize {
|
|
||||||
self.width
|
|
||||||
}
|
|
||||||
/// Preferred width of track column
|
|
||||||
fn width_mut (&mut self) -> &mut usize {
|
|
||||||
&mut self.width
|
|
||||||
}
|
|
||||||
/// Identifying color of track
|
|
||||||
fn color (&self) -> ItemColor {
|
|
||||||
self.color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasMidiBuffer for ArrangerTrack {
|
|
||||||
fn midi_buffer (&self) -> &Vec<Vec<Vec<u8>>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_buffer_mut (&self) -> &mut Vec<Vec<Vec<u8>>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn reset (&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn reset_mut (&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPhrase for ArrangerTrack {
|
|
||||||
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiInputApi for ArrangerTrack {
|
|
||||||
fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn recording(&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn recording_mut(&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn monitoring(&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn monitoring_mut(&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn overdub(&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn overdub_mut(&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiOutputApi for ArrangerTrack {
|
|
||||||
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_note (&mut self) -> &mut Vec<u8> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClockApi for ArrangerTrack {
|
|
||||||
fn timebase (&self) -> &Arc<Timebase> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn quant (&self) -> &Quantize {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &LaunchSync {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayheadApi for ArrangerTrack {
|
|
||||||
fn current(&self) -> &Instant {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn transport(&self) -> &Transport {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn playing(&self) -> &RwLock<Option<TransportState>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn started(&self) -> &RwLock<Option<(usize, usize)>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayerApi for ArrangerTrack {}
|
|
||||||
|
|
|
||||||
23
crates/tek_tui/src/tui_arranger_scene.rs
Normal file
23
crates/tek_tui/src/tui_arranger_scene.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct ArrangerScene {
|
||||||
|
/// Name of scene
|
||||||
|
pub name: Arc<RwLock<String>>,
|
||||||
|
/// Clips in scene, one per track
|
||||||
|
pub clips: Vec<Option<Arc<RwLock<Phrase>>>>,
|
||||||
|
/// Identifying color of scene
|
||||||
|
pub color: ItemColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrangerSceneApi for ArrangerScene {
|
||||||
|
fn name (&self) -> &Arc<RwLock<String>> {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
fn clips (&self) -> &Vec<Option<Arc<RwLock<Phrase>>>> {
|
||||||
|
&self.clips
|
||||||
|
}
|
||||||
|
fn color (&self) -> ItemColor {
|
||||||
|
self.color
|
||||||
|
}
|
||||||
|
}
|
||||||
150
crates/tek_tui/src/tui_arranger_track.rs
Normal file
150
crates/tek_tui/src/tui_arranger_track.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ArrangerTrack {
|
||||||
|
/// Name of track
|
||||||
|
name: Arc<RwLock<String>>,
|
||||||
|
/// Preferred width of track column
|
||||||
|
width: usize,
|
||||||
|
/// Identifying color of track
|
||||||
|
color: ItemColor,
|
||||||
|
/// Start time and phrase being played
|
||||||
|
play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
|
/// Start time and next phrase
|
||||||
|
next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
|
/// Play input through output.
|
||||||
|
monitoring: bool,
|
||||||
|
/// Write input to sequence.
|
||||||
|
recording: bool,
|
||||||
|
/// Overdub input to sequence.
|
||||||
|
overdub: bool,
|
||||||
|
/// Send all notes off
|
||||||
|
reset: bool, // TODO?: after Some(nframes)
|
||||||
|
/// Record from MIDI ports to current sequence.
|
||||||
|
midi_ins: Vec<Port<MidiIn>>,
|
||||||
|
/// Play from current sequence to MIDI ports
|
||||||
|
midi_outs: Vec<Port<MidiOut>>,
|
||||||
|
/// Notes currently held at input
|
||||||
|
notes_in: Arc<RwLock<[bool; 128]>>,
|
||||||
|
/// Notes currently held at output
|
||||||
|
notes_out: Arc<RwLock<[bool; 128]>>,
|
||||||
|
///// MIDI output buffer
|
||||||
|
//midi_note: Vec<u8>,
|
||||||
|
///// MIDI output buffer
|
||||||
|
//midi_chunk: Vec<Vec<Vec<u8>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrangerTrackApi for ArrangerTrack {
|
||||||
|
/// Name of track
|
||||||
|
fn name (&self) -> &Arc<RwLock<String>> {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
/// Preferred width of track column
|
||||||
|
fn width (&self) -> usize {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
/// Preferred width of track column
|
||||||
|
fn width_mut (&mut self) -> &mut usize {
|
||||||
|
&mut self.width
|
||||||
|
}
|
||||||
|
/// Identifying color of track
|
||||||
|
fn color (&self) -> ItemColor {
|
||||||
|
self.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasPhrase for ArrangerTrack {
|
||||||
|
fn reset (&self) -> bool {
|
||||||
|
self.reset
|
||||||
|
}
|
||||||
|
fn reset_mut (&mut self) -> &mut bool {
|
||||||
|
&mut self.reset
|
||||||
|
}
|
||||||
|
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MidiInputApi for ArrangerTrack {
|
||||||
|
fn midi_ins (&self) -> &Vec<Port<jack::MidiIn>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_ins_mut (&self) -> &mut Vec<Port<jack::MidiIn>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn recording (&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn recording_mut (&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn monitoring (&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn monitoring_mut (&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn overdub (&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn overdub_mut (&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn notes_in (&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MidiOutputApi for ArrangerTrack {
|
||||||
|
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_note (&mut self) -> &mut Vec<u8> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClockApi for ArrangerTrack {
|
||||||
|
fn timebase (&self) -> &Arc<Timebase> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayheadApi for ArrangerTrack {
|
||||||
|
fn current (&self) -> &Instant {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn transport (&self) -> &Transport {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn playing (&self) -> &RwLock<Option<TransportState>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn started (&self) -> &RwLock<Option<(usize, usize)>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayerApi for ArrangerTrack {}
|
||||||
|
|
||||||
|
|
@ -27,22 +27,84 @@ pub struct PhraseEditor<E: Engine> {
|
||||||
pub notes_out: Arc<RwLock<[bool; 128]>>,
|
pub notes_out: Arc<RwLock<[bool; 128]>>,
|
||||||
/// Current position of global playhead
|
/// Current position of global playhead
|
||||||
pub now: Arc<Pulse>,
|
pub now: Arc<Pulse>,
|
||||||
/// Width of notes area at last render
|
/// Width and height of notes area at last render
|
||||||
pub width: AtomicUsize,
|
pub size: Measure<E>
|
||||||
/// Height of notes area at last render
|
|
||||||
pub height: AtomicUsize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Content for PhraseEditor<Tui> {
|
impl Widget for PhraseEditor<Tui> {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
|
PhraseEditorRef(&self, Default::default()).layout(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||||
|
PhraseEditorRef(&self, Default::default()).render(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PhraseEditorRef<'a, T: PhraseEditorViewState>(pub &'a T);
|
||||||
|
|
||||||
|
pub trait PhraseEditorViewState: Send + Sync {
|
||||||
|
fn focused (&self) -> bool;
|
||||||
|
fn entered (&self) -> bool;
|
||||||
|
fn keys (&self) -> &Buffer;
|
||||||
|
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>>;
|
||||||
|
fn buffer (&self) -> &BigBuffer;
|
||||||
|
fn note_len (&self) -> usize;
|
||||||
|
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
||||||
|
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>;
|
||||||
|
fn size (&self) -> &Measure<Tui>;
|
||||||
|
fn now (&self) -> &Arc<Pulse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhraseEditorViewState for PhraseEditor<Tui> {
|
||||||
|
fn focused (&self) -> bool {
|
||||||
|
&self.focused
|
||||||
|
}
|
||||||
|
fn entered (&self) -> bool {
|
||||||
|
&self.entered
|
||||||
|
}
|
||||||
|
fn keys (&self) -> &Buffer {
|
||||||
|
&self.keys
|
||||||
|
}
|
||||||
|
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
|
||||||
|
&self.phrase
|
||||||
|
}
|
||||||
|
fn buffer (&self) -> &BigBuffer {
|
||||||
|
&self.buffer
|
||||||
|
}
|
||||||
|
fn note_len (&self) -> usize {
|
||||||
|
&self.note_len
|
||||||
|
}
|
||||||
|
fn note_axis (&self) -> &RwLock<FixedAxis<usize>> {
|
||||||
|
&self.note_axis
|
||||||
|
}
|
||||||
|
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>> {
|
||||||
|
&self.time_axis
|
||||||
|
}
|
||||||
|
fn size (&self) -> &Measure<Tui> {
|
||||||
|
&self.size
|
||||||
|
}
|
||||||
|
fn now (&self) -> &Arc<Pulse> {
|
||||||
|
&self.now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: PhraseEditorViewState> Content for PhraseEditorRef<'a, T> {
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
let Self { focused, entered, keys, phrase, buffer, note_len, .. } = self;
|
let phrase = self.0.phrase();
|
||||||
let FixedAxis {
|
let size = self.0.size();
|
||||||
start: note_start, point: note_point, clamp: note_clamp
|
let focused = self.0.focused();
|
||||||
} = *self.note_axis.read().unwrap();
|
let entered = self.0.entered();
|
||||||
let ScaledAxis {
|
let keys = self.0.keys();
|
||||||
start: time_start, point: time_point, clamp: time_clamp, scale: time_scale
|
let buffer = self.0.buffer();
|
||||||
} = *self.time_axis.read().unwrap();
|
let note_len = self.0.note_len();
|
||||||
|
let note_axis = self.0.note_axis();
|
||||||
|
let time_axis = self.0.time_axis();
|
||||||
|
let FixedAxis { start: note_start, point: note_point, clamp: note_clamp }
|
||||||
|
= *note_axis.read().unwrap();
|
||||||
|
let ScaledAxis { start: time_start, point: time_point, clamp: time_clamp, scale: time_scale }
|
||||||
|
= *time_axis.read().unwrap();
|
||||||
//let color = Color::Rgb(0,255,0);
|
//let color = Color::Rgb(0,255,0);
|
||||||
//let color = phrase.as_ref().map(|p|p.read().unwrap().color.base.rgb).unwrap_or(color);
|
//let color = phrase.as_ref().map(|p|p.read().unwrap().color.base.rgb).unwrap_or(color);
|
||||||
let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{
|
let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{
|
||||||
|
|
@ -59,9 +121,8 @@ impl Content for PhraseEditor<Tui> {
|
||||||
let notes = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
let notes = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||||
let area = to.area();
|
let area = to.area();
|
||||||
let h = area.h() as usize;
|
let h = area.h() as usize;
|
||||||
self.height.store(h, Ordering::Relaxed);
|
size.set_wh(area.w(), h);
|
||||||
self.width.store(area.w() as usize, Ordering::Relaxed);
|
let mut axis = note_axis.write().unwrap();
|
||||||
let mut axis = self.note_axis.write().unwrap();
|
|
||||||
if let Some(point) = axis.point {
|
if let Some(point) = axis.point {
|
||||||
if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) {
|
if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) {
|
||||||
axis.start += 2;
|
axis.start += 2;
|
||||||
|
|
@ -84,11 +145,11 @@ impl Content for PhraseEditor<Tui> {
|
||||||
})
|
})
|
||||||
}).fill_x();
|
}).fill_x();
|
||||||
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||||
Ok(if *focused && *entered {
|
Ok(if focused && entered {
|
||||||
let area = to.area();
|
let area = to.area();
|
||||||
if let (Some(time), Some(note)) = (time_point, note_point) {
|
if let (Some(time), Some(note)) = (time_point, note_point) {
|
||||||
let x1 = area.x() + (time / time_scale) as u16;
|
let x1 = area.x() + (time / time_scale) as u16;
|
||||||
let x2 = x1 + (self.note_len / time_scale) as u16;
|
let x2 = x1 + (note_len / time_scale) as u16;
|
||||||
let y = area.y() + note.saturating_sub(note_start) as u16 / 2;
|
let y = area.y() + note.saturating_sub(note_start) as u16 / 2;
|
||||||
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
let c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||||
for x in x1..x2 {
|
for x in x1..x2 {
|
||||||
|
|
@ -103,7 +164,7 @@ impl Content for PhraseEditor<Tui> {
|
||||||
|to:[u16;2]|Ok(Some(to.clip_h(1))),
|
|to:[u16;2]|Ok(Some(to.clip_h(1))),
|
||||||
move|to: &mut TuiOutput|{
|
move|to: &mut TuiOutput|{
|
||||||
if let Some(_) = phrase {
|
if let Some(_) = phrase {
|
||||||
let now = self.now.get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length;
|
let now = self.0.now().get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length;
|
||||||
let time_clamp = time_clamp
|
let time_clamp = time_clamp
|
||||||
.expect("time_axis of sequencer expected to be clamped");
|
.expect("time_axis of sequencer expected to be clamped");
|
||||||
for x in 0..(time_clamp/time_scale).saturating_sub(time_start) {
|
for x in 0..(time_clamp/time_scale).saturating_sub(time_start) {
|
||||||
|
|
@ -119,32 +180,28 @@ impl Content for PhraseEditor<Tui> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
).push_x(6).align_sw();
|
).push_x(6).align_sw();
|
||||||
let border_color = if *focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)};
|
let border_color = if focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)};
|
||||||
let title_color = if *focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)};
|
let title_color = if focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)};
|
||||||
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
|
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
|
||||||
let note_area = lay!(notes, cursor).fill_x();
|
let note_area = lay!(notes, cursor).fill_x();
|
||||||
let piano_roll = row!(keys, note_area).fill_x();
|
let piano_roll = row!(keys, note_area).fill_x();
|
||||||
let content = piano_roll.bg(Color::Rgb(40, 50, 30)).border(border);
|
let content = piano_roll.bg(Color::Rgb(40, 50, 30)).border(border);
|
||||||
let content = lay!(content, playhead);
|
let content = lay!(content, playhead);
|
||||||
let mut upper_left = format!("[{}] Sequencer", if *entered {"■"} else {" "});
|
let mut upper_left = format!("[{}] Sequencer", if entered {"■"} else {" "});
|
||||||
if let Some(phrase) = phrase {
|
if let Some(phrase) = phrase {
|
||||||
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
|
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
|
||||||
}
|
}
|
||||||
let mut lower_right = format!(
|
let mut lower_right = format!("┤{}├", size.format());
|
||||||
"┤{}x{}├",
|
|
||||||
self.width.load(Ordering::Relaxed),
|
|
||||||
self.height.load(Ordering::Relaxed),
|
|
||||||
);
|
|
||||||
lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(time_scale));
|
lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(time_scale));
|
||||||
//lower_right = format!("Zoom: {} (+{}:{}*{}|{})",
|
//lower_right = format!("Zoom: {} (+{}:{}*{}|{})",
|
||||||
//pulses_to_name(time_scale),
|
//pulses_to_name(time_scale),
|
||||||
//time_start, time_point.unwrap_or(0),
|
//time_start, time_point.unwrap_or(0),
|
||||||
//time_scale, time_clamp.unwrap_or(0),
|
//time_scale, time_clamp.unwrap_or(0),
|
||||||
//);
|
//);
|
||||||
if *focused && *entered {
|
if focused && entered {
|
||||||
lower_right = format!("┤Note: {} {}├─{lower_right}",
|
lower_right = format!("┤Note: {} {}├─{lower_right}",
|
||||||
self.note_axis.read().unwrap().point.unwrap(),
|
note_axis.read().unwrap().point.unwrap(),
|
||||||
pulses_to_name(*note_len));
|
pulses_to_name(note_len));
|
||||||
//lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
|
//lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
|
||||||
//pulses_to_name(*note_len),
|
//pulses_to_name(*note_len),
|
||||||
//note_start,
|
//note_start,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
pub type SequencerApp<E: Engine> = AppView<
|
||||||
|
E,
|
||||||
|
SequencerView<E>,
|
||||||
|
SequencerViewCommand,
|
||||||
|
SequencerStatusBar,
|
||||||
|
>;
|
||||||
|
|
||||||
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> {
|
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> {
|
||||||
type Error = Box<dyn std::error::Error>;
|
type Error = Box<dyn std::error::Error>;
|
||||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||||
|
|
@ -12,82 +19,15 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> {
|
||||||
focused: false,
|
focused: false,
|
||||||
focus: TransportViewFocus::PlayPause,
|
focus: TransportViewFocus::PlayPause,
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
phrases: PhrasePoolView::from(&model.phrases()),
|
phrases: vec![],
|
||||||
editor: PhraseEditor::new(),
|
editor: PhraseEditor::new(),
|
||||||
}.into(), None, None))
|
}.into(), None, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type SequencerApp<E: Engine> = AppView<
|
|
||||||
E,
|
|
||||||
SequencerView<E>,
|
|
||||||
SequencerViewCommand,
|
|
||||||
SequencerStatusBar,
|
|
||||||
>;
|
|
||||||
|
|
||||||
impl Handle<Tui> for SequencerApp<Tui> {
|
impl Handle<Tui> for SequencerApp<Tui> {
|
||||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||||
SequencerAppCommand::execute_with_state(self, i)
|
SequencerCommand::execute_with_state(self, i)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type SequencerAppCommand = AppViewCommand<SequencerViewCommand>;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum SequencerViewCommand {
|
|
||||||
Transport(TransportCommand),
|
|
||||||
Phrases(PhrasePoolViewCommand),
|
|
||||||
Editor(PhraseEditorCommand),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputToCommand<Tui, SequencerApp<Tui>> for SequencerAppCommand {
|
|
||||||
fn input_to_command (state: &SequencerApp<Tui>, input: &TuiInput) -> Option<Self> {
|
|
||||||
use AppViewFocus::*;
|
|
||||||
use FocusCommand::*;
|
|
||||||
use SequencerViewCommand::*;
|
|
||||||
match input.event() {
|
|
||||||
key!(KeyCode::Tab) => Some(Self::Focus(Next)),
|
|
||||||
key!(Shift-KeyCode::Tab) => Some(Self::Focus(Prev)),
|
|
||||||
key!(KeyCode::BackTab) => Some(Self::Focus(Prev)),
|
|
||||||
key!(Shift-KeyCode::BackTab) => Some(Self::Focus(Prev)),
|
|
||||||
key!(KeyCode::Up) => Some(Self::Focus(Up)),
|
|
||||||
key!(KeyCode::Down) => Some(Self::Focus(Down)),
|
|
||||||
key!(KeyCode::Left) => Some(Self::Focus(Left)),
|
|
||||||
key!(KeyCode::Right) => Some(Self::Focus(Right)),
|
|
||||||
_ => Some(Self::App(match state.focused() {
|
|
||||||
Content(SequencerFocus::Transport) =>
|
|
||||||
TransportCommand::input_to_command(&state.transport, input)
|
|
||||||
.map(Transport),
|
|
||||||
Content(SequencerFocus::PhrasePool) =>
|
|
||||||
PhrasePoolViewCommand::input_to_command(&state.phrases, input)
|
|
||||||
.map(Phrases),
|
|
||||||
Content(SequencerFocus::PhraseEditor) =>
|
|
||||||
PhraseEditorCommand::input_to_command(&state.editor, input)
|
|
||||||
.map(Editor),
|
|
||||||
_ => return None,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command<SequencerApp<Tui>> for SequencerAppCommand {
|
|
||||||
fn execute (self, state: &mut SequencerApp<Tui>) -> Perhaps<Self> {
|
|
||||||
use AppViewCommand::*;
|
|
||||||
match self {
|
|
||||||
Focus(cmd) => delegate(cmd, Focus, state),
|
|
||||||
App(cmd) => delegate(cmd, App, state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command<SequencerApp<Tui>> for SequencerViewCommand {
|
|
||||||
fn execute (self, state: &mut SequencerApp<Tui>) -> Perhaps<Self> {
|
|
||||||
use SequencerViewCommand::*;
|
|
||||||
match self {
|
|
||||||
Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases),
|
|
||||||
Editor(cmd) => delegate(cmd, Editor, &mut state.editor),
|
|
||||||
Transport(cmd) => delegate(cmd, Transport, &mut state.transport)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,7 +94,7 @@ impl Content for SequencerView<Tui> {
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
col!(
|
col!(
|
||||||
self.transport,
|
widget(&TransportRefView(self)),
|
||||||
Split::right(20,
|
Split::right(20,
|
||||||
widget(&self.phrases),
|
widget(&self.phrases),
|
||||||
widget(&self.editor)
|
widget(&self.editor)
|
||||||
|
|
@ -187,14 +127,11 @@ impl Content for SequencerStatusBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FocusGrid for SequencerApp<Tui> {
|
impl HasFocus for SequencerApp<Tui> {
|
||||||
type Item = AppViewFocus<SequencerFocus>;
|
type Item = AppViewFocus<SequencerFocus>;
|
||||||
fn cursor (&self) -> (usize, usize) {
|
}
|
||||||
self.cursor
|
|
||||||
}
|
impl FocusEnter for SequencerApp<Tui> {
|
||||||
fn cursor_mut (&mut self) -> &mut (usize, usize) {
|
|
||||||
&mut self.cursor
|
|
||||||
}
|
|
||||||
fn focus_enter (&mut self) {
|
fn focus_enter (&mut self) {
|
||||||
let focused = self.focused();
|
let focused = self.focused();
|
||||||
if !self.entered {
|
if !self.entered {
|
||||||
|
|
@ -208,14 +145,24 @@ impl FocusGrid for SequencerApp<Tui> {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn entered (&self) -> Option<Self::Item> {
|
fn focus_entered (&self) -> Option<Self::Item> {
|
||||||
if self.entered {
|
if self.entered {
|
||||||
Some(self.focused())
|
Some(self.focused())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn layout (&self) -> &[&[Self::Item]] {
|
}
|
||||||
|
|
||||||
|
impl FocusGrid for SequencerApp<Tui> {
|
||||||
|
type Item = AppViewFocus<SequencerFocus>;
|
||||||
|
fn focus_cursor (&self) -> (usize, usize) {
|
||||||
|
self.cursor
|
||||||
|
}
|
||||||
|
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
||||||
|
&mut self.cursor
|
||||||
|
}
|
||||||
|
fn focus_layout (&self) -> &[&[Self::Item]] {
|
||||||
use AppViewFocus::*;
|
use AppViewFocus::*;
|
||||||
use SequencerFocus::*;
|
use SequencerFocus::*;
|
||||||
&[
|
&[
|
||||||
|
|
@ -224,14 +171,14 @@ impl FocusGrid for SequencerApp<Tui> {
|
||||||
&[Content(PhrasePool), Content(PhraseEditor)],
|
&[Content(PhrasePool), Content(PhraseEditor)],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
fn update_focus (&mut self) {
|
fn focus_update (&mut self) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> HasJack for SequencerView<E> {
|
impl<E: Engine> HasJack for SequencerView<E> {
|
||||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
||||||
self.transport.jack()
|
&self.jack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,106 +191,121 @@ impl<E: Engine> HasPhrases for SequencerView<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> HasMidiBuffer for SequencerView<E> {
|
|
||||||
fn midi_buffer (&self) -> &Vec<Vec<Vec<u8>>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_buffer_mut (&self) -> &mut Vec<Vec<Vec<u8>>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn reset (&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn reset_mut (&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine> HasPhrase for SequencerView<E> {
|
impl<E: Engine> HasPhrase for SequencerView<E> {
|
||||||
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
fn reset (&self) -> bool {
|
||||||
todo!()
|
self.reset
|
||||||
}
|
}
|
||||||
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
fn reset_mut (&mut self) -> &mut bool {
|
||||||
todo!()
|
&mut self.reset
|
||||||
}
|
}
|
||||||
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
fn phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
fn phrase_mut (&self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
fn next_phrase (&self) -> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn next_phrase_mut (&mut self) -> &mut Option<(Instant, Option<Arc<RwLock<Phrase>>>)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> MidiInputApi for SequencerView<E> {
|
impl<E: Engine> MidiInputApi for SequencerView<E> {
|
||||||
fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> {
|
fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> {
|
fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn recording(&self) -> bool {
|
fn recording(&self) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn recording_mut(&mut self) -> &mut bool {
|
fn recording_mut(&mut self) -> &mut bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn monitoring(&self) -> bool {
|
fn monitoring(&self) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn monitoring_mut(&mut self) -> &mut bool {
|
fn monitoring_mut(&mut self) -> &mut bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn overdub(&self) -> bool {
|
fn overdub(&self) -> bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn overdub_mut(&mut self) -> &mut bool {
|
fn overdub_mut(&mut self) -> &mut bool {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> {
|
fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> MidiOutputApi for SequencerView<E> {
|
impl<E: Engine> MidiOutputApi for SequencerView<E> {
|
||||||
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
|
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
|
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn midi_note (&mut self) -> &mut Vec<u8> {
|
fn midi_note (&mut self) -> &mut Vec<u8> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> ClockApi for SequencerView<E> {
|
impl<E: Engine> ClockApi for SequencerView<E> {
|
||||||
fn timebase (&self) -> &Arc<Timebase> {
|
fn timebase (&self) -> &Arc<Timebase> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn quant (&self) -> &Quantize {
|
fn quant (&self) -> &Quantize {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn sync (&self) -> &LaunchSync {
|
fn sync (&self) -> &LaunchSync {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> PlayheadApi for SequencerView<E> {
|
impl<E: Engine> PlayheadApi for SequencerView<E> {
|
||||||
fn current(&self) -> &Instant {
|
fn current(&self) -> &Instant {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn transport(&self) -> &Transport {
|
fn transport(&self) -> &Transport {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn playing(&self) -> &RwLock<Option<TransportState>> {
|
fn playing(&self) -> &RwLock<Option<TransportState>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn started(&self) -> &RwLock<Option<(usize, usize)>> {
|
fn started(&self) -> &RwLock<Option<(usize, usize)>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> PlayerApi for SequencerView<E> {}
|
impl<E: Engine> PlayerApi for SequencerView<E> {}
|
||||||
|
|
||||||
|
impl TransportViewState for SequencerView<Tui> {
|
||||||
|
fn focus (&self) -> TransportViewFocus {
|
||||||
|
self.focus
|
||||||
|
}
|
||||||
|
fn focused (&self) -> bool {
|
||||||
|
self.focused
|
||||||
|
}
|
||||||
|
fn transport_state (&self) -> Option<TransportState> {
|
||||||
|
*self.playing().read().unwrap()
|
||||||
|
}
|
||||||
|
fn bpm_value (&self) -> f64 {
|
||||||
|
self.bpm().get()
|
||||||
|
}
|
||||||
|
fn sync_value (&self) -> f64 {
|
||||||
|
self.sync().get()
|
||||||
|
}
|
||||||
|
fn format_beat (&self) -> String {
|
||||||
|
self.current().format_beat()
|
||||||
|
}
|
||||||
|
fn format_msu (&self) -> String {
|
||||||
|
self.current().usec.format_msu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
73
crates/tek_tui/src/tui_sequencer_cmd.rs
Normal file
73
crates/tek_tui/src/tui_sequencer_cmd.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub type SequencerCommand = AppViewCommand<SequencerViewCommand>;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum SequencerViewCommand {
|
||||||
|
Transport(TransportCommand),
|
||||||
|
Phrases(PhrasePoolViewCommand),
|
||||||
|
Editor(PhraseEditorCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputToCommand<Tui, SequencerApp<Tui>> for SequencerCommand {
|
||||||
|
fn input_to_command (state: &SequencerApp<Tui>, input: &TuiInput) -> Option<Self> {
|
||||||
|
use AppViewFocus::*;
|
||||||
|
use FocusCommand::*;
|
||||||
|
use SequencerViewCommand::*;
|
||||||
|
match input.event() {
|
||||||
|
key!(KeyCode::Tab) => Some(Self::Focus(Next)),
|
||||||
|
key!(Shift-KeyCode::Tab) => Some(Self::Focus(Prev)),
|
||||||
|
key!(KeyCode::BackTab) => Some(Self::Focus(Prev)),
|
||||||
|
key!(Shift-KeyCode::BackTab) => Some(Self::Focus(Prev)),
|
||||||
|
key!(KeyCode::Up) => Some(Self::Focus(Up)),
|
||||||
|
key!(KeyCode::Down) => Some(Self::Focus(Down)),
|
||||||
|
key!(KeyCode::Left) => Some(Self::Focus(Left)),
|
||||||
|
key!(KeyCode::Right) => Some(Self::Focus(Right)),
|
||||||
|
_ => Some(Self::App(match state.focused() {
|
||||||
|
Content(SequencerFocus::Transport) =>
|
||||||
|
TransportCommand::input_to_command(&state, input)
|
||||||
|
.map(Transport),
|
||||||
|
Content(SequencerFocus::PhrasePool) =>
|
||||||
|
PhrasePoolViewCommand::input_to_command(&state.phrases, input)
|
||||||
|
.map(Phrases),
|
||||||
|
Content(SequencerFocus::PhraseEditor) =>
|
||||||
|
PhraseEditorCommand::input_to_command(&state.editor, input)
|
||||||
|
.map(Editor),
|
||||||
|
_ => return None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command<SequencerApp<Tui>> for SequencerCommand {
|
||||||
|
fn execute (self, state: &mut SequencerApp<Tui>) -> Perhaps<Self> {
|
||||||
|
use AppViewCommand::*;
|
||||||
|
match self {
|
||||||
|
Focus(cmd) => delegate(cmd, Focus, state),
|
||||||
|
App(cmd) => delegate(cmd, App, state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command<SequencerApp<Tui>> for SequencerViewCommand {
|
||||||
|
fn execute (self, state: &mut SequencerApp<Tui>) -> Perhaps<Self> {
|
||||||
|
use SequencerViewCommand::*;
|
||||||
|
match self {
|
||||||
|
Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases),
|
||||||
|
Editor(cmd) => delegate(cmd, Editor, &mut state.editor),
|
||||||
|
Transport(cmd) => delegate(cmd, Transport, &mut state.transport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransportControl for SequencerApp<Tui> {
|
||||||
|
fn bpm (&self) -> &BeatsPerMinute {
|
||||||
|
self.app.bpm()
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
self.app.quant()
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
self.app.sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
/// Root type of application.
|
||||||
|
pub type TransportApp<E: Engine> = AppView<
|
||||||
|
E,
|
||||||
|
TransportView<E>,
|
||||||
|
AppViewCommand<TransportCommand>,
|
||||||
|
TransportStatusBar
|
||||||
|
>;
|
||||||
|
|
||||||
/// Create app state from JACK handle.
|
/// Create app state from JACK handle.
|
||||||
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> {
|
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> {
|
||||||
type Error = Box<dyn std::error::Error>;
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
|
@ -15,121 +23,6 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Root type of application.
|
|
||||||
pub type TransportApp<E: Engine> = AppView<
|
|
||||||
E,
|
|
||||||
TransportView<E>,
|
|
||||||
TransportAppCommand,
|
|
||||||
TransportStatusBar
|
|
||||||
>;
|
|
||||||
|
|
||||||
/// Handle input.
|
|
||||||
impl Handle<Tui> for TransportApp<Tui> {
|
|
||||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
|
||||||
TransportAppCommand::execute_with_state(self, from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type TransportAppCommand = AppViewCommand<TransportCommand>;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum TransportCommand {
|
|
||||||
Clock(ClockCommand),
|
|
||||||
Playhead(PlayheadCommand),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputToCommand<Tui, TransportApp<Tui>> for TransportAppCommand {
|
|
||||||
fn input_to_command (app: &TransportApp<Tui>, input: &TuiInput) -> Option<Self> {
|
|
||||||
use KeyCode::{Left, Right};
|
|
||||||
use FocusCommand::{Prev, Next};
|
|
||||||
use AppViewCommand::{Focus, App};
|
|
||||||
Some(match input.event() {
|
|
||||||
key!(Left) => Focus(Prev),
|
|
||||||
key!(Right) => Focus(Next),
|
|
||||||
_ => TransportCommand::input_to_command(app.app, input).map(App)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputToCommand<Tui, TransportApp<Tui>> for TransportCommand {
|
|
||||||
fn input_to_command (app: &TransportApp<Tui>, input: &TuiInput) -> Option<Self> {
|
|
||||||
use KeyCode::Char;
|
|
||||||
use AppViewFocus::Content;
|
|
||||||
use ClockCommand::{SetBpm, SetQuant, SetSync};
|
|
||||||
use TransportViewFocus as Focus;
|
|
||||||
use TransportCommand::{Clock, Playhead};
|
|
||||||
let timebase = app.app.timebase();
|
|
||||||
Some(match input.event() {
|
|
||||||
key!(Char('.')) => match app.focused() {
|
|
||||||
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() + 1.0)),
|
|
||||||
Content(Focus::Quant) => Clock(SetQuant(next_note_length(app.app.quant().get()as usize)as f64)),
|
|
||||||
Content(Focus::Sync) => Clock(SetSync(next_note_length(app.app.sync().get()as usize)as f64+1.)),
|
|
||||||
Content(Focus::PlayPause) => Playhead(todo!()),
|
|
||||||
Content(Focus::Clock) => Playhead(todo!()),
|
|
||||||
_ => {todo!()}
|
|
||||||
},
|
|
||||||
key!(KeyCode::Char(',')) => match app.focused() {
|
|
||||||
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() - 1.0)),
|
|
||||||
Content(Focus::Quant) => Clock(SetQuant(prev_note_length(app.app.quant().get()as usize)as f64)),
|
|
||||||
Content(Focus::Sync) => Clock(SetSync(prev_note_length(app.app.sync().get()as usize)as f64+1.)),
|
|
||||||
Content(Focus::PlayPause) => Playhead(todo!()),
|
|
||||||
Content(Focus::Clock) => Playhead(todo!()),
|
|
||||||
_ => {todo!()}
|
|
||||||
},
|
|
||||||
key!(KeyCode::Char('>')) => match app.focused() {
|
|
||||||
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() + 0.001)),
|
|
||||||
Content(Focus::Quant) => Clock(SetQuant(next_note_length(app.app.quant().get()as usize)as f64)),
|
|
||||||
Content(Focus::Sync) => Clock(SetSync(next_note_length(app.app.sync().get()as usize)as f64+1.)),
|
|
||||||
Content(Focus::PlayPause) => Playhead(todo!()),
|
|
||||||
Content(Focus::Clock) => Playhead(todo!()),
|
|
||||||
_ => {todo!()}
|
|
||||||
},
|
|
||||||
key!(KeyCode::Char('<')) => match app.focused() {
|
|
||||||
Content(Focus::Bpm) => Clock(SetBpm(timebase.bpm.get() - 0.001)),
|
|
||||||
Content(Focus::Quant) => Clock(SetQuant(prev_note_length(app.app.quant().get()as usize)as f64)),
|
|
||||||
Content(Focus::Sync) => Clock(SetSync(prev_note_length(app.app.sync().get()as usize)as f64+1.)),
|
|
||||||
Content(Focus::PlayPause) => Playhead(todo!()),
|
|
||||||
Content(Focus::Clock) => Playhead(todo!()),
|
|
||||||
_ => {todo!()}
|
|
||||||
},
|
|
||||||
_ => return None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command<TransportApp<Tui>> for TransportAppCommand {
|
|
||||||
fn execute (self, state: &mut TransportApp<Tui>) -> Perhaps<Self> {
|
|
||||||
use AppViewCommand::{Focus, App};
|
|
||||||
use FocusCommand::{Next, Prev};
|
|
||||||
Ok(Some(match self {
|
|
||||||
App(command) => if let Some(undo) = TransportCommand::execute(command, state)? {
|
|
||||||
App(undo)
|
|
||||||
} else {
|
|
||||||
return Ok(None)
|
|
||||||
},
|
|
||||||
Focus(command) => Focus(match command {
|
|
||||||
Next => { todo!() },
|
|
||||||
Prev => { todo!() },
|
|
||||||
_ => { todo!() }
|
|
||||||
}),
|
|
||||||
_ => todo!()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command<TransportApp<Tui>> for TransportCommand {
|
|
||||||
fn execute (self, state: &mut TransportApp<Tui>) -> Perhaps<Self> {
|
|
||||||
use TransportCommand::{Clock, Playhead};
|
|
||||||
use ClockCommand::{SetBpm, SetQuant, SetSync};
|
|
||||||
Ok(Some(match self {
|
|
||||||
Clock(SetBpm(bpm)) => Clock(SetBpm(state.timebase().bpm.set(bpm))),
|
|
||||||
Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))),
|
|
||||||
Clock(SetSync(sync)) => Clock(SetSync(state.sync().set(sync))),
|
|
||||||
_ => return Ok(None)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores and displays time-related info.
|
/// Stores and displays time-related info.
|
||||||
pub struct TransportView<E: Engine> {
|
pub struct TransportView<E: Engine> {
|
||||||
_engine: PhantomData<E>,
|
_engine: PhantomData<E>,
|
||||||
|
|
@ -153,6 +46,15 @@ pub struct TransportView<E: Engine> {
|
||||||
size: Measure<E>,
|
size: Measure<E>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> std::fmt::Debug for TransportView<E> {
|
||||||
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("transport")
|
||||||
|
.field("jack", &self.jack)
|
||||||
|
.field("metronome", &self.metronome)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Which item of the transport toolbar is focused
|
/// Which item of the transport toolbar is focused
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum TransportViewFocus {
|
pub enum TransportViewFocus {
|
||||||
|
|
@ -195,13 +97,60 @@ impl TransportViewFocus {
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct TransportStatusBar;
|
pub struct TransportStatusBar;
|
||||||
|
|
||||||
impl Content for TransportView<Tui> {
|
impl Widget for TransportView<Tui> {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn layout (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
|
TransportRef(&self, Default::default()).layout(to)
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||||
|
TransportRef(&self, Default::default()).render(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TransportRef<'a, T: TransportViewState>(pub &'a T);
|
||||||
|
|
||||||
|
pub trait TransportViewState: Send + Sync {
|
||||||
|
fn focus (&self) -> TransportViewFocus;
|
||||||
|
fn focused (&self) -> bool;
|
||||||
|
fn transport_state (&self) -> Option<TransportState>;
|
||||||
|
fn bpm_value (&self) -> f64;
|
||||||
|
fn sync_value (&self) -> f64;
|
||||||
|
fn format_beat (&self) -> String;
|
||||||
|
fn format_msu (&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> TransportViewState for TransportView<E> {
|
||||||
|
fn focus (&self) -> TransportViewFocus {
|
||||||
|
self.focus
|
||||||
|
}
|
||||||
|
fn focused (&self) -> bool {
|
||||||
|
self.focused
|
||||||
|
}
|
||||||
|
fn transport_state (&self) -> Option<TransportState> {
|
||||||
|
*self.playing().read().unwrap()
|
||||||
|
}
|
||||||
|
fn bpm_value (&self) -> f64 {
|
||||||
|
self.bpm().get()
|
||||||
|
}
|
||||||
|
fn sync_value (&self) -> f64 {
|
||||||
|
self.sync().get()
|
||||||
|
}
|
||||||
|
fn format_beat (&self) -> String {
|
||||||
|
self.current().format_beat()
|
||||||
|
}
|
||||||
|
fn format_msu (&self) -> String {
|
||||||
|
self.current().usec.format_msu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: TransportViewState> Content for TransportRef<'a, T> {
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
|
let state = self.0;
|
||||||
lay!(
|
lay!(
|
||||||
self.focus.wrap(self.focused, TransportViewFocus::PlayPause, &Styled(
|
state.focus().wrap(state.focused(), TransportViewFocus::PlayPause, &Styled(
|
||||||
None,
|
None,
|
||||||
match *self.playing().read().unwrap() {
|
match state.transport_state() {
|
||||||
Some(TransportState::Rolling) => "▶ PLAYING",
|
Some(TransportState::Rolling) => "▶ PLAYING",
|
||||||
Some(TransportState::Starting) => "READY ...",
|
Some(TransportState::Starting) => "READY ...",
|
||||||
Some(TransportState::Stopped) => "⏹ STOPPED",
|
Some(TransportState::Stopped) => "⏹ STOPPED",
|
||||||
|
|
@ -210,21 +159,21 @@ impl Content for TransportView<Tui> {
|
||||||
).min_xy(11, 2).push_x(1)).align_x().fill_x(),
|
).min_xy(11, 2).push_x(1)).align_x().fill_x(),
|
||||||
|
|
||||||
row!(
|
row!(
|
||||||
self.focus.wrap(self.focused, TransportViewFocus::Bpm, &Outset::X(1u16, {
|
state.focus().wrap(state.focused(), TransportViewFocus::Bpm, &Outset::X(1u16, {
|
||||||
let bpm = self.timebase().bpm.get();
|
let bpm = state.bpm_value();
|
||||||
row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) }
|
row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) }
|
||||||
})),
|
})),
|
||||||
//let quant = self.focus.wrap(self.focused, TransportViewFocus::Quant, &Outset::X(1u16, row! {
|
//let quant = state.focus().wrap(state.focused(), TransportViewFocus::Quant, &Outset::X(1u16, row! {
|
||||||
//"QUANT ", ppq_to_name(self.quant as usize)
|
//"QUANT ", ppq_to_name(state.0.quant as usize)
|
||||||
//})),
|
//})),
|
||||||
self.focus.wrap(self.focused, TransportViewFocus::Sync, &Outset::X(1u16, row! {
|
state.focus().wrap(state.focused(), TransportViewFocus::Sync, &Outset::X(1u16, row! {
|
||||||
"SYNC ", pulses_to_name(self.sync().get() as usize)
|
"SYNC ", pulses_to_name(state.sync_value() as usize)
|
||||||
}))
|
}))
|
||||||
).align_w().fill_x(),
|
).align_w().fill_x(),
|
||||||
|
|
||||||
self.focus.wrap(self.focused, TransportViewFocus::Clock, &{
|
state.focus().wrap(state.focused(), TransportViewFocus::Clock, &{
|
||||||
let time1 = self.current().format_beat();
|
let time1 = state.format_beat();
|
||||||
let time2 = self.current().usec.format_msu();
|
let time2 = state.format_msu();
|
||||||
row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1)
|
row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1)
|
||||||
}).align_e().fill_x(),
|
}).align_e().fill_x(),
|
||||||
|
|
||||||
|
|
@ -234,7 +183,7 @@ impl Content for TransportView<Tui> {
|
||||||
|
|
||||||
impl Audio for TransportView<Tui> {
|
impl Audio for TransportView<Tui> {
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
self.model.process(client, scope)
|
PlayheadAudio(self).process(client, scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,35 +205,35 @@ impl Content for TransportStatusBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FocusGrid for TransportApp<Tui> {
|
impl HasFocus for TransportApp<Tui> {
|
||||||
type Item = AppViewFocus<TransportViewFocus>;
|
type Item = AppViewFocus<TransportViewFocus>;
|
||||||
fn cursor (&self) -> (usize, usize) {
|
}
|
||||||
self.cursor
|
|
||||||
}
|
impl FocusEnter for TransportApp<Tui> {
|
||||||
fn cursor_mut (&mut self) -> &mut (usize, usize) {
|
|
||||||
&mut self.cursor
|
|
||||||
}
|
|
||||||
fn focus_enter (&mut self) {
|
fn focus_enter (&mut self) {
|
||||||
let focused = self.focused();
|
self.entered = true;
|
||||||
if !self.entered {
|
|
||||||
self.entered = true;
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn focus_exit (&mut self) {
|
fn focus_exit (&mut self) {
|
||||||
if self.entered {
|
self.entered = false;
|
||||||
self.entered = false;
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn entered (&self) -> Option<Self::Item> {
|
fn focus_entered (&self) -> Option<Self::Item> {
|
||||||
if self.entered {
|
if self.entered {
|
||||||
Some(self.focused())
|
Some(self.focused())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn layout (&self) -> &[&[Self::Item]] {
|
}
|
||||||
|
|
||||||
|
impl FocusGrid for TransportApp<Tui> {
|
||||||
|
type Item = AppViewFocus<TransportViewFocus>;
|
||||||
|
fn focus_cursor (&self) -> (usize, usize) {
|
||||||
|
self.cursor
|
||||||
|
}
|
||||||
|
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
||||||
|
&mut self.cursor
|
||||||
|
}
|
||||||
|
fn focus_layout (&self) -> &[&[Self::Item]] {
|
||||||
use AppViewFocus::*;
|
use AppViewFocus::*;
|
||||||
use TransportViewFocus::*;
|
use TransportViewFocus::*;
|
||||||
&[
|
&[
|
||||||
|
|
@ -298,7 +247,7 @@ impl FocusGrid for TransportApp<Tui> {
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
fn update_focus (&mut self) {
|
fn focus_update (&mut self) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -335,14 +284,3 @@ impl<E: Engine> PlayheadApi for TransportView<E> {
|
||||||
&self.started
|
&self.started
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> std::fmt::Debug for TransportView<E> {
|
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), Error> {
|
|
||||||
f.debug_struct("transport")
|
|
||||||
.field("jack", &self.jack)
|
|
||||||
.field("transport", &"(JACK transport)")
|
|
||||||
.field("clock", &self.clock)
|
|
||||||
.field("metronome", &self.metronome)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
136
crates/tek_tui/src/tui_transport_cmd.rs
Normal file
136
crates/tek_tui/src/tui_transport_cmd.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Handle input.
|
||||||
|
impl Handle<Tui> for TransportApp<Tui> {
|
||||||
|
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||||
|
AppViewCommand::<TransportCommand>::execute_with_state(self, from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum TransportCommand {
|
||||||
|
Clock(ClockCommand),
|
||||||
|
Playhead(PlayheadCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputToCommand<Tui, TransportApp<Tui>> for AppViewCommand<TransportCommand> {
|
||||||
|
fn input_to_command (app: &TransportApp<Tui>, input: &TuiInput) -> Option<Self> {
|
||||||
|
use KeyCode::{Left, Right};
|
||||||
|
use FocusCommand::{Prev, Next};
|
||||||
|
use AppViewCommand::{Focus, App};
|
||||||
|
Some(match input.event() {
|
||||||
|
key!(Left) => Focus(Prev),
|
||||||
|
key!(Right) => Focus(Next),
|
||||||
|
_ => TransportCommand::input_to_command(&app, input).map(App)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TransportControl: FocusGrid<Item = AppViewFocus<TransportViewFocus>> {
|
||||||
|
fn quant (&self) -> &Quantize;
|
||||||
|
fn bpm (&self) -> &BeatsPerMinute;
|
||||||
|
fn next_quant (&self) -> f64 {
|
||||||
|
next_note_length(self.quant().get() as usize) as f64
|
||||||
|
}
|
||||||
|
fn prev_quant (&self) -> f64 {
|
||||||
|
prev_note_length(self.quant().get() as usize) as f64
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync;
|
||||||
|
fn next_sync (&self) -> f64 {
|
||||||
|
next_note_length(self.sync().get() as usize) as f64
|
||||||
|
}
|
||||||
|
fn prev_sync (&self) -> f64 {
|
||||||
|
prev_note_length(self.sync().get() as usize) as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransportControl for TransportApp<Tui> {
|
||||||
|
fn bpm (&self) -> &BeatsPerMinute {
|
||||||
|
self.app.bpm()
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
self.app.quant()
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
self.app.sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
|
||||||
|
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
||||||
|
use KeyCode::Char;
|
||||||
|
use AppViewFocus::Content;
|
||||||
|
use ClockCommand::{SetBpm, SetQuant, SetSync};
|
||||||
|
use TransportViewFocus as Focus;
|
||||||
|
use TransportCommand::{Clock, Playhead};
|
||||||
|
let focused = state.focused();
|
||||||
|
Some(match input.event() {
|
||||||
|
key!(Char('.')) => match focused {
|
||||||
|
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() + 1.0)),
|
||||||
|
Content(Focus::Quant) => Clock(SetQuant(state.next_quant())),
|
||||||
|
Content(Focus::Sync) => Clock(SetSync(state.next_sync())),
|
||||||
|
Content(Focus::PlayPause) => Playhead(todo!()),
|
||||||
|
Content(Focus::Clock) => Playhead(todo!()),
|
||||||
|
_ => {todo!()}
|
||||||
|
},
|
||||||
|
key!(KeyCode::Char(',')) => match focused {
|
||||||
|
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() - 1.0)),
|
||||||
|
Content(Focus::Quant) => Clock(SetQuant(state.prev_quant())),
|
||||||
|
Content(Focus::Sync) => Clock(SetSync(state.prev_sync())),
|
||||||
|
Content(Focus::PlayPause) => Playhead(todo!()),
|
||||||
|
Content(Focus::Clock) => Playhead(todo!()),
|
||||||
|
_ => {todo!()}
|
||||||
|
},
|
||||||
|
key!(KeyCode::Char('>')) => match focused {
|
||||||
|
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() + 0.001)),
|
||||||
|
Content(Focus::Quant) => Clock(SetQuant(state.next_quant())),
|
||||||
|
Content(Focus::Sync) => Clock(SetSync(state.next_sync())),
|
||||||
|
Content(Focus::PlayPause) => Playhead(todo!()),
|
||||||
|
Content(Focus::Clock) => Playhead(todo!()),
|
||||||
|
_ => {todo!()}
|
||||||
|
},
|
||||||
|
key!(KeyCode::Char('<')) => match focused {
|
||||||
|
Content(Focus::Bpm) => Clock(SetBpm(state.bpm().get() - 0.001)),
|
||||||
|
Content(Focus::Quant) => Clock(SetQuant(state.prev_quant())),
|
||||||
|
Content(Focus::Sync) => Clock(SetSync(state.prev_sync())),
|
||||||
|
Content(Focus::PlayPause) => Playhead(todo!()),
|
||||||
|
Content(Focus::Clock) => Playhead(todo!()),
|
||||||
|
_ => {todo!()}
|
||||||
|
},
|
||||||
|
_ => return None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command<TransportApp<Tui>> for AppViewCommand<TransportCommand> {
|
||||||
|
fn execute (self, state: &mut TransportApp<Tui>) -> Perhaps<Self> {
|
||||||
|
use AppViewCommand::{Focus, App};
|
||||||
|
use FocusCommand::{Next, Prev};
|
||||||
|
Ok(Some(match self {
|
||||||
|
App(command) => if let Some(undo) = TransportCommand::execute(command, &mut state.app)? {
|
||||||
|
App(undo)
|
||||||
|
} else {
|
||||||
|
return Ok(None)
|
||||||
|
},
|
||||||
|
Focus(command) => Focus(match command {
|
||||||
|
Next => { todo!() },
|
||||||
|
Prev => { todo!() },
|
||||||
|
_ => { todo!() }
|
||||||
|
}),
|
||||||
|
_ => todo!()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransportControl> Command<T> for TransportCommand {
|
||||||
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||||
|
use TransportCommand::{Clock, Playhead};
|
||||||
|
use ClockCommand::{SetBpm, SetQuant, SetSync};
|
||||||
|
Ok(Some(match self {
|
||||||
|
Clock(SetBpm(bpm)) => Clock(SetBpm(state.bpm().set(bpm))),
|
||||||
|
Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))),
|
||||||
|
Clock(SetSync(sync)) => Clock(SetSync(state.sync().set(sync))),
|
||||||
|
_ => return Ok(None)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue