mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
TransportView it is
This commit is contained in:
parent
cda0708642
commit
4ce4742959
5 changed files with 131 additions and 147 deletions
|
|
@ -166,7 +166,10 @@ impl ClockModel {
|
||||||
match self.transport.query_state()? {
|
match self.transport.query_state()? {
|
||||||
TransportState::Rolling => {
|
TransportState::Rolling => {
|
||||||
if started.is_none() {
|
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 => {
|
TransportState::Stopped => {
|
||||||
|
|
|
||||||
|
|
@ -111,14 +111,7 @@ render!(|self: ArrangerTui|{
|
||||||
let arranger_focused = self.arranger_focused();
|
let arranger_focused = self.arranger_focused();
|
||||||
let border = Lozenge(Style::default().bg(TuiTheme::border_bg()).fg(TuiTheme::border_fg(arranger_focused)));
|
let border = Lozenge(Style::default().bg(TuiTheme::border_bg()).fg(TuiTheme::border_fg(arranger_focused)));
|
||||||
col!([
|
col!([
|
||||||
lay!([
|
TransportView::from(self),
|
||||||
Tui::fill_x(Lozenge(Style::default())),
|
|
||||||
Tui::outset_xy(1, 1, row!([
|
|
||||||
WorldClock::from(self),
|
|
||||||
" ",
|
|
||||||
PlayClock::from(self),
|
|
||||||
]))
|
|
||||||
]),
|
|
||||||
col!([
|
col!([
|
||||||
Tui::fixed_y(self.splits[0], lay!([
|
Tui::fixed_y(self.splits[0], lay!([
|
||||||
border.wrap(Tui::grow_y(1, Layers::new(move |add|{
|
border.wrap(Tui::grow_y(1, Layers::new(move |add|{
|
||||||
|
|
|
||||||
|
|
@ -90,30 +90,23 @@ render!(|self: SequencerTui|{
|
||||||
//col_up!([
|
//col_up!([
|
||||||
//Tui::max_y(2, SequencerStatusBar::from(self)),
|
//Tui::max_y(2, SequencerStatusBar::from(self)),
|
||||||
col!([
|
col!([
|
||||||
lay!([
|
TransportView::from(self),
|
||||||
Tui::fill_x(Lozenge(Style::default())),
|
row!([
|
||||||
Tui::outset_xy(1, 1, row!([
|
Tui::fixed_x(20, col!([
|
||||||
WorldClock::from(self),
|
PhraseSelector::play_phrase(
|
||||||
" ",
|
|
||||||
PlayClock::from(self),
|
|
||||||
]))
|
|
||||||
]),
|
|
||||||
Tui::min_y(20, row!([
|
|
||||||
Tui::min_x(20, col!([
|
|
||||||
Tui::fixed_y(4, PhraseSelector::play_phrase(
|
|
||||||
&self.player,
|
&self.player,
|
||||||
self.focused() == SequencerFocus::PhrasePlay,
|
self.focused() == SequencerFocus::PhrasePlay,
|
||||||
self.entered()
|
self.entered()
|
||||||
)),
|
),
|
||||||
Tui::fixed_y(4, PhraseSelector::next_phrase(
|
PhraseSelector::next_phrase(
|
||||||
&self.player,
|
&self.player,
|
||||||
self.focused() == SequencerFocus::PhraseNext,
|
self.focused() == SequencerFocus::PhraseNext,
|
||||||
self.entered()
|
self.entered()
|
||||||
)),
|
),
|
||||||
PhraseListView::from(self)
|
PhraseListView::from(self)
|
||||||
])),
|
])),
|
||||||
PhraseView::from(self)
|
PhraseView::from(self)
|
||||||
]))
|
])
|
||||||
])
|
])
|
||||||
//])
|
//])
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,15 @@ use crate::api::ClockCommand::{SetBpm, SetQuant, SetSync};
|
||||||
use TransportCommand::{Focus, Clock};
|
use TransportCommand::{Focus, Clock};
|
||||||
use KeyCode::{Enter, Left, Right, Char};
|
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.
|
/// Create app state from JACK handle.
|
||||||
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportTui {
|
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportTui {
|
||||||
type Error = Box<dyn std::error::Error>;
|
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 {
|
impl std::fmt::Debug for TransportTui {
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
f.debug_struct("TransportTui")
|
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
|
/// Which item of the transport toolbar is focused
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum TransportFocus {
|
pub enum TransportFocus {
|
||||||
|
|
|
||||||
|
|
@ -35,27 +35,24 @@ impl<'a> PhraseSelector<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Display phrases always in order of appearance
|
// TODO: Display phrases always in order of appearance
|
||||||
render!(|self:PhraseSelector<'a>|{
|
render!(|self: PhraseSelector<'a>|{
|
||||||
let Self { title, phrase, focused, entered } = self;
|
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_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 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)};
|
let title_color = if *focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)};
|
||||||
Tui::over(
|
Tui::fixed_y(4, lay!([
|
||||||
border.wrap(content),
|
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)))),
|
Tui::fill_xy(Tui::at_nw(Tui::push_x(1, Tui::fg(title_color, *title)))),
|
||||||
)
|
]))
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue