use crate::*; /// Root view for standalone `tek_sequencer`. pub struct SequencerTui { pub jack: Arc>, pub clock: ClockModel, pub phrases: PhraseListModel, pub player: PhrasePlayerModel, pub editor: PhraseEditorModel, pub size: Measure, pub cursor: (usize, usize), pub split: u16, pub entered: bool, pub note_buf: Vec, pub midi_buf: Vec>>, pub focus: FocusState, pub perf: PerfModel, } impl TryFrom<&Arc>> for SequencerTui { type Error = Box; fn try_from (jack: &Arc>) -> Usually { 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>> { &self.phrases.phrases } fn phrases_mut (&mut self) -> &mut Vec>> { &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, 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 } } } } }