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::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ClockCommand {
|
||||
SetBpm(f64),
|
||||
SetQuant(f64),
|
||||
|
|
|
|||
|
|
@ -7,27 +7,10 @@ pub trait HasPlayer: HasJack {
|
|||
|
||||
pub trait PlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {}
|
||||
|
||||
pub trait HasMidiBuffer {
|
||||
fn midi_buffer (&self) -> &Vec<Vec<Vec<u8>>>;
|
||||
fn midi_buffer_mut (&self) -> &mut Vec<Vec<Vec<u8>>>;
|
||||
|
||||
pub trait HasPhrase: PlayheadApi {
|
||||
fn reset (&self) -> 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)
|
||||
-> &Option<(Instant, Option<Arc<RwLock<Phrase>>>)>;
|
||||
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_mut (&self) -> &mut Vec<Port<MidiIn>>;
|
||||
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 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;
|
||||
if let (true, Some((started, phrase))) = (self.is_rolling(), self.phrase()) {
|
||||
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)) {
|
||||
if let LiveEvent::Midi { message, .. } = event {
|
||||
if self.monitoring() {
|
||||
self.midi_buffer_mut()[sample].push(bytes.to_vec())
|
||||
midi_buf[sample].push(bytes.to_vec())
|
||||
}
|
||||
if self.recording() {
|
||||
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();
|
||||
for input in self.midi_ins_mut().iter() {
|
||||
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
|
||||
|
|
@ -144,6 +135,22 @@ pub trait MidiOutputApi: PlayheadApi + HasMidiBuffer + HasPhrase {
|
|||
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 (
|
||||
&mut self,
|
||||
scope: &ProcessScope,
|
||||
|
|
@ -298,26 +305,26 @@ pub struct PlayerAudio<'a, T: PlayerApi>(
|
|||
/// JACK process callback for a sequencer's phrase player/recorder.
|
||||
impl<'a, T: PlayerApi> Audio for PlayerAudio<'a, T> {
|
||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
let model = &mut self.0;
|
||||
let note_buffer = &mut self.1;
|
||||
let output_buffer = &mut self.2;
|
||||
let model = &mut self.0;
|
||||
let note_buf = &mut self.1;
|
||||
let midi_buf = &mut self.2;
|
||||
// Clear output buffer(s)
|
||||
model.clear(scope, false);
|
||||
model.clear(scope, midi_buf, false);
|
||||
// Write chunk of phrase to output, handle switchover
|
||||
if model.play(scope, note_buffer, output_buffer) {
|
||||
model.switchover(scope, note_buffer, output_buffer);
|
||||
if model.play(scope, note_buf, midi_buf) {
|
||||
model.switchover(scope, note_buf, midi_buf);
|
||||
}
|
||||
if model.has_midi_ins() {
|
||||
if model.recording() || model.monitoring() {
|
||||
// Record and/or monitor input
|
||||
model.record(scope)
|
||||
model.record(scope, midi_buf)
|
||||
} else if model.has_midi_outs() && model.monitoring() {
|
||||
// Monitor input to output
|
||||
model.monitor(scope)
|
||||
model.monitor(scope, midi_buf)
|
||||
}
|
||||
}
|
||||
// Write to output port(s)
|
||||
model.write(scope, output_buffer);
|
||||
model.write(scope, midi_buf);
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum PlayheadCommand {
|
||||
Play(Option<usize>),
|
||||
Pause(Option<usize>),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,18 @@ use crate::*;
|
|||
pub trait HasTracks<T: ArrangerTrackApi>: Send + Sync {
|
||||
fn tracks (&self) -> &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_del (&mut self, index: usize);
|
||||
fn track_default_name (&self) -> String {
|
||||
|
|
@ -21,16 +33,6 @@ pub enum ArrangerTrackCommand {
|
|||
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 {
|
||||
/// Name of track
|
||||
fn name (&self) -> &Arc<RwLock<String>>;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ pub enum FocusCommand {
|
|||
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> {
|
||||
use FocusCommand::*;
|
||||
match self {
|
||||
|
|
@ -24,93 +24,124 @@ impl<F: FocusGrid> Command<F> for FocusCommand {
|
|||
Right => { state.focus_right(); },
|
||||
Enter => { state.focus_enter(); },
|
||||
Exit => { state.focus_exit(); },
|
||||
_ => {}
|
||||
}
|
||||
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;
|
||||
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_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 {
|
||||
let (x, y) = self.cursor();
|
||||
self.layout()[y][x]
|
||||
let (x, y) = self.focus_cursor();
|
||||
self.focus_layout()[y][x]
|
||||
}
|
||||
fn focus (&mut self, target: Self::Item) {
|
||||
while self.focused() != target {
|
||||
self.focus_next()
|
||||
}
|
||||
}
|
||||
fn focus_up (&mut self) {
|
||||
let layout = self.layout();
|
||||
let (x, y) = self.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.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();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> FocusOrder for U
|
||||
where
|
||||
T: Copy + PartialEq + Debug,
|
||||
U: HasFocus<Item = T> + FocusGrid<Item = T> + FocusEnter
|
||||
{
|
||||
fn focus_next (&mut self) {
|
||||
let current = self.focused();
|
||||
let (x, y) = self.cursor();
|
||||
if x < self.layout()[y].len().saturating_sub(1) {
|
||||
let (x, y) = self.focus_cursor();
|
||||
if x < self.focus_layout()[y].len().saturating_sub(1) {
|
||||
self.focus_right();
|
||||
} else {
|
||||
self.focus_down();
|
||||
self.cursor_mut().0 = 0;
|
||||
self.focus_cursor_mut().0 = 0;
|
||||
}
|
||||
if self.focused() == current { // FIXME: prevent infinite loop
|
||||
self.focus_next()
|
||||
}
|
||||
self.focus_exit();
|
||||
self.update_focus();
|
||||
self.focus_update();
|
||||
}
|
||||
fn focus_prev (&mut self) {
|
||||
let current = self.focused();
|
||||
let (x, _) = self.cursor();
|
||||
let (x, _) = self.focus_cursor();
|
||||
if x > 0 {
|
||||
self.focus_left();
|
||||
} else {
|
||||
self.focus_up();
|
||||
let (_, y) = self.cursor();
|
||||
let next_x = self.layout()[y].len().saturating_sub(1);
|
||||
self.cursor_mut().0 = next_x;
|
||||
let (_, y) = self.focus_cursor();
|
||||
let next_x = self.focus_layout()[y].len().saturating_sub(1);
|
||||
self.focus_cursor_mut().0 = next_x;
|
||||
}
|
||||
if self.focused() == current { // FIXME: prevent infinite loop
|
||||
self.focus_prev()
|
||||
}
|
||||
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_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 format (&self) -> String { format!("{}x{}", self.w(), self.h()) }
|
||||
}
|
||||
|
||||
impl<E: Engine> Widget for Measure<E> {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ use std::fmt::Debug;
|
|||
|
||||
submod! {
|
||||
tui_arranger
|
||||
tui_arranger_track
|
||||
tui_arranger_scene
|
||||
//tui_mixer // TODO
|
||||
tui_phrase
|
||||
//tui_plugin // TODO
|
||||
|
|
@ -24,9 +26,11 @@ submod! {
|
|||
//tui_sampler // TODO
|
||||
//tui_sampler_cmd
|
||||
tui_sequencer
|
||||
tui_sequencer_cmd
|
||||
tui_status
|
||||
tui_theme
|
||||
tui_transport
|
||||
tui_transport_cmd
|
||||
}
|
||||
|
||||
pub struct AppView<E, A, C, S>
|
||||
|
|
|
|||
|
|
@ -6,17 +6,25 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerApp<Tui> {
|
|||
Ok(Self::new(ArrangerView {
|
||||
name: Arc::new(RwLock::new(String::new())),
|
||||
phrases: vec![],
|
||||
phrase: 0,
|
||||
scenes: vec![],
|
||||
tracks: vec![],
|
||||
metronome: false,
|
||||
playing: None.into(),
|
||||
started: None.into(),
|
||||
transport: jack.read().unwrap().transport(),
|
||||
current: Instant::default(),
|
||||
jack: jack.clone(),
|
||||
selected: ArrangerSelection::Clip(0, 0),
|
||||
mode: ArrangerMode::Vertical(2),
|
||||
color: Color::Rgb(28, 35, 25).into(),
|
||||
size: Measure::new(),
|
||||
focused: false,
|
||||
entered: false,
|
||||
quant: Default::default(),
|
||||
sync: Default::default(),
|
||||
splits: [20, 20],
|
||||
note_buf: vec![],
|
||||
midi_buf: vec![],
|
||||
}.into(), None, None))
|
||||
}
|
||||
}
|
||||
|
|
@ -28,9 +36,14 @@ pub type ArrangerApp<E: Engine> = AppView<
|
|||
ArrangerStatusBar
|
||||
>;
|
||||
|
||||
impl<E: Engine> Audio for ArrangerApp<E> {
|
||||
impl Audio for ArrangerApp<Tui> {
|
||||
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(match view.focused() {
|
||||
Content(ArrangerViewFocus::Transport) => {
|
||||
match TransportCommand::input_to_command(&view.app.transport, input)? {
|
||||
Focus(command) => {
|
||||
Content(ArrangerFocus::Transport) => {
|
||||
use TransportCommand::{Clock, Playhead};
|
||||
match TransportCommand::input_to_command(view, input)? {
|
||||
Clock(command) => {
|
||||
todo!()
|
||||
},
|
||||
App(Clock(command)) => {
|
||||
todo!()
|
||||
},
|
||||
App(Playhead(command)) => {
|
||||
Playhead(command) => {
|
||||
todo!()
|
||||
},
|
||||
}
|
||||
},
|
||||
Content(ArrangerViewFocus::PhraseEditor) => Editor(
|
||||
Content(ArrangerFocus::PhraseEditor) => Editor(
|
||||
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(
|
||||
Some(view.app.phrases.phrase().clone())
|
||||
Some(view.app.phrase().clone())
|
||||
),
|
||||
_ => 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 ArrangerTrackCommand as Track;
|
||||
use ArrangerClipCommand as Clip;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
match input.event() {
|
||||
key!(KeyCode::Char('e')) => EditPhrase(
|
||||
view.selected_phrase()
|
||||
),
|
||||
key!(KeyCode::Char('e')) => EditPhrase(view.phrase()),
|
||||
_ => match input.event() {
|
||||
// FIXME: boundary conditions
|
||||
|
||||
|
|
@ -250,7 +259,7 @@ impl Command<ArrangerApp<Tui>> for ArrangerViewCommand {
|
|||
Select(selected) => { state.selected = selected; Ok(None) },
|
||||
EditPhrase(phrase) => {
|
||||
state.editor.phrase = phrase.clone();
|
||||
state.focus(ArrangerViewFocus::PhraseEditor);
|
||||
state.focus(ArrangerFocus::PhraseEditor);
|
||||
state.focus_enter();
|
||||
Ok(None)
|
||||
}
|
||||
|
|
@ -277,10 +286,10 @@ pub struct ArrangerView<E: Engine> {
|
|||
selected: ArrangerSelection,
|
||||
mode: ArrangerMode,
|
||||
color: ItemColor,
|
||||
editor: PhraseEditor<E>,
|
||||
focused: bool,
|
||||
entered: bool,
|
||||
size: Measure<E>,
|
||||
note_buf: Vec<u8>,
|
||||
midi_buf: Vec<Vec<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl HasJack for ArrangerView<Tui> {
|
||||
|
|
@ -332,15 +341,27 @@ impl HasTracks<ArrangerTrack> for ArrangerView<Tui> {
|
|||
fn tracks_mut (&mut self) -> &mut Vec<ArrangerTrack> {
|
||||
&mut self.tracks
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrangerTracksApi<ArrangerTrack> for ArrangerView<Tui> {
|
||||
fn track_add (&mut self, name: Option<&str>, color: Option<ItemColor>)
|
||||
-> Usually<&mut ArrangerTrack>
|
||||
{
|
||||
let name = name.map_or_else(||self.track_default_name(), |x|x.to_string());
|
||||
let track = ArrangerTrack {
|
||||
name: Arc::new(name.into()),
|
||||
color: color.unwrap_or_else(||ItemColor::random()),
|
||||
width: name.len() + 2,
|
||||
//player: MIDIPlayer::new(&self.jack(), &self.clock(), name.as_str())?,
|
||||
width: name.len() + 2,
|
||||
name: Arc::new(name.into()),
|
||||
color: color.unwrap_or_else(||ItemColor::random()),
|
||||
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);
|
||||
let index = self.tracks().len() - 1;
|
||||
|
|
@ -387,7 +408,7 @@ pub enum ArrangerMode {
|
|||
|
||||
/// Sections in the arranger app that may be focused
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ArrangerViewFocus {
|
||||
pub enum ArrangerFocus {
|
||||
/// The transport (toolbar) is focused
|
||||
Transport,
|
||||
/// The arrangement (grid) is focused
|
||||
|
|
@ -444,9 +465,9 @@ impl Content for ArrangerView<Tui> {
|
|||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
Split::up(
|
||||
1,
|
||||
widget(&self.transport),
|
||||
widget(&TransportRef(self)),
|
||||
Split::down(
|
||||
self.split,
|
||||
self.splits[0],
|
||||
lay!(
|
||||
Layers::new(move |add|{
|
||||
match self.mode {
|
||||
|
|
@ -471,9 +492,9 @@ impl Content for ArrangerView<Tui> {
|
|||
.push_x(1),
|
||||
),
|
||||
Split::right(
|
||||
self.split,
|
||||
self.splits[1],
|
||||
widget(&self.phrases),
|
||||
widget(&self.editor),
|
||||
widget(&PhraseEditorRef(self)),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -481,7 +502,7 @@ impl Content for ArrangerView<Tui> {
|
|||
}
|
||||
|
||||
/// General methods for arranger
|
||||
impl<E: Engine> ArrangerView<E> {
|
||||
impl ArrangerView<Tui> {
|
||||
|
||||
/// Focus the editor with the current phrase
|
||||
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> {
|
||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
if self.process(client, scope) == Control::Quit {
|
||||
|
|
@ -635,7 +680,7 @@ impl<E: Engine> Audio for ArrangerView<E> {
|
|||
//self.arrangement.phrase_put();
|
||||
//}
|
||||
//self.show_phrase();
|
||||
//self.focus(ArrangerViewFocus::PhraseEditor);
|
||||
//self.focus(ArrangerFocus::PhraseEditor);
|
||||
//self.editor.entered = true;
|
||||
//}
|
||||
|
||||
|
|
@ -672,18 +717,10 @@ impl<E: Engine> Audio for ArrangerView<E> {
|
|||
//self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
||||
//}
|
||||
|
||||
/// Focus layout of arranger app
|
||||
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
|
||||
}
|
||||
impl FocusEnter for ArrangerApp<Tui> {
|
||||
fn focus_enter (&mut self) {
|
||||
use AppViewFocus::*;
|
||||
use ArrangerViewFocus::*;
|
||||
use ArrangerFocus::*;
|
||||
let focused = self.focused();
|
||||
if !self.entered {
|
||||
self.entered = focused == Content(Arranger);
|
||||
|
|
@ -698,16 +735,27 @@ impl FocusGrid for ArrangerApp<Tui> {
|
|||
self.app.phrases.entered = false;
|
||||
}
|
||||
}
|
||||
fn entered (&self) -> Option<Self::Item> {
|
||||
fn focus_entered (&self) -> Option<Self::Item> {
|
||||
if self.entered {
|
||||
Some(self.focused())
|
||||
} else {
|
||||
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 ArrangerViewFocus::*;
|
||||
use ArrangerFocus::*;
|
||||
&[
|
||||
&[Menu, Menu ],
|
||||
&[Content(Transport), Content(Transport) ],
|
||||
|
|
@ -715,19 +763,15 @@ impl FocusGrid for ArrangerApp<Tui> {
|
|||
&[Content(PhrasePool), Content(PhraseEditor)],
|
||||
]
|
||||
}
|
||||
fn update_focus (&mut self) {
|
||||
fn focus_update (&mut self) {
|
||||
use AppViewFocus::*;
|
||||
use ArrangerViewFocus::*;
|
||||
use ArrangerFocus::*;
|
||||
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 {
|
||||
status_bar.update(&(
|
||||
self.focused(),
|
||||
self.app.selected,
|
||||
self.app.editor.entered
|
||||
focused == Content(PhraseEditor) && self.entered
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -827,7 +871,7 @@ impl ArrangerSelection {
|
|||
|
||||
impl StatusBar for ArrangerStatusBar {
|
||||
|
||||
type State = (AppViewFocus<ArrangerViewFocus>, ArrangerSelection, bool);
|
||||
type State = (AppViewFocus<ArrangerFocus>, ArrangerSelection, bool);
|
||||
|
||||
fn hotkey_fg () -> Color where Self: Sized {
|
||||
TuiTheme::hotkey_fg()
|
||||
|
|
@ -836,15 +880,15 @@ impl StatusBar for ArrangerStatusBar {
|
|||
use AppViewFocus::*;
|
||||
if let Content(focused) = focused {
|
||||
*self = match focused {
|
||||
ArrangerViewFocus::Transport => ArrangerStatusBar::Transport,
|
||||
ArrangerViewFocus::Arranger => match selected {
|
||||
ArrangerFocus::Transport => ArrangerStatusBar::Transport,
|
||||
ArrangerFocus::Arranger => match selected {
|
||||
ArrangerSelection::Mix => ArrangerStatusBar::ArrangerMix,
|
||||
ArrangerSelection::Track(_) => ArrangerStatusBar::ArrangerTrack,
|
||||
ArrangerSelection::Scene(_) => ArrangerStatusBar::ArrangerScene,
|
||||
ArrangerSelection::Clip(_, _) => ArrangerStatusBar::ArrangerClip,
|
||||
},
|
||||
ArrangerViewFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
|
||||
ArrangerViewFocus::PhraseEditor => match entered {
|
||||
ArrangerFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
|
||||
ArrangerFocus::PhraseEditor => match entered {
|
||||
true => ArrangerStatusBar::PhraseEdit,
|
||||
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]>>,
|
||||
/// Current position of global playhead
|
||||
pub now: Arc<Pulse>,
|
||||
/// Width of notes area at last render
|
||||
pub width: AtomicUsize,
|
||||
/// Height of notes area at last render
|
||||
pub height: AtomicUsize,
|
||||
/// Width and height of notes area at last render
|
||||
pub size: Measure<E>
|
||||
}
|
||||
|
||||
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;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let Self { focused, entered, keys, phrase, buffer, note_len, .. } = self;
|
||||
let FixedAxis {
|
||||
start: note_start, point: note_point, clamp: note_clamp
|
||||
} = *self.note_axis.read().unwrap();
|
||||
let ScaledAxis {
|
||||
start: time_start, point: time_point, clamp: time_clamp, scale: time_scale
|
||||
} = *self.time_axis.read().unwrap();
|
||||
let phrase = self.0.phrase();
|
||||
let size = self.0.size();
|
||||
let focused = self.0.focused();
|
||||
let entered = self.0.entered();
|
||||
let keys = self.0.keys();
|
||||
let buffer = self.0.buffer();
|
||||
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 = 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|{
|
||||
|
|
@ -59,9 +121,8 @@ impl Content for PhraseEditor<Tui> {
|
|||
let notes = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||
let area = to.area();
|
||||
let h = area.h() as usize;
|
||||
self.height.store(h, Ordering::Relaxed);
|
||||
self.width.store(area.w() as usize, Ordering::Relaxed);
|
||||
let mut axis = self.note_axis.write().unwrap();
|
||||
size.set_wh(area.w(), h);
|
||||
let mut axis = note_axis.write().unwrap();
|
||||
if let Some(point) = axis.point {
|
||||
if point.saturating_sub(axis.start) > (h * 2).saturating_sub(1) {
|
||||
axis.start += 2;
|
||||
|
|
@ -84,11 +145,11 @@ impl Content for PhraseEditor<Tui> {
|
|||
})
|
||||
}).fill_x();
|
||||
let cursor = CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{
|
||||
Ok(if *focused && *entered {
|
||||
Ok(if focused && entered {
|
||||
let area = to.area();
|
||||
if let (Some(time), Some(note)) = (time_point, note_point) {
|
||||
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 c = if note % 2 == 0 { "▀" } else { "▄" };
|
||||
for x in x1..x2 {
|
||||
|
|
@ -103,7 +164,7 @@ impl Content for PhraseEditor<Tui> {
|
|||
|to:[u16;2]|Ok(Some(to.clip_h(1))),
|
||||
move|to: &mut TuiOutput|{
|
||||
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
|
||||
.expect("time_axis of sequencer expected to be clamped");
|
||||
for x in 0..(time_clamp/time_scale).saturating_sub(time_start) {
|
||||
|
|
@ -119,32 +180,28 @@ impl Content for PhraseEditor<Tui> {
|
|||
Ok(())
|
||||
}
|
||||
).push_x(6).align_sw();
|
||||
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 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 border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
|
||||
let note_area = lay!(notes, cursor).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 = 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 {
|
||||
upper_left = format!("{upper_left}: {}", phrase.read().unwrap().name);
|
||||
}
|
||||
let mut lower_right = format!(
|
||||
"┤{}x{}├",
|
||||
self.width.load(Ordering::Relaxed),
|
||||
self.height.load(Ordering::Relaxed),
|
||||
);
|
||||
let mut lower_right = format!("┤{}├", size.format());
|
||||
lower_right = format!("┤Zoom: {}├─{lower_right}", pulses_to_name(time_scale));
|
||||
//lower_right = format!("Zoom: {} (+{}:{}*{}|{})",
|
||||
//pulses_to_name(time_scale),
|
||||
//time_start, time_point.unwrap_or(0),
|
||||
//time_scale, time_clamp.unwrap_or(0),
|
||||
//);
|
||||
if *focused && *entered {
|
||||
if focused && entered {
|
||||
lower_right = format!("┤Note: {} {}├─{lower_right}",
|
||||
self.note_axis.read().unwrap().point.unwrap(),
|
||||
pulses_to_name(*note_len));
|
||||
note_axis.read().unwrap().point.unwrap(),
|
||||
pulses_to_name(note_len));
|
||||
//lower_right = format!("Note: {} (+{}:{}|{}) {upper_right}",
|
||||
//pulses_to_name(*note_len),
|
||||
//note_start,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
use crate::*;
|
||||
|
||||
pub type SequencerApp<E: Engine> = AppView<
|
||||
E,
|
||||
SequencerView<E>,
|
||||
SequencerViewCommand,
|
||||
SequencerStatusBar,
|
||||
>;
|
||||
|
||||
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||
|
|
@ -12,82 +19,15 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> {
|
|||
focused: false,
|
||||
focus: TransportViewFocus::PlayPause,
|
||||
size: Measure::new(),
|
||||
phrases: PhrasePoolView::from(&model.phrases()),
|
||||
phrases: vec![],
|
||||
editor: PhraseEditor::new(),
|
||||
}.into(), None, None))
|
||||
}
|
||||
}
|
||||
|
||||
pub type SequencerApp<E: Engine> = AppView<
|
||||
E,
|
||||
SequencerView<E>,
|
||||
SequencerViewCommand,
|
||||
SequencerStatusBar,
|
||||
>;
|
||||
|
||||
impl Handle<Tui> for SequencerApp<Tui> {
|
||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||
SequencerAppCommand::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)
|
||||
}
|
||||
SequencerCommand::execute_with_state(self, i)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +94,7 @@ impl Content for SequencerView<Tui> {
|
|||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
col!(
|
||||
self.transport,
|
||||
widget(&TransportRefView(self)),
|
||||
Split::right(20,
|
||||
widget(&self.phrases),
|
||||
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>;
|
||||
fn cursor (&self) -> (usize, usize) {
|
||||
self.cursor
|
||||
}
|
||||
fn cursor_mut (&mut self) -> &mut (usize, usize) {
|
||||
&mut self.cursor
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusEnter for SequencerApp<Tui> {
|
||||
fn focus_enter (&mut self) {
|
||||
let focused = self.focused();
|
||||
if !self.entered {
|
||||
|
|
@ -208,14 +145,24 @@ impl FocusGrid for SequencerApp<Tui> {
|
|||
// TODO
|
||||
}
|
||||
}
|
||||
fn entered (&self) -> Option<Self::Item> {
|
||||
fn focus_entered (&self) -> Option<Self::Item> {
|
||||
if self.entered {
|
||||
Some(self.focused())
|
||||
} else {
|
||||
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 SequencerFocus::*;
|
||||
&[
|
||||
|
|
@ -224,14 +171,14 @@ impl FocusGrid for SequencerApp<Tui> {
|
|||
&[Content(PhrasePool), Content(PhraseEditor)],
|
||||
]
|
||||
}
|
||||
fn update_focus (&mut self) {
|
||||
fn focus_update (&mut self) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> HasJack for SequencerView<E> {
|
||||
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> {
|
||||
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!()
|
||||
}
|
||||
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<E: Engine> MidiInputApi for SequencerView<E> {
|
||||
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!()
|
||||
}
|
||||
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<E: Engine> MidiOutputApi for SequencerView<E> {
|
||||
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!()
|
||||
}
|
||||
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<E: Engine> ClockApi for SequencerView<E> {
|
||||
fn timebase (&self) -> &Arc<Timebase> {
|
||||
todo!()
|
||||
}
|
||||
fn quant (&self) -> &Quantize {
|
||||
todo!()
|
||||
}
|
||||
fn sync (&self) -> &LaunchSync {
|
||||
todo!()
|
||||
}
|
||||
fn timebase (&self) -> &Arc<Timebase> {
|
||||
todo!()
|
||||
}
|
||||
fn quant (&self) -> &Quantize {
|
||||
todo!()
|
||||
}
|
||||
fn sync (&self) -> &LaunchSync {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> PlayheadApi for SequencerView<E> {
|
||||
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!()
|
||||
}
|
||||
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<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::*;
|
||||
|
||||
/// Root type of application.
|
||||
pub type TransportApp<E: Engine> = AppView<
|
||||
E,
|
||||
TransportView<E>,
|
||||
AppViewCommand<TransportCommand>,
|
||||
TransportStatusBar
|
||||
>;
|
||||
|
||||
/// Create app state from JACK handle.
|
||||
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> {
|
||||
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.
|
||||
pub struct TransportView<E: Engine> {
|
||||
_engine: PhantomData<E>,
|
||||
|
|
@ -153,6 +46,15 @@ pub struct TransportView<E: Engine> {
|
|||
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
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TransportViewFocus {
|
||||
|
|
@ -195,13 +97,60 @@ impl TransportViewFocus {
|
|||
#[derive(Copy, Clone)]
|
||||
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;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
let state = self.0;
|
||||
lay!(
|
||||
self.focus.wrap(self.focused, TransportViewFocus::PlayPause, &Styled(
|
||||
state.focus().wrap(state.focused(), TransportViewFocus::PlayPause, &Styled(
|
||||
None,
|
||||
match *self.playing().read().unwrap() {
|
||||
match state.transport_state() {
|
||||
Some(TransportState::Rolling) => "▶ PLAYING",
|
||||
Some(TransportState::Starting) => "READY ...",
|
||||
Some(TransportState::Stopped) => "⏹ STOPPED",
|
||||
|
|
@ -210,21 +159,21 @@ impl Content for TransportView<Tui> {
|
|||
).min_xy(11, 2).push_x(1)).align_x().fill_x(),
|
||||
|
||||
row!(
|
||||
self.focus.wrap(self.focused, TransportViewFocus::Bpm, &Outset::X(1u16, {
|
||||
let bpm = self.timebase().bpm.get();
|
||||
state.focus().wrap(state.focused(), TransportViewFocus::Bpm, &Outset::X(1u16, {
|
||||
let bpm = state.bpm_value();
|
||||
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! {
|
||||
//"QUANT ", ppq_to_name(self.quant as usize)
|
||||
//let quant = state.focus().wrap(state.focused(), TransportViewFocus::Quant, &Outset::X(1u16, row! {
|
||||
//"QUANT ", ppq_to_name(state.0.quant as usize)
|
||||
//})),
|
||||
self.focus.wrap(self.focused, TransportViewFocus::Sync, &Outset::X(1u16, row! {
|
||||
"SYNC ", pulses_to_name(self.sync().get() as usize)
|
||||
state.focus().wrap(state.focused(), TransportViewFocus::Sync, &Outset::X(1u16, row! {
|
||||
"SYNC ", pulses_to_name(state.sync_value() as usize)
|
||||
}))
|
||||
).align_w().fill_x(),
|
||||
|
||||
self.focus.wrap(self.focused, TransportViewFocus::Clock, &{
|
||||
let time1 = self.current().format_beat();
|
||||
let time2 = self.current().usec.format_msu();
|
||||
state.focus().wrap(state.focused(), TransportViewFocus::Clock, &{
|
||||
let time1 = state.format_beat();
|
||||
let time2 = state.format_msu();
|
||||
row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1)
|
||||
}).align_e().fill_x(),
|
||||
|
||||
|
|
@ -234,7 +183,7 @@ impl Content for TransportView<Tui> {
|
|||
|
||||
impl Audio for TransportView<Tui> {
|
||||
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>;
|
||||
fn cursor (&self) -> (usize, usize) {
|
||||
self.cursor
|
||||
}
|
||||
fn cursor_mut (&mut self) -> &mut (usize, usize) {
|
||||
&mut self.cursor
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusEnter for TransportApp<Tui> {
|
||||
fn focus_enter (&mut self) {
|
||||
let focused = self.focused();
|
||||
if !self.entered {
|
||||
self.entered = true;
|
||||
// TODO
|
||||
}
|
||||
self.entered = true;
|
||||
}
|
||||
fn focus_exit (&mut self) {
|
||||
if self.entered {
|
||||
self.entered = false;
|
||||
// TODO
|
||||
}
|
||||
self.entered = false;
|
||||
}
|
||||
fn entered (&self) -> Option<Self::Item> {
|
||||
fn focus_entered (&self) -> Option<Self::Item> {
|
||||
if self.entered {
|
||||
Some(self.focused())
|
||||
} else {
|
||||
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 TransportViewFocus::*;
|
||||
&[
|
||||
|
|
@ -298,7 +247,7 @@ impl FocusGrid for TransportApp<Tui> {
|
|||
],
|
||||
]
|
||||
}
|
||||
fn update_focus (&mut self) {
|
||||
fn focus_update (&mut self) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
|
@ -335,14 +284,3 @@ impl<E: Engine> PlayheadApi for TransportView<E> {
|
|||
&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