remove 1 more per-cell allocation

This commit is contained in:
🪞👃🪞 2025-01-20 23:12:57 +01:00
parent f7dcc28e1f
commit 93462e7501
2 changed files with 128 additions and 92 deletions

View file

@ -17,7 +17,7 @@ pub use ::tek_sampler::{self, *};
pub use ::tek_plugin::{self, *}; pub use ::tek_plugin::{self, *};
pub use ::tek_tui::{ pub use ::tek_tui::{
*, tek_edn::*, tek_input::*, tek_output::*, *, tek_edn::*, tek_input::*, tek_output::*,
ratatui, ratatui::{prelude::{Color, Style, Stylize, Buffer, Modifier}, buffer::Cell}, ratatui, ratatui::{prelude::{Color::{self, *}, Style, Stylize, Buffer, Modifier}, buffer::Cell},
crossterm, crossterm::event::{ crossterm, crossterm::event::{
Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, KeyCode::{self, *}, Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, KeyCode::{self, *},
}, },
@ -157,6 +157,7 @@ impl TekCli {
pub fmtd_sr: Arc<RwLock<String>>, pub fmtd_sr: Arc<RwLock<String>>,
pub fmtd_buf: Arc<RwLock<String>>, pub fmtd_buf: Arc<RwLock<String>>,
pub fmtd_lat: Arc<RwLock<String>>, pub fmtd_lat: Arc<RwLock<String>>,
pub fmtd_stop: Arc<str>,
} }
has_size!(<TuiOut>|self: Tek|&self.size); has_size!(<TuiOut>|self: Tek|&self.size);
has_clock!(|self: Tek|self.clock); has_clock!(|self: Tek|self.clock);
@ -238,6 +239,7 @@ impl Tek {
fmtd_sr: 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_buf: Arc::new(RwLock::new(String::with_capacity(16))),
fmtd_lat: Arc::new(RwLock::new(String::with_capacity(16))), fmtd_lat: Arc::new(RwLock::new(String::with_capacity(16))),
fmtd_stop: "".into(),
..Default::default() ..Default::default()
}; };
tek.sync_lead(sync_lead); tek.sync_lead(sync_lead);
@ -452,9 +454,9 @@ impl Tek {
else if value >= -25.0 { 3 } else if value >= -25.0 { 3 }
else if value >= -30.0 { 2 } else if value >= -30.0 { 2 }
else if value >= -40.0 { 1 } else if value >= -40.0 { 1 }
else { 0 }, 1, Tui::bg(if value >= 0.0 { Color::Red } else { 0 }, 1, Tui::bg(if value >= 0.0 { Red }
else if value >= -3.0 { Color::Yellow } else if value >= -3.0 { Yellow }
else { Color::Green }, ()))) else { Green }, ())))
} }
fn view_meters (&self, values: &[f32;2]) -> impl Content<TuiOut> + use<'_> { fn view_meters (&self, values: &[f32;2]) -> impl Content<TuiOut> + use<'_> {
col!( col!(
@ -466,14 +468,14 @@ impl Tek {
let playing = self.clock.is_rolling(); let playing = self.clock.is_rolling();
let compact = self.is_editing(); let compact = self.is_editing();
Tui::bg( Tui::bg(
if playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)}, if playing{Rgb(0,128,0)}else{Rgb(128,64,0)},
Either::new(compact, Either::new(compact,
Thunk::new(move||Fixed::x(9, Either::new(playing, Thunk::new(move||Fixed::x(9, Either::new(playing,
Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "), Tui::fg(Rgb(0, 255, 0), " PLAYING "),
Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))), Tui::fg(Rgb(255, 128, 0), " STOPPED ")))),
Thunk::new(move||Fixed::x(5, Either::new(playing, Thunk::new(move||Fixed::x(5, Either::new(playing,
Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))) Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))))))
} }
fn view_editor (&self) -> impl Content<TuiOut> + use<'_> { fn view_editor (&self) -> impl Content<TuiOut> + use<'_> {
self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())) self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status()))
@ -482,8 +484,8 @@ impl Tek {
self.pool.as_ref().map(|pool|PoolView(self.is_editing(), pool)) self.pool.as_ref().map(|pool|PoolView(self.is_editing(), pool))
} }
fn pool (&self) -> impl Content<TuiOut> + use<'_> { fn pool (&self) -> impl Content<TuiOut> + use<'_> {
self.pool.as_ref() let by_pool = |pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.is_editing(), pool)));
.map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.is_editing(), pool)))) self.pool.as_ref().map(by_pool)
} }
fn w (&self) -> u16 { fn w (&self) -> u16 {
self.tracks_sizes(self.is_editing(), self.editor_w()) self.tracks_sizes(self.is_editing(), self.editor_w())
@ -530,40 +532,65 @@ impl Tek {
move|(_, track, x1, x2), t| { move|(_, track, x1, x2), t| {
let last_color = Arc::new(RwLock::new(ItemPalette::default())); let last_color = Arc::new(RwLock::new(ItemPalette::default()));
let w = (x2 - x1) as u16; let w = (x2 - x1) as u16;
let color: ItemPalette = track.color;
map_east(x1 as u16, w, border(Map::new(scenes, move|(_, scene, y1, y2), s| { map_east(x1 as u16, w, border(Map::new(scenes, move|(_, scene, y1, y2), s| {
let last_color = last_color.clone();
let h = (1 + y2 - y1) as u16;
let color = scene.color;
let mut name = String::from("");
let mut fg = Tui::g(64);
let mut bg = ItemPalette::G[32];
if let Some(clip) = &scene.clips[t] {
let clip = clip.read().unwrap();
name.clear();
write!(&mut name, "{}", clip.name);
fg = clip.color.lightest.rgb;
bg = clip.color
};
let same_track = selected_track == Some(t+1); let same_track = selected_track == Some(t+1);
let selected = same_track && Some(s+1) == selected_scene; let selected = same_track && Some(s+1) == selected_scene;
let neighbor = same_track && Some(s) == selected_scene; let neighbor = same_track && Some(s) == selected_scene;
let active = editing && selected; let active = editing && selected;
let label = move||Tui::fg(fg, Push::x(1, Tui::bold(true, name.clone()))); let last_color = last_color.clone();
let mid = if active { bg.light } else { bg.base }; let mut fg = Tui::g(64);
let top = if neighbor { None } else { Some(last_color.read().unwrap().base.rgb) }; let mut bg = ItemPalette::G[32];
let mid = mid.rgb; if let Some(clip) = &scene.clips[t] {
let low = Color::Rgb(0, 0, 0); let clip = clip.read().unwrap();
fg = clip.color.lightest.rgb;
bg = clip.color
};
//let top = if neighbor { None } else { Some(last_color.read().unwrap().base.rgb) };
let top = if s == 0 {
Some(Reset)
} else if neighbor {
None
} else {
Some(last_color.read().unwrap().base.rgb)
};
let mid = if selected { bg.light } else { bg.base }.rgb;
let low = Some(Reset);
let h = (1 + y2 - y1) as u16;
*last_color.write().unwrap() = bg; *last_color.write().unwrap() = bg;
let tab = " Tab ";
let name = if active {
self.editor.as_ref()
.map(|e|e.clip().as_ref().map(|c|c.clone()))
.flatten()
.map(|c|c.read().unwrap().name.clone())
.unwrap_or_else(||"".into())
} else {
"edit".into()
};
let label = move||{
let stop = self.fmtd_stop.clone();
let clip = scene.clips[t].clone();
Tui::fg(fg, Push::x(1, Tui::bold(true, clip
.map(|c|c.read().unwrap().name.clone())
.unwrap_or(stop))))
};
map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Either::new(active, map_south(y1 as u16, h, Push::y(1, Fixed::y(h, Either::new(active,
Thunk::new(||Bsp::a( Thunk::new(move||Bsp::a(
Fill::xy(Align::nw(button(" Tab ", ""))), Fill::xy(Align::nw(button(tab, Tui::fg_bg(fg, bg.base.rgb, name.clone())))),
&self.editor)), &self.editor)),
Thunk::new(move||Bsp::a( Thunk::new(move||Bsp::a(
When::new(selected, Fill::y(Align::n(button(" Tab ", "edit")))), When::new(selected, Fill::y(Align::n(button(tab, "edit")))),
phat_sel_3(selected, label(), label(), top, mid, low) Fill::xy(phat_sel_3(
selected,
label(),
label(),
top, mid, low
))
)), )),
)))) ))))
}))).boxed() }))).boxed()
} }
})).boxed()).into() })).boxed()).into()
@ -679,34 +706,34 @@ handle!(TuiIn: |self: Tek, input|Ok({
Zoom(Option<usize>), Zoom(Option<usize>),
} }
atom_command!(TekCommand: |app: Tek| { atom_command!(TekCommand: |app: Tek| {
("stop-all" [] Self::StopAll) ("stop" [] Self::StopAll)
("undo" [d: usize] Self::History(-(d.unwrap_or(0)as isize))) ("undo" [d: usize] Self::History(-(d.unwrap_or(0)as isize)))
("redo" [d: usize] Self::History(d.unwrap_or(0) as isize)) ("redo" [d: usize] Self::History(d.unwrap_or(0) as isize))
("zoom" [z: usize] Self::Zoom(z)) ("zoom" [z: usize] Self::Zoom(z))
("edit" [] Self::Edit(None)) ("edit" [] Self::Edit(None))
("edit" [c: bool] Self::Edit(c)) ("edit" [c: bool] Self::Edit(c))
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default())) ("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
("enqueue" [c: Arc<RwLock<MidiClip>>] Self::Enqueue(c)) ("enqueue" [c: Arc<RwLock<MidiClip>>] Self::Enqueue(c))
("select" [t: usize, s: usize] match (t.expect("no track"), s.expect("no scene")) { ("select" [t: usize, s: usize] match (t.expect("no track"), s.expect("no scene")) {
(0, 0) => Self::Select(Selection::Mix), (0, 0) => Self::Select(Selection::Mix),
(t, 0) => Self::Select(Selection::Track(t)), (t, 0) => Self::Select(Selection::Track(t)),
(0, s) => Self::Select(Selection::Scene(s)), (0, s) => Self::Select(Selection::Scene(s)),
(t, s) => Self::Select(Selection::Clip(t, s)), (t, s) => Self::Select(Selection::Clip(t, s)),
}) })
("clip" [,..a] Self::Clip(ClipCommand::try_from_expr(app, a) ("clip" [,..a] Self::Clip(
.expect("invalid command"))) ClipCommand::try_from_expr(app, a).expect("invalid command")))
("clock" [,..a] Self::Clock(ClockCommand::try_from_expr(app.clock(), a) ("clock" [,..a] Self::Clock(
.expect("invalid command"))) ClockCommand::try_from_expr(app.clock(), a).expect("invalid command")))
("editor" [,..a] Self::Editor(MidiEditCommand::try_from_expr(app.editor.as_ref().expect("no editor"), a) ("editor" [,..a] Self::Editor(
.expect("invalid command"))) MidiEditCommand::try_from_expr(app.editor.as_ref().expect("no editor"), a).expect("invalid command")))
("pool" [,..a] Self::Pool(PoolCommand::try_from_expr(app.pool.as_ref().expect("no pool"), a) ("pool" [,..a] Self::Pool(
.expect("invalid command"))) PoolCommand::try_from_expr(app.pool.as_ref().expect("no pool"), a).expect("invalid command")))
("sampler" [,..a] Self::Sampler(SamplerCommand::try_from_expr(app.sampler.as_ref().expect("no sampler"), a) ("sampler" [,..a] Self::Sampler(
.expect("invalid command"))) SamplerCommand::try_from_expr(app.sampler.as_ref().expect("no sampler"), a).expect("invalid command")))
("scene" [,..a] Self::Scene(SceneCommand::try_from_expr(app, a) ("scene" [,..a] Self::Scene(
.expect("invalid command"))) SceneCommand::try_from_expr(app, a).expect("invalid command")))
("track" [,..a] Self::Track(TrackCommand::try_from_expr(app, a) ("track" [,..a] Self::Track(
.expect("invalid command"))) TrackCommand::try_from_expr(app, a).expect("invalid command")))
}); });
command!(|self: TekCommand, app: Tek|match self { command!(|self: TekCommand, app: Tek|match self {
Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None }, Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None },
@ -741,7 +768,10 @@ command!(|self: TekCommand, app: Tek|match self {
let (index, mut clip) = pool.add_new_clip(); let (index, mut clip) = pool.add_new_clip();
// autocolor: new clip colors from scene and track color // autocolor: new clip colors from scene and track color
clip.write().unwrap().color = ItemColor::random_near( clip.write().unwrap().color = ItemColor::random_near(
app.tracks[t.saturating_sub(1)].color.base.mix(scene.color.base, 0.5), app.tracks[t.saturating_sub(1)].color.base.mix(
scene.color.base,
0.5
),
0.2 0.2
).into(); ).into();
if let Some(ref mut editor) = app.editor { if let Some(ref mut editor) = app.editor {
@ -759,7 +789,7 @@ command!(|self: TekCommand, app: Tek|match self {
Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?, Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?,
Self::Scene(cmd) => cmd.delegate(app, Self::Scene)?, Self::Scene(cmd) => cmd.delegate(app, Self::Scene)?,
Self::Track(cmd) => cmd.delegate(app, Self::Track)?, Self::Track(cmd) => cmd.delegate(app, Self::Track)?,
Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?, Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?,
Self::Editor(cmd) => app.editor.as_mut() Self::Editor(cmd) => app.editor.as_mut()
.map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(), .map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(),
Self::Sampler(cmd) => app.sampler.as_mut() Self::Sampler(cmd) => app.sampler.as_mut()
@ -978,7 +1008,7 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
let fg = color.lightest.rgb; let fg = color.lightest.rgb;
let bg = color.base.rgb; let bg = color.base.rgb;
let active = self.selected().track() == Some(i + 1); let active = self.selected().track() == Some(i + 1);
let bfg = if active { Color::Rgb(255,255,255) } else { Color::Rgb(0,0,0) }; let bfg = if active { Rgb(255,255,255) } else { Rgb(0,0,0) };
let border = Style::default().fg(bfg).bg(bg); let border = Style::default().fg(bfg).bg(bg);
Tui::bg(bg, map_east(x1 as u16, (x2 - x1) as u16, Tui::bg(bg, map_east(x1 as u16, (x2 - x1) as u16,
Outer(false, border) Outer(false, border)
@ -998,11 +1028,11 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
} }
fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content<TuiOut> { fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content<TuiOut> {
row!( row!(
Tui::fg_bg(if rec { Color::Red } else { bg }, bg, ""), Tui::fg_bg(if rec { Red } else { bg }, bg, ""),
Tui::fg_bg(if rec { Color::White } else { Color::Rgb(0,0,0) }, bg, "REC"), Tui::fg_bg(if rec { White } else { Rgb(0,0,0) }, bg, "REC"),
Tui::fg_bg(if rec { Color::White } else { bg }, bg, ""), Tui::fg_bg(if rec { White } else { bg }, bg, ""),
Tui::fg_bg(if mon { Color::White } else { Color::Rgb(0,0,0) }, bg, "MON"), Tui::fg_bg(if mon { White } else { Rgb(0,0,0) }, bg, "MON"),
Tui::fg_bg(if mon { Color::White } else { bg }, bg, ""), Tui::fg_bg(if mon { White } else { bg }, bg, ""),
) )
} }
fn output_cells <'a> (&'a self) -> ThunkBox<'a, TuiOut> { fn output_cells <'a> (&'a self) -> ThunkBox<'a, TuiOut> {
@ -1017,16 +1047,16 @@ trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
} }
fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content<TuiOut> { fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content<TuiOut> {
row!( row!(
Tui::fg_bg(if mute { Color::White } else { Color::Rgb(0,0,0) }, bg, "MUTE"), Tui::fg_bg(if mute { White } else { Rgb(0,0,0) }, bg, "MUT"),
Tui::fg_bg(if mute { Color::White } else { bg }, bg, ""), Tui::fg_bg(if mute { White } else { bg }, bg, ""),
Tui::fg_bg(if solo { Color::White } else { Color::Rgb(0,0,0) }, bg, "SOLO"), Tui::fg_bg(if solo { White } else { Rgb(0,0,0) }, bg, "SOL"),
) )
} }
fn cell <T: Content<TuiOut>> (color: ItemPalette, field: T) -> impl Content<TuiOut> { fn cell <T: Content<TuiOut>> (color: ItemPalette, field: T) -> impl Content<TuiOut> {
Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field)) Tui::fg_bg(color.lightest.rgb, color.base.rgb, Fixed::y(1, field))
} }
} }
trait Device: Send + Sync + std::fmt::Debug {} pub trait Device: Send + Sync + std::fmt::Debug {}
impl Device for Sampler {} impl Device for Sampler {}
impl Device for Plugin {} impl Device for Plugin {}
#[derive(Debug, Default)] pub struct Scene { #[derive(Debug, Default)] pub struct Scene {
@ -1149,21 +1179,19 @@ trait HasScenes: HasSelection + HasEditor + Send + Sync {
let selected = self.selected().scene(); let selected = self.selected().scene();
let iter = ||self.scenes_sizes(self.is_editing(), 2, 15); let iter = ||self.scenes_sizes(self.is_editing(), 2, 15);
Map::new(iter, move|(_, scene, y1, y2), i| { Map::new(iter, move|(_, scene, y1, y2), i| {
let h = (1 + y2 - y1) as u16; let color = scene.color;
let name = format!("🭬{}", &scene.name); let top = if i == 0 { Some(Reset) } else if selected == Some(i+0) { None } else { Some(last_color.read().unwrap().base.rgb) };
let color = scene.color; let mid = if selected == Some(i+1) { color.light } else { color.base }.rgb;
let active = selected == Some(i + 1); let low = Some(Reset);
let neighbor = selected == Some(i);
let mid = if active { color.light } else { color.base };
let top = if neighbor { None } else { Some(last_color.read().unwrap().base.rgb) };
let mid = mid.rgb;
let low = Color::Rgb(0, 0, 0);
let cell = phat_sel_3( let cell = phat_sel_3(
active, selected == Some(i),
Tui::bold(true, name.clone()), Tui::bold(true, Bsp::e("🭬", &scene.name)),
Tui::bold(true, name), Tui::bold(true, Bsp::e("🭬", &scene.name)),
top, mid, low top,
mid,
low
); );
let h = (1 + y2 - y1) as u16;
*last_color.write().unwrap() = color; *last_color.write().unwrap() = color;
map_south(y1 as u16, h, Push::y(1, Fixed::y(h, map_south(y1 as u16, h, Push::y(1, Fixed::y(h,
Outer(false, Style::default().fg(Tui::g(0))).enclose(cell)))) Outer(false, Style::default().fg(Tui::g(0))).enclose(cell))))
@ -1275,7 +1303,10 @@ audio!(|self: Tek, client, scope|{
self.perf.update(t0, scope); self.perf.update(t0, scope);
Control::Continue Control::Continue
}); });
fn button (key: &'static str, label: &'static str) -> impl Content<TuiOut> + 'static { fn button <'a> (
key: impl Content<TuiOut> + 'a,
label: impl Content<TuiOut> + 'a
) -> impl Content<TuiOut> + 'a {
Tui::bold(true, Bsp::e( Tui::bold(true, Bsp::e(
Margin::x(1, Tui::fg_bg(Tui::g(0), Tui::orange(), key)), Margin::x(1, Tui::fg_bg(Tui::g(0), Tui::orange(), key)),
Margin::x(1, Tui::fg_bg(Tui::g(255), Tui::g(96), label)), Margin::x(1, Tui::fg_bg(Tui::g(255), Tui::g(96), label)),

View file

@ -158,17 +158,22 @@ pub fn phat_cell_3 <T: Content<TuiOut>> (
) )
} }
pub fn phat_sel_3 <T: Content<TuiOut>> ( pub fn phat_sel_3 <T: Content<TuiOut>> (
selected: bool, field_1: T, field_2: T, top: Option<Color>, middle: Color, bottom: Color selected: bool, field_1: T, field_2: T,
top: Option<Color>,
mid: Color,
low: Option<Color>,
) -> impl Content<TuiOut> { ) -> impl Content<TuiOut> {
let border = Style::default().fg(Color::Rgb(255,255,255)).bg(middle); let border = Style::default().fg(Color::Rgb(255,255,255)).bg(mid);
Either::new(selected, let top = top.map(|top|phat_lo(mid, top));
Tui::bg(middle, Outer(true, border) let low = low.map(|low|phat_hi(mid, low));
.enclose(Align::w(Bsp::s("", Bsp::n("", Fill::y(field_1)))))), Either::new(
Bsp::s(Fixed::y(1, top.map(|top|phat_lo(middle, top))), selected,
Bsp::n(Fixed::y(1, phat_hi(middle, bottom)), Tui::bg(mid, Outer(true, border).enclose(
Fill::xy(Tui::bg(middle, field_2)), Align::w(Bsp::s("", Bsp::n("", Fill::y(field_1))))
) )),
) Bsp::s(Fixed::y(1, top), Bsp::n(Fixed::y(1, low),
Fill::xy(Tui::bg(mid, field_2))
)),
) )
} }