move track io to tracks trait

This commit is contained in:
🪞👃🪞 2025-01-13 20:35:39 +01:00
parent 93fa3c26b4
commit 4af6e011b6
2 changed files with 94 additions and 118 deletions

View file

@ -1,3 +1,3 @@
(bsp/s :toolbar
(fill/x (align/c (bsp/w :pool
(bsp/s (fill/x (fixed/y (align/x :toolbar)))
(fill/x (align/c (bsp/w (align/e fixed/x :sidebar-w :pool))
(bsp/n :outputs (bsp/n :inputs (bsp/n :tracks :scenes)))))))

View file

@ -65,26 +65,19 @@ impl HasSampler for App {
fn sample_index (&self) -> usize { self.editor.as_ref().map(|e|e.note_point()).unwrap_or(0) }
}
impl HasEditor for App {
fn editor (&self) -> &Option<MidiEditor> {
&self.editor
}
fn editor_mut (&mut self) -> &Option<MidiEditor> {
&mut self.editor
}
fn is_editing (&self) -> bool {
self.editing.load(Relaxed)
}
fn editor (&self) -> &Option<MidiEditor> { &self.editor }
fn editor_mut (&mut self) -> &Option<MidiEditor> { &mut self.editor }
fn is_editing (&self) -> bool { self.editing.load(Relaxed) }
fn editor_h (&self) -> usize { 15 }
fn editor_w (&self) -> usize {
let editor = self.editor.as_ref().expect("missing editor");
(5 + (editor.time_len().get() / editor.time_zoom().get()))
.min(self.size.w().saturating_sub(20))
.max(16)
}
fn editor_h (&self) -> usize {
15
}
}
edn_provide!(u16: |self: App|{
":sidebar-w" => self.sidebar_w(),
":sample-h" => if self.compact() { 0 } else { 5 },
":samples-w" => if self.compact() { 4 } else { 11 },
":samples-y" => if self.compact() { 1 } else { 0 },
@ -102,13 +95,13 @@ edn_provide!(Arc<RwLock<MidiClip>>: |self: App| { _ => return None });
edn_provide!(Option<Arc<RwLock<MidiClip>>>: |self: App| { _ => return None });
edn_provide!('a: Box<dyn Render<TuiOut> + 'a>: |self: App|{
":editor" => (&self.editor).boxed(),
":pool" => self.pool.as_ref().map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool)))).boxed(),
":pool" => self.pool.as_ref().map(|pool|PoolView(self.compact(), pool)).boxed(),
":sample" => self.view_sample(self.is_editing()).boxed(),
":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(),
":status" => self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())).boxed(),
":toolbar" => Fill::x(Fixed::y(2, Align::x(ClockView::new(true, &self.clock)))).boxed(),
":tracks" => self.row(self.w(), 3, self.track_header(), self.track_cells()).boxed(),
":inputs" => self.row(self.w(), 3, self.input_header(), self.input_cells()).boxed(),
":toolbar" => ClockView::new(true, &self.clock).boxed(),
":tracks" => self.row(self.w(), 3, self.track_header(), self.track_cells()).boxed(),
":inputs" => self.row(self.w(), 3, self.input_header(), self.input_cells()).boxed(),
":outputs" => self.row(self.w(), 3, self.output_header(), self.output_cells()).boxed(),
":scenes" => self.row(self.w(), self.size.h().saturating_sub(9) as u16,
self.scene_header(), self.scene_cells(self.is_editing())).boxed(),
@ -137,7 +130,8 @@ render!(TuiOut: (self: Meter<'a>) => col!(
#[derive(Debug, Default)] struct Meters<'a>(pub &'a[f32]);
render!(TuiOut: (self: Meters<'a>) => col!(
format!("L/{:>+9.3}", self.0[0]),
format!("R/{:>+9.3}", self.0[1])));
format!("R/{:>+9.3}", self.0[1])
));
/// Represents the current user selection in the arranger
#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection {
/// The whole mix is selected
@ -163,11 +157,7 @@ impl Selection {
use Selection::*;
match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None }
}
pub fn description (
&self,
tracks: &[Track],
scenes: &[Scene],
) -> Arc<str> {
pub fn description (&self, tracks: &[Track], scenes: &[Scene]) -> Arc<str> {
format!("Selected: {}", match self {
Self::Mix => "Everything".to_string(),
Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
@ -225,10 +215,14 @@ impl Track {
}
}
impl HasTracks for App {
fn midi_ins (&self) -> &Vec<JackPort<MidiIn>> { &self.midi_ins }
fn midi_outs (&self) -> &Vec<JackPort<MidiOut>> { &self.midi_outs }
fn tracks (&self) -> &Vec<Track> { &self.tracks }
fn tracks_mut (&mut self) -> &mut Vec<Track> { &mut self.tracks }
}
pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
fn midi_ins (&self) -> &Vec<JackPort<MidiIn>>;
fn midi_outs (&self) -> &Vec<JackPort<MidiOut>>;
fn tracks (&self) -> &Vec<Track>;
fn tracks_mut (&mut self) -> &mut Vec<Track>;
fn tracks_sizes <'a> (&'a self, editing: bool, bigger: usize)
@ -277,6 +271,63 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync
))
})).boxed()).into()
}
fn input_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(help_tag("midi ", "I", "ns"), self.midi_ins().get(0).map(|inp|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))),
inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
))).boxed()).into()
}
fn input_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Align::x(Map::new(||self.tracks_sizes(self.is_editing(), self.editor_w()), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let color: ItemPalette = track.color.dark.into();
map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, Bsp::n(
Self::rec_mon(color.base.rgb, false, false),
phat_hi(color.base.rgb, color.dark.rgb)
))))
})).boxed()).into()
}
fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content<TuiOut> {
row!(
Tui::fg_bg(if rec { Color::Red } else { bg }, bg, ""),
Tui::fg_bg(if rec { Color::White } else { Color::Rgb(0,0,0) }, bg, "REC"),
Tui::fg_bg(if rec { Color::White } else { bg }, bg, ""),
Tui::fg_bg(if mon { Color::White } else { Color::Rgb(0,0,0) }, bg, "MON"),
Tui::fg_bg(if mon { Color::White } else { bg }, bg, ""),
)
}
fn output_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(help_tag("midi ", "O", "uts"), 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) -> BoxThunk<'a, TuiOut> {
(move||Align::x(Map::new(||self.tracks_sizes(self.is_editing(), self.editor_w()), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let color: ItemPalette = track.color.dark.into();
map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, Bsp::n(
Self::mute_solo(color.base.rgb, false, false),
phat_hi(color.dark.rgb, color.darker.rgb)
))))
})).boxed()).into()
}
fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content<TuiOut> {
row!(
Tui::fg_bg(if mute { Color::White } else { Color::Rgb(0,0,0) }, bg, "MUTE"),
Tui::fg_bg(if mute { Color::White } else { bg }, bg, ""),
Tui::fg_bg(if solo { Color::White } else { Color::Rgb(0,0,0) }, bg, "SOLO"),
)
}
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))
}
}
trait Device: Send + Sync + std::fmt::Debug {}
impl Device for Sampler {}
@ -331,7 +382,7 @@ impl Scene {
Enqueue(usize),
}
edn_command!(SceneCommand: |state: App| {
("add" [] Self::Add)
("add" [] Self::Add)
("del" [a: usize] Self::Del(0))
("zoom" [a: usize] Self::SetZoom(a.unwrap()))
("color" [a: usize] Self::SetColor(a.unwrap(), ItemPalette::random()))
@ -470,60 +521,6 @@ impl App {
let w = if self.is_editing() { 8 } else { w };
w
}
fn input_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(help_tag("midi ", "I", "ns"), self.midi_ins.get(0).map(|inp|Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(inp.name.clone())))),
inp.connect.get(0).map(|connect|Fill::x(Align::w(Tui::bold(false,
Tui::fg_bg(fg, bg, connect.info()))))),
))).boxed()).into()
}
fn input_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
(move||Align::x(Map::new(||self.tracks_sizes(self.is_editing(), self.editor_w()), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let color: ItemPalette = track.color.dark.into();
map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n(
Self::rec_mon(color.base.rgb, false, false),
phat_hi(color.base.rgb, color.dark.rgb)
))))
})).boxed()).into()
}
fn rec_mon (bg: Color, rec: bool, mon: bool) -> impl Content<TuiOut> {
row!(
Tui::fg_bg(if rec { Color::Red } else { bg }, bg, ""),
Tui::fg_bg(if rec { Color::White } else { Color::Rgb(0,0,0) }, bg, "REC"),
Tui::fg_bg(if rec { Color::White } else { bg }, bg, ""),
Tui::fg_bg(if mon { Color::White } else { Color::Rgb(0,0,0) }, bg, "MON"),
Tui::fg_bg(if mon { Color::White } else { bg }, bg, ""),
)
}
fn output_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
let fg = TuiTheme::g(224);
let bg = TuiTheme::g(64);
(move||Bsp::s(help_tag("midi ", "O", "uts"), 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) -> BoxThunk<'a, TuiOut> {
(move||Align::x(Map::new(||self.tracks_sizes(self.is_editing(), self.editor_w()), move|(_, track, x1, x2), i| {
let w = (x2 - x1) as u16;
let color: ItemPalette = track.color.dark.into();
map_east(x1 as u16, w, Fixed::x(w, cell(color, Bsp::n(
Self::mute_solo(color.base.rgb, false, false),
phat_hi(color.dark.rgb, color.darker.rgb)
))))
})).boxed()).into()
}
fn mute_solo (bg: Color, mute: bool, solo: bool) -> impl Content<TuiOut> {
row!(
Tui::fg_bg(if mute { Color::White } else { Color::Rgb(0,0,0) }, bg, "MUTE"),
Tui::fg_bg(if mute { Color::White } else { bg }, bg, ""),
Tui::fg_bg(if solo { Color::White } else { Color::Rgb(0,0,0) }, bg, "SOLO"),
)
}
}
#[derive(Clone, Debug)] pub enum AppCommand {
Clear,
@ -820,30 +817,27 @@ fn help_tag <'a> (before: &'a str, key: &'a str, after: &'a str) -> impl Content
let hi = TuiTheme::orange();
Tui::bold(true, row!(Tui::fg(lo, before), Tui::fg(hi, key), Tui::fg(lo, after)))
}
fn cell_clip <'a> (
scene: &'a Scene, index: usize, track: &'a Track, w: u16, h: u16
) -> impl Content<TuiOut> + use<'a> {
scene.clips.get(index).map(|clip|clip.as_ref().map(|clip|{
let clip = clip.read().unwrap();
let mut bg = TuiTheme::border_bg();
let name = clip.name.to_string();
let max_w = name.len().min((w as usize).saturating_sub(2));
let color = clip.color;
bg = color.dark.rgb;
if let Some((_, Some(ref playing))) = track.player.play_clip() {
if *playing.read().unwrap() == *clip {
bg = color.light.rgb
}
};
Fixed::xy(w, h, &Tui::bg(bg, Push::x(1, Fixed::x(w, &name.as_str()[0..max_w]))));
}))
}
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))
}
impl<T> Arrangement for T where T:
HasEditor + HasTracks + HasScenes + HasSelection + HasClock + HasJack {}
pub trait Arrangement: HasEditor + HasTracks + HasScenes + HasSelection + HasClock + HasJack {
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
self.scene()?.clips.get(self.selected().track()?)?.clone()
}
fn toggle_loop (&mut self) {
if let Some(clip) = self.clip() {
clip.write().unwrap().toggle_loop()
}
}
//fn randomize_color (&mut self) {
//match self.selected {
//Selection::Mix => { self.color = ItemPalette::random() },
//Selection::Track(t) => { self.tracks[t].color = ItemPalette::random() },
//Selection::Scene(s) => { self.scenes[s].color = ItemPalette::random() },
//Selection::Clip(t, s) => if let Some(clip) = &self.scenes[s].clips[t] {
//clip.write().unwrap().color = ItemPalette::random();
//}
//}
//}
fn track_add (
&mut self,
name: Option<&str>,
@ -986,24 +980,6 @@ pub trait Arrangement: HasEditor + HasTracks + HasScenes + HasSelection + HasClo
}
Ok(())
}
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
self.scene()?.clips.get(self.selected().track()?)?.clone()
}
fn toggle_loop (&mut self) {
if let Some(clip) = self.clip() {
clip.write().unwrap().toggle_loop()
}
}
//fn randomize_color (&mut self) {
//match self.selected {
//Selection::Mix => { self.color = ItemPalette::random() },
//Selection::Track(t) => { self.tracks[t].color = ItemPalette::random() },
//Selection::Scene(s) => { self.scenes[s].color = ItemPalette::random() },
//Selection::Clip(t, s) => if let Some(clip) = &self.scenes[s].clips[t] {
//clip.write().unwrap().color = ItemPalette::random();
//}
//}
//}
}
#[cfg(test)] fn test_tek () {
// TODO