tek/crates/tek_tui/src/tui_transport.rs

328 lines
11 KiB
Rust

use crate::*;
/// Create app state from JACK handle.
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> {
type Error = Box<dyn std::error::Error>;
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
Ok(Self::new(TransportView {
metronome: false,
transport: jack.read().unwrap().transport(),
jack: jack.clone(),
clock: Arc::new(Clock::from(Instant::default())),
focused: false,
focus: TransportViewFocus::PlayPause,
size: Measure::new(),
}.into(), None, None))
}
}
/// Root type of application.
pub type TransportApp<E: Engine> = AppView<
E,
TransportView<E>,
TransportAppCommand,
TransportStatusBar
>;
/// Handle input.
impl Handle<Tui> for TransportApp<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
TransportAppCommand::execute_with_state(self, from)
}
}
pub type TransportAppCommand = AppViewCommand<TransportCommand>;
#[derive(Clone, Debug)]
pub enum TransportCommand {
Clock(ClockCommand),
Playhead(PlayheadCommand),
}
impl InputToCommand<Tui, TransportApp<Tui>> for TransportAppCommand {
fn input_to_command (app: &TransportApp<Tui>, input: &TuiInput) -> Option<Self> {
use KeyCode::{Left, Right};
use FocusCommand::{Prev, Next};
use AppViewCommand::{Focus, App};
Some(match input.event() {
key!(Left) => Focus(Prev),
key!(Right) => Focus(Next),
_ => TransportCommand::input_to_command(app.app, input).map(App)
})
}
}
impl InputToCommand<Tui, TransportApp<Tui>> for TransportCommand {
fn input_to_command (app: &TransportApp<Tui>, input: &TuiInput) -> Option<Self> {
use KeyCode::Char;
use AppViewFocus::Content;
use ClockCommand::{SetBpm, SetQuant, SetSync};
use TransportViewFocus::{Bpm, Quant, Sync, PlayPause, Clock};
let clock = app.app.model.clock();
Some(match input.event() {
key!(Char('.')) => match app.focused() {
Content(Bpm) => SetBpm(clock.timebase().bpm.get() + 1.0),
Content(Quant) => SetQuant(next_note_length(clock.quant.get()as usize)as f64),
Content(Sync) => SetSync(next_note_length(clock.sync.get()as usize)as f64+1.),
Content(PlayPause) => {todo!()},
Content(Clock) => {todo!()},
_ => {todo!()}
},
key!(KeyCode::Char(',')) => match app.focused() {
Content(Bpm) => SetBpm(clock.timebase().bpm.get() - 1.0),
Content(Quant) => SetQuant(prev_note_length(clock.quant.get()as usize)as f64),
Content(Sync) => SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.),
Content(PlayPause) => {todo!()},
Content(Clock) => {todo!()}
_ => {todo!()}
},
key!(KeyCode::Char('>')) => match app.focused() {
Content(Bpm) => SetBpm(clock.timebase().bpm.get() + 0.001),
Content(Quant) => SetQuant(next_note_length(clock.quant.get()as usize)as f64),
Content(Sync) => SetSync(next_note_length(clock.sync.get()as usize)as f64+1.),
Content(PlayPause) => {todo!()},
Content(Clock) => {todo!()}
_ => {todo!()}
},
key!(KeyCode::Char('<')) => match app.focused() {
Content(Bpm) => SetBpm(clock.timebase().bpm.get() - 0.001),
Content(Quant) => SetQuant(prev_note_length(clock.quant.get()as usize)as f64),
Content(Sync) => SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.),
Content(PlayPause) => {todo!()},
Content(Clock) => {todo!()}
_ => {todo!()}
},
_ => return None
})
}
}
impl Command<TransportApp<Tui>> for TransportAppCommand {
fn execute (self, state: &mut TransportApp<Tui>) -> Perhaps<Self> {
use AppViewCommand::{Focus, App};
use FocusCommand::{Next, Prev};
Ok(Some(match self {
App(command) => if let Some(undo) = TransportCommand::execute(command, state)? {
App(undo)
} else {
return Ok(None)
},
Focus(command) => Focus(match command {
Next => { todo!() },
Prev => { todo!() },
_ => { todo!() }
}),
_ => todo!()
}))
}
}
impl Command<TransportApp<Tui>> for TransportCommand {
fn execute (self, state: &mut TransportApp<Tui>) -> Perhaps<Self> {
use ClockCommand::{SetBpm, SetQuant, SetSync};
let clock = state.app.model.clock();
Ok(Some(match self {
SetBpm(bpm) => SetBpm(clock.timebase().bpm.set(bpm)),
SetQuant(quant) => SetQuant(clock.quant.set(quant)),
SetSync(sync) => SetSync(clock.sync.set(sync)),
_ => return Ok(None)
}))
}
}
/// Stores and displays time-related info.
#[derive(Debug)]
pub struct TransportView<E: Engine> {
_engine: PhantomData<E>,
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: TransportViewFocus,
focused: bool,
size: Measure<E>,
}
/// Which item of the transport toolbar is focused
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TransportViewFocus {
Bpm,
Sync,
PlayPause,
Clock,
Quant,
}
impl TransportViewFocus {
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)
}
}
#[derive(Copy, Clone)]
pub struct TransportStatusBar;
impl Content for TransportView<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
lay!(
self.focus.wrap(self.focused, TransportViewFocus::PlayPause, &Styled(
None,
match *self.model.clock().playing.read().unwrap() {
Some(TransportState::Rolling) => "▶ PLAYING",
Some(TransportState::Starting) => "READY ...",
Some(TransportState::Stopped) => "⏹ STOPPED",
_ => unreachable!(),
}
).min_xy(11, 2).push_x(1)).align_x().fill_x(),
row!(
self.focus.wrap(self.focused, TransportViewFocus::Bpm, &Outset::X(1u16, {
let bpm = self.model.clock().timebase().bpm.get();
row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) }
})),
//let quant = self.focus.wrap(self.focused, TransportViewFocus::Quant, &Outset::X(1u16, row! {
//"QUANT ", ppq_to_name(self.quant as usize)
//})),
self.focus.wrap(self.focused, TransportViewFocus::Sync, &Outset::X(1u16, row! {
"SYNC ", pulses_to_name(self.model.clock().sync.get() as usize)
}))
).align_w().fill_x(),
self.focus.wrap(self.focused, TransportViewFocus::Clock, &{
let time1 = self.model.clock().current.format_beat();
let time2 = self.model.clock().current.usec.format_msu();
row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1)
}).align_e().fill_x(),
).fill_x().bg(Color::Rgb(40, 50, 30))
}
}
impl Audio for TransportView<Tui> {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
self.model.process(client, scope)
}
}
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!();
""
}
}
impl FocusGrid for TransportApp<Tui> {
type Item = AppViewFocus<TransportViewFocus>;
fn cursor (&self) -> (usize, usize) {
self.cursor
}
fn cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
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 entered (&self) -> Option<Self::Item> {
if self.entered {
Some(self.focused())
} else {
None
}
}
fn layout (&self) -> &[&[Self::Item]] {
use AppViewFocus::*;
use TransportViewFocus::*;
&[
&[Menu],
&[
Content(Bpm),
Content(Sync),
Content(PlayPause),
Content(Clock),
Content(Quant),
],
]
}
fn update_focus (&mut self) {
// TODO
}
}
impl HasJack for TransportView {
fn jack (&self) -> &Arc<RwLock<JackClient>> {
&self.jack
}
}
impl HasClock for TransportView {
fn clock (&self) -> &Arc<Clock> {
&self.clock
}
}
impl std::fmt::Debug for TransportView {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), Error> {
f.debug_struct("transport")
.field("jack", &self.jack)
.field("transport", &"(JACK transport)")
.field("clock", &self.clock)
.field("metronome", &self.metronome)
.finish()
}
}