From 1434adae097f33b3d1f3cb06ce27e63875a27333 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 2 Sep 2025 22:39:04 +0300 Subject: [PATCH 1/4] compiles again --- Cargo.toml | 3 +- Justfile | 17 +- crates/app/tek.rs | 14 +- crates/app/tek_bind.rs | 7 +- crates/app/tek_data.rs | 119 +++-- crates/app/tek_view.rs | 456 ++++++++++-------- crates/config/config.rs | 4 +- crates/device/src/arranger.rs | 6 - crates/device/src/arranger/arranger_tracks.rs | 60 +-- crates/device/src/arranger/arranger_view.rs | 377 ++++++++------- crates/device/src/clock/clock_view.rs | 18 +- crates/device/src/editor/editor_view.rs | 6 +- crates/device/src/editor/editor_view_h.rs | 20 +- crates/device/src/editor/editor_view_v.rs | 4 +- crates/device/src/lib.rs | 41 +- crates/device/src/lv2/lv2_tui.rs | 4 +- crates/device/src/meter.rs | 10 +- crates/device/src/sampler/sampler_browse.rs | 4 +- crates/device/src/sampler/sampler_view.rs | 69 +-- crates/device/src/sequencer/seq_launch.rs | 4 +- deps/tengri | 2 +- 21 files changed, 654 insertions(+), 591 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3bb50a68..e0ed14ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,8 +38,9 @@ tek_config = { path = "./crates/config" } tek = { path = "./crates/app" } tek_cli = { path = "./crates/cli" } -atomic_float = { version = "1.0.0" } +atomic_float = { version = "1.0.0" } backtrace = { version = "0.3.72" } +bumpalo = { version = "3.19.0" } clap = { version = "4.5.4", features = [ "derive" ] } gtk = { version = "0.18.1" } konst = { version = "0.3.16", features = [ "rust_1_83" ] } diff --git a/Justfile b/Justfile index 86e1bf36..214e45e5 100644 --- a/Justfile +++ b/Justfile @@ -18,6 +18,14 @@ default: just -l bacon: bacon -s +run: + {{debug}} +run-init: + rm -rf ~/.config/tek && {{debug}} +release: + {{release}} +build-release: + time cargo build -j4 --release tui: cargo run --example tui cloc: @@ -50,15 +58,6 @@ fpush: ftpush: git push --tags -fu codeberg && git push --tags -fu origin -run: - {{debug}} -run-init: - rm -rf ~/.config/tek && {{debug}} -release: - {{release}} -build-release: - time cargo build -j4 --release - clock: {{debug}} {{name}} {{bpm}} clock clock-release: diff --git a/crates/app/tek.rs b/crates/app/tek.rs index 12f52769..1b6c200a 100644 --- a/crates/app/tek.rs +++ b/crates/app/tek.rs @@ -131,19 +131,11 @@ impl App { _ => unreachable!(), } } + pub fn update_clock (&self) { + ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80) + } } -fn wrap_dialog (dialog: impl Content) -> impl Content { - Fixed::xy(70, 23, Tui::fg_bg(Rgb(255,255,255), Rgb(16,16,16), Bsp::b( - Repeat(" "), Outer(true, Style::default().fg(Tui::g(96))).enclose(dialog)))) -} -impl ScenesView for App { - fn w_side (&self) -> u16 { 20 } - fn w_mid (&self) -> u16 { (self.width() as u16).saturating_sub(self.w_side()) } - fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) } -} - - //#[dizzle::ns] //#[dizzle::ns::include(TuiOut)] //trait AppApi { diff --git a/crates/app/tek_bind.rs b/crates/app/tek_bind.rs index c0c4a7a7..b6b02d01 100644 --- a/crates/app/tek_bind.rs +++ b/crates/app/tek_bind.rs @@ -33,9 +33,10 @@ handle!(TuiIn:|self: App, input|{ #[derive(Debug, Copy, Clone)] pub enum Axis { X, Y, Z, I } -impl<'t> DslNs<'t, AppCommand> for App { - dsl_exprs!(|app| -> AppCommand { /* TODO */ }); - dsl_words!(|app| -> AppCommand { +impl<'a> DslNs<'a, AppCommand> for App {} +impl<'a> DslNsExprs<'a, AppCommand> for App {} +impl<'a> DslNsWords<'a, AppCommand> for App { + dsl_words!('a |app| -> AppCommand { "x/inc" => AppCommand::Inc { axis: Axis::X }, "x/dec" => AppCommand::Dec { axis: Axis::X }, "y/inc" => AppCommand::Inc { axis: Axis::Y }, diff --git a/crates/app/tek_data.rs b/crates/app/tek_data.rs index 2b27fbb8..ff1f6307 100644 --- a/crates/app/tek_data.rs +++ b/crates/app/tek_data.rs @@ -1,17 +1,14 @@ use crate::*; -impl<'t> DslNs<'t, Arc> for App { - dsl_exprs!(|app| -> Arc {}); - dsl_words!(|app| -> Arc {}); - fn from_literal (&self, dsl: &D) -> Perhaps> { - Ok(dsl.src()?.map(|x|x.into())) - } -} +dsl_ns!(App: Arc { + literal = |dsl|Ok(dsl.src()?.map(|x|x.into())); +}); -impl<'t> DslNs<'t, bool> for App { - dsl_words!(|app| -> bool { - ":mode/editor" => app.project.editor.is_some(), +dsl_ns!(App: ItemTheme {}); +dsl_ns!(App: bool { + word = |app| { + ":mode/editor" => app.project.editor.is_some(), ":focused/dialog" => !matches!(app.dialog, Dialog::None), ":focused/message" => matches!(app.dialog, Dialog::Message(..)), ":focused/add_device" => matches!(app.dialog, Dialog::Device(..)), @@ -28,16 +25,11 @@ impl<'t> DslNs<'t, bool> for App { Selection::Scene(..)), ":focused/mix" => !app.editor_focused() && matches!(app.selection(), Selection::Mix), - }); - //fn from_literal (&self, dsl: &D) -> Perhaps> { - //TODO - //} -} + }; +}); -impl<'t> DslNs<'t, ItemTheme> for App {} - -impl<'t> DslNs<'t, Dialog> for App { - dsl_words!(|app| -> Dialog { +dsl_ns!(App: Dialog { + word = |app| { ":dialog/none" => Dialog::None, ":dialog/options" => Dialog::Options, ":dialog/device" => Dialog::Device(0), @@ -56,67 +48,94 @@ impl<'t> DslNs<'t, Dialog> for App { Browse::new(None).unwrap().into()), ":dialog/export/sample" => Dialog::Browse(BrowseTarget::ExportSample(Default::default()), Browse::new(None).unwrap().into()), - }); -} + }; +}); -impl<'t> DslNs<'t, Selection> for App { - dsl_words!(|app| -> Selection { +dsl_ns!(App: Selection { + word = |app| { ":select/scene" => app.selection().select_scene(app.tracks().len()), ":select/scene/next" => app.selection().select_scene_next(app.scenes().len()), ":select/scene/prev" => app.selection().select_scene_prev(), ":select/track" => app.selection().select_track(app.tracks().len()), ":select/track/next" => app.selection().select_track_next(app.tracks().len()), ":select/track/prev" => app.selection().select_track_prev(), - }); -} + }; +}); -impl<'t> DslNs<'t, Color> for App { - dsl_words!(|app| -> Color { +dsl_ns!(App: Color { + word = |app| { ":color/bg" => Color::Rgb(28, 32, 36), - }); - dsl_exprs!(|app| -> Color { - "g" (n: u8) => Color::Rgb(n, n, n), + }; + expr = |app| { + "g" (n: u8) => Color::Rgb(n, n, n), "rgb" (r: u8, g: u8, b: u8) => Color::Rgb(r, g, b), - }); -} + }; +}); -impl<'t> DslNs<'t, Option> for App { - dsl_words!(|app| -> Option { +dsl_ns!(App: Option { + word = |app| { ":editor/pitch" => Some( (app.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8).into() ) - }); -} + }; +}); -impl<'t> DslNs<'t, Option> for App { - dsl_words!(|app| -> Option { +dsl_ns!(App: Option { + word = |app| { ":selected/scene" => app.selection().scene(), ":selected/track" => app.selection().track(), - }); -} + }; +}); -impl<'t> DslNs<'t, Option>>> for App { - dsl_words!(|app| -> Option>> { +dsl_ns!(App: Option>> { + word = |app| { ":selected/clip" => if let Selection::TrackClip { track, scene } = app.selection() { app.scenes()[*scene].clips[*track].clone() } else { None } - }); -} + }; +}); -dsl_ns! { num |app: App| - u8; - u16 => { +dsl_ns!(App: u8 { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as u8) + } else { + None + }); +}); + +dsl_ns!(App: u16 { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as u16) + } else { + None + }); + word = |app| { ":w/sidebar" => app.project.w_sidebar(app.editor().is_some()), ":h/sample-detail" => 6.max(app.height() as u16 * 3 / 9), }; - usize => { +}); + +dsl_ns!(App: usize { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as usize) + } else { + None + }); + word = |app| { ":scene-count" => app.scenes().len(), ":track-count" => app.tracks().len(), ":device-kind" => app.dialog.device_kind().unwrap_or(0), ":device-kind/next" => app.dialog.device_kind_next().unwrap_or(0), ":device-kind/prev" => app.dialog.device_kind_prev().unwrap_or(0), }; - isize; -} +}); + +dsl_ns!(App: isize { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as isize) + } else { + None + }); +}); diff --git a/crates/app/tek_view.rs b/crates/app/tek_view.rs index 880bdf6f..9804ba0e 100644 --- a/crates/app/tek_view.rs +++ b/crates/app/tek_view.rs @@ -1,206 +1,282 @@ use crate::*; -impl Content for App { - fn content (&self) -> impl Render + '_ { - Stack::above(move|add|for dsl in self.mode.view.iter() { - add(&Fill::xy(self.view(dsl.as_ref()))); - }) +impl ScenesView for App { + fn w_side (&self) -> u16 { 20 } + fn w_mid (&self) -> u16 { (self.width() as u16).saturating_sub(self.w_side()) } + fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) } +} + +impl Draw for App { + fn draw (&self, to: &mut TuiOut) { + for dsl in self.mode.view.iter() { + let _ = self.view(to, dsl).expect("render failed"); + } } } impl App { - fn view <'t, D: Dsl> (&'t self, index: D) -> TuiBox<'t> { - match index.src() { - Ok(Some(src)) => render_dsl(self, src), - Ok(None) => Box::new(Tui::fg(Color::Rgb(192, 192, 192), "empty view")), - Err(e) => Box::new(format!("{e}")), - } - } - pub fn update_clock (&self) { - ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80) - } -} -type TuiBox<'t> = Box + 't>; - -fn render_dsl <'t> ( - state: &'t impl DslNs<'t, TuiBox<'t>>, - src: &str -) -> TuiBox<'t> { - let err: Option> = match state.from(&src) { - Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e), - }; - let (fg_1, bg_1) = (Color::Rgb(240, 160, 100), Color::Rgb(48, 0, 0)); - let (fg_2, bg_2) = (Color::Rgb(250, 200, 180), Color::Rgb(48, 0, 0)); - let (fg_3, bg_3) = (Color::Rgb(250, 200, 120), Color::Rgb(0, 0, 0)); - let bg = Color::Rgb(24, 0, 0); - Box::new(col! { - Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▄")))), - Tui::bg(bg, col! { - Fill::x(Bsp::e( - Tui::bold(true, Tui::fg_bg(fg_1, bg_1, " Render error: ")), - Tui::fg_bg(fg_2, bg_2, err.map(|e|format!(" {e} "))), - )), - Fill::x(Align::x(Tui::fg_bg(fg_3, bg_3, format!(" {src} ")))), - }), - Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▀")))), - }) -} - -impl<'t> DslNs<'t, TuiBox<'t>> for App { - dsl_exprs!(|app| -> TuiBox<'t> { - "text" (tail: Arc) => Box::new(tail), - - "fg" (color: Color, x: TuiBox<'t>) => Box::new(Tui::fg(color, x)), - "bg" (color: Color, x: TuiBox<'t>) => Box::new(Tui::bg(color, x)), - "fg/bg" (fg: Color, bg: Color, x: TuiBox<'t>) => Box::new(Tui::fg_bg(fg, bg, x)), - - "either" (cond: bool, a: TuiBox<'t>, b: TuiBox<'t>) => - Box::new(Either(cond, a, b)), - - "bsp/n" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::n(a, b)), - "bsp/s" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::s(a, b)), - "bsp/e" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::e(a, b)), - "bsp/w" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::w(a, b)), - "bsp/a" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::a(a, b)), - "bsp/b" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::b(a, b)), - - "align/nw" (x: TuiBox<'t>) => Box::new(Align::nw(x)), - "align/ne" (x: TuiBox<'t>) => Box::new(Align::ne(x)), - "align/n" (x: TuiBox<'t>) => Box::new(Align::n(x)), - "align/s" (x: TuiBox<'t>) => Box::new(Align::s(x)), - "align/e" (x: TuiBox<'t>) => Box::new(Align::e(x)), - "align/w" (x: TuiBox<'t>) => Box::new(Align::w(x)), - "align/x" (x: TuiBox<'t>) => Box::new(Align::x(x)), - "align/y" (x: TuiBox<'t>) => Box::new(Align::y(x)), - "align/c" (x: TuiBox<'t>) => Box::new(Align::c(x)), - - "fill/x" (x: TuiBox<'t>) => Box::new(Fill::x(x)), - "fill/y" (x: TuiBox<'t>) => Box::new(Fill::y(x)), - "fill/xy" (x: TuiBox<'t>) => Box::new(Fill::xy(x)), - - "fixed/x" (x: u16, c: TuiBox<'t>) => Box::new(Fixed::x(x, c)), - "fixed/y" (y: u16, c: TuiBox<'t>) => Box::new(Fixed::y(y, c)), - "fixed/xy" (x: u16, y: u16, c: TuiBox<'t>) => Box::new(Fixed::xy(x, y, c)), - - "min/x" (x: u16, c: TuiBox<'t>) => Box::new(Min::x(x, c)), - "min/y" (y: u16, c: TuiBox<'t>) => Box::new(Min::y(y, c)), - "min/xy" (x: u16, y: u16, c: TuiBox<'t>) => Box::new(Min::xy(x, y, c)), - - "max/x" (x: u16, c: TuiBox<'t>) => Box::new(Max::x(x, c)), - "max/y" (y: u16, c: TuiBox<'t>) => Box::new(Max::y(y, c)), - "max/xy" (x: u16, y: u16, c: TuiBox<'t>) => Box::new(Max::xy(x, y, c)), - }); - dsl_words!(|app| -> TuiBox<'t> { - ":logo" => Box::new(Fixed::xy(32, 7, Tui::bold(true, Tui::fg(Rgb(240,200,180), Stack::south(|add|{ - add(&Fixed::y(1, "")); - add(&Fixed::y(1, "")); - add(&Fixed::y(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~")); - add(&Fixed::y(1, Bsp::e("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", Bsp::e(Tui::fg(Rgb(230,100,40), "v0.3.0"), " ~~")))); - add(&Fixed::y(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~")); - }))))), - ":meters/input" => Box::new(Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Input Meters")))), - ":meters/output" => Box::new(Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Output Meters")))), - ":status" => Box::new("Status Bar"), - ":tracks/names" => Box::new(app.project.view_track_names(app.color.clone())),//Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Names")))), - ":tracks/inputs" => Box::new(Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Inputs")))), - ":tracks/devices" => Box::new(Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Devices")))), - ":tracks/outputs" => Box::new(Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Outputs")))), - ":scenes/names" => Box::new("Scene Names"), - ":editor" => Box::new("Editor"), - ":scenes" => Box::new("Scenes"), - ":dialog/menu" => Box::new(if let Dialog::Menu(selected, items) = &app.dialog { - let items = items.clone(); - let selected = *selected; - Some(Fill::x(Stack::south(move|add|{ - for (index, MenuItem(item, _)) in items.0.iter().enumerate() { - add(&Tui::fg_bg( - if selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) }, - if selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) }, - Fixed::y(2, Align::n(Fill::x(item))) - )); - } - }))) + fn view <'a> (&'a self, to: &mut TuiOut, dsl: impl Dsl + 'a) -> Usually<()> { + if let Ok(Some(expr)) = dsl.expr() { + self.view_expr(to, expr) + } else if let Ok(Some(word)) = dsl.word() { + self.view_word(to, word) } else { - None - }), - ":templates" => Box::new({ - let modes = app.config.modes.clone(); - let height = (modes.read().unwrap().len() * 2) as u16; - Fixed::y(height, Min::x(30, Stack::south(move|add|{ - for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() { - let bg = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) }; - let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or(""); - let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or(""); - let fg1 = Rgb(224, 192, 128); - let fg2 = Rgb(224, 128, 32); - let field_name = Fill::x(Align::w(Tui::fg(fg1, name))); - let field_id = Fill::x(Align::e(Tui::fg(fg2, id))); - let field_info = Fill::x(Align::w(info)); - add(&Fixed::y(2, Fill::x(Tui::bg(bg, Bsp::s( - Bsp::a(field_name, field_id), field_info))))); - } - }))) - }), - ":sessions" => Box::new(Fixed::y(6, Min::x(30, Stack::south(move|add|{ - let fg = Rgb(224, 192, 128); - for (index, name) in ["session1", "session2", "session3"].iter().enumerate() { - let bg = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) }; - add(&Fixed::y(2, Fill::x(Tui::bg(bg, Align::w(Tui::fg(fg, name)))))); - } - })))), - ":browse/title" => Box::new(Fill::x(Align::w(FieldV(Default::default(), - match app.dialog.browser_target().unwrap() { - BrowseTarget::SaveProject => "Save project:", - BrowseTarget::LoadProject => "Load project:", - BrowseTarget::ImportSample(_) => "Import sample:", - BrowseTarget::ExportSample(_) => "Export sample:", - BrowseTarget::ImportClip(_) => "Import clip:", - BrowseTarget::ExportClip(_) => "Export clip:", - }, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))), - ":device" => { - let selected = app.dialog.device_kind().unwrap(); - Box::new(Bsp::s(Tui::bold(true, "Add device"), Map::south(1, - move||device_kinds().iter(), - move|label: &&'static str, i|{ - let bg = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) }; - let lb = if i == selected { "[ " } else { " " }; - let rb = if i == selected { " ]" } else { " " }; - Fill::x(Tui::bg(bg, Bsp::e(lb, Bsp::w(rb, "FIXME device name")))) }))) }, - }); - /// Resolve an expression if known. - fn from_expr (&self, dsl: &D) -> Perhaps> { - if let Some(head) = dsl.expr().head()? { - for (key, value) in Self::EXPRS.0.iter() { - if head == *key { - return value(self, dsl.expr().tail()?.unwrap_or("")) - } - } + panic!("{dsl:?}: invalid") } - return Ok(None) } - /// Resolve a symbol if known. - fn from_word (&self, dsl: &D) -> Perhaps> { - if let Some(dsl) = dsl.word()? { - let views = self.config.views.read().unwrap(); - if let Some(view) = views.get(dsl) { - let view = view.clone(); - std::mem::drop(views); - return Ok(Some(render_dsl(self, view.as_ref()))) - } - for (word, get) in Self::WORDS.0 { - if dsl == *word { - return get(self) + + fn view_expr <'a> (&'a self, to: &mut TuiOut, expr: impl DslExpr + 'a) -> Usually<()> { + let head = expr.head()?; let args = expr.tail(); + let mut frags = head.src()?.unwrap_or_default().split("/"); + let arg0 = args.head(); let tail0 = args.tail(); + let arg1 = tail0.head(); let tail1 = tail0.tail(); + let arg2 = tail1.head(); let tail2 = tail1.tail(); + let arg3 = tail2.head(); let tail3 = tail2.tail(); + match frags.next() { + + Some("text") => to.place(&frags.next()), + + Some("when") => to.place(&When::new( + self.from(arg0?)?.unwrap(), + Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap()) + )), + + Some("either") => to.place(&Either::new( + self.from(arg0?)?.unwrap(), + Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap()), + Thunk::new(move|to: &mut TuiOut|self.view(to, arg2).unwrap()) + )), + + Some("fg") => { + let arg0 = arg0?.expect("fg: expected arg 0 (color)"); + to.place(&Tui::fg( + DslNs::::from(self, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")), + Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap()), + )) + }, + + Some("bg") => { + let arg0 = arg0?.expect("bg: expected arg 0 (color)"); + to.place(&Tui::bg( + DslNs::::from(self, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")), + Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap()), + )) + }, + + Some("bsp") => to.place(&{ + let a = Thunk::new(move|to: &mut TuiOut|self.view(to, arg0).unwrap()); + let b = Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap()); + match frags.next() { + Some("n") => Bsp::n(a, b), + Some("s") => Bsp::s(a, b), + Some("e") => Bsp::e(a, b), + Some("w") => Bsp::w(a, b), + Some("a") => Bsp::a(a, b), + Some("b") => Bsp::b(a, b), + frag => unimplemented!("bsp/{frag:?}") } - } - } - return Ok(None) + }), + + Some("align") => to.place(&{ + let a = Thunk::new(move|to: &mut TuiOut|self.view(to, arg0).unwrap()); + match frags.next() { + Some("n") => Align::n(a), + Some("s") => Align::s(a), + Some("e") => Align::e(a), + Some("w") => Align::w(a), + Some("x") => Align::x(a), + Some("y") => Align::y(a), + Some("c") => Align::c(a), + frag => unimplemented!("align/{frag:?}") + } + }), + + Some("fill") => to.place(&{ + let a = Thunk::new(move|to: &mut TuiOut|self.view(to, arg0).unwrap()); + match frags.next() { + Some("x") => Fill::X(a), + Some("y") => Fill::Y(a), + Some("xy") => Fill::XY(a), + frag => unimplemented!("fill/{frag:?}") + } + }), + + Some("fixed") => to.place(&{ + let axis = frags.next(); + let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!() }; + let cb = Thunk::new(move|to: &mut TuiOut|self.view(to, arg).unwrap()); + match axis { + Some("x") => Fixed::X(self.from(arg0?)?.unwrap(), cb), + Some("y") => Fixed::Y(self.from(arg0?)?.unwrap(), cb), + Some("xy") => Fixed::XY(self.from(arg0?)?.unwrap(), self.from(arg1?)?.unwrap(), cb), + frag => unimplemented!("fixed/{frag:?} ({expr:?}) ({head:?}) ({:?})", + head.src()?.unwrap_or_default().split("/").next()) + } + }), + + Some("min") => to.place(&{ + let c = match frags.next() { + Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!() + }; + let cb = Thunk::new(move|to: &mut TuiOut|self.view(to, c).unwrap()); + match frags.next() { + Some("x") => Min::X(self.from(arg0?)?.unwrap(), cb), + Some("y") => Min::Y(self.from(arg0?)?.unwrap(), cb), + Some("xy") => Min::XY(self.from(arg0?)?.unwrap(), self.from(arg1?)?.unwrap(), cb), + frag => unimplemented!("min/{frag:?}") + } + }), + + Some("max") => to.place(&{ + let c = match frags.next() { + Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!() + }; + let cb = Thunk::new(move|to: &mut TuiOut|self.view(to, c).unwrap()); + match frags.next() { + Some("x") => Max::X(self.from(arg0?)?.unwrap(), cb), + Some("y") => Max::Y(self.from(arg0?)?.unwrap(), cb), + Some("xy") => Max::XY(self.from(arg0?)?.unwrap(), self.from(arg1?)?.unwrap(), cb), + frag => unimplemented!("max/{frag:?}") + } + }), + + _ => panic!("unexpected: {expr:?}") + + }; + Ok(()) } + + fn view_word <'a> (&'a self, to: &mut TuiOut, dsl: impl DslExpr + 'a) -> Usually<()> { + let mut frags = dsl.src()?.unwrap().split("/"); + match frags.next() { + + Some(":logo") => to.place(&Fixed::xy(32, 7, Tui::bold(true, Tui::fg(Rgb(240,200,180), col!{ + Fixed::y(1, ""), + Fixed::y(1, ""), + Fixed::y(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"), + Fixed::y(1, Bsp::e("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", Bsp::e(Tui::fg(Rgb(230,100,40), "v0.3.0"), " ~~"))), + Fixed::y(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"), + })))), + + Some(":status") => to.place(&"TODO: Status Bar"), + + Some(":meters") => match frags.next() { + Some("input") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Input Meters")))), + Some("output") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Output Meters")))), + _ => panic!() + }, + + Some(":tracks") => match frags.next() { + None => to.place(&"TODO tracks"), + Some("names") => to.place(&self.project.view_track_names(self.color.clone())),//Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Names")))), + Some("names") => to.place(&self.project.view_track_names(self.color.clone())),//Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Names")))), + Some("inputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Inputs")))), + Some("devices") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Devices")))), + Some("outputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Outputs")))), + _ => panic!() + }, + + Some(":scenes") => match frags.next() { + None => to.place(&"TODO scenes"), + Some(":scenes/names") => to.place(&"TODO Scene Names"), + _ => panic!() + }, + + Some(":editor") => to.place(&"TODO Editor"), + + Some(":dialog") => match frags.next() { + Some("menu") => to.place(&if let Dialog::Menu(selected, items) = &self.dialog { + let items = items.clone(); + let selected = selected; + Some(Fill::x(Thunk::new(move|to: &mut TuiOut|{ + for (index, MenuItem(item, _)) in items.0.iter().enumerate() { + to.place(&Push::y((2 * index) as u16, + Tui::fg_bg( + if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) }, + if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) }, + Fixed::y(2, Align::n(Fill::x(item))) + ))); + } + }))) + } else { + None + }), + _ => unimplemented!("App::view_word: {dsl:?} ({frags:?})"), + }, + + Some(":templates") => to.place(&{ + let modes = self.config.modes.clone(); + let height = (modes.read().unwrap().len() * 2) as u16; + Fixed::y(height, Min::x(30, Thunk::new(move |to: &mut TuiOut|{ + for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() { + let bg = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) }; + let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or(""); + let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or(""); + let fg1 = Rgb(224, 192, 128); + let fg2 = Rgb(224, 128, 32); + let field_name = Fill::x(Align::w(Tui::fg(fg1, name))); + let field_id = Fill::x(Align::e(Tui::fg(fg2, id))); + let field_info = Fill::x(Align::w(info)); + to.place(&Push::y((2 * index) as u16, + Fixed::y(2, Fill::x(Tui::bg(bg, Bsp::s( + Bsp::a(field_name, field_id), field_info)))))); + } + }))) + }), + + Some(":sessions") => to.place(&Fixed::y(6, Min::x(30, Thunk::new(|to: &mut TuiOut|{ + let fg = Rgb(224, 192, 128); + for (index, name) in ["session1", "session2", "session3"].iter().enumerate() { + let bg = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) }; + to.place(&Push::y((2 * index) as u16, + &Fixed::y(2, Fill::x(Tui::bg(bg, Align::w(Tui::fg(fg, name))))))); + } + })))), + + Some(":browse/title") => to.place(&Fill::x(Align::w(FieldV(Default::default(), + match self.dialog.browser_target().unwrap() { + BrowseTarget::SaveProject => "Save project:", + BrowseTarget::LoadProject => "Load project:", + BrowseTarget::ImportSample(_) => "Import sample:", + BrowseTarget::ExportSample(_) => "Export sample:", + BrowseTarget::ImportClip(_) => "Import clip:", + BrowseTarget::ExportClip(_) => "Export clip:", + }, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))), + + Some(":device") => { + let selected = self.dialog.device_kind().unwrap(); + to.place(&Bsp::s(Tui::bold(true, "Add device"), Map::south(1, + move||device_kinds().iter(), + move|label: &&'static str, i|{ + let bg = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) }; + let lb = if i == selected { "[ " } else { " " }; + let rb = if i == selected { " ]" } else { " " }; + Fill::x(Tui::bg(bg, Bsp::e(lb, Bsp::w(rb, "FIXME device name")))) }))) + }, + + Some(_) => { + let views = self.config.views.read().unwrap(); + if let Some(dsl) = views.get(dsl.src()?.unwrap()) { + let dsl = dsl.clone(); + std::mem::drop(views); + self.view(to, dsl)? + } else { + unimplemented!("{dsl:?}"); + } + }, + + _ => unreachable!() + + } + Ok(()) + } + } -pub fn view_nil (_: &App) -> TuiBox { - Box::new(Fill::xy("·")) -} +//pub fn view_nil (_: &App) -> TuiCb { + //|to|to.place(&Fill::xy("·")) +//} //Bsp::s("", //Map::south(1, @@ -256,7 +332,7 @@ pub fn view_nil (_: &App) -> TuiBox { ////let selection = self.selection().describe(self.tracks(), self.scenes()); //let hist_len = self.history.len(); //let hist_last = self.history.last(); - //Fixed::y(2, Stack::east(move|add: &mut dyn FnMut(&dyn Render)|{ + //Fixed::y(2, Stack::east(move|add: &mut dyn FnMut(&dyn Draw)|{ //add(&Fixed::x(5, Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, //Either::new(false, // TODO //Thunk::new(move||Fixed::x(9, Either::new(playing, diff --git a/crates/config/config.rs b/crates/config/config.rs index 3b23f99d..c9bb65b5 100644 --- a/crates/config/config.rs +++ b/crates/config/config.rs @@ -74,7 +74,7 @@ impl Config { } } pub fn init (&mut self) -> Usually<()> { - self.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(dsl))?; + self.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(&dsl))?; Ok(()) } pub fn init_file ( @@ -82,7 +82,7 @@ impl Config { ) -> Usually<()> { if self.dirs.find_config_file(path).is_none() { println!("Creating {path:?}"); - std::fs::write(self.dirs.place_config_file(path)?, defaults); + std::fs::write(self.dirs.place_config_file(path)?, defaults)?; } Ok(if let Some(path) = self.dirs.find_config_file(path) { println!("Loading {path:?}"); diff --git a/crates/device/src/arranger.rs b/crates/device/src/arranger.rs index 56ac336b..0963df93 100644 --- a/crates/device/src/arranger.rs +++ b/crates/device/src/arranger.rs @@ -13,12 +13,6 @@ def_sizes_iter!(InputsSizes => MidiInput); def_sizes_iter!(OutputsSizes => MidiOutput); def_sizes_iter!(PortsSizes => Arc, [Connect]); -pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content) -> impl Content { - let left = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▐"))); - let right = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▌"))); - Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content))) -} - #[derive(Default, Debug)] pub struct Arrangement { /// Project name. pub name: Arc, diff --git a/crates/device/src/arranger/arranger_tracks.rs b/crates/device/src/arranger/arranger_tracks.rs index 364ffab8..3f002a11 100644 --- a/crates/device/src/arranger/arranger_tracks.rs +++ b/crates/device/src/arranger/arranger_tracks.rs @@ -241,34 +241,38 @@ impl Track { pub trait HasTrack { fn track (&self) -> Option<&Track>; fn track_mut (&mut self) -> Option<&mut Track>; - fn view_midi_ins_status (&self, theme: ItemTheme) -> impl Content { + fn view_midi_ins_status (&self, theme: ItemTheme) -> impl Draw { self.track().map(|track|{ let ins = track.sequencer.midi_ins.len() as u16; - Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose( + let border = Outer(true, Style::default().fg(Tui::g(96))); + Fixed::xy(20, 1 + ins, border.enclose( Fixed::xy(20, 1 + ins, FieldV(theme, format!("MIDI ins: "), Map::south(1, ||track.sequencer.midi_ins.iter(), |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) } - fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Content { + fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw { self.track().map(|track|{ let outs = track.sequencer.midi_outs.len() as u16; - Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose( + let border = Outer(true, Style::default().fg(Tui::g(96))); + Fixed::xy(20, 1 + outs, border.enclose( Fixed::xy(20, 1 + outs, FieldV(theme, format!("MIDI outs: "), Map::south(1, ||track.sequencer.midi_outs.iter(), |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) } - fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Content { + fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw { self.track().and_then(|track|track.devices.get(0)).map(|device|{ let ins = device.audio_ins().len() as u16; - Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose( + let border = Outer(true, Style::default().fg(Tui::g(96))); + Fixed::xy(20, 1 + ins, border.enclose( Fixed::xy(20, 1 + ins, FieldV(theme, format!("Audio ins: "), Map::south(1, ||device.audio_ins().iter(), |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) } - fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Content { + fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw { self.track().and_then(|track|track.devices.last()).map(|device|{ let outs = device.audio_outs().len() as u16; - Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose( + let border = Outer(true, Style::default().fg(Tui::g(96))); + Fixed::xy(20, 1 + outs, border.enclose( Fixed::xy(20, 1 + outs, FieldV(theme, format!("Audio outs: "), Map::south(1, ||device.audio_outs().iter(), |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) @@ -276,10 +280,10 @@ pub trait HasTrack { } impl Track { - pub fn per <'a, T: Content + 'a, U: TracksSizes<'a>> ( + pub fn per <'a, T: Draw + Layout + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - ) -> impl Content + 'a { + ) -> impl Draw + Layout + 'a { Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ let width = (x2 - x1) as u16; @@ -290,10 +294,10 @@ impl Track { } } -pub(crate) fn per_track_top <'a, T: Content + 'a, U: TracksSizes<'a>> ( +pub(crate) fn per_track_top <'a, T: Draw + Layout + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a -) -> impl Content + 'a { +) -> impl Draw + Layout + 'a { Align::x(Tui::bg(Reset, Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ let width = (x2 - x1) as u16; @@ -303,37 +307,39 @@ pub(crate) fn per_track_top <'a, T: Content + 'a, U: TracksSizes<'a>> ( callback(index, track))))}))) } -pub(crate) fn per_track <'a, T: Content + 'a, U: TracksSizes<'a>> ( +pub(crate) fn per_track <'a, T: Draw + Layout + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a -) -> impl Content + 'a { +) -> impl Draw + 'a { per_track_top(tracks, move|index, track|Fill::y(Align::y(callback(index, track)))) } pub(crate) fn io_ports <'a, T: PortsSizes<'a>> ( fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a -) -> impl Content + 'a { +) -> impl Draw + Layout + 'a { Map::new(iter, move|( _index, name, connections, y, y2 ): (usize, &'a Arc, &'a [Connect], usize, usize), _| map_south(y as u16, (y2-y) as u16, Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))), + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(&" 󰣲 ", name))))), Map::new(||connections.iter(), move|connect: &'a Connect, index|map_south(index as u16, 1, Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, &connect.info))))))))) } -pub(crate) fn io_conns <'a, T: PortsSizes<'a>> ( - fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a -) -> impl Content + 'a { - Map::new(iter, move|( - _index, _name, connections, y, y2 - ): (usize, &'a Arc, &'a [Connect], usize, usize), _| - map_south(y as u16, (y2-y) as u16, Bsp::s( - Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))), - Map::new(||connections.iter(), move|_conn, index|map_south(index as u16, 1, - Fill::x(Align::w(Tui::bold(false, wrap(bg, fg, Fill::x("")))))))))) -} +//pub(crate) fn io_conns <'a, T: PortsSizes<'a>> ( + //fg: Color, bg: Color, iter: &mut impl Iterator, &'a [Connect], usize, usize)> +//) -> impl Draw + Layout + 'a { + //Fill::xy(Thunk::new(move|to: &mut TuiOut|for (_, _, connections, y, y2) in &mut *iter { + //to.place(&map_south(y as u16, (y2-y) as u16, Bsp::s( + //Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w(&"▞▞▞▞ ▞▞▞▞"))))), + //Thunk::new(|to: &mut TuiOut|for (index, _connection) in connections.iter().enumerate() { + //to.place(&map_south(index as u16, 1, Fill::x(Align::w(Tui::bold(false, + //wrap(bg, fg, Fill::x(&""))))))) + //}) + //))) + //})) +//} //track_scroll: Fill::x(Fixed::y(1, ScrollbarH { //offset: arrangement.track_scroll, //length: h_tracks_area as usize, diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 3daf8ed0..dcc87a92 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -1,38 +1,55 @@ use crate::*; +pub(crate) fn wrap ( + bg: Color, fg: Color, content: impl Draw + Layout +) -> impl Draw + Layout { + let left = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▐"))); + let right = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▌"))); + Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content))) +} + +type Add<'a> = &'a dyn FnMut(&dyn Draw); + impl Arrangement { - pub fn view_inputs <'a> (&'a self, _theme: ItemTheme) -> impl Content + 'a { - Stack::south(move|add|{ - add(&Fixed::y(1, - Bsp::e(Fixed::x(20, Align::w(button_3("i", "nput ", format!("{}", self.midi_ins.len()), false))), - Bsp::w(Fixed::x(4, button_2("I", "+", false)), - Stack::east(move|add|{ - for (_index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Tui::bg(track.color.dark.rgb, Align::w(Fixed::x(track.width as u16, row!( - Either(track.sequencer.monitoring, Tui::fg(Green, "mon "), "mon "), - Either(track.sequencer.recording, Tui::fg(Red, "rec "), "rec "), - Either(track.sequencer.overdub, Tui::fg(Yellow, "dub "), "dub "), - ))))) - } - }))))); - for (_index, port) in self.midi_ins().iter().enumerate() { - add(&Fixed::y(1, Bsp::e( - Fixed::x(20, Align::w(Bsp::e(" ● ", - Tui::bold(true, Tui::fg(Rgb(255,255,255), port.port_name()))))), - Bsp::w(Fixed::x(4, ()), - Stack::east(move|add|{ - for (_index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Tui::bg(track.color.darker.rgb, Align::w(Fixed::x(track.width as u16, row!( - Either(track.sequencer.monitoring, Tui::fg(Green, " ● "), " · "), - Either(track.sequencer.recording, Tui::fg(Red, " ● "), " · "), - Either(track.sequencer.overdub, Tui::fg(Yellow, " ● "), " · "), - ))))) - } - }))))); + + pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw + Layout + '_ { + Bsp::s(self.view_inputs_header(), Thunk::new(|to: &mut TuiOut|{ + for port in self.midi_ins().iter() { + to.place(&self.view_inputs_row(port)) } - }) + })) } - pub fn view_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { + + fn view_inputs_header (&self) -> impl Draw + Layout + '_ { + Fixed::y(1, Bsp::e( + Fixed::x(20, Align::w( + button_3("i", "nput ", format!("{}", self.midi_ins.len()), false))), + Bsp::w(Fixed::x(4, button_2("I", "+", false)), + Thunk::new(|to: &mut TuiOut|for (_index, track, _x1, _x2) in self.tracks_with_sizes() { + to.place(&Tui::bg(track.color.dark.rgb, Align::w(Fixed::x(track.width as u16, row!( + Either::new(track.sequencer.monitoring, Tui::fg(Green, "mon "), "mon "), + Either::new(track.sequencer.recording, Tui::fg(Red, "rec "), "rec "), + Either::new(track.sequencer.overdub, Tui::fg(Yellow, "dub "), "dub "), + ))))) + })))) + } + + fn view_inputs_row (&self, port: &MidiInput) -> impl Draw + Layout { + Fixed::y(1, Bsp::e( + Fixed::x(20, Align::w( + Bsp::e(" ● ", Tui::bold(true, Tui::fg(Rgb(255,255,255), port.port_name()))))), + Bsp::w(Fixed::x(4, ()), Thunk::new(|to: &mut TuiOut|{ + for (_index, track, _x1, _x2) in self.tracks_with_sizes() { + to.place(&Tui::bg(track.color.darker.rgb, Align::w(Fixed::x(track.width as u16, row!( + Either::new(track.sequencer.monitoring, Tui::fg(Green, " ● "), " · "), + Either::new(track.sequencer.recording, Tui::fg(Red, " ● "), " · "), + Either::new(track.sequencer.overdub, Tui::fg(Yellow, " ● "), " · "), + ))))) + } + })))) + } + + pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw + Layout { let mut h = 1; for output in self.midi_outs().iter() { h += 1 + output.connections.len(); @@ -40,59 +57,68 @@ impl Arrangement { let h = h as u16; let list = Bsp::s( Fixed::y(1, Fill::x(Align::w(button_3("o", "utput", format!("{}", self.midi_outs.len()), false)))), - Fixed::y(h - 1, Fill::xy(Align::nw(Stack::south(|add|{ + Fixed::y(h - 1, Fill::xy(Align::nw(Thunk::new(|to: &mut TuiOut|{ for (_index, port) in self.midi_outs().iter().enumerate() { - add(&Fixed::y(1,Fill::x(Bsp::e( + to.place(&Fixed::y(1,Fill::x(Bsp::e( Align::w(Bsp::e(" ● ", Tui::fg(Rgb(255,255,255),Tui::bold(true, port.port_name())))), Fill::x(Align::e(format!("{}/{} ", port.port().get_connections().len(), port.connections.len()))))))); for (index, conn) in port.connections.iter().enumerate() { - add(&Fixed::y(1, Fill::x(Align::w(format!(" c{index:02}{}", conn.info()))))); + to.place(&Fixed::y(1, Fill::x(Align::w(format!(" c{index:02}{}", conn.info()))))); } } }))))); - Fixed::y(h, Self::view_track_row_section(theme, list, button_2("O", "+", false), + Fixed::y(h, view_track_row_section(theme, list, button_2("O", "+", false), Tui::bg(theme.darker.rgb, Align::w(Fill::x( - Stack::east(move|add|{ + Thunk::new(|to: &mut TuiOut|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Fixed::x(self.track_width(index, track), - Stack::south(move|add|{ - add(&Fixed::y(1, Align::w(Bsp::e( - Either(true, Tui::fg(Green, "play "), "play "), - Either(false, Tui::fg(Yellow, "solo "), "solo "), + to.place(&Fixed::x(self.track_width(index, track), + Thunk::new(|to: &mut TuiOut|{ + to.place(&Fixed::y(1, Align::w(Bsp::e( + Either::new(true, Tui::fg(Green, "play "), "play "), + Either::new(false, Tui::fg(Yellow, "solo "), "solo "), )))); for (_index, port) in self.midi_outs().iter().enumerate() { - add(&Fixed::y(1, Align::w(Bsp::e( - Either(true, Tui::fg(Green, " ● "), " · "), - Either(false, Tui::fg(Yellow, " ● "), " · "), + to.place(&Fixed::y(1, Align::w(Bsp::e( + Either::new(true, Tui::fg(Green, " ● "), " · "), + Either::new(false, Tui::fg(Yellow, " ● "), " · "), )))); for (_index, _conn) in port.connections.iter().enumerate() { - add(&Fixed::y(1, Fill::x(""))); + to.place(&Fixed::y(1, Fill::x(""))); } }})))}})))))) } - pub fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { + + pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw + Layout { let mut h = 2u16; for track in self.tracks().iter() { h = h.max(track.devices.len() as u16 * 2); } - Self::view_track_row_section( - theme, + view_track_row_section(theme, button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false), button_2("D", "+", false), - Stack::east(move|add|{ - for (index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Fixed::xy(self.track_width(index, track), h + 1, - Tui::bg(track.color.dark.rgb, Align::nw(Map::south(2, move||0..h, - |_, _index|Fixed::xy(track.width as u16, 2, - Tui::fg_bg( - ItemTheme::G[32].lightest.rgb, - ItemTheme::G[32].dark.rgb, - Align::nw(format!(" · {}", "--"))))))))); - } + Thunk::new(move|to: &mut TuiOut|for (index, track, _x1, _x2) in self.tracks_with_sizes() { + to.place(&Fixed::xy(self.track_width(index, track), h + 1, + Tui::bg(track.color.dark.rgb, Align::nw(Map::south(2, move||0..h, + |_, _index|Fixed::xy(track.width as u16, 2, + Tui::fg_bg( + ItemTheme::G[32].lightest.rgb, + ItemTheme::G[32].dark.rgb, + Align::nw(format!(" · {}", "--"))))))))); })) } + +} + +fn view_track_row_section ( + _theme: ItemTheme, + button: impl Draw + Layout, + button_add: impl Draw + Layout, + content: impl Draw + Layout, +) -> impl Draw + Layout { + Bsp::w(Fill::y(Fixed::x(4, Align::nw(button_add))), + Bsp::e(Fixed::x(20, Fill::y(Align::nw(button))), Fill::xy(Align::c(content)))) } pub trait HasClipsSize { @@ -135,21 +161,12 @@ pub trait TracksView: } }) } - fn view_track_row_section ( - _theme: ItemTheme, - button: impl Content, - button_add: impl Content, - content: impl Content - ) -> impl Content { - Bsp::w(Fill::y(Fixed::x(4, Align::nw(button_add))), - Bsp::e(Fixed::x(20, Fill::y(Align::nw(button))), Fill::xy(Align::c(content)))) - } - fn view_track_header <'a, T: Content> ( - &'a self, theme: ItemTheme, content: T - ) -> impl Content { + fn view_track_header <'a> ( + &'a self, theme: ItemTheme, content: impl Draw + Layout + ) -> impl Draw + Layout { Fixed::x(12, Tui::bg(theme.darker.rgb, Fill::x(Align::e(content)))) } - fn view_track_names (&self, theme: ItemTheme) -> impl Content { + fn view_track_names (&self, theme: ItemTheme) -> impl Draw + Layout { let track_count = self.tracks().len(); let scene_count = self.scenes().len(); let selected = self.selection(); @@ -161,10 +178,10 @@ pub trait TracksView: let button_2 = Bsp::s( button_2("T", "+", false), button_2("S", "+", false)); - Self::view_track_row_section(theme, button, button_2, Tui::bg(theme.darker.rgb, - Fixed::y(2, Fill::x(Stack::east(move|add|{ + view_track_row_section(theme, button, button_2, Tui::bg(theme.darker.rgb, + Fixed::y(2, Thunk::new(|to: &mut TuiOut|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Fixed::x(self.track_width(index, track), + to.place(&Fixed::x(self.track_width(index, track), Tui::bg(if selected.track() == Some(index) { track.color.light.rgb } else { @@ -172,55 +189,49 @@ pub trait TracksView: }, Bsp::s(Fill::x(Align::nw(Bsp::e( format!("·t{index:02} "), Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name)) - ))), ""))) ); - } - }))))) + ))), ""))) );}})))) } - fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Content { - Self::view_track_row_section(theme, - Bsp::s( - Fill::x(Align::w(button_2("o", "utput", false))), - Fill::xy(Stack::south(|add|{ - for port in self.midi_outs().iter() { - add(&Fill::x(Align::w(port.port_name()))); - } - }))), + fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw + Layout { + view_track_row_section(theme, + Bsp::s(Fill::x(Align::w(button_2("o", "utput", false))), + Thunk::new(|to: &mut TuiOut|for port in self.midi_outs().iter() { + to.place(&Fill::x(Align::w(port.port_name()))); + })), button_2("O", "+", false), - Tui::bg(theme.darker.rgb, Align::w(Fill::x( - Stack::east(move|add|{ - for (index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Fixed::x(self.track_width(index, track), - Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(), - |port, index|Tui::fg(Rgb(255, 255, 255), - Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( - format!("·o{index:02} {}", port.port_name()))))))))))); - } - }))))) + Tui::bg(theme.darker.rgb, Align::w(Thunk::new(|to: &mut TuiOut|{ + for (index, track, _x1, _x2) in self.tracks_with_sizes() { + to.place(&Fixed::x(self.track_width(index, track), + Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(), + |port, index|Tui::fg(Rgb(255, 255, 255), + Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( + format!("·o{index:02} {}", port.port_name())))))))))));}})))) } - fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content { + fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw + Layout { let mut h = 0u16; for track in self.tracks().iter() { h = h.max(track.sequencer.midi_ins.len() as u16); } - Self::view_track_row_section( - theme, + view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), - Tui::bg(theme.darker.rgb, Align::w(Fill::x( - Stack::east(move|add|{ - for (index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Fixed::xy(self.track_width(index, track), h + 1, - Align::nw(Bsp::s( - Tui::bg(track.color.base.rgb, - Fill::x(Align::w(row!( - Either(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "), - Either(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "), - Either(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·dub "), - )))), - Map::south(1, ||track.sequencer.midi_ins.iter(), - |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, - Fill::x(Align::w(format!("·i{index:02} {}", port.port_name()))))))))); - }}))))) + Tui::bg(theme.darker.rgb, Align::w(Thunk::new(move|to: &mut TuiOut|{ + for (index, track, _x1, _x2) in self.tracks_with_sizes() { + to.place(&Fixed::xy(self.track_width(index, track), h + 1, + Align::nw(Bsp::s( + Tui::bg(track.color.base.rgb, + Fill::x(Align::w(row!( + Either::new(track.sequencer.monitoring, + Tui::fg(Green, "●mon "), "·mon "), + Either::new(track.sequencer.recording, + Tui::fg(Red, "●rec "), "·rec "), + Either::new(track.sequencer.overdub, + Tui::fg(Yellow, "●dub "), "·dub "), + )))), + Map::south(1, ||track.sequencer.midi_ins.iter(), + |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, + Fill::x(Align::w(format!("·i{index:02} {}", port.port_name()))))))))); + } + })))) } fn track_width (&self, index: usize, track: &Track) -> u16 { track.width as u16 @@ -259,14 +270,12 @@ pub trait ScenesView: } }) } - fn view_scenes_names (&self) -> impl Content { - Fixed::x(20, Stack::south(move|add|{ - for (index, scene, ..) in self.scenes_with_sizes() { - add(&self.view_scene_name(index, scene)); - } + fn view_scenes_names (&self) -> impl Draw + Layout { + Fixed::x(20, Thunk::new(|to: &mut TuiOut|for (index, scene, ..) in self.scenes_with_sizes() { + to.place(&self.view_scene_name(index, scene)); })) } - fn view_scene_name (&self, index: usize, scene: &Scene) -> impl Content { + fn view_scene_name <'a> (&'a self, index: usize, scene: &'a Scene) -> impl Draw + Layout + 'a { let h = if self.selection().scene() == Some(index) && let Some(_editor) = self.editor() { 7 } else { @@ -277,15 +286,13 @@ pub trait ScenesView: } else { scene.color.base.rgb }; - Fixed::xy(20, h, Tui::bg(bg, Align::nw(Bsp::s( - Fill::x(Align::w(Bsp::e( - format!("·s{index:02} "), - Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &scene.name)) - ))), - When(self.selection().scene() == Some(index) && self.is_editing(), - Fill::xy(Align::nw(Bsp::s( - self.editor().as_ref().map(|e|e.clip_status()), - self.editor().as_ref().map(|e|e.edit_status()))))))))) + let a = Fill::x(Align::w(Bsp::e(format!("·s{index:02} "), + Tui::fg(Tui::g(255), Tui::bold(true, &scene.name))))); + let b = When(self.selection().scene() == Some(index) && self.is_editing(), + Fill::xy(Align::nw(Bsp::s( + self.editor().as_ref().map(|e|e.clip_status()), + self.editor().as_ref().map(|e|e.edit_status()))))); + Fixed::xy(20, h, Tui::bg(bg, Align::nw(Bsp::s(a, b)))) } } @@ -297,70 +304,68 @@ pub trait ClipsView: Sync { fn view_scenes_clips <'a> (&'a self) - -> impl Content + 'a + -> impl Draw + 'a { self.clips_size().of(Fill::xy(Bsp::a( Fill::xy(Align::se(Tui::fg(Green, format!("{}x{}", self.clips_size().w(), self.clips_size().h())))), - Stack::::east(move|column|{ - for (track_index, track, _, _) in self.tracks_with_sizes() { - //column(&Fixed::x(5, Fill::xy(Tui::bg(Green, "kyp")))); - column(&Fixed::x( - track.width as u16, - Fill::y(self.view_track_clips(track_index, track)) - )) - } + Thunk::new(|to: &mut TuiOut|for ( + track_index, track, _, _ + ) in self.tracks_with_sizes() { + to.place(&Fixed::x(track.width as u16, + Fill::y(self.view_track_clips(track_index, track)))) })))) } - fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Content + 'a { - Stack::south(move|cell|{ - for (scene_index, scene, ..) in self.scenes_with_sizes() { - let (name, theme): (Arc, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { - let clip = clip.read().unwrap(); - (format!(" ⏹ {}", &clip.name).into(), clip.color) - } else { - (" ⏹ -- ".into(), ItemTheme::G[32]) - }; - let fg = theme.lightest.rgb; - let mut outline = theme.base.rgb; - let bg = if self.selection().track() == Some(track_index) - && self.selection().scene() == Some(scene_index) - { - outline = theme.lighter.rgb; - theme.light.rgb - } else if self.selection().track() == Some(track_index) - || self.selection().scene() == Some(scene_index) - { - outline = theme.darkest.rgb; - theme.base.rgb - } else { - theme.dark.rgb - }; - let w = if self.selection().track() == Some(track_index) - && let Some(editor) = self.editor () - { - editor.width().max(24).max(track.width) - } else { - track.width - } as u16; - let y = if self.selection().scene() == Some(scene_index) - && let Some(editor) = self.editor () - { - editor.height().max(12) - } else { - Self::H_SCENE - } as u16; - cell(&Fixed::xy(w, y, Bsp::b( - Fill::xy(Outer(true, Style::default().fg(outline))), - Fill::xy(Bsp::b( - Bsp::b( - Tui::fg_bg(outline, bg, Fill::xy("")), - Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))), - ), - Fill::xy(When(self.selection().track() == Some(track_index) - && self.selection().scene() == Some(scene_index) - && self.is_editing(), self.editor()))))))); - } + fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Draw + 'a { + Thunk::new(move|to: &mut TuiOut|for ( + scene_index, scene, .. + ) in self.scenes_with_sizes() { + let (name, theme): (Arc, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { + let clip = clip.read().unwrap(); + (format!(" ⏹ {}", &clip.name).into(), clip.color) + } else { + (" ⏹ -- ".into(), ItemTheme::G[32]) + }; + let fg = theme.lightest.rgb; + let mut outline = theme.base.rgb; + let bg = if self.selection().track() == Some(track_index) + && self.selection().scene() == Some(scene_index) + { + outline = theme.lighter.rgb; + theme.light.rgb + } else if self.selection().track() == Some(track_index) + || self.selection().scene() == Some(scene_index) + { + outline = theme.darkest.rgb; + theme.base.rgb + } else { + theme.dark.rgb + }; + let w = if self.selection().track() == Some(track_index) + && let Some(editor) = self.editor () + { + editor.width().max(24).max(track.width) + } else { + track.width + } as u16; + let y = if self.selection().scene() == Some(scene_index) + && let Some(editor) = self.editor () + { + editor.height().max(12) + } else { + Self::H_SCENE + } as u16; + + to.place(&Fixed::xy(w, y, Bsp::b( + Fill::xy(Outer(true, Style::default().fg(outline))), + Fill::xy(Bsp::b( + Bsp::b( + Tui::fg_bg(outline, bg, Fill::xy("")), + Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))), + ), + Fill::xy(When(self.selection().track() == Some(track_index) + && self.selection().scene() == Some(scene_index) + && self.is_editing(), self.editor()))))))); }) } } diff --git a/crates/device/src/clock/clock_view.rs b/crates/device/src/clock/clock_view.rs index b839a3ca..287eec9f 100644 --- a/crates/device/src/clock/clock_view.rs +++ b/crates/device/src/clock/clock_view.rs @@ -6,7 +6,7 @@ pub fn view_transport ( bpm: Arc>, beat: Arc>, time: Arc>, -) -> impl Content { +) -> impl Draw + Layout { let theme = ItemTheme::G[96]; Tui::bg(Black, row!(Bsp::a( Fill::xy(Align::w(button_play_pause(play))), @@ -23,7 +23,7 @@ pub fn view_status ( sr: Arc>, buf: Arc>, lat: Arc>, -) -> impl Content { +) -> impl Draw + Layout { let theme = ItemTheme::G[96]; Tui::bg(Black, row!(Bsp::a( Fill::xy(Align::w(sel.map(|sel|FieldH(theme, "Selected", sel)))), @@ -35,18 +35,18 @@ pub fn view_status ( ))) } -pub(crate) fn button_play_pause (playing: bool) -> impl Content { +pub(crate) fn button_play_pause (playing: bool) -> impl Draw + Layout { let compact = true;//self.is_editing(); Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, Either::new(compact, - Thunk::new(move||Fixed::x(9, Either::new(playing, + Thunk::new(move|to: &mut TuiOut|to.place(&Fixed::x(9, Either::new(playing, Tui::fg(Rgb(0, 255, 0), " PLAYING "), Tui::fg(Rgb(255, 128, 0), " STOPPED "))) - ), - Thunk::new(move||Fixed::x(5, Either::new(playing, + )), + Thunk::new(move|to: &mut TuiOut|to.place(&Fixed::x(5, Either::new(playing, Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))) - ) + )) ) ) } @@ -93,7 +93,7 @@ impl ViewCache { //} //pub fn scene_add (cache: &Arc>, scene: usize, scenes: usize, is_editing: bool) - //-> impl Content + //-> impl Draw //{ //let data = (scene, scenes); //cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); @@ -133,7 +133,7 @@ impl ViewCache { } } - //pub fn view_h2 (&self) -> impl Content { + //pub fn view_h2 (&self) -> impl Draw { //let cache = self.project.clock.view_cache.clone(); //let cache = cache.read().unwrap(); //add(&Fixed::x(15, Align::w(Bsp::s( diff --git a/crates/device/src/editor/editor_view.rs b/crates/device/src/editor/editor_view.rs index 0a11c72c..afcf904e 100644 --- a/crates/device/src/editor/editor_view.rs +++ b/crates/device/src/editor/editor_view.rs @@ -1,5 +1,7 @@ use crate::*; +tui_layout!(|self: MidiEditor, to|self.content().layout(to)); +tui_draw!(|self: MidiEditor, to|self.content().draw(to)); content!(TuiOut: |self: MidiEditor| { self.autoscroll(); //self.autozoom(); @@ -8,7 +10,7 @@ content!(TuiOut: |self: MidiEditor| { impl MidiEditor { - pub fn clip_status (&self) -> impl Content + '_ { + pub fn clip_status (&self) -> impl Draw + Layout + '_ { let (_color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { (clip.color, clip.name.clone(), clip.length, clip.looped) } else { (ItemTheme::G[64], String::new().into(), 0, false) }; @@ -25,7 +27,7 @@ impl MidiEditor { )) } - pub fn edit_status (&self) -> impl Content + '_ { + pub fn edit_status (&self) -> impl Draw + Layout + '_ { let (_color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { (clip.color, clip.length) } else { (ItemTheme::G[64], 0) }; diff --git a/crates/device/src/editor/editor_view_h.rs b/crates/device/src/editor/editor_view_h.rs index bf04f16f..e461e738 100644 --- a/crates/device/src/editor/editor_view_h.rs +++ b/crates/device/src/editor/editor_view_h.rs @@ -23,7 +23,7 @@ has!(Measure:|self:PianoHorizontal|self.size); impl PianoHorizontal { pub fn new (clip: Option<&Arc>>) -> Self { let size = Measure::new(); - let mut range = MidiRangeModel::from((12, true)); + let mut range = MidiRangeModel::from((12, true)); range.time_axis = size.x.clone(); range.note_axis = size.y.clone(); let piano = Self { @@ -46,6 +46,8 @@ pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) } +tui_layout!(|self: PianoHorizontal, to|self.content().layout(to)); +tui_draw!(|self: PianoHorizontal, to|self.content().draw(to)); content!(TuiOut:|self: PianoHorizontal| Bsp::s( Bsp::e( Fixed::x(5, format!("{}x{}", self.size.w(), self.size.h())), @@ -139,12 +141,12 @@ impl PianoHorizontal { } } - fn notes (&self) -> impl Content { + fn notes (&self) -> impl Draw { let time_start = self.get_time_start(); let note_lo = self.get_note_lo(); let note_hi = self.get_note_hi(); let buffer = self.buffer.clone(); - ThunkRender::new(move|to: &mut TuiOut|{ + Thunk::new(move|to: &mut TuiOut|{ let source = buffer.read().unwrap(); let [x0, y0, w, _h] = to.area().xywh(); //if h as usize != note_axis { @@ -170,7 +172,7 @@ impl PianoHorizontal { } }) } - fn cursor (&self) -> impl Content { + fn cursor (&self) -> impl Draw + Layout { let note_hi = self.get_note_hi(); let note_lo = self.get_note_lo(); let note_pos = self.get_note_pos(); @@ -179,7 +181,7 @@ impl PianoHorizontal { let time_start = self.get_time_start(); let time_zoom = self.get_time_zoom(); let style = Some(Style::default().fg(self.color.lightest.rgb)); - ThunkRender::new(move|to: &mut TuiOut|{ + Thunk::new(move|to: &mut TuiOut|{ let [x0, y0, w, _] = to.area().xywh(); for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { if note == note_pos { @@ -201,7 +203,7 @@ impl PianoHorizontal { } }) } - fn keys (&self) -> impl Content { + fn keys (&self) -> impl Draw + Layout { let state = self; let color = state.color; let note_lo = state.get_note_lo(); @@ -210,7 +212,7 @@ impl PianoHorizontal { let key_style = Some(Style::default().fg(Rgb(192, 192, 192)).bg(Rgb(0, 0, 0))); let off_style = Some(Style::default().fg(Tui::g(255))); let on_style = Some(Style::default().fg(Rgb(255,0,0)).bg(color.base.rgb).bold()); - Fill::y(Fixed::x(self.keys_width, ThunkRender::new(move|to: &mut TuiOut|{ + Fill::y(Fixed::x(self.keys_width, Thunk::new(move|to: &mut TuiOut|{ let [x, y0, _w, _h] = to.area().xywh(); for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { to.blit(&to_key(note), x, screen_y, key_style); @@ -225,8 +227,8 @@ impl PianoHorizontal { } }))) } - fn timeline (&self) -> impl Content + '_ { - Fill::x(Fixed::y(1, ThunkRender::new(move|to: &mut TuiOut|{ + fn timeline (&self) -> impl Draw + Layout + '_ { + Fill::x(Fixed::y(1, Thunk::new(move|to: &mut TuiOut|{ let [x, y, w, _h] = to.area(); let style = Some(Style::default().dim()); let length = self.clip.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); diff --git a/crates/device/src/editor/editor_view_v.rs b/crates/device/src/editor/editor_view_v.rs index 493c6de9..2913f04a 100644 --- a/crates/device/src/editor/editor_view_v.rs +++ b/crates/device/src/editor/editor_view_v.rs @@ -1,3 +1,5 @@ +//! TODO + use crate::*; pub struct OctaveVertical { @@ -24,7 +26,7 @@ impl OctaveVertical { } impl Content for OctaveVertical { - fn content (&self) -> impl Render { + fn content (&self) -> impl Draw + Layout + '_ { row!( Tui::fg_bg(self.color(0), self.color(1), "▙"), Tui::fg_bg(self.color(2), self.color(3), "▙"), diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 22edce2e..56df9abf 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -14,7 +14,7 @@ pub(crate) use std::error::Error; pub(crate) use std::ffi::OsString; pub(crate) use ::tengri::{from, has, maybe_has, Usually, Perhaps, Has, MaybeHas}; -pub(crate) use ::tengri::{dsl::*, input::*, output::*, tui::{*, ratatui::prelude::*}}; +pub(crate) use ::tengri::{dsl::*, input::*, output::{*, Layout}, tui::{*, ratatui::prelude::*}}; pub(crate) use ::tek_engine::*; pub(crate) use ::tek_engine::midi::{u7, LiveEvent, MidiMessage}; pub(crate) use ::tek_engine::jack::{Control, ProcessScope, RawMidi}; @@ -47,45 +47,6 @@ macro_rules! def_sizes_iter { #[cfg(feature = "vst2")] mod vst2; #[cfg(feature = "vst2")] pub use self::vst2::*; #[cfg(feature = "vst3")] mod vst3; #[cfg(feature = "vst3")] pub use self::vst3::*; -pub fn button_2 <'a> ( - key: impl Content + 'a, label: impl Content + 'a, editing: bool, -) -> impl Content + 'a { - let key = Tui::fg_bg(Tui::orange(), Tui::g(0), Bsp::e( - Tui::fg(Tui::g(0), "▐"), - Bsp::e(key, Tui::fg(Tui::g(96), "▐")) - )); - let label = When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label)); - Tui::bold(true, Bsp::e(key, label)) -} - -pub fn button_3 <'a, K, L, V> ( - key: K, - label: L, - value: V, - editing: bool, -) -> impl Content + 'a where - K: Content + 'a, - L: Content + 'a, - V: Content + 'a, -{ - let key = Tui::fg_bg(Tui::orange(), Tui::g(0), - Bsp::e(Tui::fg(Tui::g(0), "▐"), Bsp::e(key, Tui::fg(if editing { - Tui::g(128) - } else { - Tui::g(96) - }, "▐")))); - let label = Bsp::e( - When::new(!editing, Bsp::e( - Tui::fg_bg(Tui::g(255), Tui::g(96), label), - Tui::fg_bg(Tui::g(128), Tui::g(96), "▐"), - )), - Bsp::e( - Tui::fg_bg(Tui::g(224), Tui::g(128), value), - Tui::fg_bg(Tui::g(128), Reset, "▌"), - )); - Tui::bold(true, Bsp::e(key, label)) -} - pub fn swap_value ( target: &mut T, value: &T, returned: impl Fn(T)->U ) -> Perhaps { diff --git a/crates/device/src/lv2/lv2_tui.rs b/crates/device/src/lv2/lv2_tui.rs index 0c76a1c9..8bfad8a2 100644 --- a/crates/device/src/lv2/lv2_tui.rs +++ b/crates/device/src/lv2/lv2_tui.rs @@ -1,8 +1,8 @@ use crate::*; use super::*; -impl Content for Lv2 { - fn render (&self, to: &mut TuiOut) { +impl Draw for Lv2 { + fn draw (&self, to: &mut TuiOut) { let area = to.area(); let [x, y, _, height] = area; let mut width = 20u16; diff --git a/crates/device/src/meter.rs b/crates/device/src/meter.rs index 106975c7..48f2c774 100644 --- a/crates/device/src/meter.rs +++ b/crates/device/src/meter.rs @@ -9,8 +9,8 @@ pub enum MeteringMode { #[derive(Debug, Default, Clone)] pub struct Log10Meter(pub f32); - -render!(TuiOut: |self: Log10Meter, to| { +tui_layout!(|self: Log10Meter, to|to); +tui_draw!(|self: Log10Meter, to| { let [x, y, w, h] = to.area(); let signal = 100.0 - f32::max(0.0, f32::min(100.0, self.0.abs())); let v = (signal * h as f32 / 100.0).ceil() as u16; @@ -32,7 +32,7 @@ pub fn to_log10 (samples: &[f32]) -> f32 { #[derive(Debug, Default, Clone)] pub struct RmsMeter(pub f32); -render!(TuiOut: |self: RmsMeter, to| { +draw!(TuiOut: |self: RmsMeter, to| { let [x, y, w, h] = to.area(); let signal = f32::max(0.0, f32::min(100.0, self.0.abs())); let v = (signal * h as f32).ceil() as u16; @@ -53,7 +53,7 @@ pub fn to_rms (samples: &[f32]) -> f32 { (sum / samples.len() as f32).sqrt() } -pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Content + 'a { +pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Draw + 'a { col!( FieldH(ItemTheme::G[128], label, format!("{:>+9.3}", value)), Fixed::xy(if value >= 0.0 { 13 } @@ -74,7 +74,7 @@ pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Content + 'a else { Green }, ()))) } -pub fn view_meters (values: &[f32;2]) -> impl Content + use<'_> { +pub fn view_meters (values: &[f32;2]) -> impl Draw + use<'_> { let left = format!("L/{:>+9.3}", values[0]); let right = format!("R/{:>+9.3}", values[1]); Bsp::s(left, right) diff --git a/crates/device/src/sampler/sampler_browse.rs b/crates/device/src/sampler/sampler_browse.rs index f239767c..9d9bf491 100644 --- a/crates/device/src/sampler/sampler_browse.rs +++ b/crates/device/src/sampler/sampler_browse.rs @@ -143,8 +143,8 @@ fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { Ok((subdirs, files)) } -impl Content for AddSampleModal { - fn render (&self, _to: &mut TuiOut) { +impl Draw for AddSampleModal { + fn draw (&self, _to: &mut TuiOut) { todo!() //let area = to.area(); //to.make_dim(); diff --git a/crates/device/src/sampler/sampler_view.rs b/crates/device/src/sampler/sampler_view.rs index 90999e2c..20c4ab13 100644 --- a/crates/device/src/sampler/sampler_view.rs +++ b/crates/device/src/sampler/sampler_view.rs @@ -2,28 +2,31 @@ use crate::*; impl Sampler { - pub fn view_grid (&self) -> impl Content + use<'_> { + pub fn view_grid (&self) -> impl Draw + Layout + use<'_> { let cells_x = 8u16; let cells_y = 8u16; let cell_width = 10u16; let cell_height = 2u16; //let width = cells_x * cell_width; //let height = cells_y * cell_height; - let cols = Map::east( - cell_width, - move||0..cells_x, - move|x, _|Map::south( - cell_height, - move||0..cells_y, - move|y, _|self.view_grid_cell("........", x, y, cell_width, cell_height) - ) - ); - cols + //let cols = Map::east( + //cell_width, + //move||0..cells_x, + //move|x, _|Map::south( + //cell_height, + //move||0..cells_y, + //move|y, _|self.view_grid_cell("........", x, y, cell_width, cell_height) + //) + //); + //cols + //Thunk::new(|to: &mut TuiOut|{ + //}) + "TODO" } pub fn view_grid_cell <'a> ( &'a self, name: &'a str, x: u16, y: u16, w: u16, h: u16 - ) -> impl Content + use<'a> { + ) -> impl Draw + use<'a> { let cursor = self.cursor(); let hi_fg = Color::Rgb(64, 64, 64); let hi_bg = if y == 0 { Color::Reset } else { Color::Rgb(64, 64, 64) /*prev*/ }; @@ -55,7 +58,7 @@ impl Sampler { pub fn view_list <'a, T: NotePoint + NoteRange> ( &'a self, compact: bool, editor: &T - ) -> impl Content + 'a { + ) -> impl Draw + 'a { let note_lo = editor.get_note_lo(); let note_pt = editor.get_note_pos(); let note_hi = editor.get_note_hi(); @@ -97,7 +100,7 @@ impl Sampler { } } - pub fn view_sample (&self, note_pt: usize) -> impl Content + use<'_> { + pub fn view_sample (&self, note_pt: usize) -> impl Draw + use<'_> { Outer(true, Style::default().fg(Tui::g(96))) .enclose(Fill::xy(draw_viewer(if let Some((_, Some(sample))) = &self.recording { Some(sample) @@ -108,7 +111,7 @@ impl Sampler { }))) } - pub fn view_sample_info (&self, note_pt: usize) -> impl Content + use<'_> { + pub fn view_sample_info (&self, note_pt: usize) -> impl Draw + use<'_> { Fill::x(Fixed::y(1, draw_info(if let Some((_, Some(sample))) = &self.recording { Some(sample) } else if let Some(sample) = &self.mapped[note_pt] { @@ -118,7 +121,7 @@ impl Sampler { }))) } - pub fn view_sample_status (&self, note_pt: usize) -> impl Content + use<'_> { + pub fn view_sample_status (&self, note_pt: usize) -> impl Draw + use<'_> { Fixed::x(20, draw_info_v(if let Some((_, Some(sample))) = &self.recording { Some(sample) } else if let Some(sample) = &self.mapped[note_pt] { @@ -128,20 +131,20 @@ impl Sampler { })) } - pub fn view_status (&self, index: usize) -> impl Content { + pub fn view_status (&self, index: usize) -> impl Draw { draw_status(self.mapped[index].as_ref()) } - pub fn view_meters_input (&self) -> impl Content + use<'_> { + pub fn view_meters_input (&self) -> impl Draw + use<'_> { draw_meters(&self.input_meters) } - pub fn view_meters_output (&self) -> impl Content + use<'_> { + pub fn view_meters_output (&self) -> impl Draw + use<'_> { draw_meters(&self.output_meters) } } -fn draw_meters (meters: &[f32]) -> impl Content + use<'_> { +fn draw_meters (meters: &[f32]) -> impl Draw + use<'_> { Tui::bg(Black, Fixed::x(2, Map::east(1, ||meters.iter(), |value, _index|{ Fill::y(RmsMeter(*value)) }))) @@ -163,9 +166,9 @@ fn draw_list_item (sample: &Option>>) -> String { } } -fn draw_viewer (sample: Option<&Arc>>) -> impl Content + use<'_> { +fn draw_viewer (sample: Option<&Arc>>) -> impl Draw + Layout + use<'_> { let min_db = -64.0; - ThunkRender::new(move|to: &mut TuiOut|{ + Thunk::new(move|to: &mut TuiOut|{ let [x, y, width, height] = to.area(); let area = Rect { x, y, width, height }; if let Some(sample) = &sample { @@ -218,41 +221,41 @@ fn draw_viewer (sample: Option<&Arc>>) -> impl Content + }) } -fn draw_info (sample: Option<&Arc>>) -> impl Content + use<'_> { - When(sample.is_some(), Thunk::new(move||{ +fn draw_info (sample: Option<&Arc>>) -> impl Draw + Layout + use<'_> { + When(sample.is_some(), Thunk::new(move|to: &mut TuiOut|{ let sample = sample.unwrap().read().unwrap(); let theme = sample.color; - row!( + to.place(&row!( FieldH(theme, "Name", format!("{:<10}", sample.name.clone())), FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())), FieldH(theme, "Start", format!("{:<8}", sample.start)), FieldH(theme, "End", format!("{:<8}", sample.end)), FieldH(theme, "Trans", "0"), FieldH(theme, "Gain", format!("{}", sample.gain)), - ) + )) })) } -fn draw_info_v (sample: Option<&Arc>>) -> impl Content + use<'_> { - Either(sample.is_some(), Thunk::new(move||{ +fn draw_info_v (sample: Option<&Arc>>) -> impl Draw + Layout + use<'_> { + Either::new(sample.is_some(), Thunk::new(move|to: &mut TuiOut|{ let sample = sample.unwrap().read().unwrap(); let theme = sample.color; - Fixed::x(20, col!( + to.place(&Fixed::x(20, col!( Fill::x(Align::w(FieldH(theme, "Name ", format!("{:<10}", sample.name.clone())))), Fill::x(Align::w(FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())))), Fill::x(Align::w(FieldH(theme, "Start ", format!("{:<8}", sample.start)))), Fill::x(Align::w(FieldH(theme, "End ", format!("{:<8}", sample.end)))), Fill::x(Align::w(FieldH(theme, "Trans ", "0"))), Fill::x(Align::w(FieldH(theme, "Gain ", format!("{}", sample.gain)))), - )) - }), Thunk::new(move||Tui::fg(Red, col!( + ))) + }), Thunk::new(|to: &mut TuiOut|to.place(&Tui::fg(Red, col!( Tui::bold(true, "× No sample."), "[r] record", "[Shift-F9] import", - )))) + ))))) } -fn draw_status (sample: Option<&Arc>>) -> impl Content { +fn draw_status (sample: Option<&Arc>>) -> impl Draw + Layout { Tui::bold(true, Tui::fg(Tui::g(224), sample .map(|sample|{ let sample = sample.read().unwrap(); diff --git a/crates/device/src/sequencer/seq_launch.rs b/crates/device/src/sequencer/seq_launch.rs index 48d30f1c..fb8a0524 100644 --- a/crates/device/src/sequencer/seq_launch.rs +++ b/crates/device/src/sequencer/seq_launch.rs @@ -38,7 +38,7 @@ pub trait HasPlayClip: HasClock { *self.reset_mut() = true; } - fn play_status (&self) -> impl Content { + fn play_status (&self) -> impl Draw { let (name, color): (Arc, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() { let MidiClip { ref name, color, .. } = *clip.read().unwrap(); (name.clone(), color) @@ -51,7 +51,7 @@ pub trait HasPlayClip: HasClock { FieldV(color, "Now:", format!("{} {}", time, name)) } - fn next_status (&self) -> impl Content { + fn next_status (&self) -> impl Draw { let mut time: Arc = String::from("--.-.--").into(); let mut name: Arc = String::from("").into(); let mut color = ItemTheme::G[64]; diff --git a/deps/tengri b/deps/tengri index b98fccd9..5e6338fa 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit b98fccd98ba13be8eabe29d66621a15d025b2042 +Subproject commit 5e6338fad8febc217bde4879d8250373a75faf7a From 307dab868610823177321f2d1facfaaa83d5ad92 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 7 Sep 2025 21:29:02 +0300 Subject: [PATCH 2/4] just: profile --- Justfile | 2 +- deps/tengri | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Justfile b/Justfile index 214e45e5..e9bbdcb7 100644 --- a/Justfile +++ b/Justfile @@ -33,7 +33,7 @@ cloc: test: cargo test --workspace --exclude jack prof: - CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -- arranger + CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -- doc: cargo doc -j4 --workspace --document-private-items cov: diff --git a/deps/tengri b/deps/tengri index 5e6338fa..baa582b9 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 5e6338fad8febc217bde4879d8250373a75faf7a +Subproject commit baa582b9ddc1dd9c078b223fae7e3c10cf348166 From 5e2e0438a4ef25286045e06716e69935eb404861 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 7 Sep 2025 23:13:41 +0300 Subject: [PATCH 3/4] and back into 1 crate --- Cargo.lock | 43 +- Cargo.toml | 25 +- Justfile | 2 + bacon.toml | 8 +- crates/app/Cargo.toml | 36 -- crates/app/tek.rs | 172 -------- crates/app/tek_data.rs | 141 ------- crates/app/tek_jack.rs | 57 --- crates/config/Cargo.toml | 11 - crates/config/config.rs | 232 ----------- crates/device/Cargo.toml | 35 -- crates/device/src/device.rs | 87 ---- crates/engine/Cargo.toml | 12 - deps/tengri | 2 +- tek/Cargo.toml | 55 +++ tek/config.rs | 81 ++++ tek/config/bind.rs | 106 +++++ tek/config/mode.rs | 49 +++ tek/config/view.rs | 3 + crates/app/tek_deps.rs => tek/deps.rs | 19 +- tek/device.rs | 135 ++++++ {crates/device/src => tek/device}/arranger.rs | 0 .../device}/arranger/arranger_api.rs | 0 .../device}/arranger/arranger_clip.rs | 0 .../device}/arranger/arranger_scenes.rs | 0 .../device}/arranger/arranger_select.rs | 0 .../device}/arranger/arranger_tracks.rs | 0 .../device}/arranger/arranger_view.rs | 0 {crates/device/src => tek/device}/browse.rs | 0 .../src => tek/device}/browse/browse_api.rs | 0 .../src => tek/device}/browse/browse_view.rs | 0 {crates/device/src => tek/device}/clap.rs | 0 {crates/device/src => tek/device}/clock.rs | 0 .../src => tek/device}/clock/clock_api.rs | 0 .../src => tek/device}/clock/clock_model.rs | 8 +- .../src => tek/device}/clock/clock_view.rs | 0 {crates/device/src => tek/device}/editor.rs | 0 .../src => tek/device}/editor/editor_api.rs | 0 .../src => tek/device}/editor/editor_model.rs | 0 .../src => tek/device}/editor/editor_view.rs | 0 .../device}/editor/editor_view_h.rs | 0 .../device}/editor/editor_view_v.rs | 0 {crates/device/src => tek/device}/lib.rs | 0 {crates/device/src => tek/device}/lv2.rs | 0 .../src => tek/device}/lv2/lv2_audio.rs | 0 .../device/src => tek/device}/lv2/lv2_gui.rs | 0 .../src => tek/device}/lv2/lv2_model.rs | 0 .../device/src => tek/device}/lv2/lv2_tui.rs | 0 {crates/device/src => tek/device}/meter.rs | 0 {crates/device/src => tek/device}/mixer.rs | 0 {crates/device/src => tek/device}/pool.rs | 0 .../src => tek/device}/pool/pool_api.rs | 0 .../src => tek/device}/pool/pool_view.rs | 0 {crates/device/src => tek/device}/port.rs | 4 +- .../src => tek/device}/port/port_api.rs | 0 .../src => tek/device}/port/port_audio_in.rs | 0 .../src => tek/device}/port/port_audio_out.rs | 0 .../src => tek/device}/port/port_connect.rs | 0 .../src => tek/device}/port/port_midi_in.rs | 0 .../src => tek/device}/port/port_midi_out.rs | 0 {crates/device/src => tek/device}/sampler.rs | 0 .../src => tek/device}/sampler/sampler_api.rs | 0 .../device}/sampler/sampler_audio.rs | 0 .../device}/sampler/sampler_browse.rs | 0 .../device}/sampler/sampler_data.rs | 2 +- .../device}/sampler/sampler_midi.rs | 0 .../device}/sampler/sampler_view.rs | 0 .../device/src => tek/device}/sequencer.rs | 0 .../src => tek/device}/sequencer/seq_audio.rs | 0 .../src => tek/device}/sequencer/seq_clip.rs | 0 .../device}/sequencer/seq_launch.rs | 0 .../src => tek/device}/sequencer/seq_model.rs | 0 .../src => tek/device}/sequencer/seq_view.rs | 0 {crates/device/src => tek/device}/sf2.rs | 0 {crates/device/src => tek/device}/vst2.rs | 0 {crates/device/src => tek/device}/vst3.rs | 0 crates/engine/src/lib.rs => tek/engine.rs | 158 ++++--- {crates/engine/src => tek/engine}/jack.rs | 0 {crates/engine/src => tek/engine}/midi.rs | 0 {crates/engine/src => tek/engine}/note.rs | 0 .../src => tek/engine}/note/note_pitch.rs | 0 .../src => tek/engine}/note/note_point.rs | 0 .../src => tek/engine}/note/note_range.rs | 0 {crates/engine/src => tek/engine}/time.rs | 0 .../src => tek/engine}/time/time_moment.rs | 0 .../src => tek/engine}/time/time_note.rs | 0 .../src => tek/engine}/time/time_perf.rs | 0 .../src => tek/engine}/time/time_pulse.rs | 0 .../engine}/time/time_sample_count.rs | 0 .../engine}/time/time_sample_rate.rs | 0 .../src => tek/engine}/time/time_timebase.rs | 0 .../src => tek/engine}/time/time_unit.rs | 0 .../src => tek/engine}/time/time_usec.rs | 0 tek.edn => tek/tek.edn | 0 crates/app/tek_view.rs => tek/tek.rs | 389 +++++++++++++++++- {crates/app => tek}/tek_bind.rs | 0 {crates/app => tek}/tek_cli.rs | 0 {crates/app => tek}/tek_menu.rs | 0 crates/app/tek_test.rs => tek/test.rs | 0 99 files changed, 934 insertions(+), 938 deletions(-) delete mode 100644 crates/app/Cargo.toml delete mode 100644 crates/app/tek.rs delete mode 100644 crates/app/tek_data.rs delete mode 100644 crates/app/tek_jack.rs delete mode 100644 crates/config/Cargo.toml delete mode 100644 crates/config/config.rs delete mode 100644 crates/device/Cargo.toml delete mode 100644 crates/device/src/device.rs delete mode 100644 crates/engine/Cargo.toml create mode 100644 tek/Cargo.toml create mode 100644 tek/config.rs create mode 100644 tek/config/bind.rs create mode 100644 tek/config/mode.rs create mode 100644 tek/config/view.rs rename crates/app/tek_deps.rs => tek/deps.rs (58%) create mode 100644 tek/device.rs rename {crates/device/src => tek/device}/arranger.rs (100%) rename {crates/device/src => tek/device}/arranger/arranger_api.rs (100%) rename {crates/device/src => tek/device}/arranger/arranger_clip.rs (100%) rename {crates/device/src => tek/device}/arranger/arranger_scenes.rs (100%) rename {crates/device/src => tek/device}/arranger/arranger_select.rs (100%) rename {crates/device/src => tek/device}/arranger/arranger_tracks.rs (100%) rename {crates/device/src => tek/device}/arranger/arranger_view.rs (100%) rename {crates/device/src => tek/device}/browse.rs (100%) rename {crates/device/src => tek/device}/browse/browse_api.rs (100%) rename {crates/device/src => tek/device}/browse/browse_view.rs (100%) rename {crates/device/src => tek/device}/clap.rs (100%) rename {crates/device/src => tek/device}/clock.rs (100%) rename {crates/device/src => tek/device}/clock/clock_api.rs (100%) rename {crates/device/src => tek/device}/clock/clock_model.rs (96%) rename {crates/device/src => tek/device}/clock/clock_view.rs (100%) rename {crates/device/src => tek/device}/editor.rs (100%) rename {crates/device/src => tek/device}/editor/editor_api.rs (100%) rename {crates/device/src => tek/device}/editor/editor_model.rs (100%) rename {crates/device/src => tek/device}/editor/editor_view.rs (100%) rename {crates/device/src => tek/device}/editor/editor_view_h.rs (100%) rename {crates/device/src => tek/device}/editor/editor_view_v.rs (100%) rename {crates/device/src => tek/device}/lib.rs (100%) rename {crates/device/src => tek/device}/lv2.rs (100%) rename {crates/device/src => tek/device}/lv2/lv2_audio.rs (100%) rename {crates/device/src => tek/device}/lv2/lv2_gui.rs (100%) rename {crates/device/src => tek/device}/lv2/lv2_model.rs (100%) rename {crates/device/src => tek/device}/lv2/lv2_tui.rs (100%) rename {crates/device/src => tek/device}/meter.rs (100%) rename {crates/device/src => tek/device}/mixer.rs (100%) rename {crates/device/src => tek/device}/pool.rs (100%) rename {crates/device/src => tek/device}/pool/pool_api.rs (100%) rename {crates/device/src => tek/device}/pool/pool_view.rs (100%) rename {crates/device/src => tek/device}/port.rs (96%) rename {crates/device/src => tek/device}/port/port_api.rs (100%) rename {crates/device/src => tek/device}/port/port_audio_in.rs (100%) rename {crates/device/src => tek/device}/port/port_audio_out.rs (100%) rename {crates/device/src => tek/device}/port/port_connect.rs (100%) rename {crates/device/src => tek/device}/port/port_midi_in.rs (100%) rename {crates/device/src => tek/device}/port/port_midi_out.rs (100%) rename {crates/device/src => tek/device}/sampler.rs (100%) rename {crates/device/src => tek/device}/sampler/sampler_api.rs (100%) rename {crates/device/src => tek/device}/sampler/sampler_audio.rs (100%) rename {crates/device/src => tek/device}/sampler/sampler_browse.rs (100%) rename {crates/device/src => tek/device}/sampler/sampler_data.rs (97%) rename {crates/device/src => tek/device}/sampler/sampler_midi.rs (100%) rename {crates/device/src => tek/device}/sampler/sampler_view.rs (100%) rename {crates/device/src => tek/device}/sequencer.rs (100%) rename {crates/device/src => tek/device}/sequencer/seq_audio.rs (100%) rename {crates/device/src => tek/device}/sequencer/seq_clip.rs (100%) rename {crates/device/src => tek/device}/sequencer/seq_launch.rs (100%) rename {crates/device/src => tek/device}/sequencer/seq_model.rs (100%) rename {crates/device/src => tek/device}/sequencer/seq_view.rs (100%) rename {crates/device/src => tek/device}/sf2.rs (100%) rename {crates/device/src => tek/device}/vst2.rs (100%) rename {crates/device/src => tek/device}/vst3.rs (100%) rename crates/engine/src/lib.rs => tek/engine.rs (89%) rename {crates/engine/src => tek/engine}/jack.rs (100%) rename {crates/engine/src => tek/engine}/midi.rs (100%) rename {crates/engine/src => tek/engine}/note.rs (100%) rename {crates/engine/src => tek/engine}/note/note_pitch.rs (100%) rename {crates/engine/src => tek/engine}/note/note_point.rs (100%) rename {crates/engine/src => tek/engine}/note/note_range.rs (100%) rename {crates/engine/src => tek/engine}/time.rs (100%) rename {crates/engine/src => tek/engine}/time/time_moment.rs (100%) rename {crates/engine/src => tek/engine}/time/time_note.rs (100%) rename {crates/engine/src => tek/engine}/time/time_perf.rs (100%) rename {crates/engine/src => tek/engine}/time/time_pulse.rs (100%) rename {crates/engine/src => tek/engine}/time/time_sample_count.rs (100%) rename {crates/engine/src => tek/engine}/time/time_sample_rate.rs (100%) rename {crates/engine/src => tek/engine}/time/time_timebase.rs (100%) rename {crates/engine/src => tek/engine}/time/time_unit.rs (100%) rename {crates/engine/src => tek/engine}/time/time_usec.rs (100%) rename tek.edn => tek/tek.edn (100%) rename crates/app/tek_view.rs => tek/tek.rs (62%) rename {crates/app => tek}/tek_bind.rs (100%) rename {crates/app => tek}/tek_cli.rs (100%) rename {crates/app => tek}/tek_menu.rs (100%) rename crates/app/tek_test.rs => tek/test.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 051935e5..b1a63742 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2381,56 +2381,27 @@ dependencies = [ [[package]] name = "tek" -version = "0.2.1" +version = "0.3.0" dependencies = [ + "atomic_float", "backtrace", "clap", + "jack", "konst", + "livi", + "midly", "palette", "proptest", "proptest-derive", "rand 0.8.5", - "tek_config", - "tek_device", - "tek_engine", + "symphonia", "tengri", "tengri_proc", "toml", - "xdg", -] - -[[package]] -name = "tek_config" -version = "0.2.1" -dependencies = [ - "tengri", - "xdg", -] - -[[package]] -name = "tek_device" -version = "0.2.1" -dependencies = [ - "livi", - "symphonia", - "tek_engine", - "tengri", - "tengri_proc", "uuid", "wavers", "winit", -] - -[[package]] -name = "tek_engine" -version = "0.2.1" -dependencies = [ - "atomic_float", - "jack", - "midly", - "tengri", - "tengri_proc", - "uuid", + "xdg", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e0ed14ce..14b1da71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,11 @@ -[workspace.package] -edition = "2024" -version = "0.2.1" - [workspace] resolver = "2" -members = [ - "./crates/engine", - "./crates/device", - "./crates/config", - "./crates/app", -] -exclude = [ - "./deps/tengri" -] +members = [ "./tek" ] +exclude = [ "./deps/tengri" ] + +[workspace.package] +edition = "2024" +version = "0.3.0" [profile.release] lto = true @@ -32,11 +25,7 @@ path = "./deps/tengri/proc" path = "./deps/rust-jack" [workspace.dependencies] -tek_device = { path = "./crates/device" } -tek_engine = { path = "./crates/engine" } -tek_config = { path = "./crates/config" } -tek = { path = "./crates/app" } -tek_cli = { path = "./crates/cli" } +tek = { path = "./tek" } atomic_float = { version = "1.0.0" } backtrace = { version = "0.3.72" } diff --git a/Justfile b/Justfile index e9bbdcb7..199d5799 100644 --- a/Justfile +++ b/Justfile @@ -18,6 +18,8 @@ default: just -l bacon: bacon -s +check: + cargo check run: {{debug}} run-init: diff --git a/bacon.toml b/bacon.toml index e2eeae40..53b4f1ff 100644 --- a/bacon.toml +++ b/bacon.toml @@ -3,24 +3,30 @@ default_job = "check" env.CARGO_TERM_COLOR = "always" [keybindings] c = "job:check" +t = "job:test" +n = "job:nextest" l = "job:clippy" [jobs] [jobs.check] command = ["cargo", "check"] need_stdout = false -watch = ["crates", "deps"] +watch = ["tek", "deps"] [jobs.check-all] command = ["cargo", "check", "--all-targets"] need_stdout = false +watch = ["tek", "deps"] [jobs.clippy] command = ["cargo", "clippy"] need_stdout = false +watch = ["tek", "deps"] [jobs.clippy-all] command = ["cargo", "clippy", "--all-targets"] need_stdout = false +watch = ["tek", "deps"] [jobs.test] command = ["cargo", "test"] need_stdout = true +watch = ["tek", "deps"] [jobs.nextest] command = [ "cargo", "nextest", "run", diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml deleted file mode 100644 index 6f5a5768..00000000 --- a/crates/app/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "tek" -edition = { workspace = true } -version = { workspace = true } - -[dependencies] -tengri = { workspace = true } -tengri_proc = { workspace = true } - -tek_config = { workspace = true } -tek_device = { workspace = true } -tek_engine = { workspace = true } - -backtrace = { workspace = true } -clap = { workspace = true, optional = true } -konst = { workspace = true } -palette = { workspace = true } -rand = { workspace = true } -toml = { workspace = true } -xdg = { workspace = true } - -[dev-dependencies] -proptest = { workspace = true } -proptest-derive = { workspace = true } - -[features] -default = ["cli"] -cli = ["clap"] -host = ["tek_device/lv2"] - -[lib] -path = "tek.rs" - -[[bin]] -name = "tek" -path = "tek_cli.rs" diff --git a/crates/app/tek.rs b/crates/app/tek.rs deleted file mode 100644 index 1b6c200a..00000000 --- a/crates/app/tek.rs +++ /dev/null @@ -1,172 +0,0 @@ -#![allow(unused, clippy::unit_arg)] -#![feature(adt_const_params, associated_type_defaults, if_let_guard, impl_trait_in_assoc_type, - type_alias_impl_trait, trait_alias, type_changing_struct_update, closure_lifetime_binder)] -#[cfg(test)] mod tek_test; -mod tek_bind; pub use self::tek_bind::*; -mod tek_data; pub use self::tek_data::*; -mod tek_deps; pub use self::tek_deps::*; -mod tek_jack; pub use self::tek_jack::*; -mod tek_menu; pub use self::tek_menu::*; -mod tek_view; pub use self::tek_view::*; -/// Total state -#[derive(Default, Debug)] -pub struct App { - /// Base color. - pub color: ItemTheme, - /// Must not be dropped for the duration of the process - pub jack: Jack<'static>, - /// Display size - pub size: Measure, - /// Performance counter - pub perf: PerfModel, - /// Available view modes and input bindings - pub config: Config, - /// Currently selected mode - pub mode: Arc>>, - /// Undo history - pub history: Vec<(AppCommand, Option)>, - /// Dialog overlay - pub dialog: Dialog, - /// Contains all recently created clips. - pub pool: Pool, - /// Contains the currently edited musical arrangement - pub project: Arrangement, -} -has!(Jack<'static>: |self: App|self.jack); -has!(Pool: |self: App|self.pool); -has!(Dialog: |self: App|self.dialog); -has!(Clock: |self: App|self.project.clock); -has!(Option: |self: App|self.project.editor); -has!(Selection: |self: App|self.project.selection); -has!(Vec: |self: App|self.project.midi_ins); -has!(Vec: |self: App|self.project.midi_outs); -has!(Vec: |self: App|self.project.scenes); -has!(Vec: |self: App|self.project.tracks); -has!(Measure: |self: App|self.size); -has_clips!( |self: App|self.pool.clips); -maybe_has!(Track: |self: App| { MaybeHas::::get(&self.project) }; - { MaybeHas::::get_mut(&mut self.project) }); -maybe_has!(Scene: |self: App| { MaybeHas::::get(&self.project) }; - { MaybeHas::::get_mut(&mut self.project) }); -impl HasClipsSize for App { fn clips_size (&self) -> &Measure { &self.project.inner_size } } -impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } } -impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } } -impl App { - pub fn editor_focused (&self) -> bool { - false - } - pub fn toggle_dialog (&mut self, mut dialog: Dialog) -> Dialog { - std::mem::swap(&mut self.dialog, &mut dialog); - dialog - } - pub fn toggle_editor (&mut self, value: Option) { - //FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); - let value = value.unwrap_or_else(||!self.editor().is_some()); - if value { - // Create new clip in pool when entering empty cell - if let Selection::TrackClip { track, scene } = *self.selection() - && let Some(scene) = self.project.scenes.get_mut(scene) - && let Some(slot) = scene.clips.get_mut(track) - && slot.is_none() - && let Some(track) = self.project.tracks.get_mut(track) - { - let (index, mut clip) = self.pool.add_new_clip(); - // autocolor: new clip colors from scene and track color - let color = track.color.base.mix(scene.color.base, 0.5); - clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into(); - if let Some(editor) = &mut self.project.editor { - editor.set_clip(Some(&clip)); - } - *slot = Some(clip.clone()); - //Some(clip) - } else { - //None - } - } else if let Selection::TrackClip { track, scene } = *self.selection() - && let Some(scene) = self.project.scenes.get_mut(scene) - && let Some(slot) = scene.clips.get_mut(track) - && let Some(clip) = slot.as_mut() - { - // Remove clip from arrangement when exiting empty clip editor - let mut swapped = None; - if clip.read().unwrap().count_midi_messages() == 0 { - std::mem::swap(&mut swapped, slot); - } - if let Some(clip) = swapped { - self.pool.delete_clip(&clip.read().unwrap()); - } - } - } - pub fn browser (&self) -> Option<&Browse> { - if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None } - } - pub fn device_pick (&mut self, index: usize) { - self.dialog = Dialog::Device(index); - } - pub fn add_device (&mut self, index: usize) -> Usually<()> { - match index { - 0 => { - let name = self.jack.with_client(|c|c.name().to_string()); - let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name(); - let track = self.track().expect("no active track"); - let port = format!("{}/Sampler", &track.name); - let connect = Connect::exact(format!("{name}:{midi}")); - let sampler = if let Ok(sampler) = Sampler::new( - &self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]] - ) { - self.dialog = Dialog::None; - Device::Sampler(sampler) - } else { - self.dialog = Dialog::Message("Failed to add device.".into()); - return Err("failed to add device".into()) - }; - let track = self.track_mut().expect("no active track"); - track.devices.push(sampler); - Ok(()) - }, - 1 => { - todo!(); - Ok(()) - }, - _ => unreachable!(), - } - } - pub fn update_clock (&self) { - ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80) - } -} - -//#[dizzle::ns] -//#[dizzle::ns::include(TuiOut)] -//trait AppApi { - //#[dizzle::ns::word("sessions")] - //fn view_sessions (&self) -> Box> { - //Min::x(30, Fixed::y(6, Stack::south( - //move|add: &mut dyn FnMut(&dyn Render)|{ - //let fg = Rgb(224, 192, 128); - //for (index, name) in ["session1", "session2", "session3"].iter().enumerate() { - //let bg = if index == 0 { Rgb(48,64,32) } else { Rgb(16, 32, 24) }; - //add(&Fixed::y(2, Fill::x(Tui::bg(bg, Align::w(Tui::fg(fg, name)))))); - //} - //} - //))).into() - //} - //#[dizzle::ns::expr("bold")] - //fn view_bold (&self, value: bool, x: Box>) -> Box> { - //Box::new(Tui::bold(value, x)) - //} -//} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -//has_editor!(|self: App|{ - //editor = self.editor; - //editor_w = { - //let size = self.size.w(); - //let editor = self.editor.as_ref().expect("missing editor"); - //let time_len = editor.time_len().get(); - //let time_zoom = editor.time_zoom().get().max(1); - //(5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16) - //}; - //editor_h = 15; - //is_editing = self.editor.is_some(); -//}); diff --git a/crates/app/tek_data.rs b/crates/app/tek_data.rs deleted file mode 100644 index ff1f6307..00000000 --- a/crates/app/tek_data.rs +++ /dev/null @@ -1,141 +0,0 @@ -use crate::*; - -dsl_ns!(App: Arc { - literal = |dsl|Ok(dsl.src()?.map(|x|x.into())); -}); - -dsl_ns!(App: ItemTheme {}); - -dsl_ns!(App: bool { - word = |app| { - ":mode/editor" => app.project.editor.is_some(), - ":focused/dialog" => !matches!(app.dialog, Dialog::None), - ":focused/message" => matches!(app.dialog, Dialog::Message(..)), - ":focused/add_device" => matches!(app.dialog, Dialog::Device(..)), - ":focused/browser" => app.dialog.browser().is_some(), - ":focused/pool/import" => matches!(app.pool.mode, Some(PoolMode::Import(..))), - ":focused/pool/export" => matches!(app.pool.mode, Some(PoolMode::Export(..))), - ":focused/pool/rename" => matches!(app.pool.mode, Some(PoolMode::Rename(..))), - ":focused/pool/length" => matches!(app.pool.mode, Some(PoolMode::Length(..))), - ":focused/clip" => !app.editor_focused() && matches!(app.selection(), - Selection::TrackClip{..}), - ":focused/track" => !app.editor_focused() && matches!(app.selection(), - Selection::Track(..)), - ":focused/scene" => !app.editor_focused() && matches!(app.selection(), - Selection::Scene(..)), - ":focused/mix" => !app.editor_focused() && matches!(app.selection(), - Selection::Mix), - }; -}); - -dsl_ns!(App: Dialog { - word = |app| { - ":dialog/none" => Dialog::None, - ":dialog/options" => Dialog::Options, - ":dialog/device" => Dialog::Device(0), - ":dialog/device/prev" => Dialog::Device(0), - ":dialog/device/next" => Dialog::Device(0), - ":dialog/help" => Dialog::Help(0), - ":dialog/save" => Dialog::Browse(BrowseTarget::SaveProject, - Browse::new(None).unwrap().into()), - ":dialog/load" => Dialog::Browse(BrowseTarget::LoadProject, - Browse::new(None).unwrap().into()), - ":dialog/import/clip" => Dialog::Browse(BrowseTarget::ImportClip(Default::default()), - Browse::new(None).unwrap().into()), - ":dialog/export/clip" => Dialog::Browse(BrowseTarget::ExportClip(Default::default()), - Browse::new(None).unwrap().into()), - ":dialog/import/sample" => Dialog::Browse(BrowseTarget::ImportSample(Default::default()), - Browse::new(None).unwrap().into()), - ":dialog/export/sample" => Dialog::Browse(BrowseTarget::ExportSample(Default::default()), - Browse::new(None).unwrap().into()), - }; -}); - -dsl_ns!(App: Selection { - word = |app| { - ":select/scene" => app.selection().select_scene(app.tracks().len()), - ":select/scene/next" => app.selection().select_scene_next(app.scenes().len()), - ":select/scene/prev" => app.selection().select_scene_prev(), - ":select/track" => app.selection().select_track(app.tracks().len()), - ":select/track/next" => app.selection().select_track_next(app.tracks().len()), - ":select/track/prev" => app.selection().select_track_prev(), - }; -}); - -dsl_ns!(App: Color { - word = |app| { - ":color/bg" => Color::Rgb(28, 32, 36), - }; - expr = |app| { - "g" (n: u8) => Color::Rgb(n, n, n), - "rgb" (r: u8, g: u8, b: u8) => Color::Rgb(r, g, b), - }; -}); - -dsl_ns!(App: Option { - word = |app| { - ":editor/pitch" => Some( - (app.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8).into() - ) - }; -}); - -dsl_ns!(App: Option { - word = |app| { - ":selected/scene" => app.selection().scene(), - ":selected/track" => app.selection().track(), - }; -}); - -dsl_ns!(App: Option>> { - word = |app| { - ":selected/clip" => if let Selection::TrackClip { track, scene } = app.selection() { - app.scenes()[*scene].clips[*track].clone() - } else { - None - } - }; -}); - -dsl_ns!(App: u8 { - literal = |dsl|Ok(if let Some(src) = dsl.src()? { - Some(to_number(src)? as u8) - } else { - None - }); -}); - -dsl_ns!(App: u16 { - literal = |dsl|Ok(if let Some(src) = dsl.src()? { - Some(to_number(src)? as u16) - } else { - None - }); - word = |app| { - ":w/sidebar" => app.project.w_sidebar(app.editor().is_some()), - ":h/sample-detail" => 6.max(app.height() as u16 * 3 / 9), - }; -}); - -dsl_ns!(App: usize { - literal = |dsl|Ok(if let Some(src) = dsl.src()? { - Some(to_number(src)? as usize) - } else { - None - }); - word = |app| { - ":scene-count" => app.scenes().len(), - ":track-count" => app.tracks().len(), - ":device-kind" => app.dialog.device_kind().unwrap_or(0), - ":device-kind/next" => app.dialog.device_kind_next().unwrap_or(0), - ":device-kind/prev" => app.dialog.device_kind_prev().unwrap_or(0), - }; -}); - -dsl_ns!(App: isize { - literal = |dsl|Ok(if let Some(src) = dsl.src()? { - Some(to_number(src)? as isize) - } else { - None - }); -}); diff --git a/crates/app/tek_jack.rs b/crates/app/tek_jack.rs deleted file mode 100644 index 57f30f5d..00000000 --- a/crates/app/tek_jack.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::*; - -impl HasJack<'static> for App { - fn jack (&self) -> &Jack<'static> { - &self.jack - } -} - -audio!( - |self: App, client, scope|{ - let t0 = self.perf.get_t0(); - self.clock().update_from_scope(scope).unwrap(); - let midi_in = self.project.midi_input_collect(scope); - if let Some(editor) = &self.editor() { - let mut pitch: Option = None; - for port in midi_in.iter() { - for event in port.iter() { - if let (_, Ok(LiveEvent::Midi {message: MidiMessage::NoteOn {key, ..}, ..})) - = event - { - pitch = Some(key.clone()); - } - } - } - if let Some(pitch) = pitch { - editor.set_note_pos(pitch.as_int() as usize); - } - } - let result = self.project.process_tracks(client, scope); - self.perf.update_from_jack_scope(t0, scope); - result - }; - |self, event|{ - use JackEvent::*; - match event { - SampleRate(sr) => { - self.clock().timebase.sr.set(sr as f64); - }, - PortRegistration(id, true) => { - //let port = self.jack().port_by_id(id); - //println!("\rport add: {id} {port:?}"); - //println!("\rport add: {id}"); - }, - PortRegistration(id, false) => { - /*println!("\rport del: {id}")*/ - }, - PortsConnected(a, b, true) => { /*println!("\rport conn: {a} {b}")*/ }, - PortsConnected(a, b, false) => { /*println!("\rport disc: {a} {b}")*/ }, - ClientRegistration(id, true) => {}, - ClientRegistration(id, false) => {}, - ThreadInit => {}, - XRun => {}, - GraphReorder => {}, - _ => { panic!("{event:?}"); } - } - } -); diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml deleted file mode 100644 index b5605dcd..00000000 --- a/crates/config/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "tek_config" -edition = { workspace = true } -version = { workspace = true } - -[lib] -path = "config.rs" - -[dependencies] -xdg = { workspace = true } -tengri = { workspace = true } diff --git a/crates/config/config.rs b/crates/config/config.rs deleted file mode 100644 index c9bb65b5..00000000 --- a/crates/config/config.rs +++ /dev/null @@ -1,232 +0,0 @@ -#![feature(if_let_guard)] - -use ::{ - std::{ - sync::{Arc, RwLock}, - collections::BTreeMap, - path::PathBuf - }, - tengri::{ - Usually, impl_debug, - tui::TuiEvent, - dsl::{Dsl, DslWord, DslExpr} - }, - xdg::BaseDirectories, -}; - -/// Configuration. -/// -/// Contains mode, view, and bind definitions. -#[derive(Default, Debug)] -pub struct Config { - pub dirs: BaseDirectories, - pub modes: Modes, - pub views: Views, - pub binds: Binds, -} - -type Modes = Arc, Arc>>>>>; -type Binds = Arc, EventMap>>>>; -type Views = Arc, Arc>>>; - -/// A set of currently active view and keys definitions, -/// with optional name and description. -#[derive(Default, Debug)] -pub struct Mode { - pub path: PathBuf, - pub name: Vec, - pub info: Vec, - pub view: Vec, - pub keys: Vec, - pub modes: Modes, -} - -/// A collection of input bindings. -#[derive(Debug)] -pub struct EventMap( - /// Map of each event (e.g. key combination) to - /// all command expressions bound to it by - /// all loaded input layers. - pub BTreeMap>> -); - -/// An input binding. -#[derive(Debug, Clone)] -pub struct Binding { - pub commands: Arc<[C]>, - pub condition: Option, - pub description: Option>, - pub source: Option>, -} - -/// Input bindings are only returned if this evaluates to true -#[derive(Clone)] -pub struct Condition(Arcbool + Send + Sync>>); - -impl Config { - const CONFIG: &'static str = "tek.edn"; - const DEFAULTS: &'static str = include_str!("../../tek.edn"); - - pub fn new (dirs: Option) -> Self { - Self { - dirs: dirs.unwrap_or_else(||BaseDirectories::with_profile("tek", "v0")), - ..Default::default() - } - } - pub fn init (&mut self) -> Usually<()> { - self.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(&dsl))?; - Ok(()) - } - pub fn init_file ( - &mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()> - ) -> Usually<()> { - if self.dirs.find_config_file(path).is_none() { - println!("Creating {path:?}"); - std::fs::write(self.dirs.place_config_file(path)?, defaults)?; - } - Ok(if let Some(path) = self.dirs.find_config_file(path) { - println!("Loading {path:?}"); - let src = std::fs::read_to_string(&path)?; - src.as_str().each(move|item|each(self, item))?; - } else { - return Err(format!("{path}: not found").into()) - }) - } - pub fn load (&mut self, dsl: impl Dsl) -> Usually<()> { - dsl.each(|item|if let Some(expr) = item.expr()? { - let head = expr.head()?; - let tail = expr.tail()?; - let name = tail.head()?; - let body = tail.tail()?; - println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); - match head { - Some("mode") if let Some(name) = name => - Mode::>::load_into(&self.modes, &name, &body)?, - Some("keys") if let Some(name) = name => - EventMap::>::load_into(&self.binds, &name, &body)?, - Some("view") if let Some(name) = name => { - self.views.write().unwrap().insert(name.into(), body.src()?.unwrap_or_default().into()); - }, - _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) - } - Ok(()) - } else { - return Err(format!("Config::load: expected expr, got: {item:?}").into()) - }) - } -} - -impl Mode> { - pub fn load_into (modes: &Modes, name: &impl AsRef, body: &impl Dsl) -> Usually<()> { - let mut mode = Self::default(); - println!("Mode::load_into: {}: {body:?}", name.as_ref()); - body.each(|item|mode.load_one(item))?; - modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode)); - Ok(()) - } - pub fn load_one (&mut self, dsl: impl Dsl) -> Usually<()> { - Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() { - println!("Mode::load_one: {head} {:?}", expr.tail()); - let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); - match head { - "name" => self.name.push(tail.into()), - "info" => self.info.push(tail.into()), - "view" => self.view.push(tail.into()), - "keys" => tail.each(|expr|{self.keys.push(expr.trim().into()); Ok(())})?, - "mode" => if let Some(id) = tail.head()? { - Self::load_into(&self.modes, &id, &tail.tail())?; - } else { - return Err(format!("Mode::load_one: self: incomplete: {expr:?}").into()); - }, - _ => { - return Err(format!("Mode::load_one: unexpected expr: {head:?} {tail:?}").into()) - }, - }; - } else if let Ok(Some(word)) = dsl.word() { - self.view.push(word.into()); - } else { - return Err(format!("Mode::load_one: unexpected: {dsl:?}").into()); - }) - } -} - -/// Default is always empty map regardless if `E` and `C` implement [Default]. -impl Default for EventMap { - fn default () -> Self { Self(Default::default()) } -} - -impl EventMap { - /// Create a new event map - pub fn new () -> Self { - Default::default() - } - /// Add a binding to an owned event map. - pub fn def (mut self, event: E, binding: Binding) -> Self { - self.add(event, binding); - self - } - /// Add a binding to an event map. - pub fn add (&mut self, event: E, binding: Binding) -> &mut Self { - if !self.0.contains_key(&event) { - self.0.insert(event.clone(), Default::default()); - } - self.0.get_mut(&event).unwrap().push(binding); - self - } - /// Return the binding(s) that correspond to an event. - pub fn query (&self, event: &E) -> Option<&[Binding]> { - self.0.get(event).map(|x|x.as_slice()) - } - /// Return the first binding that corresponds to an event, considering conditions. - pub fn dispatch (&self, event: &E) -> Option<&Binding> { - self.query(event) - .map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next()) - .flatten() - } -} - -impl EventMap> { - pub fn load_into (binds: &Binds, name: &impl AsRef, body: &impl Dsl) -> Usually<()> { - println!("EventMap::load_into: {}: {body:?}", name.as_ref()); - let mut map = Self::new(); - body.each(|item|if item.expr().head() == Ok(Some("see")) { - // TODO - Ok(()) - } else if let Ok(Some(word)) = item.expr().head().word() { - if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? { - map.add(key, Binding { - commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(), - condition: None, - description: None, - source: None - }); - Ok(()) - } else if Some(":char") == item.expr()?.head()? { - // TODO - return Ok(()) - } else { - return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into()) - } - } else { - return Err(format!("Config::load_bind: unexpected: {item:?}").into()) - })?; - binds.write().unwrap().insert(name.as_ref().into(), map); - Ok(()) - } -} - -impl Binding { - pub fn from_dsl (dsl: impl Dsl) -> Usually { - let command: Option = None; - let condition: Option = None; - let description: Option> = None; - let source: Option> = None; - if let Some(command) = command { - Ok(Self { commands: [command].into(), condition, description, source }) - } else { - Err(format!("no command in {dsl:?}").into()) - } - } -} - -impl_debug!(Condition |self, w| { write!(w, "*") }); diff --git a/crates/device/Cargo.toml b/crates/device/Cargo.toml deleted file mode 100644 index 0a3b6dd2..00000000 --- a/crates/device/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "tek_device" -edition = { workspace = true } -version = { workspace = true } - -[dependencies] -tengri = { workspace = true } -tengri_proc = { workspace = true } - -tek_engine = { workspace = true } - -uuid = { workspace = true, optional = true } -livi = { workspace = true, optional = true } -symphonia = { workspace = true, optional = true } -wavers = { workspace = true, optional = true } -winit = { workspace = true, optional = true } - -[features] -default = [ "arranger", "sampler", "lv2" ] - -arranger = [ "port", "editor", "sequencer", "editor" ] -browse = [] -clap = [] -clock = [] -editor = [] -lv2 = [ "port", "livi", "winit" ] -meter = [] -mixer = [] -pool = [] -port = [] -sampler = [ "port", "meter", "mixer", "browse", "symphonia", "wavers" ] -sequencer = [ "port", "clock", "uuid", "pool" ] -sf2 = [] -vst2 = [] -vst3 = [] diff --git a/crates/device/src/device.rs b/crates/device/src/device.rs deleted file mode 100644 index e6667d17..00000000 --- a/crates/device/src/device.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::*; - -pub fn device_kinds () -> &'static [&'static str] { - &[ - "Sampler", - "Plugin (LV2)", - ] -} - -impl> + Has> HasDevices for T { - fn devices (&self) -> &Vec { - self.get() - } - fn devices_mut (&mut self) -> &mut Vec { - self.get_mut() - } -} - -pub trait HasDevices: Has { - fn devices (&self) -> &Vec; - fn devices_mut (&mut self) -> &mut Vec; -} - -#[derive(Debug)] -pub enum Device { - #[cfg(feature = "sampler")] - Sampler(Sampler), - #[cfg(feature = "lv2")] // TODO - Lv2(Lv2), - #[cfg(feature = "vst2")] // TODO - Vst2, - #[cfg(feature = "vst3")] // TODO - Vst3, - #[cfg(feature = "clap")] // TODO - Clap, - #[cfg(feature = "sf2")] // TODO - Sf2, -} - -impl Device { - pub fn name (&self) -> &str { - match self { - Self::Sampler(sampler) => sampler.name.as_ref(), - _ => todo!(), - } - } - pub fn midi_ins (&self) -> &[MidiInput] { - match self { - //Self::Sampler(Sampler { midi_in, .. }) => &[midi_in], - _ => todo!() - } - } - pub fn midi_outs (&self) -> &[MidiOutput] { - match self { - Self::Sampler(_) => &[], - _ => todo!() - } - } - pub fn audio_ins (&self) -> &[AudioInput] { - match self { - Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(), - _ => todo!() - } - } - pub fn audio_outs (&self) -> &[AudioOutput] { - match self { - Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(), - _ => todo!() - } - } -} - -pub struct DeviceAudio<'a>(pub &'a mut Device); - -audio!(|self: DeviceAudio<'a>, client, scope|{ - use Device::*; - match self.0 { - #[cfg(feature = "sampler")] Sampler(sampler) => sampler.process(client, scope), - #[cfg(feature = "lv2")] Lv2(lv2) => lv2.process(client, scope), - #[cfg(feature = "vst2")] Vst2 => { todo!() }, // TODO - #[cfg(feature = "vst3")] Vst3 => { todo!() }, // TODO - #[cfg(feature = "clap")] Clap => { todo!() }, // TODO - #[cfg(feature = "sf2")] Sf2 => { todo!() }, // TODO - } -}); - -def_command!(DeviceCommand: |device: Device| {}); diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml deleted file mode 100644 index d63ae531..00000000 --- a/crates/engine/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "tek_engine" -edition = { workspace = true } -version = { workspace = true } - -[dependencies] -tengri = { workspace = true } -tengri_proc = { workspace = true } -jack = { workspace = true } -midly = { workspace = true } -uuid = { workspace = true } -atomic_float = { workspace = true } diff --git a/deps/tengri b/deps/tengri index baa582b9..18b68039 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit baa582b9ddc1dd9c078b223fae7e3c10cf348166 +Subproject commit 18b6803912105cc6a23217641a68967695a2166b diff --git a/tek/Cargo.toml b/tek/Cargo.toml new file mode 100644 index 00000000..3e005db3 --- /dev/null +++ b/tek/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "tek" +edition = { workspace = true } +version = { workspace = true } + +[lib] +path = "tek.rs" + +[[bin]] +name = "tek" +path = "tek_cli.rs" + +[dependencies] +tengri = { workspace = true } +tengri_proc = { workspace = true } + +atomic_float = { workspace = true } +backtrace = { workspace = true } +clap = { workspace = true, optional = true } +jack = { workspace = true } +konst = { workspace = true } +livi = { workspace = true, optional = true } +midly = { workspace = true } +palette = { workspace = true } +rand = { workspace = true } +symphonia = { workspace = true, optional = true } +toml = { workspace = true } +uuid = { workspace = true, optional = true } +wavers = { workspace = true, optional = true } +winit = { workspace = true, optional = true } +xdg = { workspace = true } + +[dev-dependencies] +proptest = { workspace = true } +proptest-derive = { workspace = true } + +[features] +arranger = ["port", "editor", "sequencer", "editor"] +browse = [] +clap = [] +cli = ["dep:clap"] +clock = [] +default = ["cli", "arranger", "sampler", "lv2"] +editor = [] +host = ["lv2"] +lv2 = ["port", "livi", "winit"] +meter = [] +mixer = [] +pool = [] +port = [] +sampler = ["port", "meter", "mixer", "browse", "symphonia", "wavers"] +sequencer = ["port", "clock", "uuid", "pool"] +sf2 = [] +vst2 = [] +vst3 = [] diff --git a/tek/config.rs b/tek/config.rs new file mode 100644 index 00000000..ac8a2114 --- /dev/null +++ b/tek/config.rs @@ -0,0 +1,81 @@ +pub(self) use ::{ + std::{ + sync::{Arc, RwLock}, + collections::BTreeMap, + path::PathBuf + }, + tengri::{ + Usually, impl_debug, + tui::TuiEvent, + dsl::{Dsl, DslWord, DslExpr} + }, + xdg::BaseDirectories, +}; + +mod bind; pub use self::bind::*; +mod mode; pub use self::mode::*; +mod view; pub use self::view::*; + +/// Configuration. +/// +/// Contains mode, view, and bind definitions. +#[derive(Default, Debug)] +pub struct Config { + pub dirs: BaseDirectories, + pub modes: Modes, + pub views: Views, + pub binds: Binds, +} + +impl Config { + const CONFIG: &'static str = "tek.edn"; + const DEFAULTS: &'static str = include_str!("./tek.edn"); + + pub fn new (dirs: Option) -> Self { + Self { + dirs: dirs.unwrap_or_else(||BaseDirectories::with_profile("tek", "v0")), + ..Default::default() + } + } + pub fn init (&mut self) -> Usually<()> { + self.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(&dsl))?; + Ok(()) + } + pub fn init_file ( + &mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()> + ) -> Usually<()> { + if self.dirs.find_config_file(path).is_none() { + println!("Creating {path:?}"); + std::fs::write(self.dirs.place_config_file(path)?, defaults)?; + } + Ok(if let Some(path) = self.dirs.find_config_file(path) { + println!("Loading {path:?}"); + let src = std::fs::read_to_string(&path)?; + src.as_str().each(move|item|each(self, item))?; + } else { + return Err(format!("{path}: not found").into()) + }) + } + pub fn load (&mut self, dsl: impl Dsl) -> Usually<()> { + dsl.each(|item|if let Some(expr) = item.expr()? { + let head = expr.head()?; + let tail = expr.tail()?; + let name = tail.head()?; + let body = tail.tail()?; + println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); + match head { + Some("mode") if let Some(name) = name => + Mode::>::load_into(&self.modes, &name, &body)?, + Some("keys") if let Some(name) = name => + EventMap::>::load_into(&self.binds, &name, &body)?, + Some("view") if let Some(name) = name => { + self.views.write().unwrap().insert(name.into(), body.src()?.unwrap_or_default().into()); + }, + _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) + } + Ok(()) + } else { + return Err(format!("Config::load: expected expr, got: {item:?}").into()) + }) + } +} diff --git a/tek/config/bind.rs b/tek/config/bind.rs new file mode 100644 index 00000000..0795f799 --- /dev/null +++ b/tek/config/bind.rs @@ -0,0 +1,106 @@ +use super::*; + +pub type Binds = Arc, EventMap>>>>; + +/// A collection of input bindings. +#[derive(Debug)] +pub struct EventMap( + /// Map of each event (e.g. key combination) to + /// all command expressions bound to it by + /// all loaded input layers. + pub BTreeMap>> +); + +/// An input binding. +#[derive(Debug, Clone)] +pub struct Binding { + pub commands: Arc<[C]>, + pub condition: Option, + pub description: Option>, + pub source: Option>, +} + +/// Input bindings are only returned if this evaluates to true +#[derive(Clone)] +pub struct Condition(Arcbool + Send + Sync>>); + +/// Default is always empty map regardless if `E` and `C` implement [Default]. +impl Default for EventMap { + fn default () -> Self { Self(Default::default()) } +} + +impl EventMap { + /// Create a new event map + pub fn new () -> Self { + Default::default() + } + /// Add a binding to an owned event map. + pub fn def (mut self, event: E, binding: Binding) -> Self { + self.add(event, binding); + self + } + /// Add a binding to an event map. + pub fn add (&mut self, event: E, binding: Binding) -> &mut Self { + if !self.0.contains_key(&event) { + self.0.insert(event.clone(), Default::default()); + } + self.0.get_mut(&event).unwrap().push(binding); + self + } + /// Return the binding(s) that correspond to an event. + pub fn query (&self, event: &E) -> Option<&[Binding]> { + self.0.get(event).map(|x|x.as_slice()) + } + /// Return the first binding that corresponds to an event, considering conditions. + pub fn dispatch (&self, event: &E) -> Option<&Binding> { + self.query(event) + .map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next()) + .flatten() + } +} + +impl EventMap> { + pub fn load_into (binds: &Binds, name: &impl AsRef, body: &impl Dsl) -> Usually<()> { + println!("EventMap::load_into: {}: {body:?}", name.as_ref()); + let mut map = Self::new(); + body.each(|item|if item.expr().head() == Ok(Some("see")) { + // TODO + Ok(()) + } else if let Ok(Some(_word)) = item.expr().head().word() { + if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? { + map.add(key, Binding { + commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(), + condition: None, + description: None, + source: None + }); + Ok(()) + } else if Some(":char") == item.expr()?.head()? { + // TODO + return Ok(()) + } else { + return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into()) + } + } else { + return Err(format!("Config::load_bind: unexpected: {item:?}").into()) + })?; + binds.write().unwrap().insert(name.as_ref().into(), map); + Ok(()) + } +} + +impl Binding { + pub fn from_dsl (dsl: impl Dsl) -> Usually { + let command: Option = None; + let condition: Option = None; + let description: Option> = None; + let source: Option> = None; + if let Some(command) = command { + Ok(Self { commands: [command].into(), condition, description, source }) + } else { + Err(format!("no command in {dsl:?}").into()) + } + } +} + +impl_debug!(Condition |self, w| { write!(w, "*") }); diff --git a/tek/config/mode.rs b/tek/config/mode.rs new file mode 100644 index 00000000..71e435c2 --- /dev/null +++ b/tek/config/mode.rs @@ -0,0 +1,49 @@ +use super::*; + +pub type Modes = Arc, Arc>>>>>; + +/// A set of currently active view and keys definitions, +/// with optional name and description. +#[derive(Default, Debug)] +pub struct Mode { + pub path: PathBuf, + pub name: Vec, + pub info: Vec, + pub view: Vec, + pub keys: Vec, + pub modes: Modes, +} + +impl Mode> { + pub fn load_into (modes: &Modes, name: &impl AsRef, body: &impl Dsl) -> Usually<()> { + let mut mode = Self::default(); + println!("Mode::load_into: {}: {body:?}", name.as_ref()); + body.each(|item|mode.load_one(item))?; + modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode)); + Ok(()) + } + pub fn load_one (&mut self, dsl: impl Dsl) -> Usually<()> { + Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() { + println!("Mode::load_one: {head} {:?}", expr.tail()); + let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); + match head { + "name" => self.name.push(tail.into()), + "info" => self.info.push(tail.into()), + "view" => self.view.push(tail.into()), + "keys" => tail.each(|expr|{self.keys.push(expr.trim().into()); Ok(())})?, + "mode" => if let Some(id) = tail.head()? { + Self::load_into(&self.modes, &id, &tail.tail())?; + } else { + return Err(format!("Mode::load_one: self: incomplete: {expr:?}").into()); + }, + _ => { + return Err(format!("Mode::load_one: unexpected expr: {head:?} {tail:?}").into()) + }, + }; + } else if let Ok(Some(word)) = dsl.word() { + self.view.push(word.into()); + } else { + return Err(format!("Mode::load_one: unexpected: {dsl:?}").into()); + }) + } +} diff --git a/tek/config/view.rs b/tek/config/view.rs new file mode 100644 index 00000000..d8cb5e2f --- /dev/null +++ b/tek/config/view.rs @@ -0,0 +1,3 @@ +use super::*; + +pub type Views = Arc, Arc>>>; diff --git a/crates/app/tek_deps.rs b/tek/deps.rs similarity index 58% rename from crates/app/tek_deps.rs rename to tek/deps.rs index 8084c703..12e55caa 100644 --- a/crates/app/tek_deps.rs +++ b/tek/deps.rs @@ -1,11 +1,11 @@ pub use ::{ - tek_engine::*, - tek_config::*, - tek_device::{self, *}, tengri::{ - Usually, Perhaps, Has, MaybeHas, has, maybe_has, impl_debug, - dsl::*, input::*, output::*, tui::*, + Usually, Perhaps, Has, MaybeHas, has, maybe_has, impl_debug, from, + dsl::*, + input::*, + output::{*, Layout}, tui::{ + *, ratatui::{ self, prelude::{Style, Stylize, Buffer, Modifier, buffer::Cell, Color::{self, *}} }, @@ -25,3 +25,12 @@ pub use ::{ }, xdg::BaseDirectories, }; + +pub(crate) use atomic_float::*; +pub(crate) use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Line}}}; +pub(crate) use std::cmp::Ord; +pub(crate) use std::ffi::OsString; +pub(crate) use std::fmt::{Debug, Formatter}; +pub(crate) use std::fs::File; +pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem}; +pub(crate) use std::thread::JoinHandle; diff --git a/tek/device.rs b/tek/device.rs new file mode 100644 index 00000000..d534b0e9 --- /dev/null +++ b/tek/device.rs @@ -0,0 +1,135 @@ +use crate::*; + +/// Define a type alias for iterators of sized items (columns). +macro_rules! def_sizes_iter { + ($Type:ident => $($Item:ty),+) => { + pub trait $Type<'a> = + Iterator + Send + Sync + 'a; + } +} + +#[cfg(feature = "arranger")] mod arranger; #[cfg(feature = "arranger")] pub use self::arranger::*; +#[cfg(feature = "browse")] mod browse; #[cfg(feature = "browse")] pub use self::browse::*; +#[cfg(feature = "clap")] mod clap; #[cfg(feature = "clap")] pub use self::clap::*; +#[cfg(feature = "clock")] mod clock; #[cfg(feature = "clock")] pub use self::clock::*; +#[cfg(feature = "editor")] mod editor; #[cfg(feature = "editor")] pub use self::editor::*; +#[cfg(feature = "lv2")] mod lv2; #[cfg(feature = "lv2")] pub use self::lv2::*; +#[cfg(feature = "meter")] mod meter; #[cfg(feature = "meter")] pub use self::meter::*; +#[cfg(feature = "mixer")] mod mixer; #[cfg(feature = "mixer")] pub use self::mixer::*; +#[cfg(feature = "pool")] mod pool; #[cfg(feature = "pool")] pub use self::pool::*; +#[cfg(feature = "port")] mod port; #[cfg(feature = "port")] pub use self::port::*; +#[cfg(feature = "sampler")] mod sampler; #[cfg(feature = "sampler")] pub use self::sampler::*; +#[cfg(feature = "sequencer")] mod sequencer; #[cfg(feature = "sequencer")] pub use self::sequencer::*; +#[cfg(feature = "sf2")] mod sf2; #[cfg(feature = "sf2")] pub use self::sf2::*; +#[cfg(feature = "vst2")] mod vst2; #[cfg(feature = "vst2")] pub use self::vst2::*; +#[cfg(feature = "vst3")] mod vst3; #[cfg(feature = "vst3")] pub use self::vst3::*; + +pub fn swap_value ( + target: &mut T, value: &T, returned: impl Fn(T)->U +) -> Perhaps { + if *target == *value { + Ok(None) + } else { + let mut value = value.clone(); + std::mem::swap(target, &mut value); + Ok(Some(returned(value))) + } +} + +pub fn toggle_bool ( + target: &mut bool, value: &Option, returned: impl Fn(Option)->U +) -> Perhaps { + let mut value = value.unwrap_or(!*target); + if value == *target { + Ok(None) + } else { + std::mem::swap(target, &mut value); + Ok(Some(returned(Some(value)))) + } +} + +pub fn device_kinds () -> &'static [&'static str] { + &[ + "Sampler", + "Plugin (LV2)", + ] +} + +impl> + Has> HasDevices for T { + fn devices (&self) -> &Vec { + self.get() + } + fn devices_mut (&mut self) -> &mut Vec { + self.get_mut() + } +} + +pub trait HasDevices: Has { + fn devices (&self) -> &Vec; + fn devices_mut (&mut self) -> &mut Vec; +} + +#[derive(Debug)] +pub enum Device { + #[cfg(feature = "sampler")] + Sampler(Sampler), + #[cfg(feature = "lv2")] // TODO + Lv2(Lv2), + #[cfg(feature = "vst2")] // TODO + Vst2, + #[cfg(feature = "vst3")] // TODO + Vst3, + #[cfg(feature = "clap")] // TODO + Clap, + #[cfg(feature = "sf2")] // TODO + Sf2, +} + +impl Device { + pub fn name (&self) -> &str { + match self { + Self::Sampler(sampler) => sampler.name.as_ref(), + _ => todo!(), + } + } + pub fn midi_ins (&self) -> &[MidiInput] { + match self { + //Self::Sampler(Sampler { midi_in, .. }) => &[midi_in], + _ => todo!() + } + } + pub fn midi_outs (&self) -> &[MidiOutput] { + match self { + Self::Sampler(_) => &[], + _ => todo!() + } + } + pub fn audio_ins (&self) -> &[AudioInput] { + match self { + Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(), + _ => todo!() + } + } + pub fn audio_outs (&self) -> &[AudioOutput] { + match self { + Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(), + _ => todo!() + } + } +} + +pub struct DeviceAudio<'a>(pub &'a mut Device); + +audio!(|self: DeviceAudio<'a>, client, scope|{ + use Device::*; + match self.0 { + #[cfg(feature = "sampler")] Sampler(sampler) => sampler.process(client, scope), + #[cfg(feature = "lv2")] Lv2(lv2) => lv2.process(client, scope), + #[cfg(feature = "vst2")] Vst2 => { todo!() }, // TODO + #[cfg(feature = "vst3")] Vst3 => { todo!() }, // TODO + #[cfg(feature = "clap")] Clap => { todo!() }, // TODO + #[cfg(feature = "sf2")] Sf2 => { todo!() }, // TODO + } +}); + +def_command!(DeviceCommand: |device: Device| {}); diff --git a/crates/device/src/arranger.rs b/tek/device/arranger.rs similarity index 100% rename from crates/device/src/arranger.rs rename to tek/device/arranger.rs diff --git a/crates/device/src/arranger/arranger_api.rs b/tek/device/arranger/arranger_api.rs similarity index 100% rename from crates/device/src/arranger/arranger_api.rs rename to tek/device/arranger/arranger_api.rs diff --git a/crates/device/src/arranger/arranger_clip.rs b/tek/device/arranger/arranger_clip.rs similarity index 100% rename from crates/device/src/arranger/arranger_clip.rs rename to tek/device/arranger/arranger_clip.rs diff --git a/crates/device/src/arranger/arranger_scenes.rs b/tek/device/arranger/arranger_scenes.rs similarity index 100% rename from crates/device/src/arranger/arranger_scenes.rs rename to tek/device/arranger/arranger_scenes.rs diff --git a/crates/device/src/arranger/arranger_select.rs b/tek/device/arranger/arranger_select.rs similarity index 100% rename from crates/device/src/arranger/arranger_select.rs rename to tek/device/arranger/arranger_select.rs diff --git a/crates/device/src/arranger/arranger_tracks.rs b/tek/device/arranger/arranger_tracks.rs similarity index 100% rename from crates/device/src/arranger/arranger_tracks.rs rename to tek/device/arranger/arranger_tracks.rs diff --git a/crates/device/src/arranger/arranger_view.rs b/tek/device/arranger/arranger_view.rs similarity index 100% rename from crates/device/src/arranger/arranger_view.rs rename to tek/device/arranger/arranger_view.rs diff --git a/crates/device/src/browse.rs b/tek/device/browse.rs similarity index 100% rename from crates/device/src/browse.rs rename to tek/device/browse.rs diff --git a/crates/device/src/browse/browse_api.rs b/tek/device/browse/browse_api.rs similarity index 100% rename from crates/device/src/browse/browse_api.rs rename to tek/device/browse/browse_api.rs diff --git a/crates/device/src/browse/browse_view.rs b/tek/device/browse/browse_view.rs similarity index 100% rename from crates/device/src/browse/browse_view.rs rename to tek/device/browse/browse_view.rs diff --git a/crates/device/src/clap.rs b/tek/device/clap.rs similarity index 100% rename from crates/device/src/clap.rs rename to tek/device/clap.rs diff --git a/crates/device/src/clock.rs b/tek/device/clock.rs similarity index 100% rename from crates/device/src/clock.rs rename to tek/device/clock.rs diff --git a/crates/device/src/clock/clock_api.rs b/tek/device/clock/clock_api.rs similarity index 100% rename from crates/device/src/clock/clock_api.rs rename to tek/device/clock/clock_api.rs diff --git a/crates/device/src/clock/clock_model.rs b/tek/device/clock/clock_model.rs similarity index 96% rename from crates/device/src/clock/clock_model.rs rename to tek/device/clock/clock_model.rs index 0a881025..8496f93a 100644 --- a/crates/device/src/clock/clock_model.rs +++ b/tek/device/clock/clock_model.rs @@ -3,7 +3,7 @@ use crate::*; #[derive(Clone, Default)] pub struct Clock { /// JACK transport handle. - pub transport: Arc>, + pub transport: Arc>, /// Global temporal resolution (shared by [Moment] fields) pub timebase: Arc, /// Current global sample and usec (monotonic from JACK clock) @@ -130,7 +130,7 @@ impl Clock { self.set_chunk(scope.n_frames() as usize); // Store reported global frame and usec - let tek_engine::jack::CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?; + let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?; self.global.sample.set(current_frames as f64); self.global.usec.set(current_usecs as f64); @@ -160,12 +160,12 @@ impl Clock { Ok(()) } - pub fn bbt (&self) -> tek_engine::jack::PositionBBT { + pub fn bbt (&self) -> PositionBBT { let pulse = self.playhead.pulse.get() as i32; let ppq = self.timebase.ppq.get() as i32; let bpm = self.timebase.bpm.get(); let bar = (pulse / ppq) / 4; - tek_engine::jack::PositionBBT { + PositionBBT { bar: 1 + bar, beat: 1 + (pulse / ppq) % 4, tick: (pulse % ppq), diff --git a/crates/device/src/clock/clock_view.rs b/tek/device/clock/clock_view.rs similarity index 100% rename from crates/device/src/clock/clock_view.rs rename to tek/device/clock/clock_view.rs diff --git a/crates/device/src/editor.rs b/tek/device/editor.rs similarity index 100% rename from crates/device/src/editor.rs rename to tek/device/editor.rs diff --git a/crates/device/src/editor/editor_api.rs b/tek/device/editor/editor_api.rs similarity index 100% rename from crates/device/src/editor/editor_api.rs rename to tek/device/editor/editor_api.rs diff --git a/crates/device/src/editor/editor_model.rs b/tek/device/editor/editor_model.rs similarity index 100% rename from crates/device/src/editor/editor_model.rs rename to tek/device/editor/editor_model.rs diff --git a/crates/device/src/editor/editor_view.rs b/tek/device/editor/editor_view.rs similarity index 100% rename from crates/device/src/editor/editor_view.rs rename to tek/device/editor/editor_view.rs diff --git a/crates/device/src/editor/editor_view_h.rs b/tek/device/editor/editor_view_h.rs similarity index 100% rename from crates/device/src/editor/editor_view_h.rs rename to tek/device/editor/editor_view_h.rs diff --git a/crates/device/src/editor/editor_view_v.rs b/tek/device/editor/editor_view_v.rs similarity index 100% rename from crates/device/src/editor/editor_view_v.rs rename to tek/device/editor/editor_view_v.rs diff --git a/crates/device/src/lib.rs b/tek/device/lib.rs similarity index 100% rename from crates/device/src/lib.rs rename to tek/device/lib.rs diff --git a/crates/device/src/lv2.rs b/tek/device/lv2.rs similarity index 100% rename from crates/device/src/lv2.rs rename to tek/device/lv2.rs diff --git a/crates/device/src/lv2/lv2_audio.rs b/tek/device/lv2/lv2_audio.rs similarity index 100% rename from crates/device/src/lv2/lv2_audio.rs rename to tek/device/lv2/lv2_audio.rs diff --git a/crates/device/src/lv2/lv2_gui.rs b/tek/device/lv2/lv2_gui.rs similarity index 100% rename from crates/device/src/lv2/lv2_gui.rs rename to tek/device/lv2/lv2_gui.rs diff --git a/crates/device/src/lv2/lv2_model.rs b/tek/device/lv2/lv2_model.rs similarity index 100% rename from crates/device/src/lv2/lv2_model.rs rename to tek/device/lv2/lv2_model.rs diff --git a/crates/device/src/lv2/lv2_tui.rs b/tek/device/lv2/lv2_tui.rs similarity index 100% rename from crates/device/src/lv2/lv2_tui.rs rename to tek/device/lv2/lv2_tui.rs diff --git a/crates/device/src/meter.rs b/tek/device/meter.rs similarity index 100% rename from crates/device/src/meter.rs rename to tek/device/meter.rs diff --git a/crates/device/src/mixer.rs b/tek/device/mixer.rs similarity index 100% rename from crates/device/src/mixer.rs rename to tek/device/mixer.rs diff --git a/crates/device/src/pool.rs b/tek/device/pool.rs similarity index 100% rename from crates/device/src/pool.rs rename to tek/device/pool.rs diff --git a/crates/device/src/pool/pool_api.rs b/tek/device/pool/pool_api.rs similarity index 100% rename from crates/device/src/pool/pool_api.rs rename to tek/device/pool/pool_api.rs diff --git a/crates/device/src/pool/pool_view.rs b/tek/device/pool/pool_view.rs similarity index 100% rename from crates/device/src/pool/pool_view.rs rename to tek/device/pool/pool_view.rs diff --git a/crates/device/src/port.rs b/tek/device/port.rs similarity index 96% rename from crates/device/src/port.rs rename to tek/device/port.rs index edfae329..d25ca0c0 100644 --- a/crates/device/src/port.rs +++ b/tek/device/port.rs @@ -2,8 +2,8 @@ use crate::*; mod port_api; pub use self::port_api::*; mod port_connect; pub use self::port_connect::*; -mod port_audio_out; pub use self::port_audio_out::*; -mod port_audio_in; pub use self::port_audio_in::*; +mod port_audio_out; //pub use self::port_audio_out::*; +mod port_audio_in; //pub use self::port_audio_in::*; mod port_midi_out; pub use self::port_midi_out::*; mod port_midi_in; pub use self::port_midi_in::*; pub(crate) use ConnectName::*; diff --git a/crates/device/src/port/port_api.rs b/tek/device/port/port_api.rs similarity index 100% rename from crates/device/src/port/port_api.rs rename to tek/device/port/port_api.rs diff --git a/crates/device/src/port/port_audio_in.rs b/tek/device/port/port_audio_in.rs similarity index 100% rename from crates/device/src/port/port_audio_in.rs rename to tek/device/port/port_audio_in.rs diff --git a/crates/device/src/port/port_audio_out.rs b/tek/device/port/port_audio_out.rs similarity index 100% rename from crates/device/src/port/port_audio_out.rs rename to tek/device/port/port_audio_out.rs diff --git a/crates/device/src/port/port_connect.rs b/tek/device/port/port_connect.rs similarity index 100% rename from crates/device/src/port/port_connect.rs rename to tek/device/port/port_connect.rs diff --git a/crates/device/src/port/port_midi_in.rs b/tek/device/port/port_midi_in.rs similarity index 100% rename from crates/device/src/port/port_midi_in.rs rename to tek/device/port/port_midi_in.rs diff --git a/crates/device/src/port/port_midi_out.rs b/tek/device/port/port_midi_out.rs similarity index 100% rename from crates/device/src/port/port_midi_out.rs rename to tek/device/port/port_midi_out.rs diff --git a/crates/device/src/sampler.rs b/tek/device/sampler.rs similarity index 100% rename from crates/device/src/sampler.rs rename to tek/device/sampler.rs diff --git a/crates/device/src/sampler/sampler_api.rs b/tek/device/sampler/sampler_api.rs similarity index 100% rename from crates/device/src/sampler/sampler_api.rs rename to tek/device/sampler/sampler_api.rs diff --git a/crates/device/src/sampler/sampler_audio.rs b/tek/device/sampler/sampler_audio.rs similarity index 100% rename from crates/device/src/sampler/sampler_audio.rs rename to tek/device/sampler/sampler_audio.rs diff --git a/crates/device/src/sampler/sampler_browse.rs b/tek/device/sampler/sampler_browse.rs similarity index 100% rename from crates/device/src/sampler/sampler_browse.rs rename to tek/device/sampler/sampler_browse.rs diff --git a/crates/device/src/sampler/sampler_data.rs b/tek/device/sampler/sampler_data.rs similarity index 97% rename from crates/device/src/sampler/sampler_data.rs rename to tek/device/sampler/sampler_data.rs index b4834174..2bca4558 100644 --- a/crates/device/src/sampler/sampler_data.rs +++ b/tek/device/sampler/sampler_data.rs @@ -58,7 +58,7 @@ impl Sample { // Decode a packet let decoded = decoder .decode(&packet) - .map_err(|e|Box::::from(e))?; + .map_err(|e|Box::::from(e))?; // Determine sample rate let spec = *decoded.spec(); if let Some(rate) = self.rate { diff --git a/crates/device/src/sampler/sampler_midi.rs b/tek/device/sampler/sampler_midi.rs similarity index 100% rename from crates/device/src/sampler/sampler_midi.rs rename to tek/device/sampler/sampler_midi.rs diff --git a/crates/device/src/sampler/sampler_view.rs b/tek/device/sampler/sampler_view.rs similarity index 100% rename from crates/device/src/sampler/sampler_view.rs rename to tek/device/sampler/sampler_view.rs diff --git a/crates/device/src/sequencer.rs b/tek/device/sequencer.rs similarity index 100% rename from crates/device/src/sequencer.rs rename to tek/device/sequencer.rs diff --git a/crates/device/src/sequencer/seq_audio.rs b/tek/device/sequencer/seq_audio.rs similarity index 100% rename from crates/device/src/sequencer/seq_audio.rs rename to tek/device/sequencer/seq_audio.rs diff --git a/crates/device/src/sequencer/seq_clip.rs b/tek/device/sequencer/seq_clip.rs similarity index 100% rename from crates/device/src/sequencer/seq_clip.rs rename to tek/device/sequencer/seq_clip.rs diff --git a/crates/device/src/sequencer/seq_launch.rs b/tek/device/sequencer/seq_launch.rs similarity index 100% rename from crates/device/src/sequencer/seq_launch.rs rename to tek/device/sequencer/seq_launch.rs diff --git a/crates/device/src/sequencer/seq_model.rs b/tek/device/sequencer/seq_model.rs similarity index 100% rename from crates/device/src/sequencer/seq_model.rs rename to tek/device/sequencer/seq_model.rs diff --git a/crates/device/src/sequencer/seq_view.rs b/tek/device/sequencer/seq_view.rs similarity index 100% rename from crates/device/src/sequencer/seq_view.rs rename to tek/device/sequencer/seq_view.rs diff --git a/crates/device/src/sf2.rs b/tek/device/sf2.rs similarity index 100% rename from crates/device/src/sf2.rs rename to tek/device/sf2.rs diff --git a/crates/device/src/vst2.rs b/tek/device/vst2.rs similarity index 100% rename from crates/device/src/vst2.rs rename to tek/device/vst2.rs diff --git a/crates/device/src/vst3.rs b/tek/device/vst3.rs similarity index 100% rename from crates/device/src/vst3.rs rename to tek/device/vst3.rs diff --git a/crates/engine/src/lib.rs b/tek/engine.rs similarity index 89% rename from crates/engine/src/lib.rs rename to tek/engine.rs index e3ff52aa..460b33b5 100644 --- a/crates/engine/src/lib.rs +++ b/tek/engine.rs @@ -1,4 +1,75 @@ -#![feature(type_alias_impl_trait)] +use crate::*; + +mod time; pub use self::time::*; +mod note; pub use self::note::*; +mod jack; pub use self::jack::*; +mod midi; pub use self::midi::*; + +//pub trait MaybeHas: Send + Sync { + //fn get (&self) -> Option<&T>; +//} + +//impl>> MaybeHas for U { + //fn get (&self) -> Option<&T> { + //Has::>::get(self).as_ref() + //} +//} + +pub trait HasN: Send + Sync { + fn get_nth (&self, key: usize) -> &T; + fn get_nth_mut (&mut self, key: usize) -> &mut T; +} + +pub trait Gettable { + /// Returns current value + fn get (&self) -> T; +} + +pub trait Mutable: Gettable { + /// Sets new value, returns old + fn set (&mut self, value: T) -> T; +} + +pub trait InteriorMutable: Gettable { + /// Sets new value, returns old + fn set (&self, value: T) -> T; +} + +impl Gettable for AtomicBool { + fn get (&self) -> bool { self.load(Relaxed) } +} + +impl InteriorMutable for AtomicBool { + fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) } +} + +impl Gettable for AtomicUsize { + fn get (&self) -> usize { self.load(Relaxed) } +} + +impl InteriorMutable for AtomicUsize { + fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) } +} + +#[cfg(test)] #[test] fn test_time () -> Usually<()> { + // TODO! + Ok(()) +} + +#[cfg(test)] #[test] fn test_midi_range () { + let model = MidiRangeModel::from((1, false)); + + let _ = model.get_time_len(); + let _ = model.get_time_zoom(); + let _ = model.get_time_lock(); + let _ = model.get_time_start(); + let _ = model.get_time_axis(); + let _ = model.get_time_end(); + + let _ = model.get_note_lo(); + let _ = model.get_note_axis(); + let _ = model.get_note_hi(); +} //macro_rules! impl_port { //($Name:ident : $Spec:ident -> $Pair:ident |$jack:ident, $name:ident|$port:expr) => { @@ -83,88 +154,3 @@ //} //}; //} - -mod time; pub use self::time::*; -mod note; pub use self::note::*; -pub mod jack; pub use self::jack::*; -pub mod midi; pub use self::midi::*; -pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}}; -pub(crate) use std::fmt::Debug; -pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem}; -pub(crate) use ::tengri::{from, Usually}; - -pub use ::atomic_float; pub(crate) use atomic_float::*; - -//pub trait MaybeHas: Send + Sync { - //fn get (&self) -> Option<&T>; -//} - -//impl>> MaybeHas for U { - //fn get (&self) -> Option<&T> { - //Has::>::get(self).as_ref() - //} -//} - -#[macro_export] macro_rules! as_ref { - ($T:ty: |$self:ident : $S:ty| $x:expr) => { - impl AsRef<$T> for $S { - fn as_ref (&$self) -> &$T { &$x } - } - }; -} - -pub trait HasN: Send + Sync { - fn get_nth (&self, key: usize) -> &T; - fn get_nth_mut (&mut self, key: usize) -> &mut T; -} - -pub trait Gettable { - /// Returns current value - fn get (&self) -> T; -} - -pub trait Mutable: Gettable { - /// Sets new value, returns old - fn set (&mut self, value: T) -> T; -} - -pub trait InteriorMutable: Gettable { - /// Sets new value, returns old - fn set (&self, value: T) -> T; -} - -impl Gettable for AtomicBool { - fn get (&self) -> bool { self.load(Relaxed) } -} - -impl InteriorMutable for AtomicBool { - fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) } -} - -impl Gettable for AtomicUsize { - fn get (&self) -> usize { self.load(Relaxed) } -} - -impl InteriorMutable for AtomicUsize { - fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) } -} - -#[cfg(test)] #[test] fn test_time () -> Usually<()> { - // TODO! - Ok(()) -} - -#[cfg(test)] #[test] fn test_midi_range () { - let model = MidiRangeModel::from((1, false)); - - let _ = model.get_time_len(); - let _ = model.get_time_zoom(); - let _ = model.get_time_lock(); - let _ = model.get_time_start(); - let _ = model.get_time_axis(); - let _ = model.get_time_end(); - - let _ = model.get_note_lo(); - let _ = model.get_note_axis(); - let _ = model.get_note_hi(); -} diff --git a/crates/engine/src/jack.rs b/tek/engine/jack.rs similarity index 100% rename from crates/engine/src/jack.rs rename to tek/engine/jack.rs diff --git a/crates/engine/src/midi.rs b/tek/engine/midi.rs similarity index 100% rename from crates/engine/src/midi.rs rename to tek/engine/midi.rs diff --git a/crates/engine/src/note.rs b/tek/engine/note.rs similarity index 100% rename from crates/engine/src/note.rs rename to tek/engine/note.rs diff --git a/crates/engine/src/note/note_pitch.rs b/tek/engine/note/note_pitch.rs similarity index 100% rename from crates/engine/src/note/note_pitch.rs rename to tek/engine/note/note_pitch.rs diff --git a/crates/engine/src/note/note_point.rs b/tek/engine/note/note_point.rs similarity index 100% rename from crates/engine/src/note/note_point.rs rename to tek/engine/note/note_point.rs diff --git a/crates/engine/src/note/note_range.rs b/tek/engine/note/note_range.rs similarity index 100% rename from crates/engine/src/note/note_range.rs rename to tek/engine/note/note_range.rs diff --git a/crates/engine/src/time.rs b/tek/engine/time.rs similarity index 100% rename from crates/engine/src/time.rs rename to tek/engine/time.rs diff --git a/crates/engine/src/time/time_moment.rs b/tek/engine/time/time_moment.rs similarity index 100% rename from crates/engine/src/time/time_moment.rs rename to tek/engine/time/time_moment.rs diff --git a/crates/engine/src/time/time_note.rs b/tek/engine/time/time_note.rs similarity index 100% rename from crates/engine/src/time/time_note.rs rename to tek/engine/time/time_note.rs diff --git a/crates/engine/src/time/time_perf.rs b/tek/engine/time/time_perf.rs similarity index 100% rename from crates/engine/src/time/time_perf.rs rename to tek/engine/time/time_perf.rs diff --git a/crates/engine/src/time/time_pulse.rs b/tek/engine/time/time_pulse.rs similarity index 100% rename from crates/engine/src/time/time_pulse.rs rename to tek/engine/time/time_pulse.rs diff --git a/crates/engine/src/time/time_sample_count.rs b/tek/engine/time/time_sample_count.rs similarity index 100% rename from crates/engine/src/time/time_sample_count.rs rename to tek/engine/time/time_sample_count.rs diff --git a/crates/engine/src/time/time_sample_rate.rs b/tek/engine/time/time_sample_rate.rs similarity index 100% rename from crates/engine/src/time/time_sample_rate.rs rename to tek/engine/time/time_sample_rate.rs diff --git a/crates/engine/src/time/time_timebase.rs b/tek/engine/time/time_timebase.rs similarity index 100% rename from crates/engine/src/time/time_timebase.rs rename to tek/engine/time/time_timebase.rs diff --git a/crates/engine/src/time/time_unit.rs b/tek/engine/time/time_unit.rs similarity index 100% rename from crates/engine/src/time/time_unit.rs rename to tek/engine/time/time_unit.rs diff --git a/crates/engine/src/time/time_usec.rs b/tek/engine/time/time_usec.rs similarity index 100% rename from crates/engine/src/time/time_usec.rs rename to tek/engine/time/time_usec.rs diff --git a/tek.edn b/tek/tek.edn similarity index 100% rename from tek.edn rename to tek/tek.edn diff --git a/crates/app/tek_view.rs b/tek/tek.rs similarity index 62% rename from crates/app/tek_view.rs rename to tek/tek.rs index 9804ba0e..8fb554c2 100644 --- a/crates/app/tek_view.rs +++ b/tek/tek.rs @@ -1,4 +1,264 @@ -use crate::*; +#![feature( + adt_const_params, + associated_type_defaults, + closure_lifetime_binder, + if_let_guard, + impl_trait_in_assoc_type, + trait_alias, + type_alias_impl_trait, + type_changing_struct_update, +)] + +#![allow( + clippy::unit_arg +)] + +mod deps; pub use self::deps::*; + +mod config; pub use self::config::*; +mod device; pub use self::device::*; +mod engine; pub use self::engine::*; + +mod tek_bind; pub use self::tek_bind::*; +mod tek_menu; pub use self::tek_menu::*; + +#[cfg(test)] mod test; + +/// Total state +#[derive(Default, Debug)] +pub struct App { + /// Base color. + pub color: ItemTheme, + /// Must not be dropped for the duration of the process + pub jack: Jack<'static>, + /// Display size + pub size: Measure, + /// Performance counter + pub perf: PerfModel, + /// Available view modes and input bindings + pub config: Config, + /// Currently selected mode + pub mode: Arc>>, + /// Undo history + pub history: Vec<(AppCommand, Option)>, + /// Dialog overlay + pub dialog: Dialog, + /// Contains all recently created clips. + pub pool: Pool, + /// Contains the currently edited musical arrangement + pub project: Arrangement, +} + +audio!( + |self: App, client, scope|{ + let t0 = self.perf.get_t0(); + self.clock().update_from_scope(scope).unwrap(); + let midi_in = self.project.midi_input_collect(scope); + if let Some(editor) = &self.editor() { + let mut pitch: Option = None; + for port in midi_in.iter() { + for event in port.iter() { + if let (_, Ok(LiveEvent::Midi {message: MidiMessage::NoteOn {key, ..}, ..})) + = event + { + pitch = Some(key.clone()); + } + } + } + if let Some(pitch) = pitch { + editor.set_note_pos(pitch.as_int() as usize); + } + } + let result = self.project.process_tracks(client, scope); + self.perf.update_from_jack_scope(t0, scope); + result + }; + |self, event|{ + use JackEvent::*; + match event { + SampleRate(sr) => { + self.clock().timebase.sr.set(sr as f64); + }, + PortRegistration(id, true) => { + //let port = self.jack().port_by_id(id); + //println!("\rport add: {id} {port:?}"); + //println!("\rport add: {id}"); + }, + PortRegistration(id, false) => { + /*println!("\rport del: {id}")*/ + }, + PortsConnected(a, b, true) => { /*println!("\rport conn: {a} {b}")*/ }, + PortsConnected(a, b, false) => { /*println!("\rport disc: {a} {b}")*/ }, + ClientRegistration(id, true) => {}, + ClientRegistration(id, false) => {}, + ThreadInit => {}, + XRun => {}, + GraphReorder => {}, + _ => { panic!("{event:?}"); } + } + } +); + +dsl_ns!(App: Arc { + literal = |dsl|Ok(dsl.src()?.map(|x|x.into())); +}); + +dsl_ns!(App: ItemTheme {}); + +dsl_ns!(App: bool { + word = |app| { + ":mode/editor" => app.project.editor.is_some(), + ":focused/dialog" => !matches!(app.dialog, Dialog::None), + ":focused/message" => matches!(app.dialog, Dialog::Message(..)), + ":focused/add_device" => matches!(app.dialog, Dialog::Device(..)), + ":focused/browser" => app.dialog.browser().is_some(), + ":focused/pool/import" => matches!(app.pool.mode, Some(PoolMode::Import(..))), + ":focused/pool/export" => matches!(app.pool.mode, Some(PoolMode::Export(..))), + ":focused/pool/rename" => matches!(app.pool.mode, Some(PoolMode::Rename(..))), + ":focused/pool/length" => matches!(app.pool.mode, Some(PoolMode::Length(..))), + ":focused/clip" => !app.editor_focused() && matches!(app.selection(), + Selection::TrackClip{..}), + ":focused/track" => !app.editor_focused() && matches!(app.selection(), + Selection::Track(..)), + ":focused/scene" => !app.editor_focused() && matches!(app.selection(), + Selection::Scene(..)), + ":focused/mix" => !app.editor_focused() && matches!(app.selection(), + Selection::Mix), + }; +}); + +dsl_ns!(App: Dialog { + word = |app| { + ":dialog/none" => Dialog::None, + ":dialog/options" => Dialog::Options, + ":dialog/device" => Dialog::Device(0), + ":dialog/device/prev" => Dialog::Device(0), + ":dialog/device/next" => Dialog::Device(0), + ":dialog/help" => Dialog::Help(0), + ":dialog/save" => Dialog::Browse(BrowseTarget::SaveProject, + Browse::new(None).unwrap().into()), + ":dialog/load" => Dialog::Browse(BrowseTarget::LoadProject, + Browse::new(None).unwrap().into()), + ":dialog/import/clip" => Dialog::Browse(BrowseTarget::ImportClip(Default::default()), + Browse::new(None).unwrap().into()), + ":dialog/export/clip" => Dialog::Browse(BrowseTarget::ExportClip(Default::default()), + Browse::new(None).unwrap().into()), + ":dialog/import/sample" => Dialog::Browse(BrowseTarget::ImportSample(Default::default()), + Browse::new(None).unwrap().into()), + ":dialog/export/sample" => Dialog::Browse(BrowseTarget::ExportSample(Default::default()), + Browse::new(None).unwrap().into()), + }; +}); + +dsl_ns!(App: Selection { + word = |app| { + ":select/scene" => app.selection().select_scene(app.tracks().len()), + ":select/scene/next" => app.selection().select_scene_next(app.scenes().len()), + ":select/scene/prev" => app.selection().select_scene_prev(), + ":select/track" => app.selection().select_track(app.tracks().len()), + ":select/track/next" => app.selection().select_track_next(app.tracks().len()), + ":select/track/prev" => app.selection().select_track_prev(), + }; +}); + +dsl_ns!(App: Color { + word = |app| { + ":color/bg" => Color::Rgb(28, 32, 36), + }; + expr = |app| { + "g" (n: u8) => Color::Rgb(n, n, n), + "rgb" (r: u8, g: u8, b: u8) => Color::Rgb(r, g, b), + }; +}); + +dsl_ns!(App: Option { + word = |app| { + ":editor/pitch" => Some( + (app.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8).into() + ) + }; +}); + +dsl_ns!(App: Option { + word = |app| { + ":selected/scene" => app.selection().scene(), + ":selected/track" => app.selection().track(), + }; +}); + +dsl_ns!(App: Option>> { + word = |app| { + ":selected/clip" => if let Selection::TrackClip { track, scene } = app.selection() { + app.scenes()[*scene].clips[*track].clone() + } else { + None + } + }; +}); + +dsl_ns!(App: u8 { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as u8) + } else { + None + }); +}); + +dsl_ns!(App: u16 { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as u16) + } else { + None + }); + word = |app| { + ":w/sidebar" => app.project.w_sidebar(app.editor().is_some()), + ":h/sample-detail" => 6.max(app.height() as u16 * 3 / 9), + }; +}); + +dsl_ns!(App: usize { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as usize) + } else { + None + }); + word = |app| { + ":scene-count" => app.scenes().len(), + ":track-count" => app.tracks().len(), + ":device-kind" => app.dialog.device_kind().unwrap_or(0), + ":device-kind/next" => app.dialog.device_kind_next().unwrap_or(0), + ":device-kind/prev" => app.dialog.device_kind_prev().unwrap_or(0), + }; +}); + +dsl_ns!(App: isize { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as isize) + } else { + None + }); +}); + +impl HasClipsSize for App { fn clips_size (&self) -> &Measure { &self.project.inner_size } } +impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } } +impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } } +impl HasJack<'static> for App { fn jack (&self) -> &Jack<'static> { &self.jack } } +has!(Jack<'static>: |self: App|self.jack); +has!(Pool: |self: App|self.pool); +has!(Dialog: |self: App|self.dialog); +has!(Clock: |self: App|self.project.clock); +has!(Option: |self: App|self.project.editor); +has!(Selection: |self: App|self.project.selection); +has!(Vec: |self: App|self.project.midi_ins); +has!(Vec: |self: App|self.project.midi_outs); +has!(Vec: |self: App|self.project.scenes); +has!(Vec: |self: App|self.project.tracks); +has!(Measure: |self: App|self.size); +has_clips!( |self: App|self.pool.clips); +maybe_has!(Track: |self: App| { MaybeHas::::get(&self.project) }; + { MaybeHas::::get_mut(&mut self.project) }); +maybe_has!(Scene: |self: App| { MaybeHas::::get(&self.project) }; + { MaybeHas::::get_mut(&mut self.project) }); impl ScenesView for App { fn w_side (&self) -> u16 { 20 } @@ -274,6 +534,98 @@ impl App { } +impl App { + + pub fn editor_focused (&self) -> bool { + false + } + + pub fn toggle_dialog (&mut self, mut dialog: Dialog) -> Dialog { + std::mem::swap(&mut self.dialog, &mut dialog); + dialog + } + + pub fn toggle_editor (&mut self, value: Option) { + //FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); + let value = value.unwrap_or_else(||!self.editor().is_some()); + if value { + // Create new clip in pool when entering empty cell + if let Selection::TrackClip { track, scene } = *self.selection() + && let Some(scene) = self.project.scenes.get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) + && slot.is_none() + && let Some(track) = self.project.tracks.get_mut(track) + { + let (index, mut clip) = self.pool.add_new_clip(); + // autocolor: new clip colors from scene and track color + let color = track.color.base.mix(scene.color.base, 0.5); + clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into(); + if let Some(editor) = &mut self.project.editor { + editor.set_clip(Some(&clip)); + } + *slot = Some(clip.clone()); + //Some(clip) + } else { + //None + } + } else if let Selection::TrackClip { track, scene } = *self.selection() + && let Some(scene) = self.project.scenes.get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) + && let Some(clip) = slot.as_mut() + { + // Remove clip from arrangement when exiting empty clip editor + let mut swapped = None; + if clip.read().unwrap().count_midi_messages() == 0 { + std::mem::swap(&mut swapped, slot); + } + if let Some(clip) = swapped { + self.pool.delete_clip(&clip.read().unwrap()); + } + } + } + + pub fn browser (&self) -> Option<&Browse> { + if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None } + } + + pub fn device_pick (&mut self, index: usize) { + self.dialog = Dialog::Device(index); + } + + pub fn add_device (&mut self, index: usize) -> Usually<()> { + match index { + 0 => { + let name = self.jack.with_client(|c|c.name().to_string()); + let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name(); + let track = self.track().expect("no active track"); + let port = format!("{}/Sampler", &track.name); + let connect = Connect::exact(format!("{name}:{midi}")); + let sampler = if let Ok(sampler) = Sampler::new( + &self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]] + ) { + self.dialog = Dialog::None; + Device::Sampler(sampler) + } else { + self.dialog = Dialog::Message("Failed to add device.".into()); + return Err("failed to add device".into()) + }; + let track = self.track_mut().expect("no active track"); + track.devices.push(sampler); + Ok(()) + }, + 1 => { + todo!(); + Ok(()) + }, + _ => unreachable!(), + } + } + + pub fn update_clock (&self) { + ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80) + } +} + //pub fn view_nil (_: &App) -> TuiCb { //|to|to.place(&Fill::xy("·")) //} @@ -488,3 +840,38 @@ impl App { ////let options = ||["Projects", "Settings", "Help", "Quit"].iter(); ////let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a)); ////Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option))) + +//#[dizzle::ns] +//#[dizzle::ns::include(TuiOut)] +//trait AppApi { + //#[dizzle::ns::word("sessions")] + //fn view_sessions (&self) -> Box> { + //Min::x(30, Fixed::y(6, Stack::south( + //move|add: &mut dyn FnMut(&dyn Render)|{ + //let fg = Rgb(224, 192, 128); + //for (index, name) in ["session1", "session2", "session3"].iter().enumerate() { + //let bg = if index == 0 { Rgb(48,64,32) } else { Rgb(16, 32, 24) }; + //add(&Fixed::y(2, Fill::x(Tui::bg(bg, Align::w(Tui::fg(fg, name)))))); + //} + //} + //))).into() + //} + //#[dizzle::ns::expr("bold")] + //fn view_bold (&self, value: bool, x: Box>) -> Box> { + //Box::new(Tui::bold(value, x)) + //} +//} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +//has_editor!(|self: App|{ + //editor = self.editor; + //editor_w = { + //let size = self.size.w(); + //let editor = self.editor.as_ref().expect("missing editor"); + //let time_len = editor.time_len().get(); + //let time_zoom = editor.time_zoom().get().max(1); + //(5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16) + //}; + //editor_h = 15; + //is_editing = self.editor.is_some(); +//}); diff --git a/crates/app/tek_bind.rs b/tek/tek_bind.rs similarity index 100% rename from crates/app/tek_bind.rs rename to tek/tek_bind.rs diff --git a/crates/app/tek_cli.rs b/tek/tek_cli.rs similarity index 100% rename from crates/app/tek_cli.rs rename to tek/tek_cli.rs diff --git a/crates/app/tek_menu.rs b/tek/tek_menu.rs similarity index 100% rename from crates/app/tek_menu.rs rename to tek/tek_menu.rs diff --git a/crates/app/tek_test.rs b/tek/test.rs similarity index 100% rename from crates/app/tek_test.rs rename to tek/test.rs From 86941305a459a47e7377943690efd9b3d0cefae0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 8 Sep 2025 00:31:37 +0300 Subject: [PATCH 4/4] perf: use mold --- Cargo.toml | 2 +- Justfile | 6 ++++-- deps/tengri | 2 +- shell.nix | 46 +++++++++++++++++++++++++++------------------- tek/Cargo.toml | 3 +++ tek/tek.edn | 2 +- tek/tek.rs | 7 ++++--- 7 files changed, 41 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14b1da71..396c69f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ path = "./deps/tengri/proc" path = "./deps/rust-jack" [workspace.dependencies] -tek = { path = "./tek" } +tek = { path = "./tek" } atomic_float = { version = "1.0.0" } backtrace = { version = "0.3.72" } diff --git a/Justfile b/Justfile index 199d5799..b783e089 100644 --- a/Justfile +++ b/Justfile @@ -1,4 +1,4 @@ -export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace" +export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg=-fuse-ld=mold" export RUST_BACKTRACE := "1" debug := "reset && cargo run --" @@ -19,7 +19,9 @@ default: bacon: bacon -s check: - cargo check + reset && cargo check +build: + reset && cargo build run: {{debug}} run-init: diff --git a/deps/tengri b/deps/tengri index 18b68039..a4dbf882 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 18b6803912105cc6a23217641a68967695a2166b +Subproject commit a4dbf88220f75ccaf9d14cc2e4fb7c00479e3940 diff --git a/shell.nix b/shell.nix index 7f22e603..8782e6dd 100755 --- a/shell.nix +++ b/shell.nix @@ -1,32 +1,40 @@ #!/usr/bin/env nix-shell {pkgs?import{}}:let - stdenv = pkgs.clang19Stdenv; name = "tek"; - nativeBuildInputs = with pkgs; [ pkg-config freetype libclang ]; - buildInputs = with pkgs; let - #suil = pkgs.enableDebugging (pkgs.suil.overrideAttrs (a: b: { - #dontStrip = true; separateDebugInfo = true; - #})); - in [ jack2 lilv serd libclang /*suil*/ glib gtk3 ]; + stdenv = pkgs.clang19Stdenv; + nativeBuildInputs = [ + pkgs.pkg-config + pkgs.freetype + pkgs.libclang + pkgs.mold + ]; + buildInputs = [ + pkgs.jack2 + pkgs.lilv + pkgs.serd + pkgs.libclang + ]; VST3_SDK_DIR = "/home/user/Lab/Music/tek/vst3sdk/"; LIBCLANG_PATH = "${pkgs.libclang.lib.outPath}/lib"; - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (with pkgs; [ - pipewire.jack + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ + pkgs.pipewire.jack # for ChowKick.lv2: - freetype - libgcc.lib + pkgs.freetype + pkgs.libgcc.lib # for Panagement - xorg.libX11 - xorg.libXcursor - xorg.libXi - libxkbcommon + pkgs.xorg.libX11 + pkgs.xorg.libXcursor + pkgs.xorg.libXi + pkgs.libxkbcommon + pkgs.lilv + pkgs.serd #suil # for Helm: - alsa-lib - curl - libglvnd + pkgs.alsa-lib + pkgs.curl + pkgs.libglvnd #xorg_sys_opengl - ]); + ]; in pkgs.mkShell.override { inherit stdenv; } { diff --git a/tek/Cargo.toml b/tek/Cargo.toml index 3e005db3..f29213a0 100644 --- a/tek/Cargo.toml +++ b/tek/Cargo.toml @@ -10,6 +10,9 @@ path = "tek.rs" name = "tek" path = "tek_cli.rs" +[target.'cfg(target_os = "linux")'] +rustflags = ["-C", "link-arg=-fuse-ld=mold"] + [dependencies] tengri = { workspace = true } tengri_proc = { workspace = true } diff --git a/tek/tek.edn b/tek/tek.edn index 8f4c82d0..30849ccb 100644 --- a/tek/tek.edn +++ b/tek/tek.edn @@ -32,7 +32,7 @@ (mode :menu (keys :axis/y :confirm) :menu) (keys :confirm (@enter confirm)) -(view :menu (bg (g 40) (bsp/s +(view :menu (bg (g 0) (bsp/s :ports/out (bsp/n :ports/in (bg (g 30) (bsp/s (fixed/y 7 :logo) (fill/xy :dialog/menu))))))) (view :ports/out (fill/x (fixed/y 3 (bsp/a diff --git a/tek/tek.rs b/tek/tek.rs index 8fb554c2..7ff10600 100644 --- a/tek/tek.rs +++ b/tek/tek.rs @@ -268,8 +268,9 @@ impl ScenesView for App { impl Draw for App { fn draw (&self, to: &mut TuiOut) { - for dsl in self.mode.view.iter() { - let _ = self.view(to, dsl).expect("render failed"); + for (index, dsl) in self.mode.view.iter().enumerate() { + to.place(&Align::nw(Push::y(1 + index as u16 * 2, dsl.src().unwrap()))); + //let _ = self.view(to, dsl).expect("render failed"); } } } @@ -449,7 +450,7 @@ impl App { Some("menu") => to.place(&if let Dialog::Menu(selected, items) = &self.dialog { let items = items.clone(); let selected = selected; - Some(Fill::x(Thunk::new(move|to: &mut TuiOut|{ + Some(Fill::xy(Thunk::new(move|to: &mut TuiOut|{ for (index, MenuItem(item, _)) in items.0.iter().enumerate() { to.place(&Push::y((2 * index) as u16, Tui::fg_bg(