From 50728729b7378d9c50c6a7c3769b5cc435801243 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 10 Aug 2025 16:13:54 +0300 Subject: [PATCH] wip: unify default config --- config/binds.edn | 164 --------------------------------------- config/views.edn | 74 ------------------ crates/app/app.rs | 171 ++++++++++++++++++++--------------------- crates/app/app_ctrl.rs | 6 +- crates/app/app_view.rs | 8 +- crates/cli/tek.rs | 4 +- deps/tengri | 2 +- tek.edn | 138 +++++++++++++++++++++++++++++++++ 8 files changed, 232 insertions(+), 335 deletions(-) delete mode 100644 config/binds.edn delete mode 100644 config/views.edn create mode 100644 tek.edn diff --git a/config/binds.edn b/config/binds.edn deleted file mode 100644 index e4c7be2a..00000000 --- a/config/binds.edn +++ /dev/null @@ -1,164 +0,0 @@ -(def :keys/cancel - (@escape cancel)) - -(def :keys/delete - (@delete delete)) - -(def :keys/history - (@u undo 1) (@r redo 1)) - -(def :keys/nextprev - (@comma prev) (@period next)) - -(def :keys/swap - (@lt swap/prev) (@gt swap/next)) - -(def :keys/list - (@up list/prev) (@down list/next) (@enter list/select) (@escape list/cancel)) - -(def :keys/clock - (@space clock/toggle 0) (@shift/space clock/toggle 0)) - -(def :keys/color - (@c color)) - -(def :keys/launch - (@q launch)) - -(def :keys/filter - (see :keys/list :keys/input)) - -(def :keys/input - (see :keys/cancel :keys/delete) - (@left cursor/prev) (@right cursor/next) (@backspace delete/prev) (:char input)) - -(def :keys/global - (see :keys/cancel :keys/history) - (@f1 dialog :help) (@f8 dialog :options) (@f10 dialog :quit) - (@f6 dialog :save) (@f9 dialog :load)) - -(def :keys/arranger - (see :keys/color :keys/launch :keys/arranger/direction :keys/arranger/cycle) - (@tab project/edit) (@enter project/edit) - (@shift/I project/input/add) (@shift/O project/output/add) - (@shift/S project/scene/add) (@shift/T project/track/add) - (@shift/D dialog/show :dialog/device)) - -(def :keys/arranger/cycle - (@t select :select/track) (@s select :select/scene)) - -(def :keys/arranger/direction - (@up select :select/scene/prev) (@down select :select/scene/next) - (@left select :select/track/prev) (@right select :select/track/next)) - -(def :keys/scene (see :keys/color :keys/launch :keys/nextprev :keys/swap :keys/delete)) - -(def :keys/track (see :keys/color :keys/launch :keys/nextprev :keys/swap :keys/delete) - (@r toggle :rec) (@m toggle :mon) (@p toggle :play) (@P toggle :solo)) - -(def :keys/clip (see :keys/color :keys/launch :keys/nextprev :keys/swap :keys/delete) - (@l toggle :loop)) - -(def :keys/sampler (see :keys/sampler/directions :keys/sampler/record :keys/sampler/play)) - -(def :keys/sampler/play - (@p sampler/play/sample :sample/selected) - (@P sampler/stop/sample :sample/selected)) - -(def :keys/sampler/record - (@r sampler/record/toggle :sample/selected) - (@shift/R sampler/record/cancel)) - -(def :keys/sampler/import-export - (@shift/f6 dialog :dialog/export/sample) - (@shift/f9 dialog :dialog/import/sample)) - -(def :keys/sampler/directions - (@up sampler/select :sample/above) - (@down sampler/select :sample/below) - (@left sampler/select :sample/to/left) - (@right sampler/select :sample/to/right)) - -(def :keys/sequencer (see :keys/color :keys/launch) - (@shift/I input/add) - (@shift/O output/add)) - -(module :browser - (@escape browser/cancel) - (@return browser/confirm) - (@up browser/cursor/set :browser/cursor/prev) - (@down browser/cursor/set :browser/cursor/next) - (@right browser/address/set :browser/address/selected) - (@left browser/address/set :browser/address/parent) - (:char browser/filter/append :char) - (@backspace browser/filter/delete :last)) - -(module :device/add - (@up dialog :dialog/device/prev) - (@down dialog :dialog/device/next)) - -(module :editor - (@left editor/time/set :time/pos/prev) - (@shift/left editor/time/set :time/pos/prev/fine) - (@right editor/time/set :time/pos/next) - (@shift/right editor/time/set :time/pos/next/fine) - (@equal editor/zoom/set :time/zoom/prev) - (@minus editor/zoom/set :time/zoom/next) - (@plus editor/zoom/set :time/zoom/next/fine) - (@underscore editor/zoom/set :time/zoom/prev/fine) - (@z editor/lock/set) - (@comma editor/length/set :note/len/prev) - (@period editor/length/set :note/len/next) - (@lt editor/length/set :note/len/prev) - (@gt editor/length/set :note/len/next) - (@up editor/pitch/set :note/pos/next) - (@down editor/pitch/set :note/pos/prev) - (@pgup editor/pitch/set :note/pos/next/octave) - (@pgdn editor/pitch/set :note/pos/prev/octave) - (@a editor/append :true) - (@enter editor/append :false) - (@del editor/delete/note) - (@shift/del editor/delete/note)) - -(module :groovebox - (@r sampler/record/toggle :sample) - (@tab focus/next) - (@shift/tab focus/prev)) - -(module :length - (@up inc) - (@down dec) - (@right next) - (@left prev) - (@return set :length) - (@escape cancel)) - -(module :pool - (@n rename/begin) - (@t length/begin) - (@m import/begin) - (@x export/begin) - (@c clip/color :clip :random/color) - (@openbracket select :clip/prev) - (@closebracket select :clip/next) - (@lt swap :clip :clip/prev) - (@gt swap :clip :clip/next) - (@delete clip/delete :clip) - (@shift/A clip/add :after :new/clip) - (@shift/D clip/add :after :cloned/clip)) - -(module :pool/file - (@up select :prev) - (@down select :next) - (@right chdir :selected) - (@left chdir :parent) - (@return confirm) - (@escape cancel) - (:char append :char) - (@backspace delete :last)) - -(module :rename - (:char append :char) - (@backspace delete :last) - (@return confirm) - (@escape cancel)) diff --git a/config/views.edn b/config/views.edn deleted file mode 100644 index 0943020c..00000000 --- a/config/views.edn +++ /dev/null @@ -1,74 +0,0 @@ -(mode :transport - (name Transport) - (info A JACK transport controller.) - (keys :clock :global) - :view/transport) - -(mode :arranger - (name Arranger) - (info A grid of launchable clips arranged by track and scene.) - (mode :editor (keys :editor)) - (mode :dialog (keys :dialog)) - (mode :message (keys :message)) - (mode :add-device (keys :add-device)) - (mode :browser (keys :browser)) - (mode :rename (keys :pool-rename)) - (mode :length (keys :pool-length)) - (mode :clip (keys :clip)) - (mode :track (keys :track)) - (mode :scene (keys :scene)) - (mode :mix (keys :mix)) - (keys :clock :arranger :global) - (bsp/w :view/meters/output - (bsp/e :view/meters/input - (stack/n - (fixed/y 2 :view/status/h2) - :view/tracks/inputs - (stack/s - :view/tracks/devices - :view/tracks/outputs - :view/tracks/names - (fill/xy (either :mode/editor - (bsp/e :view/scenes/names :view/editor) - :view/scenes))))))) - -(mode :groovebox - (name Groovebox) - (info A sequencer with built-in sampler.) - (mode :browser (keys :browser)) - (mode :rename (keys :pool-rename)) - (mode :length (keys :pool-length)) - (keys :clock :editor :sampler :global) - (bsp/w :view/meters/output - (bsp/e :view/meters/input - (bsp/w - (fill/y (align/n (stack/s :view/midi-ins/status - :view/midi-outs/status - :view/audio-ins/status - :view/audio-outs/status - :view/pool))) - (bsp/n (fixed/y :h-sample-detail (bsp/e (fill/y (fixed/x 20 (align/nw :view/sample-status))) - :view/sample-viewer)) - (bsp/e (fill/y (align/n (bsp/s :view/status/v :view/editor-status))) - (bsp/e :view/samples/keys - :view/editor))))))) - -(mode :sampler - (name Sampler) - (info A sampling soundboard.) - (keys :sampler :global) - (bsp/s (fixed/y 1 :view/transport) - (bsp/n (fixed/y 1 :view/status) - (fill/xy :view/samples/grid)))) - -(mode :sequencer - (name Sequencer) - (info A MIDI sequencer.) - (mode :browser (keys :browser)) - (mode :rename (keys :pool-rename)) - (mode :length (keys :pool-length)) - (keys :editor :clock :global) - (bsp/s (fixed/y 1 :view/transport) - (bsp/n (fixed/y 1 :view/status) - (fill/xy (bsp/a (fill/xy (align/e :view/pool)) - :view/editor))))) diff --git a/crates/app/app.rs b/crates/app/app.rs index 50773c17..ec916c15 100644 --- a/crates/app/app.rs +++ b/crates/app/app.rs @@ -54,10 +54,10 @@ pub struct App { pub size: Measure, /// Performance counter pub perf: PerfModel, - /// Available view profiles and input bindings + /// Available view modes and input bindings pub config: Config, - /// Currently selected profile - pub profile: Profile, + /// Currently selected mode + pub mode: Mode>, /// Contains the currently edited musical arrangement pub project: Arrangement, /// Contains all recently created clips. @@ -72,124 +72,121 @@ pub struct App { /// Configuration #[derive(Default, Debug)] pub struct Config { - /// XDG basedirs pub dirs: BaseDirectories, - /// Available view profiles - pub views: Arc, Profile>>>, - /// Available input bindings + pub modes: Arc, Arc>>>>>, pub binds: Arc, EventMap, Arc>>>>, } -/// Profile #[derive(Default, Debug)] -pub struct Profile { - /// Path of configuration entrypoint +pub struct Mode { pub path: PathBuf, - /// Default mode - pub mode: Mode, - /// Optional modes - pub modes: BTreeMap, Mode>, -} -#[derive(Default, Debug)] -pub struct Mode { - /// Name of configuration - pub name: Vec>, - /// Description of configuration - pub info: Vec>, - /// View definition - pub view: Vec>, - // Input keymap - pub keys: Vec>, + pub name: Vec, + pub info: Vec, + pub view: Vec, + pub keys: Vec, + pub modes: BTreeMap>, } impl Config { - const VIEWS: &'static str = "views.edn"; - const BINDS: &'static str = "binds.edn"; - const DEFAULT_VIEWS: &'static str = include_str!("../../config/views.edn"); - const DEFAULT_BINDS: &'static str = include_str!("../../config/binds.edn"); + const CONFIG: &'static str = "tek.edn"; + const DEFAULTS: &'static str = include_str!("../../tek.edn"); pub fn init () -> Usually { let mut cfgs: Self = Default::default(); cfgs.dirs = BaseDirectories::with_profile("tek", "v0"); - cfgs.load(Self::BINDS, Self::DEFAULT_BINDS, |cfgs, dsl|cfgs.load_bind(dsl))?; - cfgs.load(Self::VIEWS, Self::DEFAULT_VIEWS, |cfgs, dsl|cfgs.load_view(dsl))?; + cfgs.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load_defs(dsl))?; Ok(cfgs) } - pub fn load ( + 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_bind (&mut self, dsl: D) -> Usually<()> { - load_modules(dsl, |id, tail|Ok(self.binds.write().unwrap().insert(id.into(), { - let mut map = EventMap::new(); - tail.each(|item|{ - map.add(TuiEvent::from_dsl(item.exp()?.head()?)?, Binding { - command: item.exp()?.tail()?.unwrap_or_default().into(), - condition: None, - description: None, - source: None - }); - Ok(()) - })?; - map - }))) - } - pub fn load_view (&mut self, dsl: D) -> Usually<()> { - load_modules(&dsl, |id, tail|Ok(self.views.write().unwrap().insert(id.into(), { - let mut profile = Profile::default(); - tail.each(|item|Ok(if let Ok(Some(exp)) = item.exp() { - match exp.head()? { - Some("name") => profile.mode.name.push( - exp.tail()?.map(|x|x.trim()).unwrap_or("").into() - ), - Some("info") => profile.mode.info.push( - exp.tail()?.map(|x|x.trim()).unwrap_or("").into() - ), - Some("keys") => if let Some(tail) = exp.tail()? { - tail.each(|keys|Ok(profile.mode.keys.push(keys.trim().into())))?; - } else { - return Err(format!("load_view: empty keys: {exp}").into()) - }, - Some("mode") => if let (Some(name), Some(tail)) = ( - exp.tail()?.head()?, exp.tail()?.tail()?, - ) { - let mut mode: Mode = Default::default(); - tail.each(|item|Ok(if let Ok(Some(exp)) = item.exp() { + pub fn load_defs (&mut self, dsl: D) -> Usually<()> { + dsl.each(|item|{ + println!("{item:?}"); + Ok(match item.exp().head() { + Ok(Some("keys")) if let Some(id) = item.exp().tail().head()? => { + self.binds.write().unwrap().insert(id.into(), { + let mut map = EventMap::new(); + item.exp().tail().tail()?.each(|item|Ok({ + if let Ok(Some(sym)) = item.exp().head().sym() { + map.add(TuiEvent::from_dsl(item.exp()?.head()?)?, Binding { + command: item.exp()?.tail()?.unwrap_or_default().into(), + condition: None, + description: None, + source: None + }); + } else if item.exp().head() == Ok(Some("see")) { + // TODO + } else { + return Err(format!("load_defs: unexpected: {item:?}").into()) + } + }))?; + map + }); + }, + Ok(Some("mode")) if let Some(id) = item.exp().tail().head()? => { + self.modes.write().unwrap().insert(id.into(), { + let mut mode = Mode::default(); + item.exp().tail().tail()?.each(|item|Ok(if let Ok(Some(exp)) = item.exp() { match exp.head()? { + Some("name") => mode.name.push( + exp.tail()?.map(|x|x.trim()).unwrap_or("").into() + ), + Some("info") => mode.info.push( + exp.tail()?.map(|x|x.trim()).unwrap_or("").into() + ), Some("keys") => if let Some(tail) = exp.tail()? { tail.each(|keys|Ok(mode.keys.push(keys.trim().into())))?; } else { return Err(format!("load_view: empty keys: {exp}").into()) }, - _ => { - return Err(format!("load_view: unexpected in mode {name}: {item:?}").into()) - } + Some("mode") => if let (Some(name), Some(tail)) = ( + exp.tail()?.head()?, exp.tail()?.tail()?, + ) { + let mut submode: Mode> = Default::default(); + tail.each(|item|Ok(if let Ok(Some(exp)) = item.exp() { + match exp.head()? { + Some("keys") => if let Some(tail) = exp.tail()? { + tail.each(|keys|Ok(mode.keys.push(keys.trim().into())))?; + } else { + return Err(format!("load_view: empty keys: {exp}").into()) + }, + _ => { + return Err(format!("load_view: unexpected in mode {name}: {item:?}").into()) + } + } + } else if let Ok(Some(sym)) = item.sym() { + // TODO + } else { + return Err(format!("load_view: unexpected in mode {name}: {item:?}").into()) + }))?; + mode.modes.insert(name.trim().into(), submode); + } else { + return Err(format!("load_view: empty mode: {exp}").into()) + }, + Some(_) => mode.view.push(exp.into()), + None => return Err(format!("load_view: empty: {exp}").into()) } } else if let Ok(Some(sym)) = item.sym() { - // TODO + mode.view.push(sym.into()); } else { - return Err(format!("load_view: unexpected in mode {name}: {item:?}").into()) + return Err(format!("load_view: unexpected: {dsl:?}").into()) }))?; - profile.modes.insert(name.trim().into(), mode); - } else { - return Err(format!("load_view: empty mode: {exp}").into()) - }, - Some(_) => profile.mode.view.push(exp.into()), - None => return Err(format!("load_view: empty: {exp}").into()) - } - } else if let Ok(Some(sym)) = item.sym() { - profile.mode.view.push(sym.into()); - } else { - return Err(format!("load_view: unexpected: {dsl:?}").into()) - }))?; - profile - }))) + mode.into() + }); + }, + _ => return Err(format!("load_defs: unexpected: {item:?}").into()) + }) + }) } } fn load_modules (dsl: D, cb: impl Fn(&str, &str)->Usually) -> Usually<()> { diff --git a/crates/app/app_ctrl.rs b/crates/app/app_ctrl.rs index 44ab3bcf..3bcabf55 100644 --- a/crates/app/app_ctrl.rs +++ b/crates/app/app_ctrl.rs @@ -2,15 +2,15 @@ use crate::*; #[derive(Debug)] pub enum AppCommand {} handle!(TuiIn:|self: App, input|{ - panic!("wat: {:?}", self.profile); - for keys in self.profile.mode.keys.iter() { + panic!("wat: {:?}", self.mode); + for keys in self.mode.keys.iter() { panic!("{keys} {:?}", self.config.binds.read().unwrap()); if let Some(binding) = self.config.binds.read().unwrap().get(keys.as_ref()) { panic!("{binding:?}"); } } Ok(None) - //Ok(if let Some(binding) = self.profile.as_ref() + //Ok(if let Some(binding) = self.mode.as_ref() //.map(|c|c.keys.dispatch(input.event())).flatten() //{ //let binding = binding.clone(); diff --git a/crates/app/app_view.rs b/crates/app/app_view.rs index 03dfbacb..5ea8b571 100644 --- a/crates/app/app_view.rs +++ b/crates/app/app_view.rs @@ -16,12 +16,12 @@ pub const VIEW: DslNs<'static, DslCb> = DslNs(&[ (":view/ports/ins", |state|Box::new(Fill::x(Fixed::y(3, Bsp::a(Fill::x(Align::w(" L AUDIO INS")), Bsp::a("MIDI INS", Fill::x(Align::e("AUDIO INS R ")))))))), (":view/profiles", |state: &App|Box::new({ - let views = state.config.views.clone(); + let modes = state.config.modes.clone(); Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, (id, profile)) in views.read().unwrap().iter().enumerate() { + for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() { let bg = if index == 0 { Rgb(64,64,64) } else { Rgb(32,32,32) }; - let name = profile.mode.name.get(0).map(|x|x.as_ref()).unwrap_or(""); - let info = profile.mode.info.get(0).map(|x|x.as_ref()).unwrap_or(""); + 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(""); add(&Fixed::y(3, Tui::bg(bg, Bsp::s( Fill::x(Bsp::a( Fill::x(Align::w(Tui::fg(Rgb(224,192,128), name))), diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index 529eb63a..ec6c35e7 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -52,6 +52,7 @@ pub enum LaunchMode { impl Cli { pub fn run (&self) -> Usually<()> { let name = self.name.as_ref().map_or("tek", |x|x.as_str()); + let config = Config::init()?; let empty = &[] as &[&str]; let mut midi_ins = vec![]; let mut midi_outs = vec![]; @@ -72,11 +73,10 @@ impl Cli { for (index, connect) in midi_tos.iter().enumerate() { midi_outs.push(jack.midi_out(&format!("{index}/M"), &[connect.clone()])?); }; - let configs = Config::init(); let clock = Clock::new(&jack, self.bpm)?; let mut app = App { jack: jack.clone(), - config: Config::init()?, + config, color: ItemTheme::random(), dialog: Dialog::Menu(0), project: Arrangement { diff --git a/deps/tengri b/deps/tengri index e839096c..7fd6c916 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit e839096cf33f72c000d60b73958e1d6a0ec82be5 +Subproject commit 7fd6c91643cbcfece56ebc14500c6a1ab775fc9e diff --git a/tek.edn b/tek.edn new file mode 100644 index 00000000..5cdd6c8d --- /dev/null +++ b/tek.edn @@ -0,0 +1,138 @@ +(keys :help (@f1 dialog :help)) +(keys :back (@escape back)) +(keys :axis/x (@left x/prev) (@right x/next)) +(keys :axis/x2 (@shift/left x2/prev) (@shift/right x2/next)) +(keys :axis/y (@up y/prev) (@down y/next)) +(keys :axis/y2 (@shift/up y2/prev) (@shift/down y2/next)) +(keys :axis/z (@comma z/prev) (@period z/next)) +(keys :axis/z2 (@lt z2/prev) (@gt z2/next)) +(keys :axis/w (@openbracket w/prev) (@closebracket w/next)) +(keys :axis/w2 (@openbrace w2/prev) (@closebrace w2/next)) +(keys :axis/page (@pgup page/up) (@pgdn page/down)) +(keys :confirm (@enter confirm)) +(keys :delete (@delete delete)) +(keys :backspace (@backspace backspace)) +(keys :input (see :delete :axis/x :backspace) (:char input)) +(keys :length (see :confirm :axis/y :axis/x)) +(keys :list (see :confirm :axis/y)) +(keys :browse (see :list :input :focus)) +(keys :focus) +(keys :history (@u undo 1) (@r redo 1)) +(keys :clock (@space clock/toggle 0) (@shift/space clock/toggle 0)) +(keys :color (@c color)) +(keys :launch (@q launch)) +(keys :saveload (@f6 dialog :save) (@f9 dialog :load)) +(keys :global (see :history :saveload) (@f8 dialog :options) (@f10 dialog :quit)) + +(mode :transport (name Transport) (info A JACK transport controller.) + (keys :clock :global) :view/transport) + +(mode :arranger (name Arranger) (info A grid of launchable clips arranged by track and scene.) + (when :mode/editor (keys :editor)) + (when :mode/dialog (keys :dialog)) + (when :mode/message (keys :message)) + (when :mode/add-device (keys :add-device)) + (when :mode/browse (keys :browse)) + (when :mode/rename (keys :pool/rename)) + (when :mode/length (keys :pool/length)) + (when :mode/clip (keys :clip)) + (when :mode/track (keys :track)) + (when :mode/scene (keys :scene)) + (when :mode/mix (keys :mix)) + (keys :clock :arranger :global) + (bsp/w :view/meters/output + (bsp/e :view/meters/input + (stack/n + (fixed/y 2 :view/status/h2) + :view/tracks/inputs + (stack/s + :view/tracks/devices + :view/tracks/outputs + :view/tracks/names + (fill/xy (either :mode/editor + (bsp/e :view/scenes/names :view/editor) + :view/scenes))))))) +(keys :arranger (see :color :launch :scenes :tracks) + (@tab project/edit) (@enter project/edit) + (@shift/I project/input/add) (@shift/O project/output/add) + (@shift/S project/scene/add) (@shift/T project/track/add) + (@shift/D dialog/show :dialog/device)) +(keys :tracks + (@t select :select/track) (@left select :select/track/prev) (@right select :select/track/next)) +(keys :track (see :color :launch :axis/z :axis/z2 :delete) + (@r toggle :rec) (@m toggle :mon) (@p toggle :play) (@P toggle :solo)) +(keys :scenes + (@s select :select/scene) (@up select :select/scene/prev) (@down select :select/scene/next)) +(keys :scene + (see :color :launch :axis/z :axis/z2 :delete)) +(keys :clip (see :color :launch :axis/z :axis/z2 :delete) + (@l toggle :loop)) + +(mode :groovebox (name Groovebox) (info A sequencer with built-in sampler.) + (when :mode/browse (keys :browse)) + (when :mode/rename (keys :pool-rename)) + (when :mode/length (keys :pool-length)) + (keys :clock :editor :sampler :global) + (bsp/w :view/meters/output + (bsp/e :view/meters/input + (bsp/w + (fill/y (align/n (stack/s :view/midi-ins/status + :view/midi-outs/status + :view/audio-ins/status + :view/audio-outs/status + :view/pool))) + (bsp/n (fixed/y :h-sample-detail (bsp/e (fill/y (fixed/x 20 (align/nw :view/sample-status))) + :view/sample-viewer)) + (bsp/e (fill/y (align/n (bsp/s :view/status/v :view/editor-status))) + (bsp/e :view/samples/keys + :view/editor))))))) + +(mode :sampler (name Sampler) (info A sampling soundboard.) + (keys :sampler :global) + (bsp/s (fixed/y 1 :view/transport) + (bsp/n (fixed/y 1 :view/status) + (fill/xy :view/samples/grid)))) +(keys :sampler (see :sampler/directions :sampler/record :sampler/play)) +(keys :sampler/play (@p sampler/play/sample :sample/selected) (@P sampler/stop/sample :sample/selected)) +(keys :sampler/record (@r sampler/record/toggle :sample/selected) (@shift/R sampler/record/back)) +(keys :sampler/import-export (@shift/f6 dialog :dialog/export/sample) (@shift/f9 dialog :dialog/import/sample)) +(keys :sampler/directions (@up sampler/select :sample/above) (@down sampler/select :sample/below) (@left sampler/select :sample/to/left) (@right sampler/select :sample/to/right)) + +(mode :sequencer (name Sequencer) (info A MIDI sequencer.) + (when :mode/browse (keys :browse)) + (when :mode/rename (keys :pool-rename)) + (when :mode/length (keys :pool-length)) + (keys :editor :clock :global) + (bsp/s (fixed/y 1 :view/transport) + (bsp/n (fixed/y 1 :view/status) + (fill/xy (bsp/a (fill/xy (align/e :view/pool)) + :view/editor))))) +(keys :sequencer (see :color :launch) (@shift/I input/add) (@shift/O output/add)) +(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete) + (@n rename/begin) (@t length/begin) (@m import/begin) (@x export/begin) + (@shift/A clip/add :after :new/clip) + (@shift/D clip/add :after :cloned/clip)) +(keys :editor (see :editor/view :editor/note)) +(keys :editor/view + (@left editor/time/set :time/pos/prev) + (@shift/left editor/time/set :time/pos/prev/fine) + (@right editor/time/set :time/pos/next) + (@shift/right editor/time/set :time/pos/next/fine) + (@equal editor/zoom/set :time/zoom/prev) + (@minus editor/zoom/set :time/zoom/next) + (@plus editor/zoom/set :time/zoom/next/fine) + (@underscore editor/zoom/set :time/zoom/prev/fine) + (@z editor/lock/set)) +(keys :editor/note + (@comma editor/length/set :note/len/prev) + (@period editor/length/set :note/len/next) + (@lt editor/length/set :note/len/prev) + (@gt editor/length/set :note/len/next) + (@up editor/pitch/set :note/pos/next) + (@down editor/pitch/set :note/pos/prev) + (@pgup editor/pitch/set :note/pos/next/octave) + (@pgdn editor/pitch/set :note/pos/prev/octave) + (@a editor/append :true) + (@enter editor/append :false) + (@del editor/delete/note) + (@shift/del editor/delete/note))