TransportView it is

This commit is contained in:
🪞👃🪞 2024-12-09 23:17:46 +01:00
parent cda0708642
commit 4ce4742959
5 changed files with 131 additions and 147 deletions

View file

@ -166,7 +166,10 @@ impl ClockModel {
match self.transport.query_state()? {
TransportState::Rolling => {
if started.is_none() {
*started = Some(Moment::from_sample(&self.timebase, current_frames as f64));
let moment = Moment::zero(&self.timebase);
moment.sample.set(current_frames as f64);
moment.usec.set(current_usecs as f64);
*started = Some(moment);
}
},
TransportState::Stopped => {

View file

@ -111,14 +111,7 @@ render!(|self: ArrangerTui|{
let arranger_focused = self.arranger_focused();
let border = Lozenge(Style::default().bg(TuiTheme::border_bg()).fg(TuiTheme::border_fg(arranger_focused)));
col!([
lay!([
Tui::fill_x(Lozenge(Style::default())),
Tui::outset_xy(1, 1, row!([
WorldClock::from(self),
" ",
PlayClock::from(self),
]))
]),
TransportView::from(self),
col!([
Tui::fixed_y(self.splits[0], lay!([
border.wrap(Tui::grow_y(1, Layers::new(move |add|{

View file

@ -90,30 +90,23 @@ render!(|self: SequencerTui|{
//col_up!([
//Tui::max_y(2, SequencerStatusBar::from(self)),
col!([
lay!([
Tui::fill_x(Lozenge(Style::default())),
Tui::outset_xy(1, 1, row!([
WorldClock::from(self),
" ",
PlayClock::from(self),
]))
]),
Tui::min_y(20, row!([
Tui::min_x(20, col!([
Tui::fixed_y(4, PhraseSelector::play_phrase(
TransportView::from(self),
row!([
Tui::fixed_x(20, col!([
PhraseSelector::play_phrase(
&self.player,
self.focused() == SequencerFocus::PhrasePlay,
self.entered()
)),
Tui::fixed_y(4, PhraseSelector::next_phrase(
),
PhraseSelector::next_phrase(
&self.player,
self.focused() == SequencerFocus::PhraseNext,
self.entered()
)),
),
PhraseListView::from(self)
])),
PhraseView::from(self)
]))
])
])
//])
});

View file

@ -3,6 +3,15 @@ use crate::api::ClockCommand::{SetBpm, SetQuant, SetSync};
use TransportCommand::{Focus, Clock};
use KeyCode::{Enter, Left, Right, Char};
/// Transport clock app.
pub struct TransportTui {
pub jack: Arc<RwLock<JackClient>>,
pub clock: ClockModel,
pub size: Measure<Tui>,
pub cursor: (usize, usize),
pub focus: FocusState<TransportFocus>,
}
/// Create app state from JACK handle.
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportTui {
type Error = Box<dyn std::error::Error>;
@ -17,111 +26,6 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for TransportTui {
}
}
/// Stores and displays time-related info.
pub struct TransportTui {
pub jack: Arc<RwLock<JackClient>>,
pub clock: ClockModel,
pub size: Measure<Tui>,
pub cursor: (usize, usize),
pub focus: FocusState<TransportFocus>,
}
impl JackApi for TransportTui {
fn jack (&self) -> &Arc<RwLock<JackClient>> {
&self.jack
}
}
impl Audio for TransportTui {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
ClockAudio(self).process(client, scope)
}
}
pub struct WorldClock {
rate: String,
sample: String,
second: String,
}
impl<T: HasClock> From<&T> for WorldClock {
fn from (state: &T) -> Self {
let clock = state.clock();
let rate = format!("{}", clock.timebase.sr.get());
if let Some(started) = clock.started.read().unwrap().as_ref() {
Self {
rate,
sample: format!("{:.0}", started.sample.get()),
second: format!("{:.0}", started.usec.get()),
}
} else {
Self {
rate,
sample: format!("{:.0}", clock.global.sample.get()),
second: format!("{:.0}", clock.global.usec.get()),
}
}
}
}
render!(|self: WorldClock|row!([
col!(["Rate", self.rate ]), " ",
col!(["Sample", self.sample]), " ",
col!(["Second", self.second]), " ",
]));
pub struct PlayClock {
started: bool,
sample: String,
second: String,
}
impl<T: HasClock> From<&T> for PlayClock {
fn from (state: &T) -> Self {
let clock = state.clock();
if let Some(started) = clock.started.read().unwrap().as_ref() {
Self {
started: true,
sample: format!("{:.0}", clock.global.sample.get() - started.sample.get()),
second: format!("{:.0}", clock.global.usec.get() - started.usec.get()),
}
} else {
Self {
started: false,
sample: "".to_string(),
second: "".to_string(),
}
}
}
}
render!(|self: PlayClock|lay!(|add|{
if self.started {
add(&row!([
col!(["", Tui::fg(Color::Rgb(0, 255, 0), "▶ PLAYING ")]),
" ",
col!(["Sample", self.sample]),
" ",
col!(["Second", self.second]),
" ",
col!(["Beat", "00B 0b 00/00"]),
]))
} else {
add(&col!([Tui::fg(Color::Rgb(255, 128, 0), "⏹ STOPPED "), ""]))
}
}));
render!(|self: TransportTui|{
let bg = TuiTheme::border_bg();
let border_style = Style::default().bg(bg).fg(TuiTheme::border_fg(false));
lay!([
Tui::fill_x(Lozenge(border_style)),
Tui::bg(bg, Tui::outset_xy(1, 1, row!([
WorldClock::from(self),
" ",
PlayClock::from(self),
])))
])//Tui::to_south(world, timer)))
});
impl std::fmt::Debug for TransportTui {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("TransportTui")
@ -138,6 +42,100 @@ impl HasClock for TransportTui {
}
}
impl JackApi for TransportTui {
fn jack (&self) -> &Arc<RwLock<JackClient>> {
&self.jack
}
}
impl Audio for TransportTui {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
ClockAudio(self).process(client, scope)
}
}
render!(|self: TransportTui|TransportView::from(self));
pub struct TransportView {
sr: String,
bpm: String,
ppq: String,
global_sample: String,
global_second: String,
started: bool,
current_sample: String,
current_second: String,
}
impl<T: HasClock> From<&T> for TransportView {
fn from (state: &T) -> Self {
let clock = state.clock();
let sr = format!("{:.1}k", clock.timebase.sr.get() / 1000.0);
let bpm = format!("{:.3}", clock.timebase.bpm.get());
let ppq = format!("{:.0}", clock.timebase.ppq.get());
if let Some(started) = clock.started.read().unwrap().as_ref() {
Self {
sr,
bpm,
ppq,
started: true,
global_sample: format!("{:.0}", started.sample.get()),
global_second: format!("{:.0}", started.usec.get()),
current_sample: format!("{:.0}", clock.global.sample.get() - started.sample.get()),
current_second: format!("{:.0}", clock.global.usec.get() - started.usec.get()),
}
} else {
Self {
sr,
bpm,
ppq,
started: false,
global_sample: format!("{:.0}", clock.global.sample.get()),
global_second: format!("{:.0}", clock.global.usec.get()),
current_sample: "".to_string(),
current_second: "".to_string(),
}
}
}
}
render!(|self: TransportView|{
let bg = TuiTheme::border_bg();
let border_style = Style::default().bg(bg).fg(TuiTheme::border_fg(false));
lay!([
Tui::fill_x(Lozenge(border_style)),
Tui::bg(bg, Tui::outset_xy(1, 1, row!([
row!([
col!(["SR", self.sr ]), " ",
col!(["BPM", self.bpm]), " ",
col!(["PPQ", self.ppq]), " ",
]),
row!([
col!(["Sample", self.global_sample]), " ",
col!(["Second", self.global_second]), " ",
]),
lay!(|add|{
if self.started {
add(&row!([
col!(["", Tui::fg(Color::Rgb(0, 255, 0), "▶ PLAYING ")]),
" ",
col!(["Sample", self.current_sample]),
" ",
col!(["Second", self.current_second]),
" ",
col!(["Beat", "00B 0b 00/00"]),
]))
} else {
add(&col!([Tui::fg(Color::Rgb(255, 128, 0), "⏹ STOPPED "), ""]))
}
}),
])))
])
});
/// Which item of the transport toolbar is focused
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TransportFocus {

View file

@ -35,27 +35,24 @@ impl<'a> PhraseSelector<'a> {
}
// TODO: Display phrases always in order of appearance
render!(|self:PhraseSelector<'a>|{
render!(|self: PhraseSelector<'a>|{
let Self { title, phrase, focused, entered } = self;
let content = Layers::new(move|add|{
if let Some((instant, Some(phrase))) = phrase {
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
let length = PhraseLength::new(length, None);
let length = Tui::fill_x(Tui::at_e(length));
let row1 = Tui::fill_x(lay!([Tui::fill_x(Tui::at_w(format!(" "))), length]));
let row2 = format!(" {name}");
let row2 = Tui::bold(true, row2);
add(&Tui::bg(color.base.rgb, Tui::fill_x(Tui::to_south(row1, row2))))?;
}
Ok(())
});
let border_color = if *focused {Color::Rgb(100, 110, 40)} else {Color::Rgb(70, 80, 50)};
let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color));
//let content = Tui::bg(Color::Rgb(28, 35, 25), Tui::fill_xy(content)).border(border);
let content = Tui::bg(Color::Rgb(28, 35, 25), Tui::fill_xy(content));
let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)};
Tui::over(
border.wrap(content),
Tui::fixed_y(4, lay!([
border.wrap(Tui::bg(Color::Rgb(28, 35, 25), Tui::fill_xy(Layers::new(move|add|{
if let Some((instant, Some(phrase))) = phrase {
let Phrase { ref name, color, length, .. } = *phrase.read().unwrap();
let length = PhraseLength::new(length, None);
let length = Tui::fill_x(Tui::at_e(length));
let row1 = Tui::fill_x(lay!([Tui::fill_x(Tui::at_w(format!(" "))), length]));
let row2 = format!(" {name}");
let row2 = Tui::bold(true, row2);
add(&Tui::bg(color.base.rgb, Tui::fill_x(Tui::to_south(row1, row2))))?;
}
Ok(())
})))),
Tui::fill_xy(Tui::at_nw(Tui::push_x(1, Tui::fg(title_color, *title)))),
)
]))
});