wip: p.53, e=118, fixed focus trait loop

This commit is contained in:
🪞👃🪞 2024-11-17 19:19:46 +01:00
parent 9b996878c2
commit 76af9d9bac
15 changed files with 737 additions and 727 deletions

View file

@ -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>;
}

View file

@ -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)
)
) )
} )
} }

View file

@ -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);
}
}
}
}
}

View file

@ -1 +0,0 @@
use crate::*;

View 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)
}
}

View 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
}
}

View file

@ -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
)) ))
} }

View file

@ -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))
} }
} }

View file

@ -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)
}
}

View file

@ -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

View file

@ -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>
} }

View file

@ -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);
}
}
}
}
}

View file

@ -1 +0,0 @@
use crate::*;

View file

@ -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,

View file

@ -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)