mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 20:26:42 +01:00
wip: p.51, e=7, collecting tui by layer
This commit is contained in:
parent
b3ac9e60e3
commit
7b3c013aa7
26 changed files with 786 additions and 799 deletions
|
|
@ -21,17 +21,16 @@ pub trait Coordinate: Send + Sync + Copy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Coordinate for T where
|
impl<T> Coordinate for T where T: Send + Sync + Copy
|
||||||
T: Send + Sync + Copy
|
+ Add<Self, Output=Self>
|
||||||
+ Add<Self, Output=Self>
|
+ Sub<Self, Output=Self>
|
||||||
+ Sub<Self, Output=Self>
|
+ Mul<Self, Output=Self>
|
||||||
+ Mul<Self, Output=Self>
|
+ Div<Self, Output=Self>
|
||||||
+ Div<Self, Output=Self>
|
+ Ord + PartialEq + Eq
|
||||||
+ Ord + PartialEq + Eq
|
+ Debug + Display + Default
|
||||||
+ Debug + Display + Default
|
+ From<u16> + Into<u16>
|
||||||
+ From<u16> + Into<u16>
|
+ Into<usize>
|
||||||
+ Into<usize>
|
+ Into<f64>
|
||||||
+ Into<f64>
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
pub struct FixedAxis<T> {
|
pub struct FixedAxis<T> {
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,18 @@ pub(crate) use std::fs::read_dir;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
|
|
||||||
|
tui_model
|
||||||
|
tui_jack
|
||||||
|
tui_handle
|
||||||
|
tui_focus
|
||||||
|
tui_status
|
||||||
|
tui_menu
|
||||||
|
|
||||||
tui_arranger
|
tui_arranger
|
||||||
tui_arranger_cmd
|
tui_arranger_cmd
|
||||||
tui_arranger_focus
|
|
||||||
tui_arranger_jack
|
|
||||||
tui_arranger_scene
|
tui_arranger_scene
|
||||||
tui_arranger_select
|
tui_arranger_select
|
||||||
tui_arranger_status
|
|
||||||
tui_arranger_track
|
tui_arranger_track
|
||||||
tui_arranger_view
|
tui_arranger_view
|
||||||
|
|
||||||
|
|
@ -38,17 +43,11 @@ submod! {
|
||||||
//tui_sampler_cmd
|
//tui_sampler_cmd
|
||||||
tui_sequencer
|
tui_sequencer
|
||||||
tui_sequencer_cmd
|
tui_sequencer_cmd
|
||||||
tui_sequencer_jack
|
|
||||||
tui_sequencer_view
|
tui_sequencer_view
|
||||||
tui_sequencer_focus
|
|
||||||
tui_sequencer_status
|
|
||||||
|
|
||||||
tui_status
|
|
||||||
tui_theme
|
tui_theme
|
||||||
tui_transport
|
tui_transport
|
||||||
tui_transport_cmd
|
tui_transport_cmd
|
||||||
tui_transport_focus
|
|
||||||
tui_transport_jack
|
|
||||||
tui_transport_view
|
tui_transport_view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Root view for standalone `tek_arranger`
|
|
||||||
pub struct ArrangerTui {
|
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
|
||||||
pub transport: jack::Transport,
|
|
||||||
pub playing: RwLock<Option<TransportState>>,
|
|
||||||
pub started: RwLock<Option<(usize, usize)>>,
|
|
||||||
pub current: Instant,
|
|
||||||
pub quant: Quantize,
|
|
||||||
pub sync: LaunchSync,
|
|
||||||
pub metronome: bool,
|
|
||||||
pub phrases: Vec<Arc<RwLock<Phrase>>>,
|
|
||||||
pub phrase: usize,
|
|
||||||
pub tracks: Vec<ArrangerTrack>,
|
|
||||||
pub scenes: Vec<ArrangerScene>,
|
|
||||||
pub name: Arc<RwLock<String>>,
|
|
||||||
pub splits: [u16;2],
|
|
||||||
pub selected: ArrangerSelection,
|
|
||||||
pub mode: ArrangerMode,
|
|
||||||
pub color: ItemColor,
|
|
||||||
pub entered: bool,
|
|
||||||
pub size: Measure<Tui>,
|
|
||||||
pub note_buf: Vec<u8>,
|
|
||||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
|
||||||
pub cursor: (usize, usize),
|
|
||||||
pub menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>,
|
|
||||||
pub status_bar: Option<ArrangerStatus>,
|
|
||||||
pub history: Vec<ArrangerCommand>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
|
||||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
name: Arc::new(RwLock::new(String::new())),
|
|
||||||
phrases: vec![],
|
|
||||||
phrase: 0,
|
|
||||||
scenes: vec![],
|
|
||||||
tracks: vec![],
|
|
||||||
metronome: false,
|
|
||||||
playing: None.into(),
|
|
||||||
started: None.into(),
|
|
||||||
transport: jack.read().unwrap().transport(),
|
|
||||||
current: Instant::default(),
|
|
||||||
jack: jack.clone(),
|
|
||||||
selected: ArrangerSelection::Clip(0, 0),
|
|
||||||
mode: ArrangerMode::Vertical(2),
|
|
||||||
color: Color::Rgb(28, 35, 25).into(),
|
|
||||||
size: Measure::new(),
|
|
||||||
entered: false,
|
|
||||||
quant: Default::default(),
|
|
||||||
sync: Default::default(),
|
|
||||||
splits: [20, 20],
|
|
||||||
note_buf: vec![],
|
|
||||||
midi_buf: vec![],
|
|
||||||
cursor: (0, 0),
|
|
||||||
entered: false,
|
|
||||||
history: vec![],
|
|
||||||
size: Measure::new(),
|
|
||||||
menu_bar: None,
|
|
||||||
status_bar: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPhrases for ArrangerTui {
|
impl HasPhrases for ArrangerTui {
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
&self.phrases
|
&self.phrases
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Handle top-level events in standalone arranger.
|
|
||||||
impl Handle<Tui> for ArrangerTui {
|
|
||||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
|
||||||
ArrangerCommand::execute_with_state(self, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ArrangerCommand {
|
pub enum ArrangerCommand {
|
||||||
Focus(FocusCommand),
|
Focus(FocusCommand),
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Sections in the arranger app that may be focused
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub enum ArrangerFocus {
|
|
||||||
/// The menu bar is focused
|
|
||||||
Menu,
|
|
||||||
/// The transport (toolbar) is focused
|
|
||||||
Transport,
|
|
||||||
/// The arrangement (grid) is focused
|
|
||||||
Arranger,
|
|
||||||
/// The phrase list (pool) is focused
|
|
||||||
PhrasePool,
|
|
||||||
/// The phrase editor (sequencer) is focused
|
|
||||||
PhraseEditor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FocusEnter for ArrangerTui {
|
|
||||||
type Item = ArrangerFocus;
|
|
||||||
fn focus_enter (&mut self) {
|
|
||||||
use ArrangerFocus::*;
|
|
||||||
let focused = self.focused();
|
|
||||||
if !self.entered {
|
|
||||||
self.entered = focused == Arranger;
|
|
||||||
self.app.editor.entered = focused == PhraseEditor;
|
|
||||||
self.app.phrases.entered = focused == PhrasePool;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn focus_exit (&mut self) {
|
|
||||||
if self.entered {
|
|
||||||
self.entered = false;
|
|
||||||
self.app.editor.entered = false;
|
|
||||||
self.app.phrases.entered = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn focus_entered (&self) -> Option<Self::Item> {
|
|
||||||
if self.entered {
|
|
||||||
Some(self.focused())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Focus layout of arranger app
|
|
||||||
impl FocusGrid for ArrangerTui {
|
|
||||||
type Item = ArrangerFocus;
|
|
||||||
fn focus_cursor (&self) -> (usize, usize) {
|
|
||||||
self.cursor
|
|
||||||
}
|
|
||||||
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
|
||||||
&mut self.cursor
|
|
||||||
}
|
|
||||||
fn focus_layout (&self) -> &[&[Self::Item]] {
|
|
||||||
use ArrangerFocus::*;
|
|
||||||
&[
|
|
||||||
&[Menu, Menu ],
|
|
||||||
&[Transport, Transport ],
|
|
||||||
&[Arranger, Arranger ],
|
|
||||||
&[PhrasePool, PhraseEditor],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
fn focus_update (&mut self) {
|
|
||||||
use ArrangerFocus::*;
|
|
||||||
let focused = self.focused();
|
|
||||||
if let Some(mut status_bar) = self.status_bar {
|
|
||||||
status_bar.update(&(
|
|
||||||
self.focused(),
|
|
||||||
self.app.selected,
|
|
||||||
focused == PhraseEditor && self.entered
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl JackApi for ArrangerTui {
|
|
||||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
|
||||||
&self.jack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Audio for ArrangerTui {
|
|
||||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
if TracksAudio(
|
|
||||||
&mut self.app.tracks,
|
|
||||||
&mut self.app.note_buf,
|
|
||||||
&mut self.app.midi_buf,
|
|
||||||
Default::default(),
|
|
||||||
).process(client, scope) == Control::Quit {
|
|
||||||
return Control::Quit
|
|
||||||
}
|
|
||||||
// FIXME: one of these per playing track
|
|
||||||
if let ArrangerSelection::Clip(t, s) = self.selected {
|
|
||||||
let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t));
|
|
||||||
if let Some(Some(Some(phrase))) = phrase {
|
|
||||||
if let Some(track) = self.tracks().get(t) {
|
|
||||||
if let Some((ref started_at, Some(ref playing))) = track.player.phrase {
|
|
||||||
let phrase = phrase.read().unwrap();
|
|
||||||
if *playing.read().unwrap() == *phrase {
|
|
||||||
let pulse = self.current().pulse.get();
|
|
||||||
let start = started_at.pulse.get();
|
|
||||||
let now = (pulse - start) % phrase.length as f64;
|
|
||||||
self.editor.now.set(now);
|
|
||||||
return Control::Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.editor.now.set(0.);
|
|
||||||
return Control::Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClockApi for ArrangerTui {
|
|
||||||
fn timebase (&self) -> &Arc<Timebase> {
|
|
||||||
&self.current.timebase
|
|
||||||
}
|
|
||||||
fn quant (&self) -> &Quantize {
|
|
||||||
&self.quant
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &LaunchSync {
|
|
||||||
&self.sync
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayheadApi for ArrangerTui {
|
|
||||||
fn current (&self) -> &Instant {
|
|
||||||
&self.current
|
|
||||||
}
|
|
||||||
fn transport (&self) -> &jack::Transport {
|
|
||||||
&self.transport
|
|
||||||
}
|
|
||||||
fn playing (&self) -> &RwLock<Option<TransportState>> {
|
|
||||||
&self.playing
|
|
||||||
}
|
|
||||||
fn started (&self) -> &RwLock<Option<(usize, usize)>> {
|
|
||||||
&self.started
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Status bar for arranger app
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum ArrangerStatus {
|
|
||||||
Transport,
|
|
||||||
ArrangerMix,
|
|
||||||
ArrangerTrack,
|
|
||||||
ArrangerScene,
|
|
||||||
ArrangerClip,
|
|
||||||
PhrasePool,
|
|
||||||
PhraseView,
|
|
||||||
PhraseEdit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatusBar for ArrangerStatus {
|
|
||||||
type State = (ArrangerFocus, ArrangerSelection, bool);
|
|
||||||
fn hotkey_fg () -> Color where Self: Sized {
|
|
||||||
TuiTheme::hotkey_fg()
|
|
||||||
}
|
|
||||||
fn update (&mut self, (focused, selected, entered): &Self::State) {
|
|
||||||
*self = match focused {
|
|
||||||
ArrangerFocus::Transport => ArrangerStatus::Transport,
|
|
||||||
ArrangerFocus::Arranger => match selected {
|
|
||||||
ArrangerSelection::Mix => ArrangerStatus::ArrangerMix,
|
|
||||||
ArrangerSelection::Track(_) => ArrangerStatus::ArrangerTrack,
|
|
||||||
ArrangerSelection::Scene(_) => ArrangerStatus::ArrangerScene,
|
|
||||||
ArrangerSelection::Clip(_, _) => ArrangerStatus::ArrangerClip,
|
|
||||||
},
|
|
||||||
ArrangerFocus::PhrasePool => ArrangerStatus::PhrasePool,
|
|
||||||
ArrangerFocus::PhraseEditor => match entered {
|
|
||||||
true => ArrangerStatus::PhraseEdit,
|
|
||||||
false => ArrangerStatus::PhraseView,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Content for ArrangerStatus {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
|
||||||
let label = match self {
|
|
||||||
Self::Transport => "TRANSPORT",
|
|
||||||
Self::ArrangerMix => "PROJECT",
|
|
||||||
Self::ArrangerTrack => "TRACK",
|
|
||||||
Self::ArrangerScene => "SCENE",
|
|
||||||
Self::ArrangerClip => "CLIP",
|
|
||||||
Self::PhrasePool => "SEQ LIST",
|
|
||||||
Self::PhraseView => "VIEW SEQ",
|
|
||||||
Self::PhraseEdit => "EDIT SEQ",
|
|
||||||
};
|
|
||||||
let status_bar_bg = TuiTheme::status_bar_bg();
|
|
||||||
let mode_bg = TuiTheme::mode_bg();
|
|
||||||
let mode_fg = TuiTheme::mode_fg();
|
|
||||||
let mode = TuiStyle::bold(format!(" {label} "), true).bg(mode_bg).fg(mode_fg);
|
|
||||||
let commands = match self {
|
|
||||||
Self::ArrangerMix => Self::command(&[
|
|
||||||
["", "c", "olor"],
|
|
||||||
["", "<>", "resize"],
|
|
||||||
["", "+-", "zoom"],
|
|
||||||
["", "n", "ame/number"],
|
|
||||||
["", "Enter", " stop all"],
|
|
||||||
]),
|
|
||||||
Self::ArrangerClip => Self::command(&[
|
|
||||||
["", "g", "et"],
|
|
||||||
["", "s", "et"],
|
|
||||||
["", "a", "dd"],
|
|
||||||
["", "i", "ns"],
|
|
||||||
["", "d", "up"],
|
|
||||||
["", "e", "dit"],
|
|
||||||
["", "c", "olor"],
|
|
||||||
["re", "n", "ame"],
|
|
||||||
["", ",.", "select"],
|
|
||||||
["", "Enter", " launch"],
|
|
||||||
]),
|
|
||||||
Self::ArrangerTrack => Self::command(&[
|
|
||||||
["re", "n", "ame"],
|
|
||||||
["", ",.", "resize"],
|
|
||||||
["", "<>", "move"],
|
|
||||||
["", "i", "nput"],
|
|
||||||
["", "o", "utput"],
|
|
||||||
["", "m", "ute"],
|
|
||||||
["", "s", "olo"],
|
|
||||||
["", "Del", "ete"],
|
|
||||||
["", "Enter", " stop"],
|
|
||||||
]),
|
|
||||||
Self::ArrangerScene => Self::command(&[
|
|
||||||
["re", "n", "ame"],
|
|
||||||
["", "Del", "ete"],
|
|
||||||
["", "Enter", " launch"],
|
|
||||||
]),
|
|
||||||
Self::PhrasePool => Self::command(&[
|
|
||||||
["", "a", "ppend"],
|
|
||||||
["", "i", "nsert"],
|
|
||||||
["", "d", "uplicate"],
|
|
||||||
["", "Del", "ete"],
|
|
||||||
["", "c", "olor"],
|
|
||||||
["re", "n", "ame"],
|
|
||||||
["leng", "t", "h"],
|
|
||||||
["", ",.", "move"],
|
|
||||||
["", "+-", "resize view"],
|
|
||||||
]),
|
|
||||||
Self::PhraseView => Self::command(&[
|
|
||||||
["", "enter", " edit"],
|
|
||||||
["", "arrows/pgup/pgdn", " scroll"],
|
|
||||||
["", "+=", "zoom"],
|
|
||||||
]),
|
|
||||||
Self::PhraseEdit => Self::command(&[
|
|
||||||
["", "esc", " exit"],
|
|
||||||
["", "a", "ppend"],
|
|
||||||
["", "s", "et"],
|
|
||||||
["", "][", "length"],
|
|
||||||
["", "+-", "zoom"],
|
|
||||||
]),
|
|
||||||
_ => Self::command(&[])
|
|
||||||
};
|
|
||||||
//let commands = commands.iter().reduce(String::new(), |s, (a, b, c)| format!("{s} {a}{b}{c}"));
|
|
||||||
row!(mode, commands).fill_x().bg(status_bar_bg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
215
crates/tek_tui/src/tui_focus.rs
Normal file
215
crates/tek_tui/src/tui_focus.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Which item of the transport toolbar is focused
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum TransportFocus {
|
||||||
|
Menu,
|
||||||
|
Bpm,
|
||||||
|
Sync,
|
||||||
|
PlayPause,
|
||||||
|
Clock,
|
||||||
|
Quant,
|
||||||
|
}
|
||||||
|
/// Sections in the sequencer app that may be focused
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum SequencerFocus {
|
||||||
|
/// The menu bar is focused
|
||||||
|
Menu,
|
||||||
|
/// The transport (toolbar) is focused
|
||||||
|
Transport,
|
||||||
|
/// The phrase list (pool) is focused
|
||||||
|
PhrasePool,
|
||||||
|
/// The phrase editor (sequencer) is focused
|
||||||
|
PhraseEditor,
|
||||||
|
}
|
||||||
|
/// Sections in the arranger app that may be focused
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum ArrangerFocus {
|
||||||
|
/// The menu bar is focused
|
||||||
|
Menu,
|
||||||
|
/// The transport (toolbar) is focused
|
||||||
|
Transport,
|
||||||
|
/// The arrangement (grid) is focused
|
||||||
|
Arranger,
|
||||||
|
/// The phrase list (pool) is focused
|
||||||
|
PhrasePool,
|
||||||
|
/// The phrase editor (sequencer) is focused
|
||||||
|
PhraseEditor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransportFocus {
|
||||||
|
pub fn next (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::PlayPause => Self::Bpm,
|
||||||
|
Self::Bpm => Self::Quant,
|
||||||
|
Self::Quant => Self::Sync,
|
||||||
|
Self::Sync => Self::Clock,
|
||||||
|
Self::Clock => Self::PlayPause,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn prev (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::PlayPause => Self::Clock,
|
||||||
|
Self::Bpm => Self::PlayPause,
|
||||||
|
Self::Quant => Self::Bpm,
|
||||||
|
Self::Sync => Self::Quant,
|
||||||
|
Self::Clock => Self::Sync,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn wrap <'a, W: Widget<Engine = Tui>> (
|
||||||
|
self, parent_focus: bool, focus: Self, widget: &'a W
|
||||||
|
) -> impl Widget<Engine = Tui> + 'a {
|
||||||
|
let focused = parent_focus && focus == self;
|
||||||
|
let corners = focused.then_some(CORNERS);
|
||||||
|
let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50)));
|
||||||
|
lay!(corners, highlight, *widget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasFocus for TransportTui {
|
||||||
|
type Item = TransportFocus;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusEnter for TransportTui {
|
||||||
|
type Item = TransportFocus;
|
||||||
|
fn focus_enter (&mut self) {
|
||||||
|
self.entered = true;
|
||||||
|
}
|
||||||
|
fn focus_exit (&mut self) {
|
||||||
|
self.entered = false;
|
||||||
|
}
|
||||||
|
fn focus_entered (&self) -> Option<Self::Item> {
|
||||||
|
if self.entered {
|
||||||
|
Some(self.focused())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusGrid for TransportTui {
|
||||||
|
type Item = TransportFocus;
|
||||||
|
fn focus_cursor (&self) -> (usize, usize) {
|
||||||
|
self.cursor
|
||||||
|
}
|
||||||
|
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
||||||
|
&mut self.cursor
|
||||||
|
}
|
||||||
|
fn focus_layout (&self) -> &[&[Self::Item]] {
|
||||||
|
use TransportFocus::*;
|
||||||
|
&[
|
||||||
|
&[Menu],
|
||||||
|
&[Bpm, Sync, PlayPause, Clock, Quant],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
fn focus_update (&mut self) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasFocus for SequencerTui {
|
||||||
|
type Item = SequencerFocus;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusEnter for SequencerTui {
|
||||||
|
type Item = SequencerFocus;
|
||||||
|
fn focus_enter (&mut self) {
|
||||||
|
let focused = self.focused();
|
||||||
|
if !self.entered {
|
||||||
|
self.entered = true;
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn focus_exit (&mut self) {
|
||||||
|
if self.entered {
|
||||||
|
self.entered = false;
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn focus_entered (&self) -> Option<Self::Item> {
|
||||||
|
if self.entered {
|
||||||
|
Some(self.focused())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusGrid for SequencerTui {
|
||||||
|
type Item = SequencerFocus;
|
||||||
|
fn focus_cursor (&self) -> (usize, usize) {
|
||||||
|
self.cursor
|
||||||
|
}
|
||||||
|
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
||||||
|
&mut self.cursor
|
||||||
|
}
|
||||||
|
fn focus_layout (&self) -> &[&[Self::Item]] {
|
||||||
|
use SequencerFocus::*;
|
||||||
|
&[
|
||||||
|
&[Menu, Menu ],
|
||||||
|
&[Transport, Transport ],
|
||||||
|
&[PhrasePool, PhraseEditor],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
fn focus_update (&mut self) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FocusEnter for ArrangerTui {
|
||||||
|
type Item = ArrangerFocus;
|
||||||
|
fn focus_enter (&mut self) {
|
||||||
|
use ArrangerFocus::*;
|
||||||
|
let focused = self.focused();
|
||||||
|
if !self.entered {
|
||||||
|
self.entered = focused == Arranger;
|
||||||
|
self.app.editor.entered = focused == PhraseEditor;
|
||||||
|
self.app.phrases.entered = focused == PhrasePool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn focus_exit (&mut self) {
|
||||||
|
if self.entered {
|
||||||
|
self.entered = false;
|
||||||
|
self.app.editor.entered = false;
|
||||||
|
self.app.phrases.entered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn focus_entered (&self) -> Option<Self::Item> {
|
||||||
|
if self.entered {
|
||||||
|
Some(self.focused())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Focus layout of arranger app
|
||||||
|
impl FocusGrid for ArrangerTui {
|
||||||
|
type Item = ArrangerFocus;
|
||||||
|
fn focus_cursor (&self) -> (usize, usize) {
|
||||||
|
self.cursor
|
||||||
|
}
|
||||||
|
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
||||||
|
&mut self.cursor
|
||||||
|
}
|
||||||
|
fn focus_layout (&self) -> &[&[Self::Item]] {
|
||||||
|
use ArrangerFocus::*;
|
||||||
|
&[
|
||||||
|
&[Menu, Menu ],
|
||||||
|
&[Transport, Transport ],
|
||||||
|
&[Arranger, Arranger ],
|
||||||
|
&[PhrasePool, PhraseEditor],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
fn focus_update (&mut self) {
|
||||||
|
use ArrangerFocus::*;
|
||||||
|
let focused = self.focused();
|
||||||
|
if let Some(mut status_bar) = self.status_bar {
|
||||||
|
status_bar.update(&(
|
||||||
|
self.focused(),
|
||||||
|
self.app.selected,
|
||||||
|
focused == PhraseEditor && self.entered
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
crates/tek_tui/src/tui_handle.rs
Normal file
27
crates/tek_tui/src/tui_handle.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl Handle<Tui> for TransportTui {
|
||||||
|
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||||
|
TransportCommand::execute_with_state(self, from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Handle<Tui> for SequencerTui {
|
||||||
|
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||||
|
SequencerCommand::execute_with_state(self, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Handle<Tui> for ArrangerTui {
|
||||||
|
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||||
|
ArrangerCommand::execute_with_state(self, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Handle<Tui> for PhrasesTui {
|
||||||
|
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||||
|
PhrasesCommand::execute_with_state(self, from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Handle<Tui> for PhraseTui {
|
||||||
|
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||||
|
PhraseCommand::execute_with_state(self, from)
|
||||||
|
}
|
||||||
|
}
|
||||||
68
crates/tek_tui/src/tui_init.rs
Normal file
68
crates/tek_tui/src/tui_init.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Create app state from JACK handle.
|
||||||
|
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportTui {
|
||||||
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
metronome: false,
|
||||||
|
transport: jack.read().unwrap().transport(),
|
||||||
|
jack: jack.clone(),
|
||||||
|
focused: false,
|
||||||
|
focus: TransportFocus::PlayPause,
|
||||||
|
size: Measure::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
|
||||||
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||||
|
Ok(Self::new(SequencerTui {
|
||||||
|
phrases: vec![],
|
||||||
|
metronome: false,
|
||||||
|
transport: jack.read().unwrap().transport(),
|
||||||
|
jack: jack.clone(),
|
||||||
|
focused: false,
|
||||||
|
focus: TransportFocus::PlayPause,
|
||||||
|
size: Measure::new(),
|
||||||
|
phrases: vec![],
|
||||||
|
}.into(), None, None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerTui {
|
||||||
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
name: Arc::new(RwLock::new(String::new())),
|
||||||
|
phrases: vec![],
|
||||||
|
phrase: 0,
|
||||||
|
scenes: vec![],
|
||||||
|
tracks: vec![],
|
||||||
|
metronome: false,
|
||||||
|
playing: None.into(),
|
||||||
|
started: None.into(),
|
||||||
|
transport: jack.read().unwrap().transport(),
|
||||||
|
current: Instant::default(),
|
||||||
|
jack: jack.clone(),
|
||||||
|
selected: ArrangerSelection::Clip(0, 0),
|
||||||
|
mode: ArrangerMode::Vertical(2),
|
||||||
|
color: Color::Rgb(28, 35, 25).into(),
|
||||||
|
size: Measure::new(),
|
||||||
|
entered: false,
|
||||||
|
quant: Default::default(),
|
||||||
|
sync: Default::default(),
|
||||||
|
splits: [20, 20],
|
||||||
|
note_buf: vec![],
|
||||||
|
midi_buf: vec![],
|
||||||
|
cursor: (0, 0),
|
||||||
|
entered: false,
|
||||||
|
history: vec![],
|
||||||
|
size: Measure::new(),
|
||||||
|
menu_bar: None,
|
||||||
|
status_bar: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
191
crates/tek_tui/src/tui_jack.rs
Normal file
191
crates/tek_tui/src/tui_jack.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl JackApi for TransportTui {
|
||||||
|
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
||||||
|
&self.jack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl JackApi for SequencerTui {
|
||||||
|
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
||||||
|
&self.jack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl JackApi for ArrangerTui {
|
||||||
|
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
||||||
|
&self.jack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Audio for SequencerTui {
|
||||||
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
self.model.process(client, scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Audio for ArrangerTui {
|
||||||
|
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
if TracksAudio(
|
||||||
|
&mut self.app.tracks,
|
||||||
|
&mut self.app.note_buf,
|
||||||
|
&mut self.app.midi_buf,
|
||||||
|
Default::default(),
|
||||||
|
).process(client, scope) == Control::Quit {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
// FIXME: one of these per playing track
|
||||||
|
if let ArrangerSelection::Clip(t, s) = self.selected {
|
||||||
|
let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t));
|
||||||
|
if let Some(Some(Some(phrase))) = phrase {
|
||||||
|
if let Some(track) = self.tracks().get(t) {
|
||||||
|
if let Some((ref started_at, Some(ref playing))) = track.player.phrase {
|
||||||
|
let phrase = phrase.read().unwrap();
|
||||||
|
if *playing.read().unwrap() == *phrase {
|
||||||
|
let pulse = self.current().pulse.get();
|
||||||
|
let start = started_at.pulse.get();
|
||||||
|
let now = (pulse - start) % phrase.length as f64;
|
||||||
|
self.editor.now.set(now);
|
||||||
|
return Control::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.editor.now.set(0.);
|
||||||
|
return Control::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClockApi for ArrangerTui {
|
||||||
|
fn timebase (&self) -> &Arc<Timebase> {
|
||||||
|
&self.current.timebase
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
&self.quant
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
&self.sync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayheadApi for ArrangerTui {
|
||||||
|
fn current (&self) -> &Instant {
|
||||||
|
&self.current
|
||||||
|
}
|
||||||
|
fn transport (&self) -> &jack::Transport {
|
||||||
|
&self.transport
|
||||||
|
}
|
||||||
|
fn playing (&self) -> &RwLock<Option<TransportState>> {
|
||||||
|
&self.playing
|
||||||
|
}
|
||||||
|
fn started (&self) -> &RwLock<Option<(usize, usize)>> {
|
||||||
|
&self.started
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Audio for TransportTui {
|
||||||
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
PlayheadAudio(self).process(client, scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ClockApi for TransportTui {
|
||||||
|
fn timebase (&self) -> &Arc<Timebase> {
|
||||||
|
&self.current.timebase
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
&self.quant
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
&self.sync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayheadApi for TransportTui {
|
||||||
|
fn current (&self) -> &Instant {
|
||||||
|
&self.current
|
||||||
|
}
|
||||||
|
fn transport (&self) -> &jack::Transport {
|
||||||
|
&self.transport
|
||||||
|
}
|
||||||
|
fn playing (&self) -> &RwLock<Option<TransportState>> {
|
||||||
|
&self.playing
|
||||||
|
}
|
||||||
|
fn started (&self) -> &RwLock<Option<(usize, usize)>> {
|
||||||
|
&self.started
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl MidiInputApi for SequencerTui {
|
||||||
|
fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn recording(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn recording_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn monitoring(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn monitoring_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn overdub(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn overdub_mut(&mut self) -> &mut bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MidiOutputApi for SequencerTui {
|
||||||
|
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn midi_note (&mut self) -> &mut Vec<u8> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClockApi for SequencerTui {
|
||||||
|
fn timebase (&self) -> &Arc<Timebase> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn quant (&self) -> &Quantize {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn sync (&self) -> &LaunchSync {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayheadApi for SequencerTui {
|
||||||
|
fn current(&self) -> &Instant {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn transport(&self) -> &Transport {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn playing(&self) -> &RwLock<Option<TransportState>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn started(&self) -> &RwLock<Option<(usize, usize)>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayerApi for SequencerTui {}
|
||||||
1
crates/tek_tui/src/tui_menu.rs
Normal file
1
crates/tek_tui/src/tui_menu.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use crate::*;
|
||||||
100
crates/tek_tui/src/tui_model.rs
Normal file
100
crates/tek_tui/src/tui_model.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Stores and displays time-related info.
|
||||||
|
pub struct TransportTui {
|
||||||
|
jack: Arc<RwLock<JackClient>>,
|
||||||
|
/// Playback state
|
||||||
|
playing: RwLock<Option<TransportState>>,
|
||||||
|
/// Global sample and usec at which playback started
|
||||||
|
started: RwLock<Option<(usize, usize)>>,
|
||||||
|
/// Current moment in time
|
||||||
|
current: Instant,
|
||||||
|
/// Note quantization factor
|
||||||
|
quant: Quantize,
|
||||||
|
/// Launch quantization factor
|
||||||
|
sync: LaunchSync,
|
||||||
|
/// JACK transport handle.
|
||||||
|
transport: jack::Transport,
|
||||||
|
/// Enable metronome?
|
||||||
|
metronome: bool,
|
||||||
|
focus: TransportFocus,
|
||||||
|
focused: bool,
|
||||||
|
size: Measure<Tui>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for TransportTui {
|
||||||
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("transport")
|
||||||
|
.field("jack", &self.jack)
|
||||||
|
.field("metronome", &self.metronome)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Root view for standalone `tek_sequencer`.
|
||||||
|
pub struct SequencerTui {
|
||||||
|
jack: Arc<RwLock<JackClient>>,
|
||||||
|
playing: RwLock<Option<TransportState>>,
|
||||||
|
started: RwLock<Option<(usize, usize)>>,
|
||||||
|
current: Instant,
|
||||||
|
quant: Quantize,
|
||||||
|
sync: LaunchSync,
|
||||||
|
transport: jack::Transport,
|
||||||
|
metronome: bool,
|
||||||
|
phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||||
|
view_phrase: usize,
|
||||||
|
split: u16,
|
||||||
|
/// Start time and phrase being played
|
||||||
|
play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
|
/// Start time and next phrase
|
||||||
|
next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
|
/// Play input through output.
|
||||||
|
monitoring: bool,
|
||||||
|
/// Write input to sequence.
|
||||||
|
recording: bool,
|
||||||
|
/// Overdub input to sequence.
|
||||||
|
overdub: bool,
|
||||||
|
/// Send all notes off
|
||||||
|
reset: bool, // TODO?: after Some(nframes)
|
||||||
|
/// Record from MIDI ports to current sequence.
|
||||||
|
midi_inputs: Vec<Port<MidiIn>>,
|
||||||
|
/// Play from current sequence to MIDI ports
|
||||||
|
midi_outputs: Vec<Port<MidiOut>>,
|
||||||
|
/// MIDI output buffer
|
||||||
|
midi_note: Vec<u8>,
|
||||||
|
/// MIDI output buffer
|
||||||
|
midi_chunk: Vec<Vec<Vec<u8>>>,
|
||||||
|
/// Notes currently held at input
|
||||||
|
notes_in: Arc<RwLock<[bool; 128]>>,
|
||||||
|
/// Notes currently held at output
|
||||||
|
notes_out: Arc<RwLock<[bool; 128]>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Root view for standalone `tek_arranger`
|
||||||
|
pub struct ArrangerTui {
|
||||||
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
|
pub transport: jack::Transport,
|
||||||
|
pub playing: RwLock<Option<TransportState>>,
|
||||||
|
pub started: RwLock<Option<(usize, usize)>>,
|
||||||
|
pub current: Instant,
|
||||||
|
pub quant: Quantize,
|
||||||
|
pub sync: LaunchSync,
|
||||||
|
pub metronome: bool,
|
||||||
|
pub phrases: Vec<Arc<RwLock<Phrase>>>,
|
||||||
|
pub phrase: usize,
|
||||||
|
pub tracks: Vec<ArrangerTrack>,
|
||||||
|
pub scenes: Vec<ArrangerScene>,
|
||||||
|
pub name: Arc<RwLock<String>>,
|
||||||
|
pub splits: [u16;2],
|
||||||
|
pub selected: ArrangerSelection,
|
||||||
|
pub mode: ArrangerMode,
|
||||||
|
pub color: ItemColor,
|
||||||
|
pub entered: bool,
|
||||||
|
pub size: Measure<Tui>,
|
||||||
|
pub note_buf: Vec<u8>,
|
||||||
|
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
|
pub cursor: (usize, usize),
|
||||||
|
pub menu_bar: Option<MenuBar<Tui, Self, ArrangerCommand>>,
|
||||||
|
pub status_bar: Option<ArrangerStatus>,
|
||||||
|
pub history: Vec<ArrangerCommand>,
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
impl Handle<Tui> for PhraseTui {
|
|
||||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
|
||||||
PhraseCommand::execute_with_state(self, from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub enum PhraseCommand {
|
pub enum PhraseCommand {
|
||||||
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
impl Handle<Tui> for PhrasesTui {
|
|
||||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
|
||||||
PhrasesCommand::execute_with_state(self, from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub enum PhrasesCommand {
|
pub enum PhrasesCommand {
|
||||||
Select(usize),
|
Select(usize),
|
||||||
|
|
|
||||||
|
|
@ -1,79 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
|
||||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
|
||||||
Ok(Self::new(SequencerTui {
|
|
||||||
phrases: vec![],
|
|
||||||
metronome: false,
|
|
||||||
transport: jack.read().unwrap().transport(),
|
|
||||||
jack: jack.clone(),
|
|
||||||
focused: false,
|
|
||||||
focus: TransportFocus::PlayPause,
|
|
||||||
size: Measure::new(),
|
|
||||||
phrases: vec![],
|
|
||||||
}.into(), None, None))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Root view for standalone `tek_sequencer`.
|
|
||||||
pub struct SequencerTui {
|
|
||||||
jack: Arc<RwLock<JackClient>>,
|
|
||||||
playing: RwLock<Option<TransportState>>,
|
|
||||||
started: RwLock<Option<(usize, usize)>>,
|
|
||||||
current: Instant,
|
|
||||||
quant: Quantize,
|
|
||||||
sync: LaunchSync,
|
|
||||||
transport: jack::Transport,
|
|
||||||
metronome: bool,
|
|
||||||
phrases: Vec<Arc<RwLock<Phrase>>>,
|
|
||||||
view_phrase: usize,
|
|
||||||
split: u16,
|
|
||||||
/// Start time and phrase being played
|
|
||||||
play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
|
||||||
/// Start time and next phrase
|
|
||||||
next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
|
||||||
/// Play input through output.
|
|
||||||
monitoring: bool,
|
|
||||||
/// Write input to sequence.
|
|
||||||
recording: bool,
|
|
||||||
/// Overdub input to sequence.
|
|
||||||
overdub: bool,
|
|
||||||
/// Send all notes off
|
|
||||||
reset: bool, // TODO?: after Some(nframes)
|
|
||||||
/// Record from MIDI ports to current sequence.
|
|
||||||
midi_inputs: Vec<Port<MidiIn>>,
|
|
||||||
/// Play from current sequence to MIDI ports
|
|
||||||
midi_outputs: Vec<Port<MidiOut>>,
|
|
||||||
/// MIDI output buffer
|
|
||||||
midi_note: Vec<u8>,
|
|
||||||
/// MIDI output buffer
|
|
||||||
midi_chunk: Vec<Vec<Vec<u8>>>,
|
|
||||||
/// Notes currently held at input
|
|
||||||
notes_in: Arc<RwLock<[bool; 128]>>,
|
|
||||||
/// Notes currently held at output
|
|
||||||
notes_out: Arc<RwLock<[bool; 128]>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sections in the sequencer app that may be focused
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub enum SequencerFocus {
|
|
||||||
/// The menu bar is focused
|
|
||||||
Menu,
|
|
||||||
/// The transport (toolbar) is focused
|
|
||||||
Transport,
|
|
||||||
/// The phrase list (pool) is focused
|
|
||||||
PhrasePool,
|
|
||||||
/// The phrase editor (sequencer) is focused
|
|
||||||
PhraseEditor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Audio for SequencerTui {
|
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
self.model.process(client, scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasPhrases for SequencerTui {
|
impl HasPhrases for SequencerTui {
|
||||||
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
|
||||||
&self.phrases
|
&self.phrases
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
impl Handle<Tui> for SequencerTui {
|
|
||||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
|
||||||
SequencerCommand::execute_with_state(self, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum SequencerCommand {
|
pub enum SequencerCommand {
|
||||||
Focus(FocusCommand),
|
Focus(FocusCommand),
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl HasFocus for SequencerTui {
|
|
||||||
type Item = SequencerFocus;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FocusEnter for SequencerTui {
|
|
||||||
type Item = SequencerFocus;
|
|
||||||
fn focus_enter (&mut self) {
|
|
||||||
let focused = self.focused();
|
|
||||||
if !self.entered {
|
|
||||||
self.entered = true;
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn focus_exit (&mut self) {
|
|
||||||
if self.entered {
|
|
||||||
self.entered = false;
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn focus_entered (&self) -> Option<Self::Item> {
|
|
||||||
if self.entered {
|
|
||||||
Some(self.focused())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FocusGrid for SequencerTui {
|
|
||||||
type Item = SequencerFocus;
|
|
||||||
fn focus_cursor (&self) -> (usize, usize) {
|
|
||||||
self.cursor
|
|
||||||
}
|
|
||||||
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
|
||||||
&mut self.cursor
|
|
||||||
}
|
|
||||||
fn focus_layout (&self) -> &[&[Self::Item]] {
|
|
||||||
use SequencerFocus::*;
|
|
||||||
&[
|
|
||||||
&[Menu, Menu ],
|
|
||||||
&[Transport, Transport ],
|
|
||||||
&[PhrasePool, PhraseEditor],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
fn focus_update (&mut self) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl JackApi for SequencerTui {
|
|
||||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
|
||||||
&self.jack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiInputApi for SequencerTui {
|
|
||||||
fn midi_ins(&self) -> &Vec<Port<jack::MidiIn>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_ins_mut(&self) -> &mut Vec<Port<jack::MidiIn>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn recording(&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn recording_mut(&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn monitoring(&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn monitoring_mut(&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn overdub(&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn overdub_mut(&mut self) -> &mut bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn notes_in(&self) -> &Arc<RwLock<[bool; 128]>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiOutputApi for SequencerTui {
|
|
||||||
fn midi_outs (&self) -> &Vec<Port<jack::MidiOut>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<Port<jack::MidiOut>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn midi_note (&mut self) -> &mut Vec<u8> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClockApi for SequencerTui {
|
|
||||||
fn timebase (&self) -> &Arc<Timebase> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn quant (&self) -> &Quantize {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &LaunchSync {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayheadApi for SequencerTui {
|
|
||||||
fn current(&self) -> &Instant {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn transport(&self) -> &Transport {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn playing(&self) -> &RwLock<Option<TransportState>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn started(&self) -> &RwLock<Option<(usize, usize)>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayerApi for SequencerTui {}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Status bar for sequencer app
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub enum SequencerStatusBar {
|
|
||||||
Transport,
|
|
||||||
PhrasePool,
|
|
||||||
PhraseEditor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatusBar for SequencerStatusBar {
|
|
||||||
type State = ();
|
|
||||||
fn hotkey_fg () -> Color {
|
|
||||||
TuiTheme::hotkey_fg()
|
|
||||||
}
|
|
||||||
fn update (&mut self, state: &()) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Content for SequencerStatusBar {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
|
||||||
todo!();
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -26,3 +26,169 @@ pub trait StatusBar: Copy + Widget<Engine = Tui> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct TransportStatusBar;
|
||||||
|
|
||||||
|
impl StatusBar for TransportStatusBar {
|
||||||
|
type State = ();
|
||||||
|
fn hotkey_fg () -> Color {
|
||||||
|
TuiTheme::hotkey_fg()
|
||||||
|
}
|
||||||
|
fn update (&mut self, state: &()) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content for TransportStatusBar {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
|
todo!();
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Status bar for sequencer app
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum SequencerStatusBar {
|
||||||
|
Transport,
|
||||||
|
PhrasePool,
|
||||||
|
PhraseEditor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusBar for SequencerStatusBar {
|
||||||
|
type State = ();
|
||||||
|
fn hotkey_fg () -> Color {
|
||||||
|
TuiTheme::hotkey_fg()
|
||||||
|
}
|
||||||
|
fn update (&mut self, state: &()) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content for SequencerStatusBar {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
|
todo!();
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Status bar for arranger app
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum ArrangerStatus {
|
||||||
|
Transport,
|
||||||
|
ArrangerMix,
|
||||||
|
ArrangerTrack,
|
||||||
|
ArrangerScene,
|
||||||
|
ArrangerClip,
|
||||||
|
PhrasePool,
|
||||||
|
PhraseView,
|
||||||
|
PhraseEdit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusBar for ArrangerStatus {
|
||||||
|
type State = (ArrangerFocus, ArrangerSelection, bool);
|
||||||
|
fn hotkey_fg () -> Color where Self: Sized {
|
||||||
|
TuiTheme::hotkey_fg()
|
||||||
|
}
|
||||||
|
fn update (&mut self, (focused, selected, entered): &Self::State) {
|
||||||
|
*self = match focused {
|
||||||
|
ArrangerFocus::Transport => ArrangerStatus::Transport,
|
||||||
|
ArrangerFocus::Arranger => match selected {
|
||||||
|
ArrangerSelection::Mix => ArrangerStatus::ArrangerMix,
|
||||||
|
ArrangerSelection::Track(_) => ArrangerStatus::ArrangerTrack,
|
||||||
|
ArrangerSelection::Scene(_) => ArrangerStatus::ArrangerScene,
|
||||||
|
ArrangerSelection::Clip(_, _) => ArrangerStatus::ArrangerClip,
|
||||||
|
},
|
||||||
|
ArrangerFocus::PhrasePool => ArrangerStatus::PhrasePool,
|
||||||
|
ArrangerFocus::PhraseEditor => match entered {
|
||||||
|
true => ArrangerStatus::PhraseEdit,
|
||||||
|
false => ArrangerStatus::PhraseView,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content for ArrangerStatus {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
|
let label = match self {
|
||||||
|
Self::Transport => "TRANSPORT",
|
||||||
|
Self::ArrangerMix => "PROJECT",
|
||||||
|
Self::ArrangerTrack => "TRACK",
|
||||||
|
Self::ArrangerScene => "SCENE",
|
||||||
|
Self::ArrangerClip => "CLIP",
|
||||||
|
Self::PhrasePool => "SEQ LIST",
|
||||||
|
Self::PhraseView => "VIEW SEQ",
|
||||||
|
Self::PhraseEdit => "EDIT SEQ",
|
||||||
|
};
|
||||||
|
let status_bar_bg = TuiTheme::status_bar_bg();
|
||||||
|
let mode_bg = TuiTheme::mode_bg();
|
||||||
|
let mode_fg = TuiTheme::mode_fg();
|
||||||
|
let mode = TuiStyle::bold(format!(" {label} "), true).bg(mode_bg).fg(mode_fg);
|
||||||
|
let commands = match self {
|
||||||
|
Self::ArrangerMix => Self::command(&[
|
||||||
|
["", "c", "olor"],
|
||||||
|
["", "<>", "resize"],
|
||||||
|
["", "+-", "zoom"],
|
||||||
|
["", "n", "ame/number"],
|
||||||
|
["", "Enter", " stop all"],
|
||||||
|
]),
|
||||||
|
Self::ArrangerClip => Self::command(&[
|
||||||
|
["", "g", "et"],
|
||||||
|
["", "s", "et"],
|
||||||
|
["", "a", "dd"],
|
||||||
|
["", "i", "ns"],
|
||||||
|
["", "d", "up"],
|
||||||
|
["", "e", "dit"],
|
||||||
|
["", "c", "olor"],
|
||||||
|
["re", "n", "ame"],
|
||||||
|
["", ",.", "select"],
|
||||||
|
["", "Enter", " launch"],
|
||||||
|
]),
|
||||||
|
Self::ArrangerTrack => Self::command(&[
|
||||||
|
["re", "n", "ame"],
|
||||||
|
["", ",.", "resize"],
|
||||||
|
["", "<>", "move"],
|
||||||
|
["", "i", "nput"],
|
||||||
|
["", "o", "utput"],
|
||||||
|
["", "m", "ute"],
|
||||||
|
["", "s", "olo"],
|
||||||
|
["", "Del", "ete"],
|
||||||
|
["", "Enter", " stop"],
|
||||||
|
]),
|
||||||
|
Self::ArrangerScene => Self::command(&[
|
||||||
|
["re", "n", "ame"],
|
||||||
|
["", "Del", "ete"],
|
||||||
|
["", "Enter", " launch"],
|
||||||
|
]),
|
||||||
|
Self::PhrasePool => Self::command(&[
|
||||||
|
["", "a", "ppend"],
|
||||||
|
["", "i", "nsert"],
|
||||||
|
["", "d", "uplicate"],
|
||||||
|
["", "Del", "ete"],
|
||||||
|
["", "c", "olor"],
|
||||||
|
["re", "n", "ame"],
|
||||||
|
["leng", "t", "h"],
|
||||||
|
["", ",.", "move"],
|
||||||
|
["", "+-", "resize view"],
|
||||||
|
]),
|
||||||
|
Self::PhraseView => Self::command(&[
|
||||||
|
["", "enter", " edit"],
|
||||||
|
["", "arrows/pgup/pgdn", " scroll"],
|
||||||
|
["", "+=", "zoom"],
|
||||||
|
]),
|
||||||
|
Self::PhraseEdit => Self::command(&[
|
||||||
|
["", "esc", " exit"],
|
||||||
|
["", "a", "ppend"],
|
||||||
|
["", "s", "et"],
|
||||||
|
["", "][", "length"],
|
||||||
|
["", "+-", "zoom"],
|
||||||
|
]),
|
||||||
|
_ => Self::command(&[])
|
||||||
|
};
|
||||||
|
//let commands = commands.iter().reduce(String::new(), |s, (a, b, c)| format!("{s} {a}{b}{c}"));
|
||||||
|
row!(mode, commands).fill_x().bg(status_bar_bg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Stores and displays time-related info.
|
|
||||||
pub struct TransportTui {
|
|
||||||
jack: Arc<RwLock<JackClient>>,
|
|
||||||
/// Playback state
|
|
||||||
playing: RwLock<Option<TransportState>>,
|
|
||||||
/// Global sample and usec at which playback started
|
|
||||||
started: RwLock<Option<(usize, usize)>>,
|
|
||||||
/// Current moment in time
|
|
||||||
current: Instant,
|
|
||||||
/// Note quantization factor
|
|
||||||
quant: Quantize,
|
|
||||||
/// Launch quantization factor
|
|
||||||
sync: LaunchSync,
|
|
||||||
/// JACK transport handle.
|
|
||||||
transport: jack::Transport,
|
|
||||||
/// Enable metronome?
|
|
||||||
metronome: bool,
|
|
||||||
focus: TransportFocus,
|
|
||||||
focused: bool,
|
|
||||||
size: Measure<Tui>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create app state from JACK handle.
|
|
||||||
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportTui {
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
|
||||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
metronome: false,
|
|
||||||
transport: jack.read().unwrap().transport(),
|
|
||||||
jack: jack.clone(),
|
|
||||||
focused: false,
|
|
||||||
focus: TransportFocus::PlayPause,
|
|
||||||
size: Measure::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for TransportTui {
|
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
||||||
f.debug_struct("transport")
|
|
||||||
.field("jack", &self.jack)
|
|
||||||
.field("metronome", &self.metronome)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Handle input.
|
|
||||||
impl Handle<Tui> for TransportTui {
|
|
||||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
|
||||||
TransportCommand::execute_with_state(self, from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum TransportCommand {
|
pub enum TransportCommand {
|
||||||
Focus(FocusCommand),
|
Focus(FocusCommand),
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Which item of the transport toolbar is focused
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum TransportFocus {
|
|
||||||
Menu,
|
|
||||||
Bpm,
|
|
||||||
Sync,
|
|
||||||
PlayPause,
|
|
||||||
Clock,
|
|
||||||
Quant,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TransportFocus {
|
|
||||||
pub fn next (&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
Self::PlayPause => Self::Bpm,
|
|
||||||
Self::Bpm => Self::Quant,
|
|
||||||
Self::Quant => Self::Sync,
|
|
||||||
Self::Sync => Self::Clock,
|
|
||||||
Self::Clock => Self::PlayPause,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn prev (&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
Self::PlayPause => Self::Clock,
|
|
||||||
Self::Bpm => Self::PlayPause,
|
|
||||||
Self::Quant => Self::Bpm,
|
|
||||||
Self::Sync => Self::Quant,
|
|
||||||
Self::Clock => Self::Sync,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn wrap <'a, W: Widget<Engine = Tui>> (
|
|
||||||
self, parent_focus: bool, focus: Self, widget: &'a W
|
|
||||||
) -> impl Widget<Engine = Tui> + 'a {
|
|
||||||
let focused = parent_focus && focus == self;
|
|
||||||
let corners = focused.then_some(CORNERS);
|
|
||||||
let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50)));
|
|
||||||
lay!(corners, highlight, *widget)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasFocus for TransportTui {
|
|
||||||
type Item = TransportFocus;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FocusEnter for TransportTui {
|
|
||||||
type Item = TransportFocus;
|
|
||||||
fn focus_enter (&mut self) {
|
|
||||||
self.entered = true;
|
|
||||||
}
|
|
||||||
fn focus_exit (&mut self) {
|
|
||||||
self.entered = false;
|
|
||||||
}
|
|
||||||
fn focus_entered (&self) -> Option<Self::Item> {
|
|
||||||
if self.entered {
|
|
||||||
Some(self.focused())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FocusGrid for TransportTui {
|
|
||||||
type Item = TransportFocus;
|
|
||||||
fn focus_cursor (&self) -> (usize, usize) {
|
|
||||||
self.cursor
|
|
||||||
}
|
|
||||||
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
|
|
||||||
&mut self.cursor
|
|
||||||
}
|
|
||||||
fn focus_layout (&self) -> &[&[Self::Item]] {
|
|
||||||
use TransportFocus::*;
|
|
||||||
&[
|
|
||||||
&[Menu],
|
|
||||||
&[Bpm, Sync, PlayPause, Clock, Quant],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
fn focus_update (&mut self) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl Audio for TransportTui {
|
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
PlayheadAudio(self).process(client, scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JackApi for TransportTui {
|
|
||||||
fn jack (&self) -> &Arc<RwLock<JackClient>> {
|
|
||||||
&self.jack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClockApi for TransportTui {
|
|
||||||
fn timebase (&self) -> &Arc<Timebase> {
|
|
||||||
&self.current.timebase
|
|
||||||
}
|
|
||||||
fn quant (&self) -> &Quantize {
|
|
||||||
&self.quant
|
|
||||||
}
|
|
||||||
fn sync (&self) -> &LaunchSync {
|
|
||||||
&self.sync
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlayheadApi for TransportTui {
|
|
||||||
fn current (&self) -> &Instant {
|
|
||||||
&self.current
|
|
||||||
}
|
|
||||||
fn transport (&self) -> &jack::Transport {
|
|
||||||
&self.transport
|
|
||||||
}
|
|
||||||
fn playing (&self) -> &RwLock<Option<TransportState>> {
|
|
||||||
&self.playing
|
|
||||||
}
|
|
||||||
fn started (&self) -> &RwLock<Option<(usize, usize)>> {
|
|
||||||
&self.started
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct TransportStatusBar;
|
|
||||||
|
|
||||||
impl StatusBar for TransportStatusBar {
|
|
||||||
type State = ();
|
|
||||||
fn hotkey_fg () -> Color {
|
|
||||||
TuiTheme::hotkey_fg()
|
|
||||||
}
|
|
||||||
fn update (&mut self, state: &()) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Content for TransportStatusBar {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
|
||||||
todo!();
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue