mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: p.53, e=118, fixed focus trait loop
This commit is contained in:
parent
9b996878c2
commit
76af9d9bac
15 changed files with 737 additions and 727 deletions
|
|
@ -12,7 +12,7 @@ pub enum FocusCommand {
|
||||||
Exit
|
Exit
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: FocusOrder + FocusGrid + FocusEnter> Command<F> for FocusCommand {
|
impl<F: HasFocus + 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 {
|
||||||
|
|
@ -30,24 +30,25 @@ impl<F: FocusOrder + FocusGrid + FocusEnter> Command<F> for FocusCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for things that have ordered focusable subparts.
|
||||||
pub trait HasFocus {
|
pub trait HasFocus {
|
||||||
|
/// Type that identifies of focused item.
|
||||||
type Item: Copy + PartialEq + Debug;
|
type Item: Copy + PartialEq + Debug;
|
||||||
|
/// Get the currently focused item.
|
||||||
fn focused (&self) -> Self::Item;
|
fn focused (&self) -> Self::Item;
|
||||||
fn focus (&mut self, target: Self::Item);
|
/// Focus the next item.
|
||||||
}
|
|
||||||
|
|
||||||
pub trait FocusOrder {
|
|
||||||
fn focus_next (&mut self);
|
fn focus_next (&mut self);
|
||||||
|
/// Focus the previous item.
|
||||||
fn focus_prev (&mut self);
|
fn focus_prev (&mut self);
|
||||||
|
/// Loop forward until a specific item is focused.
|
||||||
|
fn focus (&mut self, target: Self::Item) {
|
||||||
|
while self.focused() != target {
|
||||||
|
self.focus_next()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FocusEnter {
|
/// Trait for things that implement directional focus.
|
||||||
type Item: Copy + PartialEq + Debug;
|
|
||||||
fn focus_enter (&mut self) {}
|
|
||||||
fn focus_exit (&mut self) {}
|
|
||||||
fn focus_entered (&self) -> Option<Self::Item>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait FocusGrid {
|
pub trait FocusGrid {
|
||||||
type Item: Copy + PartialEq + Debug;
|
type Item: Copy + PartialEq + Debug;
|
||||||
fn focus_layout (&self) -> &[&[Self::Item]];
|
fn focus_layout (&self) -> &[&[Self::Item]];
|
||||||
|
|
@ -93,25 +94,13 @@ pub trait FocusGrid {
|
||||||
impl<T, U> HasFocus for U
|
impl<T, U> HasFocus for U
|
||||||
where
|
where
|
||||||
T: Copy + PartialEq + Debug,
|
T: Copy + PartialEq + Debug,
|
||||||
U: FocusGrid<Item = T> + FocusOrder,
|
U: FocusGrid<Item = T> + FocusEnter<Item = T>,
|
||||||
{
|
{
|
||||||
type Item = T;
|
type Item = T;
|
||||||
fn focused (&self) -> Self::Item {
|
fn focused (&self) -> Self::Item {
|
||||||
let (x, y) = self.focus_cursor();
|
let (x, y) = self.focus_cursor();
|
||||||
self.focus_layout()[y][x]
|
self.focus_layout()[y][x]
|
||||||
}
|
}
|
||||||
fn focus (&mut self, target: Self::Item) {
|
|
||||||
while self.focused() != target {
|
|
||||||
self.focus_next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> FocusOrder for U
|
|
||||||
where
|
|
||||||
T: Copy + PartialEq + Debug,
|
|
||||||
U: HasFocus<Item = T> + FocusGrid<Item = T> + FocusEnter
|
|
||||||
{
|
|
||||||
fn focus_next (&mut self) {
|
fn focus_next (&mut self) {
|
||||||
let current = self.focused();
|
let current = self.focused();
|
||||||
let (x, y) = self.focus_cursor();
|
let (x, y) = self.focus_cursor();
|
||||||
|
|
@ -145,3 +134,11 @@ where
|
||||||
self.focus_update();
|
self.focus_update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for things that can be focused into.
|
||||||
|
pub trait FocusEnter {
|
||||||
|
type Item: Copy + PartialEq + Debug;
|
||||||
|
fn focus_enter (&mut self) {}
|
||||||
|
fn focus_exit (&mut self) {}
|
||||||
|
fn focus_entered (&self) -> Option<Self::Item>;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,58 +12,49 @@ pub(crate) use std::fs::read_dir;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
|
|
||||||
tui_apis
|
tui_apis
|
||||||
tui_cmd
|
tui_command
|
||||||
|
tui_control
|
||||||
tui_focus
|
tui_focus
|
||||||
tui_handle
|
tui_handle
|
||||||
|
tui_init
|
||||||
|
tui_input
|
||||||
tui_jack
|
tui_jack
|
||||||
tui_menu
|
tui_menu
|
||||||
tui_model
|
tui_model
|
||||||
|
tui_select
|
||||||
tui_status
|
tui_status
|
||||||
tui_theme
|
tui_theme
|
||||||
tui_select
|
tui_view
|
||||||
|
|
||||||
tui_phrase
|
|
||||||
tui_sequencer
|
|
||||||
|
|
||||||
//tui_mixer // TODO
|
|
||||||
//tui_plugin // TODO
|
|
||||||
//tui_plugin_lv2
|
|
||||||
//tui_plugin_lv2_gui
|
|
||||||
//tui_plugin_vst2
|
|
||||||
//tui_plugin_vst3
|
|
||||||
//tui_sampler // TODO
|
|
||||||
//tui_sampler_cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
fn content_with_menu_and_status <'a, S: Send + Sync + 'a, C: Command<S>> (
|
||||||
impl<A, C, S> Content for AppView<Tui, A, C, S>
|
content: &'a impl Widget<Engine = Tui>,
|
||||||
where
|
menu_bar: Option<&'a MenuBar<Tui, S, C>>,
|
||||||
A: Widget<Engine = Tui> + Audio,
|
status_bar: Option<impl Widget<Engine = Tui>>
|
||||||
C: Command<Self>,
|
) -> impl Widget<Engine = Tui> + 'a {
|
||||||
S: StatusBar,
|
let menus = menu_bar.as_ref().map_or_else(
|
||||||
{
|
||&[] as &[Menu<_, _, _>],
|
||||||
type Engine = Tui;
|
|m|m.menus.as_slice()
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
);
|
||||||
let menus = self.menu_bar.as_ref().map_or_else(
|
let content = Either(
|
||||||
||&[] as &[Menu<_, _, _>],
|
status_bar.is_none(),
|
||||||
|m|m.menus.as_slice()
|
widget(&content),
|
||||||
);
|
Split::up(
|
||||||
|
1,
|
||||||
|
widget(status_bar.as_ref().unwrap()),
|
||||||
|
widget(&content)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Either(
|
||||||
|
menu_bar.is_none(),
|
||||||
|
widget(&content),
|
||||||
Split::down(
|
Split::down(
|
||||||
if self.menu_bar.is_some() { 1 } else { 0 },
|
1,
|
||||||
row!(menu in menus.iter() => {
|
row!(menu in menus.iter() => {
|
||||||
row!(" ", menu.title.as_str(), " ")
|
row!(" ", menu.title.as_str(), " ")
|
||||||
}),
|
}),
|
||||||
Either(
|
widget(&content)
|
||||||
self.status_bar.is_some(),
|
|
||||||
Split::up(
|
|
||||||
1,
|
|
||||||
widget(self.status_bar.as_ref().unwrap()),
|
|
||||||
widget(&self.app)
|
|
||||||
),
|
|
||||||
widget(&self.app)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,15 @@ impl HasPhrases for SequencerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasPhrases for PhrasesTui {
|
||||||
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&self.phrases
|
||||||
|
}
|
||||||
|
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
|
||||||
|
&mut self.phrases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HasPhrase for SequencerTui {
|
impl HasPhrase for SequencerTui {
|
||||||
fn reset (&self) -> bool {
|
fn reset (&self) -> bool {
|
||||||
self.reset
|
self.reset
|
||||||
|
|
@ -564,3 +573,143 @@ impl PhrasesTui {
|
||||||
//pub fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> {
|
//pub fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> {
|
||||||
//self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
//self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
impl PhraseTui {
|
||||||
|
pub fn new () -> Self {
|
||||||
|
Self {
|
||||||
|
phrase: None,
|
||||||
|
note_len: 24,
|
||||||
|
notes_in: Arc::new(RwLock::new([false;128])),
|
||||||
|
notes_out: Arc::new(RwLock::new([false;128])),
|
||||||
|
keys: keys_vert(),
|
||||||
|
buffer: Default::default(),
|
||||||
|
focused: false,
|
||||||
|
entered: false,
|
||||||
|
mode: false,
|
||||||
|
now: Arc::new(0.into()),
|
||||||
|
width: 0.into(),
|
||||||
|
height: 0.into(),
|
||||||
|
note_axis: RwLock::new(FixedAxis {
|
||||||
|
start: 12,
|
||||||
|
point: Some(36),
|
||||||
|
clamp: Some(127)
|
||||||
|
}),
|
||||||
|
time_axis: RwLock::new(ScaledAxis {
|
||||||
|
start: 00,
|
||||||
|
point: Some(00),
|
||||||
|
clamp: Some(000),
|
||||||
|
scale: 24
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time_cursor_advance (&self) {
|
||||||
|
let point = self.time_axis.read().unwrap().point;
|
||||||
|
let length = self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||||
|
let forward = |time|(time + self.note_len) % length;
|
||||||
|
self.time_axis.write().unwrap().point = point.map(forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put (&mut self) {
|
||||||
|
if let (Some(phrase), Some(time), Some(note)) = (
|
||||||
|
&self.phrase,
|
||||||
|
self.time_axis.read().unwrap().point,
|
||||||
|
self.note_axis.read().unwrap().point,
|
||||||
|
) {
|
||||||
|
let mut phrase = phrase.write().unwrap();
|
||||||
|
let key: u7 = u7::from((127 - note) as u8);
|
||||||
|
let vel: u7 = 100.into();
|
||||||
|
let start = time;
|
||||||
|
let end = (start + self.note_len) % phrase.length;
|
||||||
|
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
|
||||||
|
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
|
||||||
|
self.buffer = Self::redraw(&phrase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
|
||||||
|
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
||||||
|
if let Some(phrase) = phrase {
|
||||||
|
self.phrase = Some(phrase.clone());
|
||||||
|
self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length);
|
||||||
|
self.buffer = Self::redraw(&*phrase.read().unwrap());
|
||||||
|
} else {
|
||||||
|
self.phrase = None;
|
||||||
|
self.time_axis.write().unwrap().clamp = Some(0);
|
||||||
|
self.buffer = Default::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn redraw (phrase: &Phrase) -> BigBuffer {
|
||||||
|
let mut buf = BigBuffer::new(usize::MAX.min(phrase.length), 65);
|
||||||
|
Self::fill_seq_bg(&mut buf, phrase.length, phrase.ppq);
|
||||||
|
Self::fill_seq_fg(&mut buf, &phrase);
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) {
|
||||||
|
for x in 0..buf.width {
|
||||||
|
// Only fill as far as phrase length
|
||||||
|
if x as usize >= length { break }
|
||||||
|
// Fill each row with background characters
|
||||||
|
for y in 0 .. buf.height {
|
||||||
|
buf.get_mut(x, y).map(|cell|{
|
||||||
|
cell.set_char(if ppq == 0 {
|
||||||
|
'·'
|
||||||
|
} else if x % (4 * ppq) == 0 {
|
||||||
|
'│'
|
||||||
|
} else if x % ppq == 0 {
|
||||||
|
'╎'
|
||||||
|
} else {
|
||||||
|
'·'
|
||||||
|
});
|
||||||
|
cell.set_fg(Color::Rgb(48, 64, 56));
|
||||||
|
cell.modifier = Modifier::DIM;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) {
|
||||||
|
let mut notes_on = [false;128];
|
||||||
|
for x in 0..buf.width {
|
||||||
|
if x as usize >= phrase.length {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let Some(notes) = phrase.notes.get(x as usize) {
|
||||||
|
if phrase.percussive {
|
||||||
|
for note in notes {
|
||||||
|
match note {
|
||||||
|
MidiMessage::NoteOn { key, .. } =>
|
||||||
|
notes_on[key.as_int() as usize] = true,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for note in notes {
|
||||||
|
match note {
|
||||||
|
MidiMessage::NoteOn { key, .. } =>
|
||||||
|
notes_on[key.as_int() as usize] = true,
|
||||||
|
MidiMessage::NoteOff { key, .. } =>
|
||||||
|
notes_on[key.as_int() as usize] = false,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for y in 0..buf.height {
|
||||||
|
if y >= 64 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let Some(block) = half_block(
|
||||||
|
notes_on[y as usize * 2],
|
||||||
|
notes_on[y as usize * 2 + 1],
|
||||||
|
) {
|
||||||
|
buf.get_mut(x, y).map(|cell|{
|
||||||
|
cell.set_char(block);
|
||||||
|
cell.set_fg(Color::White);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if phrase.percussive {
|
||||||
|
notes_on.fill(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
348
crates/tek_tui/src/tui_command.rs
Normal file
348
crates/tek_tui/src/tui_command.rs
Normal file
|
|
@ -0,0 +1,348 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum TransportCommand {
|
||||||
|
Focus(FocusCommand),
|
||||||
|
Clock(ClockCommand),
|
||||||
|
Playhead(PlayheadCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum SequencerCommand {
|
||||||
|
Focus(FocusCommand),
|
||||||
|
Undo,
|
||||||
|
Redo,
|
||||||
|
Clear,
|
||||||
|
Clock(ClockCommand),
|
||||||
|
Playhead(PlayheadCommand),
|
||||||
|
Phrases(PhrasesCommand),
|
||||||
|
Editor(PhraseCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ArrangerCommand {
|
||||||
|
Focus(FocusCommand),
|
||||||
|
Undo,
|
||||||
|
Redo,
|
||||||
|
Clear,
|
||||||
|
Clock(ClockCommand),
|
||||||
|
Playhead(PlayheadCommand),
|
||||||
|
Scene(ArrangerSceneCommand),
|
||||||
|
Track(ArrangerTrackCommand),
|
||||||
|
Clip(ArrangerClipCommand),
|
||||||
|
Select(ArrangerSelection),
|
||||||
|
Zoom(usize),
|
||||||
|
Phrases(PhrasePoolCommand),
|
||||||
|
Editor(PhraseCommand),
|
||||||
|
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum PhrasesCommand {
|
||||||
|
Select(usize),
|
||||||
|
Edit(PhrasePoolCommand),
|
||||||
|
Rename(PhraseRenameCommand),
|
||||||
|
Length(PhraseLengthCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum PhraseRenameCommand {
|
||||||
|
Begin,
|
||||||
|
Set(String),
|
||||||
|
Confirm,
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub enum PhraseLengthCommand {
|
||||||
|
Begin,
|
||||||
|
Next,
|
||||||
|
Prev,
|
||||||
|
Inc,
|
||||||
|
Dec,
|
||||||
|
Set(usize),
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub enum PhraseCommand {
|
||||||
|
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
||||||
|
ToggleDirection,
|
||||||
|
EnterEditMode,
|
||||||
|
ExitEditMode,
|
||||||
|
NoteAppend,
|
||||||
|
NoteSet,
|
||||||
|
NoteCursorSet(usize),
|
||||||
|
NoteLengthSet(usize),
|
||||||
|
NoteScrollSet(usize),
|
||||||
|
TimeCursorSet(usize),
|
||||||
|
TimeScrollSet(usize),
|
||||||
|
TimeZoomSet(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TransportControl> Command<T> for TransportCommand {
|
||||||
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||||
|
use TransportCommand::{Focus, Clock, Playhead};
|
||||||
|
use ClockCommand::{SetBpm, SetQuant, SetSync};
|
||||||
|
Ok(Some(match self {
|
||||||
|
Focus(Next) => { todo!() }
|
||||||
|
Focus(Prev) => { todo!() },
|
||||||
|
Focus(_) => { unimplemented!() },
|
||||||
|
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)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command<SequencerTui> for SequencerCommand {
|
||||||
|
fn execute (self, state: &mut SequencerTui) -> Perhaps<Self> {
|
||||||
|
use SequencerCommand::*;
|
||||||
|
match self {
|
||||||
|
Focus(cmd) => delegate(cmd, Focus, &mut state),
|
||||||
|
Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases),
|
||||||
|
Editor(cmd) => delegate(cmd, Editor, &mut state.editor),
|
||||||
|
Clock(cmd) => delegate(cmd, Clock, &mut state.transport),
|
||||||
|
Playhead(cmd) => delegate(cmd, Playhead, &mut state.transport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ArrangerControl + FocusGrid> Command<T> for ArrangerCommand {
|
||||||
|
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||||
|
use ArrangerCommand::*;
|
||||||
|
match self {
|
||||||
|
Focus(cmd) => { delegate(cmd, Focus, &mut state) },
|
||||||
|
Scene(cmd) => { delegate(cmd, Scene, &mut state) },
|
||||||
|
Track(cmd) => { delegate(cmd, Track, &mut state) },
|
||||||
|
Clip(cmd) => { delegate(cmd, Clip, &mut state) },
|
||||||
|
Phrases(cmd) => { delegate(cmd, Phrases, &mut state) },
|
||||||
|
Editor(cmd) => { delegate(cmd, Editor, &mut state) },
|
||||||
|
Clock(cmd) => { delegate(cmd, Clock, &mut state) },
|
||||||
|
Playhead(cmd) => { delegate(cmd, Playhead, &mut state) },
|
||||||
|
Zoom(zoom) => { todo!(); },
|
||||||
|
Select(selected) => { state.selected = selected; Ok(None) },
|
||||||
|
EditPhrase(phrase) => {
|
||||||
|
state.editor.phrase = phrase.clone();
|
||||||
|
state.focus(ArrangerFocus::PhraseEditor);
|
||||||
|
state.focus_enter();
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command<PhrasesTui> for PhrasesCommand {
|
||||||
|
fn execute (self, view: &mut PhrasesTui) -> Perhaps<Self> {
|
||||||
|
use PhraseRenameCommand as Rename;
|
||||||
|
use PhraseLengthCommand as Length;
|
||||||
|
match self {
|
||||||
|
Self::Select(phrase) => {
|
||||||
|
view.phrase = phrase
|
||||||
|
},
|
||||||
|
Self::Edit(command) => {
|
||||||
|
return Ok(command.execute(&mut view)?.map(Self::Edit))
|
||||||
|
}
|
||||||
|
Self::Rename(command) => match command {
|
||||||
|
Rename::Begin => {
|
||||||
|
view.mode = Some(PhrasesMode::Rename(
|
||||||
|
view.phrase,
|
||||||
|
view.phrases[view.phrase].read().unwrap().name.clone()
|
||||||
|
))
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Ok(command.execute(view)?.map(Self::Rename))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::Length(command) => match command {
|
||||||
|
Length::Begin => {
|
||||||
|
view.mode = Some(PhrasesMode::Length(
|
||||||
|
view.phrase,
|
||||||
|
view.phrases[view.phrase].read().unwrap().length,
|
||||||
|
PhraseLengthFocus::Bar
|
||||||
|
))
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Ok(command.execute(view)?.map(Self::Length))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command<PhrasesTui> for PhraseLengthCommand {
|
||||||
|
fn execute (self, view: &mut PhrasesTui) -> Perhaps<Self> {
|
||||||
|
use PhraseLengthFocus::*;
|
||||||
|
use PhraseLengthCommand::*;
|
||||||
|
if let Some(PhrasesMode::Length(phrase, ref mut length, ref mut focus)) = view.mode {
|
||||||
|
match self {
|
||||||
|
Self::Cancel => {
|
||||||
|
view.mode = None;
|
||||||
|
},
|
||||||
|
Self::Prev => {
|
||||||
|
focus.prev()
|
||||||
|
},
|
||||||
|
Self::Next => {
|
||||||
|
focus.next()
|
||||||
|
},
|
||||||
|
Self::Inc => match focus {
|
||||||
|
Bar => { *length += 4 * PPQ },
|
||||||
|
Beat => { *length += PPQ },
|
||||||
|
Tick => { *length += 1 },
|
||||||
|
},
|
||||||
|
Self::Dec => match focus {
|
||||||
|
Bar => { *length = length.saturating_sub(4 * PPQ) },
|
||||||
|
Beat => { *length = length.saturating_sub(PPQ) },
|
||||||
|
Tick => { *length = length.saturating_sub(1) },
|
||||||
|
},
|
||||||
|
Self::Set(length) => {
|
||||||
|
let mut phrase = view.phrases[phrase].write().unwrap();
|
||||||
|
let old_length = phrase.length;
|
||||||
|
phrase.length = length;
|
||||||
|
view.mode = None;
|
||||||
|
return Ok(Some(Self::Set(old_length)))
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
} else if self == Begin {
|
||||||
|
view.mode = Some(PhrasesMode::Length(
|
||||||
|
view.phrase,
|
||||||
|
view.phrases[view.phrase].read().unwrap().length,
|
||||||
|
PhraseLengthFocus::Bar
|
||||||
|
));
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command<PhrasesTui> for PhraseRenameCommand {
|
||||||
|
fn execute (self, view: &mut PhrasesTui) -> Perhaps<Self> {
|
||||||
|
use PhraseRenameCommand::*;
|
||||||
|
if let Some(PhrasesMode::Rename(phrase, ref mut old_name)) = view.mode {
|
||||||
|
match self {
|
||||||
|
Set(s) => {
|
||||||
|
view.phrases[phrase].write().unwrap().name = s.into();
|
||||||
|
return Ok(Some(Self::Set(old_name.clone())))
|
||||||
|
},
|
||||||
|
Confirm => {
|
||||||
|
let old_name = old_name.clone();
|
||||||
|
view.mode = None;
|
||||||
|
return Ok(Some(Self::Set(old_name)))
|
||||||
|
},
|
||||||
|
Cancel => {
|
||||||
|
let mut phrase = view.phrases[phrase].write().unwrap();
|
||||||
|
phrase.name = old_name.clone();
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
Ok(None)
|
||||||
|
} else if self == Begin {
|
||||||
|
view.mode = Some(PhrasesMode::Rename(
|
||||||
|
view.phrase,
|
||||||
|
view.phrases[view.phrase].read().unwrap().name.clone()
|
||||||
|
));
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command<PhraseTui> for PhraseCommand {
|
||||||
|
//fn translate (self, state: &PhraseTui<E>) -> Self {
|
||||||
|
//use PhraseCommand::*;
|
||||||
|
//match self {
|
||||||
|
//GoUp => match state.entered { true => NoteCursorInc, false => NoteScrollInc, },
|
||||||
|
//GoDown => match state.entered { true => NoteCursorDec, false => NoteScrollDec, },
|
||||||
|
//GoLeft => match state.entered { true => TimeCursorDec, false => TimeScrollDec, },
|
||||||
|
//GoRight => match state.entered { true => TimeCursorInc, false => TimeScrollInc, },
|
||||||
|
//_ => self
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
fn execute (self, state: &mut PhraseTui) -> Perhaps<Self> {
|
||||||
|
use PhraseCommand::*;
|
||||||
|
match self.translate(state) {
|
||||||
|
ToggleDirection => {
|
||||||
|
state.mode = !state.mode;
|
||||||
|
},
|
||||||
|
EnterEditMode => {
|
||||||
|
state.entered = true;
|
||||||
|
},
|
||||||
|
ExitEditMode => {
|
||||||
|
state.entered = false;
|
||||||
|
},
|
||||||
|
TimeZoomOut => {
|
||||||
|
let scale = state.time_axis.read().unwrap().scale;
|
||||||
|
state.time_axis.write().unwrap().scale = next_note_length(scale)
|
||||||
|
},
|
||||||
|
TimeZoomIn => {
|
||||||
|
let scale = state.time_axis.read().unwrap().scale;
|
||||||
|
state.time_axis.write().unwrap().scale = prev_note_length(scale)
|
||||||
|
},
|
||||||
|
TimeCursorDec => {
|
||||||
|
let scale = state.time_axis.read().unwrap().scale;
|
||||||
|
state.time_axis.write().unwrap().point_dec(scale);
|
||||||
|
},
|
||||||
|
TimeCursorInc => {
|
||||||
|
let scale = state.time_axis.read().unwrap().scale;
|
||||||
|
state.time_axis.write().unwrap().point_inc(scale);
|
||||||
|
},
|
||||||
|
TimeScrollDec => {
|
||||||
|
let scale = state.time_axis.read().unwrap().scale;
|
||||||
|
state.time_axis.write().unwrap().start_dec(scale);
|
||||||
|
},
|
||||||
|
TimeScrollInc => {
|
||||||
|
let scale = state.time_axis.read().unwrap().scale;
|
||||||
|
state.time_axis.write().unwrap().start_inc(scale);
|
||||||
|
},
|
||||||
|
NoteCursorDec => {
|
||||||
|
let mut axis = state.note_axis.write().unwrap();
|
||||||
|
axis.point_inc(1);
|
||||||
|
if let Some(point) = axis.point { if point > 73 { axis.point = Some(73); } }
|
||||||
|
},
|
||||||
|
NoteCursorInc => {
|
||||||
|
let mut axis = state.note_axis.write().unwrap();
|
||||||
|
axis.point_dec(1);
|
||||||
|
if let Some(point) = axis.point { if point < axis.start { axis.start = (point / 2) * 2; } }
|
||||||
|
},
|
||||||
|
NoteScrollDec => {
|
||||||
|
state.note_axis.write().unwrap().start_inc(1);
|
||||||
|
},
|
||||||
|
NoteScrollInc => {
|
||||||
|
state.note_axis.write().unwrap().start_dec(1);
|
||||||
|
},
|
||||||
|
NoteLengthDec => {
|
||||||
|
state.note_len = prev_note_length(state.note_len)
|
||||||
|
},
|
||||||
|
NoteLengthInc => {
|
||||||
|
state.note_len = next_note_length(state.note_len)
|
||||||
|
},
|
||||||
|
NotePageUp => {
|
||||||
|
let mut axis = state.note_axis.write().unwrap();
|
||||||
|
axis.start_dec(3);
|
||||||
|
axis.point_dec(3);
|
||||||
|
},
|
||||||
|
NotePageDown => {
|
||||||
|
let mut axis = state.note_axis.write().unwrap();
|
||||||
|
axis.start_inc(3);
|
||||||
|
axis.point_inc(3);
|
||||||
|
},
|
||||||
|
NoteAppend => {
|
||||||
|
if state.entered {
|
||||||
|
state.put();
|
||||||
|
state.time_cursor_advance();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NoteSet => {
|
||||||
|
if state.entered { state.put(); }
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
53
crates/tek_tui/src/tui_control.rs
Normal file
53
crates/tek_tui/src/tui_control.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub trait TransportControl {
|
||||||
|
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 TransportTui {
|
||||||
|
fn bpm (&self) -> &BeatsPerMinute {
|
||||||
|
self.bpm()
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
self.quant()
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
self.sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransportControl for SequencerTui {
|
||||||
|
fn bpm (&self) -> &BeatsPerMinute {
|
||||||
|
self.bpm()
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
self.quant()
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
self.sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ArrangerControl {
|
||||||
|
fn selected (&self) -> ArrangerSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArrangerControl for ArrangerTui {
|
||||||
|
fn selected (&self) -> ArrangerSelection {
|
||||||
|
self.selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,6 +45,7 @@ impl TransportFocus {
|
||||||
Self::Quant => Self::Sync,
|
Self::Quant => Self::Sync,
|
||||||
Self::Sync => Self::Clock,
|
Self::Sync => Self::Clock,
|
||||||
Self::Clock => Self::PlayPause,
|
Self::Clock => Self::PlayPause,
|
||||||
|
Self::Menu => { todo!() },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn prev (&mut self) {
|
pub fn prev (&mut self) {
|
||||||
|
|
@ -54,6 +55,7 @@ impl TransportFocus {
|
||||||
Self::Quant => Self::Bpm,
|
Self::Quant => Self::Bpm,
|
||||||
Self::Sync => Self::Quant,
|
Self::Sync => Self::Quant,
|
||||||
Self::Clock => Self::Sync,
|
Self::Clock => Self::Sync,
|
||||||
|
Self::Menu => { todo!() },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn wrap <'a, W: Widget<Engine = Tui>> (
|
pub fn wrap <'a, W: Widget<Engine = Tui>> (
|
||||||
|
|
@ -66,9 +68,9 @@ impl TransportFocus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasFocus for TransportTui {
|
//impl HasFocus for TransportTui {
|
||||||
type Item = TransportFocus;
|
//type Item = TransportFocus;
|
||||||
}
|
//}
|
||||||
|
|
||||||
impl FocusEnter for TransportTui {
|
impl FocusEnter for TransportTui {
|
||||||
type Item = TransportFocus;
|
type Item = TransportFocus;
|
||||||
|
|
@ -107,9 +109,9 @@ impl FocusGrid for TransportTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasFocus for SequencerTui {
|
//impl HasFocus for SequencerTui {
|
||||||
type Item = SequencerFocus;
|
//type Item = SequencerFocus;
|
||||||
}
|
//}
|
||||||
|
|
||||||
impl FocusEnter for SequencerTui {
|
impl FocusEnter for SequencerTui {
|
||||||
type Item = SequencerFocus;
|
type Item = SequencerFocus;
|
||||||
|
|
@ -163,15 +165,15 @@ impl FocusEnter for ArrangerTui {
|
||||||
let focused = self.focused();
|
let focused = self.focused();
|
||||||
if !self.entered {
|
if !self.entered {
|
||||||
self.entered = focused == Arranger;
|
self.entered = focused == Arranger;
|
||||||
self.app.editor.entered = focused == PhraseEditor;
|
self.editor.entered = focused == PhraseEditor;
|
||||||
self.app.phrases.entered = focused == PhrasePool;
|
self.phrases.entered = focused == PhrasePool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn focus_exit (&mut self) {
|
fn focus_exit (&mut self) {
|
||||||
if self.entered {
|
if self.entered {
|
||||||
self.entered = false;
|
self.entered = false;
|
||||||
self.app.editor.entered = false;
|
self.editor.entered = false;
|
||||||
self.app.phrases.entered = false;
|
self.phrases.entered = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn focus_entered (&self) -> Option<Self::Item> {
|
fn focus_entered (&self) -> Option<Self::Item> {
|
||||||
|
|
@ -207,7 +209,7 @@ impl FocusGrid for ArrangerTui {
|
||||||
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.selected,
|
||||||
focused == PhraseEditor && self.entered
|
focused == PhraseEditor && self.entered
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
|
||||||
focused: false,
|
focused: false,
|
||||||
focus: TransportFocus::PlayPause,
|
focus: TransportFocus::PlayPause,
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
phrases: vec![],
|
|
||||||
}.into(), None, None))
|
}.into(), None, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum TransportCommand {
|
|
||||||
Focus(FocusCommand),
|
|
||||||
Clock(ClockCommand),
|
|
||||||
Playhead(PlayheadCommand),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
|
impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
|
||||||
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &T, input: &TuiInput) -> Option<Self> {
|
||||||
use KeyCode::Char;
|
use KeyCode::Char;
|
||||||
|
|
@ -54,64 +47,6 @@ impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TransportControl {
|
|
||||||
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 TransportTui {
|
|
||||||
fn bpm (&self) -> &BeatsPerMinute {
|
|
||||||
self.bpm()
|
|
||||||
}
|
|
||||||
fn quant (&self) -> &Quantize {
|
|
||||||
self.quant()
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &LaunchSync {
|
|
||||||
self.sync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: TransportControl> Command<T> for TransportCommand {
|
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
||||||
use TransportCommand::{Focus, Clock, Playhead};
|
|
||||||
use ClockCommand::{SetBpm, SetQuant, SetSync};
|
|
||||||
Ok(Some(match self {
|
|
||||||
Focus(Next) => { todo!() }
|
|
||||||
Focus(Prev) => { todo!() },
|
|
||||||
Focus(_) => { unimplemented!() },
|
|
||||||
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)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum SequencerCommand {
|
|
||||||
Focus(FocusCommand),
|
|
||||||
Undo,
|
|
||||||
Redo,
|
|
||||||
Clear,
|
|
||||||
Clock(ClockCommand),
|
|
||||||
Playhead(PlayheadCommand),
|
|
||||||
Phrases(PhrasesCommand),
|
|
||||||
Editor(PhraseCommand),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
|
impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
|
||||||
fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &SequencerTui, input: &TuiInput) -> Option<Self> {
|
||||||
use FocusCommand::*;
|
use FocusCommand::*;
|
||||||
|
|
@ -138,55 +73,6 @@ impl InputToCommand<Tui, SequencerTui> for SequencerCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command<SequencerTui> for SequencerCommand {
|
|
||||||
fn execute (self, state: &mut SequencerTui) -> Perhaps<Self> {
|
|
||||||
use SequencerCommand::*;
|
|
||||||
match self {
|
|
||||||
Focus(cmd) => delegate(cmd, Focus, state),
|
|
||||||
Phrases(cmd) => delegate(cmd, Phrases, &mut state.phrases),
|
|
||||||
Editor(cmd) => delegate(cmd, Editor, &mut state.editor),
|
|
||||||
Clock(cmd) => delegate(cmd, Clock, &mut state.transport),
|
|
||||||
Playhead(cmd) => delegate(cmd, Playhead, &mut state.transport)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransportControl for SequencerTui {
|
|
||||||
fn bpm (&self) -> &BeatsPerMinute {
|
|
||||||
self.app.bpm()
|
|
||||||
}
|
|
||||||
fn quant (&self) -> &Quantize {
|
|
||||||
self.app.quant()
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &LaunchSync {
|
|
||||||
self.app.sync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum ArrangerCommand {
|
|
||||||
Focus(FocusCommand),
|
|
||||||
Undo,
|
|
||||||
Redo,
|
|
||||||
Clear,
|
|
||||||
Clock(ClockCommand),
|
|
||||||
Playhead(PlayheadCommand),
|
|
||||||
Scene(ArrangerSceneCommand),
|
|
||||||
Track(ArrangerTrackCommand),
|
|
||||||
Clip(ArrangerClipCommand),
|
|
||||||
Select(ArrangerSelection),
|
|
||||||
Zoom(usize),
|
|
||||||
Phrases(PhrasePoolCommand),
|
|
||||||
Editor(PhraseCommand),
|
|
||||||
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ArrangerControl {
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArrangerControl for ArrangerTui {
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ArrangerControl> InputToCommand<Tui, T> for ArrangerCommand {
|
impl<T: ArrangerControl> InputToCommand<Tui, T> for ArrangerCommand {
|
||||||
fn input_to_command (view: &T, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (view: &T, input: &TuiInput) -> Option<Self> {
|
||||||
use FocusCommand::*;
|
use FocusCommand::*;
|
||||||
|
|
@ -238,28 +124,28 @@ impl<T: ArrangerControl> InputToCommand<Tui, T> for ArrangerCommand {
|
||||||
_ => match input.event() {
|
_ => match input.event() {
|
||||||
// FIXME: boundary conditions
|
// FIXME: boundary conditions
|
||||||
|
|
||||||
key!(KeyCode::Up) => match view.selected {
|
key!(KeyCode::Up) => match view.selected() {
|
||||||
Select::Mix => return None,
|
Select::Mix => return None,
|
||||||
Select::Track(t) => return None,
|
Select::Track(t) => return None,
|
||||||
Select::Scene(s) => Select(Select::Scene(s - 1)),
|
Select::Scene(s) => Select(Select::Scene(s - 1)),
|
||||||
Select::Clip(t, s) => Select(Select::Clip(t, s - 1)),
|
Select::Clip(t, s) => Select(Select::Clip(t, s - 1)),
|
||||||
},
|
},
|
||||||
|
|
||||||
key!(KeyCode::Down) => match view.selected {
|
key!(KeyCode::Down) => match view.selected() {
|
||||||
Select::Mix => Select(Select::Scene(0)),
|
Select::Mix => Select(Select::Scene(0)),
|
||||||
Select::Track(t) => Select(Select::Clip(t, 0)),
|
Select::Track(t) => Select(Select::Clip(t, 0)),
|
||||||
Select::Scene(s) => Select(Select::Scene(s + 1)),
|
Select::Scene(s) => Select(Select::Scene(s + 1)),
|
||||||
Select::Clip(t, s) => Select(Select::Clip(t, s + 1)),
|
Select::Clip(t, s) => Select(Select::Clip(t, s + 1)),
|
||||||
},
|
},
|
||||||
|
|
||||||
key!(KeyCode::Left) => match view.selected {
|
key!(KeyCode::Left) => match view.selected() {
|
||||||
Select::Mix => return None,
|
Select::Mix => return None,
|
||||||
Select::Track(t) => Select(Select::Track(t - 1)),
|
Select::Track(t) => Select(Select::Track(t - 1)),
|
||||||
Select::Scene(s) => return None,
|
Select::Scene(s) => return None,
|
||||||
Select::Clip(t, s) => Select(Select::Clip(t - 1, s)),
|
Select::Clip(t, s) => Select(Select::Clip(t - 1, s)),
|
||||||
},
|
},
|
||||||
|
|
||||||
key!(KeyCode::Right) => match view.selected {
|
key!(KeyCode::Right) => match view.selected() {
|
||||||
Select::Mix => return None,
|
Select::Mix => return None,
|
||||||
Select::Track(t) => Select(Select::Track(t + 1)),
|
Select::Track(t) => Select(Select::Track(t + 1)),
|
||||||
Select::Scene(s) => Select(Select::Clip(0, s)),
|
Select::Scene(s) => Select(Select::Clip(0, s)),
|
||||||
|
|
@ -276,42 +162,42 @@ impl<T: ArrangerControl> InputToCommand<Tui, T> for ArrangerCommand {
|
||||||
|
|
||||||
key!(KeyCode::Char('`')) => { todo!("toggle view mode") },
|
key!(KeyCode::Char('`')) => { todo!("toggle view mode") },
|
||||||
|
|
||||||
key!(KeyCode::Char(',')) => match view.selected {
|
key!(KeyCode::Char(',')) => match view.selected() {
|
||||||
Select::Mix => Zoom(0),
|
Select::Mix => Zoom(0),
|
||||||
Select::Track(t) => Track(Track::Swap(t, t - 1)),
|
Select::Track(t) => Track(Track::Swap(t, t - 1)),
|
||||||
Select::Scene(s) => Scene(Scene::Swap(s, s - 1)),
|
Select::Scene(s) => Scene(Scene::Swap(s, s - 1)),
|
||||||
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
},
|
},
|
||||||
|
|
||||||
key!(KeyCode::Char('.')) => match view.selected {
|
key!(KeyCode::Char('.')) => match view.selected() {
|
||||||
Select::Mix => Zoom(0),
|
Select::Mix => Zoom(0),
|
||||||
Select::Track(t) => Track(Track::Swap(t, t + 1)),
|
Select::Track(t) => Track(Track::Swap(t, t + 1)),
|
||||||
Select::Scene(s) => Scene(Scene::Swap(s, s + 1)),
|
Select::Scene(s) => Scene(Scene::Swap(s, s + 1)),
|
||||||
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
},
|
},
|
||||||
|
|
||||||
key!(KeyCode::Char('<')) => match view.selected {
|
key!(KeyCode::Char('<')) => match view.selected() {
|
||||||
Select::Mix => Zoom(0),
|
Select::Mix => Zoom(0),
|
||||||
Select::Track(t) => Track(Track::Swap(t, t - 1)),
|
Select::Track(t) => Track(Track::Swap(t, t - 1)),
|
||||||
Select::Scene(s) => Scene(Scene::Swap(s, s - 1)),
|
Select::Scene(s) => Scene(Scene::Swap(s, s - 1)),
|
||||||
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
},
|
},
|
||||||
|
|
||||||
key!(KeyCode::Char('>')) => match view.selected {
|
key!(KeyCode::Char('>')) => match view.selected() {
|
||||||
Select::Mix => Zoom(0),
|
Select::Mix => Zoom(0),
|
||||||
Select::Track(t) => Track(Track::Swap(t, t + 1)),
|
Select::Track(t) => Track(Track::Swap(t, t + 1)),
|
||||||
Select::Scene(s) => Scene(Scene::Swap(s, s + 1)),
|
Select::Scene(s) => Scene(Scene::Swap(s, s + 1)),
|
||||||
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
},
|
},
|
||||||
|
|
||||||
key!(KeyCode::Enter) => match view.selected {
|
key!(KeyCode::Enter) => match view.selected() {
|
||||||
Select::Mix => return None,
|
Select::Mix => return None,
|
||||||
Select::Track(t) => return None,
|
Select::Track(t) => return None,
|
||||||
Select::Scene(s) => Scene(Scene::Play(s)),
|
Select::Scene(s) => Scene(Scene::Play(s)),
|
||||||
Select::Clip(t, s) => return None,
|
Select::Clip(t, s) => return None,
|
||||||
},
|
},
|
||||||
|
|
||||||
key!(KeyCode::Delete) => match view.selected {
|
key!(KeyCode::Delete) => match view.selected() {
|
||||||
Select::Mix => Clear,
|
Select::Mix => Clear,
|
||||||
Select::Track(t) => Track(Track::Delete(t)),
|
Select::Track(t) => Track(Track::Delete(t)),
|
||||||
Select::Scene(s) => Scene(Scene::Delete(s)),
|
Select::Scene(s) => Scene(Scene::Delete(s)),
|
||||||
|
|
@ -320,12 +206,12 @@ impl<T: ArrangerControl> InputToCommand<Tui, T> for ArrangerCommand {
|
||||||
|
|
||||||
key!(KeyCode::Char('c')) => Clip(Clip::RandomColor),
|
key!(KeyCode::Char('c')) => Clip(Clip::RandomColor),
|
||||||
|
|
||||||
key!(KeyCode::Char('s')) => match view.selected {
|
key!(KeyCode::Char('s')) => match view.selected() {
|
||||||
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
Select::Clip(t, s) => Clip(Clip::Set(t, s, None)),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
|
|
||||||
key!(KeyCode::Char('g')) => match view.selected {
|
key!(KeyCode::Char('g')) => match view.selected() {
|
||||||
Select::Clip(t, s) => Clip(Clip::Get(t, s)),
|
Select::Clip(t, s) => Clip(Clip::Get(t, s)),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
|
|
@ -345,57 +231,6 @@ impl<T: ArrangerControl> InputToCommand<Tui, T> for ArrangerCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ArrangerControl> Command<T> for ArrangerCommand {
|
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
|
||||||
use ArrangerCommand::*;
|
|
||||||
match self {
|
|
||||||
Focus(cmd) => { delegate(cmd, Focus, state) },
|
|
||||||
Scene(cmd) => { delegate(cmd, Scene, &mut state) },
|
|
||||||
Track(cmd) => { delegate(cmd, Track, &mut state) },
|
|
||||||
Clip(cmd) => { delegate(cmd, Clip, &mut state) },
|
|
||||||
Phrases(cmd) => { delegate(cmd, Phrases, &mut state) },
|
|
||||||
Editor(cmd) => { delegate(cmd, Editor, &mut state) },
|
|
||||||
Clock(cmd) => { delegate(cmd, Clock, &mut state) },
|
|
||||||
Playhead(cmd) => { delegate(cmd, Playhead, &mut state) },
|
|
||||||
Zoom(zoom) => { todo!(); },
|
|
||||||
Select(selected) => { state.selected = selected; Ok(None) },
|
|
||||||
EditPhrase(phrase) => {
|
|
||||||
state.editor.phrase = phrase.clone();
|
|
||||||
state.focus(ArrangerFocus::PhraseEditor);
|
|
||||||
state.focus_enter();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub enum PhrasesCommand {
|
|
||||||
Select(usize),
|
|
||||||
Edit(PhrasePoolCommand),
|
|
||||||
Rename(PhraseRenameCommand),
|
|
||||||
Length(PhraseLengthCommand),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum PhraseRenameCommand {
|
|
||||||
Begin,
|
|
||||||
Set(String),
|
|
||||||
Confirm,
|
|
||||||
Cancel,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub enum PhraseLengthCommand {
|
|
||||||
Begin,
|
|
||||||
Next,
|
|
||||||
Prev,
|
|
||||||
Inc,
|
|
||||||
Dec,
|
|
||||||
Set(usize),
|
|
||||||
Cancel,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputToCommand<Tui, PhrasesTui> for PhrasesCommand {
|
impl InputToCommand<Tui, PhrasesTui> for PhrasesCommand {
|
||||||
fn input_to_command (state: &PhrasesTui, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &PhrasesTui, input: &TuiInput) -> Option<Self> {
|
||||||
use PhrasesCommand as Cmd;
|
use PhrasesCommand as Cmd;
|
||||||
|
|
@ -427,45 +262,6 @@ impl InputToCommand<Tui, PhrasesTui> for PhrasesCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command<PhrasesTui> for PhrasesCommand {
|
|
||||||
fn execute (self, view: &mut PhrasesTui) -> Perhaps<Self> {
|
|
||||||
use PhraseRenameCommand as Rename;
|
|
||||||
use PhraseLengthCommand as Length;
|
|
||||||
match self {
|
|
||||||
Self::Select(phrase) => {
|
|
||||||
view.phrase = phrase
|
|
||||||
},
|
|
||||||
Self::Edit(command) => {
|
|
||||||
return Ok(command.execute(&mut view)?.map(Self::Edit))
|
|
||||||
}
|
|
||||||
Self::Rename(command) => match command {
|
|
||||||
Rename::Begin => {
|
|
||||||
view.mode = Some(PhrasesMode::Rename(
|
|
||||||
view.phrase,
|
|
||||||
view.phrases[view.phrase].read().unwrap().name.clone()
|
|
||||||
))
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Ok(command.execute(view)?.map(Self::Rename))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Self::Length(command) => match command {
|
|
||||||
Length::Begin => {
|
|
||||||
view.mode = Some(PhrasesMode::Length(
|
|
||||||
view.phrase,
|
|
||||||
view.phrases[view.phrase].read().unwrap().length,
|
|
||||||
PhraseLengthFocus::Bar
|
|
||||||
))
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Ok(command.execute(view)?.map(Self::Length))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputToCommand<Tui, PhrasesTui> for PhraseLengthCommand {
|
impl InputToCommand<Tui, PhrasesTui> for PhraseLengthCommand {
|
||||||
fn input_to_command (view: &PhrasesTui, from: &TuiInput) -> Option<Self> {
|
fn input_to_command (view: &PhrasesTui, from: &TuiInput) -> Option<Self> {
|
||||||
if let Some(PhrasesMode::Length(_, length, _)) = view.mode {
|
if let Some(PhrasesMode::Length(_, length, _)) = view.mode {
|
||||||
|
|
@ -484,54 +280,6 @@ impl InputToCommand<Tui, PhrasesTui> for PhraseLengthCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command<PhrasesTui> for PhraseLengthCommand {
|
|
||||||
fn execute (self, view: &mut PhrasesTui) -> Perhaps<Self> {
|
|
||||||
use PhraseLengthFocus::*;
|
|
||||||
use PhraseLengthCommand::*;
|
|
||||||
if let Some(PhrasesMode::Length(phrase, ref mut length, ref mut focus)) = view.mode {
|
|
||||||
match self {
|
|
||||||
Self::Cancel => {
|
|
||||||
view.mode = None;
|
|
||||||
},
|
|
||||||
Self::Prev => {
|
|
||||||
focus.prev()
|
|
||||||
},
|
|
||||||
Self::Next => {
|
|
||||||
focus.next()
|
|
||||||
},
|
|
||||||
Self::Inc => match focus {
|
|
||||||
Bar => { *length += 4 * PPQ },
|
|
||||||
Beat => { *length += PPQ },
|
|
||||||
Tick => { *length += 1 },
|
|
||||||
},
|
|
||||||
Self::Dec => match focus {
|
|
||||||
Bar => { *length = length.saturating_sub(4 * PPQ) },
|
|
||||||
Beat => { *length = length.saturating_sub(PPQ) },
|
|
||||||
Tick => { *length = length.saturating_sub(1) },
|
|
||||||
},
|
|
||||||
Self::Set(length) => {
|
|
||||||
let mut phrase = view.phrases[phrase].write().unwrap();
|
|
||||||
let old_length = phrase.length;
|
|
||||||
phrase.length = length;
|
|
||||||
view.mode = None;
|
|
||||||
return Ok(Some(Self::Set(old_length)))
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
} else if self == Begin {
|
|
||||||
view.mode = Some(PhrasesMode::Length(
|
|
||||||
view.phrase,
|
|
||||||
view.phrases[view.phrase].read().unwrap().length,
|
|
||||||
PhraseLengthFocus::Bar
|
|
||||||
));
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputToCommand<Tui, PhrasesTui> for PhraseRenameCommand {
|
impl InputToCommand<Tui, PhrasesTui> for PhraseRenameCommand {
|
||||||
fn input_to_command (view: &PhrasesTui, from: &TuiInput) -> Option<Self> {
|
fn input_to_command (view: &PhrasesTui, from: &TuiInput) -> Option<Self> {
|
||||||
if let Some(PhrasesMode::Rename(_, ref old_name)) = view.mode {
|
if let Some(PhrasesMode::Rename(_, ref old_name)) = view.mode {
|
||||||
|
|
@ -556,55 +304,6 @@ impl InputToCommand<Tui, PhrasesTui> for PhraseRenameCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command<PhrasesTui> for PhraseRenameCommand {
|
|
||||||
fn execute (self, view: &mut PhrasesTui) -> Perhaps<Self> {
|
|
||||||
use PhraseRenameCommand::*;
|
|
||||||
if let Some(PhrasesMode::Rename(phrase, ref mut old_name)) = view.mode {
|
|
||||||
match self {
|
|
||||||
Set(s) => {
|
|
||||||
view.phrases[phrase].write().unwrap().name = s.into();
|
|
||||||
return Ok(Some(Self::Set(old_name.clone())))
|
|
||||||
},
|
|
||||||
Confirm => {
|
|
||||||
let old_name = old_name.clone();
|
|
||||||
view.mode = None;
|
|
||||||
return Ok(Some(Self::Set(old_name)))
|
|
||||||
},
|
|
||||||
Cancel => {
|
|
||||||
let mut phrase = view.phrases[phrase].write().unwrap();
|
|
||||||
phrase.name = old_name.clone();
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
};
|
|
||||||
Ok(None)
|
|
||||||
} else if self == Begin {
|
|
||||||
view.mode = Some(PhrasesMode::Rename(
|
|
||||||
view.phrase,
|
|
||||||
view.phrases[view.phrase].read().unwrap().name.clone()
|
|
||||||
));
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub enum PhraseCommand {
|
|
||||||
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
|
||||||
ToggleDirection,
|
|
||||||
EnterEditMode,
|
|
||||||
ExitEditMode,
|
|
||||||
NoteAppend,
|
|
||||||
NoteSet,
|
|
||||||
NoteCursorSet(usize),
|
|
||||||
NoteLengthSet(usize),
|
|
||||||
NoteScrollSet(usize),
|
|
||||||
TimeCursorSet(usize),
|
|
||||||
TimeScrollSet(usize),
|
|
||||||
TimeZoomSet(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InputToCommand<Tui, PhraseTui> for PhraseCommand {
|
impl InputToCommand<Tui, PhraseTui> for PhraseCommand {
|
||||||
fn input_to_command (state: &PhraseTui, from: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &PhraseTui, from: &TuiInput) -> Option<Self> {
|
||||||
use PhraseCommand::*;
|
use PhraseCommand::*;
|
||||||
|
|
@ -642,97 +341,3 @@ impl InputToCommand<Tui, PhraseTui> for PhraseCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command<PhraseTui> for PhraseCommand {
|
|
||||||
//fn translate (self, state: &PhraseTui<E>) -> Self {
|
|
||||||
//use PhraseCommand::*;
|
|
||||||
//match self {
|
|
||||||
//GoUp => match state.entered { true => NoteCursorInc, false => NoteScrollInc, },
|
|
||||||
//GoDown => match state.entered { true => NoteCursorDec, false => NoteScrollDec, },
|
|
||||||
//GoLeft => match state.entered { true => TimeCursorDec, false => TimeScrollDec, },
|
|
||||||
//GoRight => match state.entered { true => TimeCursorInc, false => TimeScrollInc, },
|
|
||||||
//_ => self
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
fn execute (self, state: &mut PhraseTui) -> Perhaps<Self> {
|
|
||||||
use PhraseCommand::*;
|
|
||||||
match self.translate(state) {
|
|
||||||
ToggleDirection => {
|
|
||||||
state.mode = !state.mode;
|
|
||||||
},
|
|
||||||
EnterEditMode => {
|
|
||||||
state.entered = true;
|
|
||||||
},
|
|
||||||
ExitEditMode => {
|
|
||||||
state.entered = false;
|
|
||||||
},
|
|
||||||
TimeZoomOut => {
|
|
||||||
let scale = state.time_axis.read().unwrap().scale;
|
|
||||||
state.time_axis.write().unwrap().scale = next_note_length(scale)
|
|
||||||
},
|
|
||||||
TimeZoomIn => {
|
|
||||||
let scale = state.time_axis.read().unwrap().scale;
|
|
||||||
state.time_axis.write().unwrap().scale = prev_note_length(scale)
|
|
||||||
},
|
|
||||||
TimeCursorDec => {
|
|
||||||
let scale = state.time_axis.read().unwrap().scale;
|
|
||||||
state.time_axis.write().unwrap().point_dec(scale);
|
|
||||||
},
|
|
||||||
TimeCursorInc => {
|
|
||||||
let scale = state.time_axis.read().unwrap().scale;
|
|
||||||
state.time_axis.write().unwrap().point_inc(scale);
|
|
||||||
},
|
|
||||||
TimeScrollDec => {
|
|
||||||
let scale = state.time_axis.read().unwrap().scale;
|
|
||||||
state.time_axis.write().unwrap().start_dec(scale);
|
|
||||||
},
|
|
||||||
TimeScrollInc => {
|
|
||||||
let scale = state.time_axis.read().unwrap().scale;
|
|
||||||
state.time_axis.write().unwrap().start_inc(scale);
|
|
||||||
},
|
|
||||||
NoteCursorDec => {
|
|
||||||
let mut axis = state.note_axis.write().unwrap();
|
|
||||||
axis.point_inc(1);
|
|
||||||
if let Some(point) = axis.point { if point > 73 { axis.point = Some(73); } }
|
|
||||||
},
|
|
||||||
NoteCursorInc => {
|
|
||||||
let mut axis = state.note_axis.write().unwrap();
|
|
||||||
axis.point_dec(1);
|
|
||||||
if let Some(point) = axis.point { if point < axis.start { axis.start = (point / 2) * 2; } }
|
|
||||||
},
|
|
||||||
NoteScrollDec => {
|
|
||||||
state.note_axis.write().unwrap().start_inc(1);
|
|
||||||
},
|
|
||||||
NoteScrollInc => {
|
|
||||||
state.note_axis.write().unwrap().start_dec(1);
|
|
||||||
},
|
|
||||||
NoteLengthDec => {
|
|
||||||
state.note_len = prev_note_length(state.note_len)
|
|
||||||
},
|
|
||||||
NoteLengthInc => {
|
|
||||||
state.note_len = next_note_length(state.note_len)
|
|
||||||
},
|
|
||||||
NotePageUp => {
|
|
||||||
let mut axis = state.note_axis.write().unwrap();
|
|
||||||
axis.start_dec(3);
|
|
||||||
axis.point_dec(3);
|
|
||||||
},
|
|
||||||
NotePageDown => {
|
|
||||||
let mut axis = state.note_axis.write().unwrap();
|
|
||||||
axis.start_inc(3);
|
|
||||||
axis.point_inc(3);
|
|
||||||
},
|
|
||||||
NoteAppend => {
|
|
||||||
if state.entered {
|
|
||||||
state.put();
|
|
||||||
state.time_cursor_advance();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
NoteSet => {
|
|
||||||
if state.entered { state.put(); }
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -24,9 +24,9 @@ impl Audio for SequencerTui {
|
||||||
impl Audio for ArrangerTui {
|
impl Audio for ArrangerTui {
|
||||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
if TracksAudio(
|
if TracksAudio(
|
||||||
&mut self.app.tracks,
|
&mut self.tracks,
|
||||||
&mut self.app.note_buf,
|
&mut self.note_buf,
|
||||||
&mut self.app.midi_buf,
|
&mut self.midi_buf,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
).process(client, scope) == Control::Quit {
|
).process(client, scope) == Control::Quit {
|
||||||
return Control::Quit
|
return Control::Quit
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,25 @@ use crate::*;
|
||||||
|
|
||||||
/// Stores and displays time-related info.
|
/// Stores and displays time-related info.
|
||||||
pub struct TransportTui {
|
pub struct TransportTui {
|
||||||
jack: Arc<RwLock<JackClient>>,
|
pub(crate) jack: Arc<RwLock<JackClient>>,
|
||||||
/// Playback state
|
/// Playback state
|
||||||
playing: RwLock<Option<TransportState>>,
|
pub(crate) playing: RwLock<Option<TransportState>>,
|
||||||
/// Global sample and usec at which playback started
|
/// Global sample and usec at which playback started
|
||||||
started: RwLock<Option<(usize, usize)>>,
|
pub(crate) started: RwLock<Option<(usize, usize)>>,
|
||||||
/// Current moment in time
|
/// Current moment in time
|
||||||
current: Instant,
|
pub(crate) current: Instant,
|
||||||
/// Note quantization factor
|
/// Note quantization factor
|
||||||
quant: Quantize,
|
pub(crate) quant: Quantize,
|
||||||
/// Launch quantization factor
|
/// Launch quantization factor
|
||||||
sync: LaunchSync,
|
pub(crate) sync: LaunchSync,
|
||||||
/// JACK transport handle.
|
/// JACK transport handle.
|
||||||
transport: jack::Transport,
|
pub(crate) transport: jack::Transport,
|
||||||
/// Enable metronome?
|
/// Enable metronome?
|
||||||
metronome: bool,
|
pub(crate) metronome: bool,
|
||||||
focus: TransportFocus,
|
pub(crate) focus: TransportFocus,
|
||||||
focused: bool,
|
pub(crate) focused: bool,
|
||||||
size: Measure<Tui>,
|
pub(crate) size: Measure<Tui>,
|
||||||
|
pub(crate) cursor: (usize, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for TransportTui {
|
impl std::fmt::Debug for TransportTui {
|
||||||
|
|
@ -33,129 +34,136 @@ impl std::fmt::Debug for TransportTui {
|
||||||
|
|
||||||
/// Root view for standalone `tek_sequencer`.
|
/// Root view for standalone `tek_sequencer`.
|
||||||
pub struct SequencerTui {
|
pub struct SequencerTui {
|
||||||
jack: Arc<RwLock<JackClient>>,
|
pub(crate) jack: Arc<RwLock<JackClient>>,
|
||||||
playing: RwLock<Option<TransportState>>,
|
pub(crate) playing: RwLock<Option<TransportState>>,
|
||||||
started: RwLock<Option<(usize, usize)>>,
|
pub(crate) started: RwLock<Option<(usize, usize)>>,
|
||||||
current: Instant,
|
pub(crate) current: Instant,
|
||||||
quant: Quantize,
|
pub(crate) quant: Quantize,
|
||||||
sync: LaunchSync,
|
pub(crate) sync: LaunchSync,
|
||||||
transport: jack::Transport,
|
pub(crate) transport: jack::Transport,
|
||||||
metronome: bool,
|
pub(crate) metronome: bool,
|
||||||
phrases: Vec<Arc<RwLock<Phrase>>>,
|
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||||
view_phrase: usize,
|
pub(crate) view_phrase: usize,
|
||||||
split: u16,
|
pub(crate) split: u16,
|
||||||
/// Start time and phrase being played
|
/// Start time and phrase being played
|
||||||
play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
pub(crate) play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
/// Start time and next phrase
|
/// Start time and next phrase
|
||||||
next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
pub(crate) next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
/// Play input through output.
|
/// Play input through output.
|
||||||
monitoring: bool,
|
pub(crate) monitoring: bool,
|
||||||
/// Write input to sequence.
|
/// Write input to sequence.
|
||||||
recording: bool,
|
pub(crate) recording: bool,
|
||||||
/// Overdub input to sequence.
|
/// Overdub input to sequence.
|
||||||
overdub: bool,
|
pub(crate) overdub: bool,
|
||||||
/// Send all notes off
|
/// Send all notes off
|
||||||
reset: bool, // TODO?: after Some(nframes)
|
pub(crate) reset: bool, // TODO?: after Some(nframes)
|
||||||
/// Record from MIDI ports to current sequence.
|
/// Record from MIDI ports to current sequence.
|
||||||
midi_inputs: Vec<Port<MidiIn>>,
|
pub(crate) midi_inputs: Vec<Port<MidiIn>>,
|
||||||
/// Play from current sequence to MIDI ports
|
/// Play from current sequence to MIDI ports
|
||||||
midi_outputs: Vec<Port<MidiOut>>,
|
pub(crate) midi_outputs: Vec<Port<MidiOut>>,
|
||||||
/// MIDI output buffer
|
/// MIDI output buffer
|
||||||
midi_note: Vec<u8>,
|
pub(crate) note_buf: Vec<u8>,
|
||||||
/// MIDI output buffer
|
/// MIDI output buffer
|
||||||
midi_chunk: Vec<Vec<Vec<u8>>>,
|
pub(crate) midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
/// Notes currently held at input
|
/// Notes currently held at input
|
||||||
notes_in: Arc<RwLock<[bool; 128]>>,
|
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
||||||
/// Notes currently held at output
|
/// Notes currently held at output
|
||||||
notes_out: Arc<RwLock<[bool; 128]>>,
|
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
|
||||||
|
|
||||||
|
pub(crate) entered: bool,
|
||||||
|
pub(crate) cursor: (usize, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Root view for standalone `tek_arranger`
|
/// Root view for standalone `tek_arranger`
|
||||||
pub struct ArrangerTui {
|
pub struct ArrangerTui {
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
pub(crate) jack: Arc<RwLock<JackClient>>,
|
||||||
pub transport: jack::Transport,
|
pub(crate) transport: jack::Transport,
|
||||||
pub playing: RwLock<Option<TransportState>>,
|
pub(crate) playing: RwLock<Option<TransportState>>,
|
||||||
pub started: RwLock<Option<(usize, usize)>>,
|
pub(crate) started: RwLock<Option<(usize, usize)>>,
|
||||||
pub current: Instant,
|
pub(crate) current: Instant,
|
||||||
pub quant: Quantize,
|
pub(crate) quant: Quantize,
|
||||||
pub sync: LaunchSync,
|
pub(crate) sync: LaunchSync,
|
||||||
pub metronome: bool,
|
pub(crate) metronome: bool,
|
||||||
pub phrases: Vec<Arc<RwLock<Phrase>>>,
|
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||||
pub phrase: usize,
|
pub(crate) phrase: usize,
|
||||||
pub tracks: Vec<ArrangerTrack>,
|
pub(crate) tracks: Vec<ArrangerTrack>,
|
||||||
pub scenes: Vec<ArrangerScene>,
|
pub(crate) scenes: Vec<ArrangerScene>,
|
||||||
pub name: Arc<RwLock<String>>,
|
pub(crate) name: Arc<RwLock<String>>,
|
||||||
pub splits: [u16;2],
|
pub(crate) splits: [u16;2],
|
||||||
pub selected: ArrangerSelection,
|
pub(crate) selected: ArrangerSelection,
|
||||||
pub mode: ArrangerMode,
|
pub(crate) mode: ArrangerMode,
|
||||||
pub color: ItemColor,
|
pub(crate) color: ItemColor,
|
||||||
pub entered: bool,
|
pub(crate) entered: bool,
|
||||||
pub size: Measure<Tui>,
|
pub(crate) size: Measure<Tui>,
|
||||||
pub note_buf: Vec<u8>,
|
pub(crate) note_buf: Vec<u8>,
|
||||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
pub(crate) midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
pub cursor: (usize, usize),
|
pub(crate) cursor: (usize, usize),
|
||||||
pub menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>,
|
pub(crate) menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>,
|
||||||
pub status_bar: Option<ArrangerStatus>,
|
pub(crate) status_bar: Option<ArrangerStatus>,
|
||||||
pub history: Vec<ArrangerCommand>,
|
pub(crate) history: Vec<ArrangerCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct ArrangerScene {
|
pub struct ArrangerScene {
|
||||||
/// Name of scene
|
/// Name of scene
|
||||||
pub name: Arc<RwLock<String>>,
|
pub(crate) name: Arc<RwLock<String>>,
|
||||||
/// Clips in scene, one per track
|
/// Clips in scene, one per track
|
||||||
pub clips: Vec<Option<Arc<RwLock<Phrase>>>>,
|
pub(crate) clips: Vec<Option<Arc<RwLock<Phrase>>>>,
|
||||||
/// Identifying color of scene
|
/// Identifying color of scene
|
||||||
pub color: ItemColor,
|
pub(crate) color: ItemColor,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ArrangerTrack {
|
pub struct ArrangerTrack {
|
||||||
/// Name of track
|
/// Name of track
|
||||||
name: Arc<RwLock<String>>,
|
pub(crate) name: Arc<RwLock<String>>,
|
||||||
/// Preferred width of track column
|
/// Preferred width of track column
|
||||||
width: usize,
|
pub(crate) width: usize,
|
||||||
/// Identifying color of track
|
/// Identifying color of track
|
||||||
color: ItemColor,
|
pub(crate) color: ItemColor,
|
||||||
/// Start time and phrase being played
|
/// Start time and phrase being played
|
||||||
play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
pub(crate) play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
/// Start time and next phrase
|
/// Start time and next phrase
|
||||||
next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
pub(crate) next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
/// Play input through output.
|
/// Play input through output.
|
||||||
monitoring: bool,
|
pub(crate) monitoring: bool,
|
||||||
/// Write input to sequence.
|
/// Write input to sequence.
|
||||||
recording: bool,
|
pub(crate) recording: bool,
|
||||||
/// Overdub input to sequence.
|
/// Overdub input to sequence.
|
||||||
overdub: bool,
|
pub(crate) overdub: bool,
|
||||||
/// Send all notes off
|
/// Send all notes off
|
||||||
reset: bool, // TODO?: after Some(nframes)
|
pub(crate) reset: bool, // TODO?: after Some(nframes)
|
||||||
/// Record from MIDI ports to current sequence.
|
/// Record from MIDI ports to current sequence.
|
||||||
midi_ins: Vec<Port<MidiIn>>,
|
pub(crate) midi_ins: Vec<Port<MidiIn>>,
|
||||||
/// Play from current sequence to MIDI ports
|
/// Play from current sequence to MIDI ports
|
||||||
midi_outs: Vec<Port<MidiOut>>,
|
pub(crate) midi_outs: Vec<Port<MidiOut>>,
|
||||||
/// Notes currently held at input
|
/// Notes currently held at input
|
||||||
notes_in: Arc<RwLock<[bool; 128]>>,
|
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
||||||
/// Notes currently held at output
|
/// Notes currently held at output
|
||||||
notes_out: Arc<RwLock<[bool; 128]>>,
|
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
|
||||||
///// MIDI output buffer
|
///// MIDI output buffer
|
||||||
//midi_note: Vec<u8>,
|
//midi_note: Vec<u8>,
|
||||||
///// MIDI output buffer
|
///// MIDI output buffer
|
||||||
//midi_chunk: Vec<Vec<Vec<u8>>>,
|
//midi_chunk: Vec<Vec<Vec<u8>>>,
|
||||||
|
/// Whether this widget is focused
|
||||||
|
pub(crate) focused: bool,
|
||||||
|
/// Width and height of notes area at last render
|
||||||
|
pub(crate) size: Measure<Tui>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PhrasesTui {
|
pub struct PhrasesTui {
|
||||||
/// Collection of phrases
|
/// Collection of phrases
|
||||||
pub phrases: Vec<Arc<RwLock<Phrase>>>,
|
pub(crate) phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||||
/// Selected phrase
|
/// Selected phrase
|
||||||
pub phrase: usize,
|
pub(crate) phrase: usize,
|
||||||
/// Scroll offset
|
/// Scroll offset
|
||||||
pub scroll: usize,
|
pub(crate) scroll: usize,
|
||||||
/// Mode switch
|
/// Mode switch
|
||||||
pub mode: Option<PhrasesMode>,
|
pub(crate) mode: Option<PhrasesMode>,
|
||||||
/// Whether this widget is focused
|
/// Whether this widget is focused
|
||||||
pub focused: bool,
|
pub(crate) focused: bool,
|
||||||
/// Whether this widget is entered
|
/// Whether this widget is entered
|
||||||
pub entered: bool,
|
pub(crate) entered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modes for phrase pool
|
/// Modes for phrase pool
|
||||||
|
|
@ -169,29 +177,29 @@ pub enum PhrasesMode {
|
||||||
/// Contains state for viewing and editing a phrase
|
/// Contains state for viewing and editing a phrase
|
||||||
pub struct PhraseTui {
|
pub struct PhraseTui {
|
||||||
/// Phrase being played
|
/// Phrase being played
|
||||||
pub phrase: Option<Arc<RwLock<Phrase>>>,
|
pub(crate) phrase: Option<Arc<RwLock<Phrase>>>,
|
||||||
/// Length of note that will be inserted, in pulses
|
/// Length of note that will be inserted, in pulses
|
||||||
pub note_len: usize,
|
pub(crate) note_len: usize,
|
||||||
/// The full piano keys are rendered to this buffer
|
/// The full piano keys are rendered to this buffer
|
||||||
pub keys: Buffer,
|
pub(crate) keys: Buffer,
|
||||||
/// The full piano roll is rendered to this buffer
|
/// The full piano roll is rendered to this buffer
|
||||||
pub buffer: BigBuffer,
|
pub(crate) buffer: BigBuffer,
|
||||||
/// Cursor/scroll/zoom in pitch axis
|
/// Cursor/scroll/zoom in pitch axis
|
||||||
pub note_axis: RwLock<FixedAxis<usize>>,
|
pub(crate) note_axis: RwLock<FixedAxis<usize>>,
|
||||||
/// Cursor/scroll/zoom in time axis
|
/// Cursor/scroll/zoom in time axis
|
||||||
pub time_axis: RwLock<ScaledAxis<usize>>,
|
pub(crate) time_axis: RwLock<ScaledAxis<usize>>,
|
||||||
/// Whether this widget is focused
|
/// Whether this widget is focused
|
||||||
pub focused: bool,
|
pub(crate) focused: bool,
|
||||||
/// Whether note enter mode is enabled
|
/// Whether note enter mode is enabled
|
||||||
pub entered: bool,
|
pub(crate) entered: bool,
|
||||||
/// Display mode
|
/// Display mode
|
||||||
pub mode: bool,
|
pub(crate) mode: bool,
|
||||||
/// Notes currently held at input
|
/// Notes currently held at input
|
||||||
pub notes_in: Arc<RwLock<[bool; 128]>>,
|
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
||||||
/// Notes currently held at output
|
/// Notes currently held at output
|
||||||
pub notes_out: Arc<RwLock<[bool; 128]>>,
|
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
|
||||||
/// Current position of global playhead
|
/// Current position of global playhead
|
||||||
pub now: Arc<Pulse>,
|
pub(crate) now: Arc<Pulse>,
|
||||||
/// Width and height of notes area at last render
|
/// Width and height of notes area at last render
|
||||||
pub size: Measure<Tui>
|
pub(crate) size: Measure<Tui>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl PhraseTui {
|
|
||||||
pub fn new () -> Self {
|
|
||||||
Self {
|
|
||||||
phrase: None,
|
|
||||||
note_len: 24,
|
|
||||||
notes_in: Arc::new(RwLock::new([false;128])),
|
|
||||||
notes_out: Arc::new(RwLock::new([false;128])),
|
|
||||||
keys: keys_vert(),
|
|
||||||
buffer: Default::default(),
|
|
||||||
focused: false,
|
|
||||||
entered: false,
|
|
||||||
mode: false,
|
|
||||||
now: Arc::new(0.into()),
|
|
||||||
width: 0.into(),
|
|
||||||
height: 0.into(),
|
|
||||||
note_axis: RwLock::new(FixedAxis {
|
|
||||||
start: 12,
|
|
||||||
point: Some(36),
|
|
||||||
clamp: Some(127)
|
|
||||||
}),
|
|
||||||
time_axis: RwLock::new(ScaledAxis {
|
|
||||||
start: 00,
|
|
||||||
point: Some(00),
|
|
||||||
clamp: Some(000),
|
|
||||||
scale: 24
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn time_cursor_advance (&self) {
|
|
||||||
let point = self.time_axis.read().unwrap().point;
|
|
||||||
let length = self.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
|
||||||
let forward = |time|(time + self.note_len) % length;
|
|
||||||
self.time_axis.write().unwrap().point = point.map(forward);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn put (&mut self) {
|
|
||||||
if let (Some(phrase), Some(time), Some(note)) = (
|
|
||||||
&self.phrase,
|
|
||||||
self.time_axis.read().unwrap().point,
|
|
||||||
self.note_axis.read().unwrap().point,
|
|
||||||
) {
|
|
||||||
let mut phrase = phrase.write().unwrap();
|
|
||||||
let key: u7 = u7::from((127 - note) as u8);
|
|
||||||
let vel: u7 = 100.into();
|
|
||||||
let start = time;
|
|
||||||
let end = (start + self.note_len) % phrase.length;
|
|
||||||
phrase.notes[time].push(MidiMessage::NoteOn { key, vel });
|
|
||||||
phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
|
|
||||||
self.buffer = Self::redraw(&phrase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
|
|
||||||
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
|
||||||
if let Some(phrase) = phrase {
|
|
||||||
self.phrase = Some(phrase.clone());
|
|
||||||
self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length);
|
|
||||||
self.buffer = Self::redraw(&*phrase.read().unwrap());
|
|
||||||
} else {
|
|
||||||
self.phrase = None;
|
|
||||||
self.time_axis.write().unwrap().clamp = Some(0);
|
|
||||||
self.buffer = Default::default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn redraw (phrase: &Phrase) -> BigBuffer {
|
|
||||||
let mut buf = BigBuffer::new(usize::MAX.min(phrase.length), 65);
|
|
||||||
Self::fill_seq_bg(&mut buf, phrase.length, phrase.ppq);
|
|
||||||
Self::fill_seq_fg(&mut buf, &phrase);
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) {
|
|
||||||
for x in 0..buf.width {
|
|
||||||
// Only fill as far as phrase length
|
|
||||||
if x as usize >= length { break }
|
|
||||||
// Fill each row with background characters
|
|
||||||
for y in 0 .. buf.height {
|
|
||||||
buf.get_mut(x, y).map(|cell|{
|
|
||||||
cell.set_char(if ppq == 0 {
|
|
||||||
'·'
|
|
||||||
} else if x % (4 * ppq) == 0 {
|
|
||||||
'│'
|
|
||||||
} else if x % ppq == 0 {
|
|
||||||
'╎'
|
|
||||||
} else {
|
|
||||||
'·'
|
|
||||||
});
|
|
||||||
cell.set_fg(Color::Rgb(48, 64, 56));
|
|
||||||
cell.modifier = Modifier::DIM;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) {
|
|
||||||
let mut notes_on = [false;128];
|
|
||||||
for x in 0..buf.width {
|
|
||||||
if x as usize >= phrase.length {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if let Some(notes) = phrase.notes.get(x as usize) {
|
|
||||||
if phrase.percussive {
|
|
||||||
for note in notes {
|
|
||||||
match note {
|
|
||||||
MidiMessage::NoteOn { key, .. } =>
|
|
||||||
notes_on[key.as_int() as usize] = true,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for note in notes {
|
|
||||||
match note {
|
|
||||||
MidiMessage::NoteOn { key, .. } =>
|
|
||||||
notes_on[key.as_int() as usize] = true,
|
|
||||||
MidiMessage::NoteOff { key, .. } =>
|
|
||||||
notes_on[key.as_int() as usize] = false,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for y in 0..buf.height {
|
|
||||||
if y >= 64 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if let Some(block) = half_block(
|
|
||||||
notes_on[y as usize * 2],
|
|
||||||
notes_on[y as usize * 2 + 1],
|
|
||||||
) {
|
|
||||||
buf.get_mut(x, y).map(|cell|{
|
|
||||||
cell.set_char(block);
|
|
||||||
cell.set_fg(Color::White);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if phrase.percussive {
|
|
||||||
notes_on.fill(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
@ -94,6 +94,7 @@ impl StatusBar for ArrangerStatus {
|
||||||
}
|
}
|
||||||
fn update (&mut self, (focused, selected, entered): &Self::State) {
|
fn update (&mut self, (focused, selected, entered): &Self::State) {
|
||||||
*self = match focused {
|
*self = match focused {
|
||||||
|
ArrangerFocus::Menu => { todo!() },
|
||||||
ArrangerFocus::Transport => ArrangerStatus::Transport,
|
ArrangerFocus::Transport => ArrangerStatus::Transport,
|
||||||
ArrangerFocus::Arranger => match selected {
|
ArrangerFocus::Arranger => match selected {
|
||||||
ArrangerSelection::Mix => ArrangerStatus::ArrangerMix,
|
ArrangerSelection::Mix => ArrangerStatus::ArrangerMix,
|
||||||
|
|
|
||||||
|
|
@ -808,10 +808,10 @@ pub trait PhraseViewState: Send + Sync {
|
||||||
|
|
||||||
impl PhraseViewState for PhraseTui {
|
impl PhraseViewState for PhraseTui {
|
||||||
fn focused (&self) -> bool {
|
fn focused (&self) -> bool {
|
||||||
&self.focused
|
self.focused
|
||||||
}
|
}
|
||||||
fn entered (&self) -> bool {
|
fn entered (&self) -> bool {
|
||||||
&self.entered
|
self.entered
|
||||||
}
|
}
|
||||||
fn keys (&self) -> &Buffer {
|
fn keys (&self) -> &Buffer {
|
||||||
&self.keys
|
&self.keys
|
||||||
|
|
@ -823,7 +823,7 @@ impl PhraseViewState for PhraseTui {
|
||||||
&self.buffer
|
&self.buffer
|
||||||
}
|
}
|
||||||
fn note_len (&self) -> usize {
|
fn note_len (&self) -> usize {
|
||||||
&self.note_len
|
self.note_len
|
||||||
}
|
}
|
||||||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>> {
|
fn note_axis (&self) -> &RwLock<FixedAxis<usize>> {
|
||||||
&self.note_axis
|
&self.note_axis
|
||||||
|
|
@ -1066,7 +1066,7 @@ pub struct PhraseLength {
|
||||||
|
|
||||||
impl PhraseLength {
|
impl PhraseLength {
|
||||||
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
|
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
|
||||||
Self { _engine: Default::default(), ppq: PPQ, bpb: 4, pulses, focus }
|
Self { ppq: PPQ, bpb: 4, pulses, focus }
|
||||||
}
|
}
|
||||||
pub fn bars (&self) -> usize {
|
pub fn bars (&self) -> usize {
|
||||||
self.pulses / (self.bpb * self.ppq)
|
self.pulses / (self.bpb * self.ppq)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue