move string format results to ViewCache

This commit is contained in:
🪞👃🪞 2025-01-21 15:51:27 +01:00
parent 415dc444ea
commit 2ea6a7bd8b
4 changed files with 133 additions and 138 deletions

View file

@ -110,13 +110,6 @@ impl Tek {
keys_track: SourceIter(KEYS_TRACK),
keys_scene: SourceIter(KEYS_SCENE),
keys_mix: SourceIter(KEYS_MIX),
fmtd_beat: Arc::new(RwLock::new(String::with_capacity(16))),
fmtd_time: Arc::new(RwLock::new(String::with_capacity(16))),
fmtd_bpm: Arc::new(RwLock::new(String::with_capacity(16))),
fmtd_sr: Arc::new(RwLock::new(String::with_capacity(16))),
fmtd_buf: Arc::new(RwLock::new(String::with_capacity(16))),
fmtd_lat: Arc::new(RwLock::new(String::with_capacity(16))),
fmtd_stop: "".into(),
..Default::default()
};
tek.sync_lead(sync_lead);

View file

@ -27,4 +27,3 @@ mod model; pub use self::model::*;
mod view; pub use self::view::*;
mod keys; pub use self::keys::*;
mod audio; pub use self::audio::*;
use std::fmt::Write;

View file

@ -34,13 +34,7 @@ use crate::*;
pub keys_scene: SourceIter<'static>,
pub keys_mix: SourceIter<'static>,
pub fmtd_beat: Arc<RwLock<String>>,
pub fmtd_time: Arc<RwLock<String>>,
pub fmtd_bpm: Arc<RwLock<String>>,
pub fmtd_sr: Arc<RwLock<String>>,
pub fmtd_buf: Arc<RwLock<String>>,
pub fmtd_lat: Arc<RwLock<String>>,
pub fmtd_stop: Arc<str>,
pub fmtd: ViewCache,
}
has_size!(<TuiOut>|self: Tek|&self.size);
has_clock!(|self: Tek|self.clock);

View file

@ -1,4 +1,27 @@
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>>,
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))),
stop: "".into(),
}
}
}
view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); {
":editor" => (&self.editor).boxed(),
":pool" => self.view_pool().boxed(),
@ -15,7 +38,7 @@ view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); {
self.scenes().len()))))).boxed(),
});
provide_num!(u16: |self: Tek| {
":sidebar-w" => self.sidebar_w(),
":sidebar-w" => self.w_sidebar(),
":sample-h" => if self.is_editing() { 0 } else { 5 },
":samples-w" => if self.is_editing() { 4 } else { 11 },
":samples-y" => if self.is_editing() { 1 } else { 0 },
@ -34,6 +57,14 @@ macro_rules! per_track {
}))).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()
}
}
impl Tek {
fn view_clock (&self) -> impl Content<TuiOut> + use<'_> {
Outer(false, Style::default().fg(Tui::g(0))).enclose(row!(
@ -46,9 +77,9 @@ impl Tek {
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();
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();
@ -63,21 +94,21 @@ impl Tek {
}
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()))))
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<'_> {
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();
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();
@ -86,12 +117,12 @@ impl Tek {
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())))
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())))
}
fn view_meter <'a> (&'a self, label: &'a str, value: f32) -> impl Content<TuiOut> + 'a {
col!(
@ -114,7 +145,7 @@ impl Tek {
else { Green }, ())))
}
fn view_meters (&self, values: &[f32;2]) -> impl Content<TuiOut> + use<'_> {
col!(
Bsp::s(
format!("L/{:>+9.3}", values[0]),
format!("R/{:>+9.3}", values[1]),
)
@ -143,30 +174,82 @@ impl Tek {
self.selected().scene().unwrap_or(0),
self.scenes().len()))
}
fn view_tracks (&self) -> impl Content<TuiOut> + use<'_> {
let h = 1;
self.view_row(self.w(), 1, self.track_header(), self.track_cells())
fn view_row <'a> (
&'a self, w: u16, h: u16, a: impl Content<TuiOut> + 'a, b: impl Content<TuiOut> + 'a
) -> impl Content<TuiOut> + 'a {
Fixed::y(h, Bsp::e(
Fixed::x(self.w_sidebar() as u16, a),
Fill::x(Align::c(Fixed::xy(w, h, b)))
))
}
fn view_inputs (&self) -> impl Content<TuiOut> + use<'_> {
let h = 1 + self.midi_ins.len() as u16;
self.view_row(self.w(), h, self.input_header(), self.input_cells())
let fg = Tui::g(224);
let bg = Tui::g(64);
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())))),
input.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))))));
let rec = false;
let mon = false;
let cells: ThunkBox<_> = per_track!(|self, track, _t|Bsp::s(Tui::bold(true, row!(
Tui::fg_bg(if rec { White } else { track.color.light.rgb }, track.color.dark.rgb, "Rcrd"),
Tui::fg_bg(if rec { White } else { track.color.dark.rgb }, track.color.dark.rgb, ""),
Tui::fg_bg(if mon { White } else { track.color.light.rgb }, track.color.dark.rgb, "Mntr"),
)), row!(
Tui::fg_bg(if rec { White } else { track.color.light.rgb }, track.color.darker.rgb, "CH**"),
Tui::fg_bg(if rec { White } else { track.color.dark.rgb }, track.color.darker.rgb, ""),
Tui::fg_bg(if mon { White } else { track.color.light.rgb }, track.color.darker.rgb, "CH**"),
)));
self.view_row(self.w(), h, header, cells)
}
fn view_outputs (&self) -> impl Content<TuiOut> + use<'_> {
let h = 1 + self.midi_outs.len();
self.view_row(self.w(), h as u16,
self.output_header(),
self.output_cells())
let fg = Tui::g(224);
let bg = Tui::g(64);
let h = 1 + self.midi_outs.len() as u16;
let header: ThunkBox<_> = io_header!(self, " O ", " midi outs", self.midi_outs.len(), self.midi_outs().get(0).map(
move|output: &JackPort<MidiOut>|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(output.name.clone())))),
output.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))))));
let mute = false;
let solo = false;
let cells: ThunkBox<_> = per_track!(|self, track, _t|Bsp::s(Tui::bold(true, row!(
Tui::fg_bg(if mute { White } else { track.color.light.rgb }, track.color.dark.rgb, "Mute"),
Tui::fg_bg(if mute { White } else { track.color.dark.rgb }, track.color.dark.rgb, ""),
Tui::fg_bg(if solo { White } else { track.color.light.rgb }, track.color.dark.rgb, "Solo"),
)), row!(
Tui::fg_bg(if mute { White } else { track.color.light.rgb }, track.color.darker.rgb, "CH**"),
Tui::fg_bg(if mute { White } else { track.color.darker.rgb }, track.color.darker.rgb, ""),
Tui::fg_bg(if solo { White } else { track.color.light.rgb }, track.color.darker.rgb, "CH**"),
)));
self.view_row(self.w(), h, header, cells)
}
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();
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 cell = Bsp::e(
Tui::fg_bg(bg, bg2, ""),
Tui::fg_bg(fg, bg, Tui::bold(true, Fill::x(Align::nw(name)))));
Outer(active, bs).enclose(cell)
});
self.view_row(self.w(), 1, header, cells)
}
fn view_scenes (&self) -> impl Content<TuiOut> + use<'_> {
Outer(false, Style::default().fg(Tui::g(0))).enclose_bg({
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, self.scene_header(), self.clip_columns())
})
}
fn scene_header <'a> (&'a self) -> ThunkBox<'a, TuiOut> {
(move||{
let header: ThunkBox<_> = (move||{
let last_color = Arc::new(RwLock::new(ItemPalette::G[0]));
let iter = ||self.scenes_sizes(self.is_editing(), 2, 15);
Map::new(iter, move|(_, scene, y1, y2), i| {
@ -185,9 +268,7 @@ impl Tek {
map_south(y1 as u16, h, Push::y(1, Fixed::y(h,
Outer(false, Style::default().fg(Tui::g(0))).enclose(cell))))
}).boxed()
}).into()
}
fn clip_columns <'a> (&'a self) -> ThunkBox<'a, TuiOut> {
}).into();
let editing = self.is_editing();
let tracks = move||self.tracks_sizes(editing, self.editor_w());
let scenes = move||self.scenes_sizes(editing, 2, 15);
@ -195,9 +276,9 @@ impl Tek {
let selected_scene = self.selected().scene();
let border = |x|Outer(false, Style::default().fg(Tui::g(0))).enclose(x);
let d = 6 + self.midi_ins.len() + self.midi_outs.len();
(move||Align::c(Map::new(tracks, {
let cells: ThunkBox<_> = (move||Align::c(Map::new(tracks, {
let last_color = Arc::new(RwLock::new(ItemPalette::default()));
let area = self.size.w().saturating_sub(self.sidebar_w() as usize * 2);
let area = self.size.w().saturating_sub(self.w_sidebar() as usize * 2);
move|(_, track, x1, x2), t| {
let last_color = last_color.clone();
let same_track = selected_track == Some(t+1);
@ -247,7 +328,7 @@ impl Tek {
let name = clip.map(|c|c.read().unwrap().name.clone());
Align::nw(Tui::fg(fg, Bsp::e(icon, Bsp::e(Tui::bold(true, name), " "))))
};
let area = self.size.h().saturating_sub(d);//self.sidebar_w() as usize * 2);
let area = self.size.h().saturating_sub(d);//self.w_sidebar() as usize * 2);
Either(y2 > area, (), map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Either::new(active,
Thunk::new(move||Bsp::a(
Fill::xy(Align::nw(button(tab, label()))),
@ -266,96 +347,24 @@ impl Tek {
}))
}))).boxed()
}
})).boxed()).into()
}
fn view_row <'a> (
&'a self, w: u16, h: u16, a: impl Content<TuiOut> + 'a, b: impl Content<TuiOut> + 'a
) -> impl Content<TuiOut> + 'a {
Fixed::y(h, Bsp::e(
Fixed::x(self.sidebar_w() as u16, a),
Fill::x(Align::c(Fixed::xy(w, h, b)))
))
}
})).boxed()).into();
Outer(false, Style::default().fg(Tui::g(0))).enclose_bg({
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)}) }
fn w (&self) -> u16 {
self.tracks_sizes(self.is_editing(), self.editor_w())
.last()
.map(|x|x.3 as u16)
.unwrap_or(0)
}
fn sidebar_w (&self) -> u16 {
fn w_sidebar (&self) -> u16 {
let w = self.size.w();
let w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
let w = if self.is_editing() { 8 } else { w };
w
}
fn input_header <'a> (&'a self) -> ThunkBox<'a, TuiOut> {
let fg = Tui::g(224);
let bg = Tui::g(64);
let input = move|input: &JackPort<MidiIn>|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(input.name.clone())))),
input.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))));
(move||{
let label = format!(" midi ins ({})", self.midi_ins().len());
let button = Fill::x(Align::w(self.button(" I ", label)));
Bsp::s(button, self.midi_ins().get(0).map(input)).boxed()
}).into()
}
fn input_cells <'a> (&'a self) -> ThunkBox<'a, TuiOut> {
let rec = false;
let mon = false;
per_track!(|self, track, _t|Bsp::s(Tui::bold(true, row!(
Tui::fg_bg(if rec { White } else { track.color.light.rgb }, track.color.dark.rgb, "Rcrd"),
Tui::fg_bg(if rec { White } else { track.color.dark.rgb }, track.color.dark.rgb, ""),
Tui::fg_bg(if mon { White } else { track.color.light.rgb }, track.color.dark.rgb, "Mntr"),
)), row!(
Tui::fg_bg(if rec { White } else { track.color.light.rgb }, track.color.darker.rgb, "CH**"),
Tui::fg_bg(if rec { White } else { track.color.dark.rgb }, track.color.darker.rgb, ""),
Tui::fg_bg(if mon { White } else { track.color.light.rgb }, track.color.darker.rgb, "CH**"),
)))
}
fn output_header <'a> (&'a self) -> ThunkBox<'a, TuiOut> {
let fg = Tui::g(224);
let bg = Tui::g(64);
(move||Bsp::s(Fill::x(Align::w(self.button(" O ", format!(" midi outs ({}) ", self.midi_outs().len())))), self.midi_outs().get(0).map(|out|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(out.name.clone())))),
out.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, connect.info()))))),
))).boxed()).into()
}
fn output_cells <'a> (&'a self) -> ThunkBox<'a, TuiOut> {
let mute = false;
let solo = false;
per_track!(|self, track, _t|Bsp::s(Tui::bold(true, row!(
Tui::fg_bg(if mute { White } else { track.color.light.rgb }, track.color.dark.rgb, "Mute"),
Tui::fg_bg(if mute { White } else { track.color.dark.rgb }, track.color.dark.rgb, ""),
Tui::fg_bg(if solo { White } else { track.color.light.rgb }, track.color.dark.rgb, "Solo"),
)), row!(
Tui::fg_bg(if mute { White } else { track.color.light.rgb }, track.color.darker.rgb, "CH**"),
Tui::fg_bg(if mute { White } else { track.color.darker.rgb }, track.color.darker.rgb, ""),
Tui::fg_bg(if solo { White } else { track.color.light.rgb }, track.color.darker.rgb, "CH**"),
)))
}
fn track_header <'a> (&'a self) -> ThunkBox<'a, TuiOut> {
let add_track = ||self.button(" C-t ", format!(" add track ({}/{})",
self.selected.track().unwrap_or(0),
self.tracks().len()));
(move||Tui::bg(Tui::g(32), Fill::x(Align::w(add_track()))).boxed()).into()
}
fn track_cells <'a> (&'a self) -> ThunkBox<'a, TuiOut> {
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 cell = Bsp::e(
Tui::fg_bg(bg, bg2, ""),
Tui::fg_bg(fg, bg, Tui::bold(true, Fill::x(Align::nw(Bsp::e(" ", name))))));
Outer(active, bs).enclose(cell)
})
}
fn button <'a> (
&'a self, key: impl Content<TuiOut> + 'a, label: impl Content<TuiOut> + 'a
) -> impl Content<TuiOut> + 'a {