use crate::*; use KeyCode::*; use ClockCommand::{Play, Pause}; /// Transport clock app. pub struct TransportTui { pub jack: Arc>, pub clock: Clock, } handle!(TuiIn: |self: TransportTui, input|ClockCommand::execute_with_state(self, input.event())); keymap!(TRANSPORT_KEYS = |state: TransportTui, input: Event| ClockCommand { key(Char(' ')) => if state.clock().is_stopped() { Play(None) } else { Pause(None) }, shift(key(Char(' '))) => if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } }); has_clock!(|self: TransportTui|&self.clock); audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope)); render!(TuiOut: (self: TransportTui) => TransportView { compact: false, clock: &self.clock }); pub struct TransportView<'a> { pub compact: bool, pub clock: &'a Clock } impl<'a> TransportView<'a> { pub fn new (compact: bool, clock: &'a Clock) -> Self { Self { compact, clock } } } render!(TuiOut: (self: TransportView<'a>) => Outer( Style::default().fg(TuiTheme::g(255)) ).enclose(row!( OutputStats::new(self.compact, self.clock), " ", PlayPause { compact: false, playing: self.clock.is_rolling() }, " ", BeatStats::new(self.compact, self.clock), ))); pub struct PlayPause { pub compact: bool, pub playing: bool } render!(TuiOut: (self: PlayPause) => Tui::bg( if self.playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)}, Either(self.compact, Thunk::new(||Fixed::x(9, Either(self.playing, Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "), Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))), Thunk::new(||Fixed::x(5, Either(self.playing, Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))))))); pub struct BeatStats { compact: bool, bpm: Arc, beat: Arc, time: Arc, } impl BeatStats { fn new (compact: bool, clock: &Clock) -> Self { let bpm = format!("{:.3}", clock.timebase.bpm.get()).into(); let (beat, time) = if let Some(started) = clock.started.read().unwrap().as_ref() { let now = clock.global.usec.get() - started.usec.get(); ( clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(now)).into(), format!("{:.3}s", now/1000000.).into() ) } else { ("-.-.--".to_string().into(), "-.---s".to_string().into()) }; Self { compact, bpm, beat, time } } } render!(TuiOut: (self: BeatStats) => Either(self.compact, row!( FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm), FieldV(TuiTheme::g(128).into(), "Beat", &self.beat), FieldV(TuiTheme::g(128).into(), "Time", &self.time), ), col!( Bsp::e(Tui::fg(TuiTheme::g(255), &self.bpm), " BPM"), Bsp::e("Beat ", Tui::fg(TuiTheme::g(255), &self.beat)), Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time)), ))); pub struct OutputStats { compact: bool, sample_rate: Arc, buffer_size: Arc, latency: Arc, } impl OutputStats { fn new (compact: bool, clock: &Clock) -> Self { let rate = clock.timebase.sr.get(); let chunk = clock.chunk.load(Relaxed); Self { compact, sample_rate: if compact { format!("{:.1}kHz", rate / 1000.) } else { format!("{:.0}Hz", rate) }.into(), buffer_size: format!("{chunk}").into(), latency: format!("{:.1}ms", chunk as f64 / rate * 1000.).into(), } } } render!(TuiOut: (self: OutputStats) => Either(self.compact, row!( FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate), FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size), FieldV(TuiTheme::g(128).into(), "Lat", &self.latency), ), col!( Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.sample_rate)), " sample rate"), Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.buffer_size)), " sample buffer"), Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"), ))); // TODO: //keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand { //key(Char(',')) => SetBpm(state.bpm().get() - 1.0), //key(Char('.')) => SetBpm(state.bpm().get() + 1.0), //key(Char('<')) => SetBpm(state.bpm().get() - 0.001), //key(Char('>')) => SetBpm(state.bpm().get() + 0.001), //}); //keymap!(TRANSPORT_QUANT_KEYS = |state: Clock, input: Event| ClockCommand { //key(Char(',')) => SetQuant(state.quant.prev()), //key(Char('.')) => SetQuant(state.quant.next()), //key(Char('<')) => SetQuant(state.quant.prev()), //key(Char('>')) => SetQuant(state.quant.next()), //}); //keymap!(TRANSPORT_SYNC_KEYS = |sync: Clock, input: Event | ClockCommand { //key(Char(',')) => SetSync(state.sync.prev()), //key(Char('.')) => SetSync(state.sync.next()), //key(Char('<')) => SetSync(state.sync.prev()), //key(Char('>')) => SetSync(state.sync.next()), //}); //keymap!(TRANSPORT_SEEK_KEYS = |state: Clock, input: Event| ClockCommand { //key(Char(',')) => todo!("transport seek bar"), //key(Char('.')) => todo!("transport seek bar"), //key(Char('<')) => todo!("transport seek beat"), //key(Char('>')) => todo!("transport seek beat"), //});