mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
187 lines
5.5 KiB
Rust
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|