down to 5 format macro calls in main view.rs

This commit is contained in:
🪞👃🪞 2025-01-21 17:07:13 +01:00
parent 2ea6a7bd8b
commit 85507bf27e
2 changed files with 123 additions and 106 deletions

View file

@ -34,7 +34,7 @@ use crate::*;
pub keys_scene: SourceIter<'static>,
pub keys_mix: SourceIter<'static>,
pub fmtd: ViewCache,
pub(crate) fmtd: Arc<RwLock<ViewCache>>,
}
has_size!(<TuiOut>|self: Tek|&self.size);
has_clock!(|self: Tek|self.clock);

View file

@ -1,41 +1,58 @@
use crate::*;
use std::fmt::Write;
#[derive(Debug)] pub(crate) struct ViewCache {
beat: Arc<RwLock<String>>,
time: Arc<RwLock<String>>,
bpm: Arc<RwLock<String>>,
sr: Arc<RwLock<String>>,
buf: Arc<RwLock<String>>,
lat: Arc<RwLock<String>>,
sr: ViewMemo<Option<(bool, f64)>, String>,
buf: ViewMemo<Option<f64>, String>,
lat: ViewMemo<Option<f64>, String>,
bpm: ViewMemo<Option<f64>, String>,
beat: ViewMemo<Option<f64>, String>,
time: ViewMemo<Option<f64>, String>,
scns: ViewMemo<Option<(usize, usize)>, String>,
trks: ViewMemo<Option<(usize, usize)>, String>,
stop: Arc<str>,
}
impl Default for ViewCache {
fn default () -> Self {
Self {
beat: Arc::new(RwLock::new(String::with_capacity(16))),
time: Arc::new(RwLock::new(String::with_capacity(16))),
bpm: Arc::new(RwLock::new(String::with_capacity(16))),
sr: Arc::new(RwLock::new(String::with_capacity(16))),
buf: Arc::new(RwLock::new(String::with_capacity(16))),
lat: Arc::new(RwLock::new(String::with_capacity(16))),
beat: ViewMemo::new(None, String::with_capacity(16)),
time: ViewMemo::new(None, String::with_capacity(16)),
bpm: ViewMemo::new(None, String::with_capacity(16)),
sr: ViewMemo::new(None, String::with_capacity(16)),
buf: ViewMemo::new(None, String::with_capacity(16)),
lat: ViewMemo::new(None, String::with_capacity(16)),
scns: ViewMemo::new(None, String::with_capacity(16)),
trks: ViewMemo::new(None, String::with_capacity(16)),
stop: "".into(),
}
}
}
#[derive(Debug, Default)] struct ViewMemo<T, U> { value: T, view: Arc<RwLock<U>> }
impl<T, U> ViewMemo<T, U> {
fn new (value: T, view: U) -> Self { Self { value, view: Arc::new(view.into()) } }
}
impl<T: PartialEq, U> ViewMemo<T, U> {
fn update <R> (&mut self, newval: T, render: impl Fn(&mut U, &T, &T)->R) -> Option<R> {
if newval != self.value {
let result = render(&mut*self.view.write().unwrap(), &newval, &self.value);
self.value = newval;
return Some(result);
}
None
}
}
view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); {
":editor" => (&self.editor).boxed(),
":pool" => self.view_pool().boxed(),
":sample" => self.view_sample(self.is_editing()).boxed(),
":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(),
":status" => self.view_editor().boxed(),
":toolbar" => self.view_clock().boxed(),
":tracks" => self.view_tracks().boxed(),
":inputs" => self.view_inputs().boxed(),
":outputs" => self.view_outputs().boxed(),
":scenes" => self.view_scenes().boxed(),
":scene-add" => Fill::x(Align::x(Fixed::x(23, button(" C-a ", format!(" add scene ({}/{})",
self.selected().scene().unwrap_or(0),
self.scenes().len()))))).boxed(),
":editor" => (&self.editor).boxed(),
":pool" => self.view_pool().boxed(),
":sample" => self.view_sample(self.is_editing()).boxed(),
":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(),
":status" => self.view_editor().boxed(),
":toolbar" => self.view_clock().boxed(),
":tracks" => self.view_tracks().boxed(),
":track-add" => self.view_track_add().boxed(),
":inputs" => self.view_inputs().boxed(),
":outputs" => self.view_outputs().boxed(),
":scenes" => self.view_scenes().boxed(),
":scene-add" => self.view_scene_add().boxed(),
});
provide_num!(u16: |self: Tek| {
":sidebar-w" => self.w_sidebar(),
@ -44,8 +61,7 @@ provide_num!(u16: |self: Tek| {
":samples-y" => if self.is_editing() { 1 } else { 0 },
":pool-w" => if self.is_editing() { 5 } else {
let w = self.size.w();
if w > 60 { 20 } else if w > 40 { 15 } else { 10 }
} });
if w > 60 { 20 } else if w > 40 { 15 } else { 10 } } });
macro_rules! per_track {
(|$self:ident,$track:ident,$index:ident|$content:expr) => {{
let tracks = ||$self.tracks_sizes($self.is_editing(), $self.editor_w());
@ -53,76 +69,70 @@ macro_rules! per_track {
let width = (x2 - x1) as u16;
let content = Fixed::y(1, $content);
let styled = Tui::fg_bg($track.color.lightest.rgb, $track.color.base.rgb, content);
map_east(x1 as u16, width, Fixed::x(width, styled))
}))).into()
}}
}
map_east(x1 as u16, width, Fixed::x(width, styled)) }))).into() }} }
macro_rules! io_header {
($self:ident, $key:expr, $label:expr, $count:expr, $content:expr) => {
(move||{
let button = $self.button($key, format!("{:10} ({})", $label, $count));
Bsp::s(Fill::x(Align::w(button)), $content).boxed()
}).into()
}
}
Bsp::s(Fill::x(Align::w(button)), $content).boxed() }).into() } }
macro_rules! rewrite {
($buf:ident, $($rest:tt)*) => { |$buf,_,_|{$buf.clear();write!($buf, $($rest)*)} } }
impl Tek {
fn view_clock (&self) -> impl Content<TuiOut> + use<'_> {
Outer(false, Style::default().fg(Tui::g(0))).enclose(row!(
self.view_engine_stats(), " ",
self.view_play_pause(), " ",
self.view_beat_stats(),
))
}
fn view_beat_stats (&self) -> impl Content<TuiOut> + use<'_> {
let compact = self.size.w() > 80;
let clock = self.clock();
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
let mut fmtd_beat = self.fmtd.beat.write().unwrap();
let mut fmtd_time = self.fmtd.time.write().unwrap();
let mut fmtd_bpm = self.fmtd.bpm.write().unwrap();
fmtd_beat.clear();
fmtd_time.clear();
fmtd_bpm.clear();
if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) {
clock.timebase.format_beats_1_to(&mut*fmtd_beat, clock.timebase.usecs_to_pulse(now));
write!(&mut fmtd_time, "{:.3}s", now/1000000.);
write!(&mut fmtd_bpm, "{:.3}", clock.timebase.bpm.get());
} else {
write!(&mut fmtd_beat, "-.-.--");
write!(&mut fmtd_time, "-.---s");
write!(&mut fmtd_bpm, "---.---");
}
let theme = ItemPalette::G[128];
Thunk::new(move||Either::new(compact,
row!(FieldH(theme, "BPM", self.fmtd.bpm.clone()),
FieldH(theme, "Beat", self.fmtd.beat.clone()),
FieldH(theme, "Time", self.fmtd.time.clone())),
row!(FieldV(theme, "BPM", self.fmtd.bpm.clone()),
FieldV(theme, "Beat", self.fmtd.beat.clone()),
FieldV(theme, "Time", self.fmtd.time.clone()))))
}
fn view_engine_stats (&self) -> impl Content<TuiOut> + use<'_> {
fn update_clock (&self) {
let compact = self.size.w() > 80;
let clock = self.clock();
let rate = clock.timebase.sr.get();
let chunk = clock.chunk.load(Relaxed);
let mut fmtd_sr = self.fmtd.sr.write().unwrap();
let mut fmtd_buf = self.fmtd.buf.write().unwrap();
let mut fmtd_lat = self.fmtd.lat.write().unwrap();
fmtd_sr.clear();
write!(&mut fmtd_sr, "{}", if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)});
fmtd_buf.clear();
write!(&mut fmtd_buf, "{chunk}");
fmtd_lat.clear();
write!(&mut fmtd_lat, "{:.1}ms", chunk as f64 / rate * 1000.);
let theme = ItemPalette::G[128];
Either::new(compact,
row!(FieldH(theme, "SR", self.fmtd.sr.clone()),
FieldH(theme, "Buf", self.fmtd.buf.clone()),
FieldH(theme, "Lat", self.fmtd.lat.clone())),
row!(FieldV(theme, "SR", self.fmtd.sr.clone()),
FieldV(theme, "Buf", self.fmtd.buf.clone()),
FieldV(theme, "Lat", self.fmtd.lat.clone())))
let chunk = clock.chunk.load(Relaxed) as f64;
let lat = chunk / rate * 1000.;
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
let mut fmtd = self.fmtd.write().unwrap();
fmtd.buf.update(Some(chunk), rewrite!(buf, "{chunk}"));
fmtd.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms"));
fmtd.sr.update(Some((compact, rate)), |buf,_,_|if compact {
buf.clear(); write!(buf, "{:.1}kHz", rate / 1000.)
} else {
buf.clear(); write!(buf, "{:.0}Hz", rate)
});
if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) {
let pulse = clock.timebase.usecs_to_pulse(now);
let time = now/1000000.;
let bpm = clock.timebase.bpm.get();
fmtd.beat.update(Some(pulse), |buf, _, _|clock.timebase.format_beats_1_to(buf, pulse));
fmtd.time.update(Some(time), rewrite!(buf, "{:.3}s", time));
fmtd.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm));
} else {
fmtd.beat.update(None, rewrite!(buf, "-.-.--"));
fmtd.time.update(None, rewrite!(buf, "-.---s"));
fmtd.bpm.update(None, rewrite!(buf, "---.---"));
}
}
fn view_clock (&self) -> impl Content<TuiOut> + use<'_> {
self.update_clock();
let compact = self.size.w() > 80;
let theme = ItemPalette::G[96];
Outer(false, Style::default().fg(Tui::g(0))).enclose(row!(
Thunk::new(move||{
let fmtd = self.fmtd.read().unwrap();
Either::new(compact,
row!(FieldH(theme, "SR", fmtd.sr.view.clone()),
FieldH(theme, "Buf", fmtd.buf.view.clone()),
FieldH(theme, "Lat", fmtd.lat.view.clone())),
row!(FieldV(theme, "SR", fmtd.sr.view.clone()),
FieldV(theme, "Buf", fmtd.buf.view.clone()),
FieldV(theme, "Lat", fmtd.lat.view.clone()))) }),
" ",
self.view_play_pause(),
" ",
Thunk::new(move||{
let fmtd = self.fmtd.read().unwrap();
Either::new(compact,
row!(FieldH(theme, "BPM", fmtd.bpm.view.clone()),
FieldH(theme, "Beat", fmtd.beat.view.clone()),
FieldH(theme, "Time", fmtd.time.view.clone())),
row!(FieldV(theme, "BPM", fmtd.bpm.view.clone()),
FieldV(theme, "Beat", fmtd.beat.view.clone()),
FieldV(theme, "Time", fmtd.time.view.clone()))) }),
))
}
fn view_meter <'a> (&'a self, label: &'a str, value: f32) -> impl Content<TuiOut> + 'a {
col!(
@ -169,11 +179,6 @@ impl Tek {
fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
self.pool.as_ref().map(|pool|PoolView(self.is_editing(), pool))
}
fn view_scene_add (&self) -> impl Content<TuiOut> + use<'_> {
button(" C-a ", format!(" add scene ({}/{})",
self.selected().scene().unwrap_or(0),
self.scenes().len()))
}
fn view_row <'a> (
&'a self, w: u16, h: u16, a: impl Content<TuiOut> + 'a, b: impl Content<TuiOut> + 'a
) -> impl Content<TuiOut> + 'a {
@ -185,7 +190,7 @@ impl Tek {
fn view_inputs (&self) -> impl Content<TuiOut> + use<'_> {
let fg = Tui::g(224);
let bg = Tui::g(64);
let h = 1 + self.midi_ins.len() as u16;
let h = 1 + self.midi_ins.len() as u16;
let header: ThunkBox<_> = io_header!(self, " I ", " midi ins", self.midi_ins.len(), self.midi_ins().get(0).map(
move|input: &JackPort<MidiIn>|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(input.name.clone())))),
@ -228,19 +233,16 @@ impl Tek {
}
fn view_tracks (&self) -> impl Content<TuiOut> + use<'_> {
let h = 1;
let add_track = ||self.button(" C-t ", format!(" add track ({}/{})",
self.selected.track().unwrap_or(0),
self.tracks().len()));
let header: ThunkBox<_> =
(move||Tui::bg(Tui::g(32), Fill::x(Align::w(add_track()))).boxed()).into();
(move||Tui::bg(Tui::g(32), Fill::x(Align::w(self.view_track_add()))).boxed()).into();
let cells: ThunkBox<_> = per_track!(|self, track, t|{
let active = self.selected().track() == Some(t+1);
let name = &track.name;
let fg = track.color.lightest.rgb;
let bg = if active { track.color.light.rgb } else { track.color.base.rgb };
let bg2 = if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset };
let bfg = if active { Rgb(255,255,255) } else { Rgb(0,0,0) };
let bs = Style::default().fg(bfg).bg(bg);
let fg = track.color.lightest.rgb;
let bg = if active { track.color.light.rgb } else { track.color.base.rgb };
let bg2 = if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset };
let bfg = if active { Rgb(255,255,255) } else { Rgb(0,0,0) };
let bs = Style::default().fg(bfg).bg(bg);
let cell = Bsp::e(
Tui::fg_bg(bg, bg2, ""),
Tui::fg_bg(fg, bg, Tui::bold(true, Fill::x(Align::nw(name)))));
@ -248,6 +250,20 @@ impl Tek {
});
self.view_row(self.w(), 1, header, cells)
}
fn view_track_add (&self) -> impl Content<TuiOut> + use<'_> {
let data = (self.selected.track().unwrap_or(0), self.tracks().len());
self.fmtd.write().unwrap().trks.update(Some(data),
rewrite!(buf, "({}/{})", data.0, data.1));
button(" C-t ", Bsp::e(" add track ",
self.fmtd.read().unwrap().trks.view.clone()))
}
fn view_scene_add (&self) -> impl Content<TuiOut> + use<'_> {
let data = (self.selected().scene().unwrap_or(0), self.scenes().len());
self.fmtd.write().unwrap().scns.update(Some(data),
rewrite!(buf, "({}/{})", data.0, data.1));
button(" C-a ", Bsp::e(" add scene ",
self.fmtd.read().unwrap().scns.view.clone()))
}
fn view_scenes (&self) -> impl Content<TuiOut> + use<'_> {
let header: ThunkBox<_> = (move||{
let last_color = Arc::new(RwLock::new(ItemPalette::G[0]));
@ -352,7 +368,8 @@ impl Tek {
let w = self.w();
let d = 6 + self.midi_ins.len() + self.midi_outs.len();
let h = self.size.h().saturating_sub(d) as u16;
self.view_row(w, h, header, cells)}) }
self.view_row(w, h, header, cells)})
}
fn w (&self) -> u16 {
self.tracks_sizes(self.is_editing(), self.editor_w())
.last()