tek/crates/tek_tui/src/tui_app_sequencer.rs
2024-11-28 17:39:07 +01:00

187 lines
5.5 KiB
Rust

use crate::*;
/// Root view for standalone `tek_sequencer`.
pub struct SequencerTui {
pub jack: Arc<RwLock<JackClient>>,
pub clock: ClockModel,
pub phrases: PhraseListModel,
pub player: PhrasePlayerModel,
pub editor: PhraseEditorModel,
pub size: Measure<Tui>,
pub cursor: (usize, usize),
pub split: u16,
pub entered: bool,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub focus: FocusState<SequencerFocus>,
pub perf: PerfModel,
}
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
type Error = Box<dyn std::error::Error>;
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
let clock = ClockModel::from(jack);
Ok(Self {
jack: jack.clone(),
phrases: PhraseListModel::default(),
player: PhrasePlayerModel::from(&clock),
editor: PhraseEditorModel::default(),
size: Measure::new(),
cursor: (0, 0),
entered: false,
split: 20,
midi_buf: vec![vec![];65536],
note_buf: vec![],
clock,
perf: PerfModel::default(),
focus: FocusState::Focused(SequencerFocus::Transport(TransportFocus::PlayPause))
})
}
}
impl HasClock for SequencerTui {
fn clock (&self) -> &ClockModel {
&self.clock
}
}
impl HasPhrases for SequencerTui {
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
&self.phrases.phrases
}
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
&mut self.phrases.phrases
}
}
/// Sections in the sequencer app that may be focused
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum SequencerFocus {
/// The transport (toolbar) is focused
Transport(TransportFocus),
/// The phrase list (pool) is focused
PhraseList,
/// The phrase editor (sequencer) is focused
PhraseEditor,
PhrasePlay,
PhraseNext,
}
impl_focus!(SequencerTui SequencerFocus [
//&[
//Menu,
//Menu,
//Menu,
//Menu,
//Menu,
//],
&[
Transport(TransportFocus::PlayPause),
Transport(TransportFocus::Bpm),
Transport(TransportFocus::Sync),
Transport(TransportFocus::Quant),
Transport(TransportFocus::Clock),
],
&[
PhrasePlay,
PhrasePlay,
PhraseEditor,
PhraseEditor,
PhraseEditor,
],
&[
PhraseNext,
PhraseNext,
PhraseEditor,
PhraseEditor,
PhraseEditor,
],
&[
PhraseList,
PhraseList,
PhraseEditor,
PhraseEditor,
PhraseEditor,
],
]);
/// Status bar for sequencer app
#[derive(Clone)]
pub struct SequencerStatusBar {
pub(crate) cpu: Option<String>,
pub(crate) size: String,
pub(crate) res: String,
pub(crate) mode: &'static str,
pub(crate) help: &'static [(&'static str, &'static str, &'static str)]
}
impl StatusBar for SequencerStatusBar {
type State = SequencerTui;
fn hotkey_fg () -> Color {
TuiTheme::hotkey_fg()
}
fn update (&mut self, state: &SequencerTui) {
todo!()
}
}
impl From<&SequencerTui> for SequencerStatusBar {
fn from (state: &SequencerTui) -> Self {
use SequencerFocus::*;
use TransportFocus::*;
let samples = state.clock.chunk.load(Ordering::Relaxed);
let rate = state.clock.timebase.sr.get() as f64;
let buffer = samples as f64 / rate;
let default_help = &[("", "", " enter"), ("", "", " navigate")];
Self {
cpu: state.perf.percentage().map(|cpu|format!("{cpu:.01}%")),
size: format!("{}x{}│", state.size.w(), state.size.h()),
res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.),
mode: match state.focused() {
Transport(PlayPause) => " PLAY/PAUSE ",
Transport(Bpm) => " TEMPO ",
Transport(Sync) => " LAUNCH SYNC ",
Transport(Quant) => " REC QUANT ",
Transport(Clock) => " SEEK ",
PhraseList => " PHRASES ",
PhraseEditor => " EDIT MIDI ",
PhrasePlay => " TO PLAY ",
PhraseNext => " UP NEXT ",
},
help: match state.focused() {
Transport(PlayPause) => &[
("", "", " play/pause"),
("", "", " navigate"),
],
Transport(Bpm) => &[
("", ".,", " inc/dec"),
("", "><", " fine"),
],
Transport(Sync) => &[
("", ".,", " inc/dec"),
],
Transport(Quant) => &[
("", ".,", " inc/dec"),
],
PhraseList => if state.entered() {
&[
("", "", " pick"),
("", ".,", " move"),
("", "", " play"),
("", "e", " edit"),
]
} else {
default_help
}
_ => if state.entered() {
&[
("", "Esc", " exit")
]
} else {
default_help
}
}
}
}
}