diff --git a/config/binds.edn b/config/binds.edn new file mode 100644 index 00000000..28db6897 --- /dev/null +++ b/config/binds.edn @@ -0,0 +1,167 @@ +(module :global + (@esc dialog/hide) + (@f1 dialog/show :dialog/help) + (@f6 dialog/show :dialog/save) + (@f8 dialog/show :dialog/options) + (@f9 dialog/show :dialog/load) + (@f10 dialog/show :dialog/quit) + (@u history/undo 1) + (@r history/redo 1)) + +(module :clock + (@space clock/toggle 0) + (@shift/space clock/toggle 0)) + +(module :arranger + (@c color) + (@q launch) + (@tab project/edit) + (@enter project/edit) + (@escape project/home) + (@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) + (@up select :select/scene/prev) + (@down select :select/scene/next) + (@left select :select/track/prev) + (@right select :select/track/next) + (@t select :select/track) + (@s select :select/scene)) + +(module :track + (@delete track/delete :track) + (@q track/launch :track) + (@c track/color :track) + (@comma track/prev) + (@period track/next) + (@lt track/swap/prev) + (@gt track/swap/next) + (@r track/rec) + (@m track/mon) + (@p track/play) + (@P track/solo)) + +(module :scene + (@delete scene/delete :scene) + (@q scene/launch :scene) + (@c scene/color :scene) + (@comma scene/prev) + (@period scene/next) + (@lt scene/swap/prev) + (@gt scene/swap/next)) + +(module :clip + (@g clip/get) + (@p clip/put) + (@delete clip/del) + (@comma clip/prev) + (@period clip/next) + (@lt clip/swap/prev) + (@gt clip/swap/next) + (@l clip/loop/toggle)) + +(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 :message + (@esc message/dismiss) + (@enter message/dismiss)) + +(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)) + +(module :sampler + (@up sampler/select :sample/above) + (@down sampler/select :sample/below) + (@left sampler/select :sample/to/left) + (@right sampler/select :sample/to/right) + + (@r sampler/record/toggle :sample/selected) + (@shift/R sampler/record/cancel) + (@p sampler/play/sample :sample/selected) + (@P sampler/stop/sample :sample/selected) + + (@shift/f6 dialog :dialog/export/sample) + (@shift/f9 dialog :dialog/import/sample)) + +(module :sequencer + (@c color) + (@q launch) + (@shift/I input/add) + (@shift/O output/add)) diff --git a/config/views.edn b/config/views.edn new file mode 100644 index 00000000..435a11e9 --- /dev/null +++ b/config/views.edn @@ -0,0 +1,78 @@ +(module :transport + (name "Transport") + (info "A JACK transport controller.") + (keys :clock) + (keys :global) + :view/transport) + +(module :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) + (keys :arranger) + (keys :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))))))) + +(module :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) + (keys :editor) + (keys :sampler) + (keys :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))))))) + +(module :sampler + (name "Sampler") + (info "A sampling soundboard.") + (keys :sampler) + (keys :global) + (bsp/s (fixed/y 1 :view/transport) + (bsp/n (fixed/y 1 :view/status) + (fill/xy :view/samples/grid)))) + +(module :sequencer + (name "Sequencer") + (info "A MIDI sequencer.") + (mode :browser (keys :browser)) + (mode :rename (keys :pool-rename)) + (mode :length (keys :pool-length)) + (keys :editor) + (keys :clock) + (keys :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 ec916c15..9731d67e 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 modes and input bindings + /// Available view profiles and input bindings pub config: Config, - /// Currently selected mode - pub mode: Mode>, + /// Currently selected profile + pub profile: Profile, /// Contains the currently edited musical arrangement pub project: Arrangement, /// Contains all recently created clips. @@ -69,127 +69,66 @@ pub struct App { /// Base color. pub color: ItemTheme, } +type ModuleMap = Arc, T>>>; /// Configuration #[derive(Default, Debug)] pub struct Config { - pub dirs: BaseDirectories, - pub modes: Arc, Arc>>>>>, - pub binds: Arc, EventMap, Arc>>>>, -} -#[derive(Default, Debug)] -pub struct Mode { - pub path: PathBuf, - pub name: Vec, - pub info: Vec, - pub view: Vec, - pub keys: Vec, - pub modes: BTreeMap>, + /// XDG basedirs + pub dirs: BaseDirectories, + /// Available view profiles + pub views: ModuleMap, + /// Available input bindings + pub binds: ModuleMap, Arc>>, } impl Config { - const CONFIG: &'static str = "tek.edn"; - const DEFAULTS: &'static str = include_str!("../../tek.edn"); + 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"); pub fn init () -> Usually { let mut cfgs: Self = Default::default(); cfgs.dirs = BaseDirectories::with_profile("tek", "v0"); - cfgs.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load_defs(dsl))?; + cfgs.load(Self::VIEWS, Self::DEFAULT_VIEWS, |cfgs, dsl|cfgs.load_view(dsl))?; + cfgs.load(Self::BINDS, Self::DEFAULT_BINDS, |cfgs, dsl|cfgs.load_bind(dsl))?; + println!("{cfgs:#?}"); Ok(cfgs) } - pub fn init_file ( + pub fn load ( &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:?}"); + println!("init: {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_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()) - }, - 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() { - mode.view.push(sym.into()); - } else { - return Err(format!("load_view: unexpected: {dsl:?}").into()) - }))?; - mode.into() - }); - }, - _ => return Err(format!("load_defs: unexpected: {item:?}").into()) - }) - }) + pub fn load_view (&mut self, dsl: D) -> Usually<()> { + load_mod(dsl, |id, tail|Ok(self.views.write().unwrap().insert(id.into(), + Profile::from_dsl(tail)?))) + } + pub fn load_bind (&mut self, dsl: D) -> Usually<()> { + load_mod(dsl, |id, tail|Ok(self.binds.write().unwrap().insert(id.into(), { + let mut map = EventMap::new(); + tail.each(|item|{ + println!("{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 + }))) } } -fn load_modules (dsl: D, cb: impl Fn(&str, &str)->Usually) -> Usually<()> { +fn load_mod (dsl: D, cb: impl Fn(&str, &str)->Usually) -> Usually<()> { //dsl!(dsl|module :id ..body|dsl!(body|@bind #info? ..commands|) if let Some(exp) = dsl.exp()? && Some("module") == exp.head().key()? @@ -203,6 +142,66 @@ fn load_modules (dsl: D, cb: impl Fn(&str, &str)->Usually) -> Usu return Err("unexpected: {exp:?}".into()); } } +/// Profile +#[derive(Default, Debug)] +pub struct Profile { + /// Path of configuration entrypoint + pub path: PathBuf, + /// Name of configuration + pub name: Option>, + /// Description of configuration + pub info: Option>, + /// View definition + pub view: Arc, + // Input keymap + pub keys: EventMap, +} +impl Profile { + fn from_dsl (dsl: impl Dsl) -> Usually { + let mut profile = Self { ..Default::default() }; + dsl.each(|dsl|{ + let head = dsl.head(); + let exp = dsl.exp(); + Ok(if exp.head().key() == Ok(Some("name")) { + profile.name = Some(exp.tail()?.unwrap_or_default().into()); + } else if exp.head().key() == Ok(Some("info")) { + profile.info = Some(exp.tail()?.unwrap_or_default().into()); + }) + })?; + Ok(profile) + } +} +impl Profile { + fn load_template (&mut self, dsl: impl Dsl) -> Usually<&mut Self> { + //dsl.src()?.unwrap_or_default().each(|item|Ok(match () { + //_ if let Some(exp) = dsl.exp()? => match exp.head()?.key()? { + //Some("name") => match exp.tail()?.text()? { + //Some(name) => self.name = Some(name.into()), + //_ => return Err(format!("missing name definition").into()) + //}, + //Some("info") => match exp.tail()?.text()? { + //Some(info) => self.info = Some(info.into()), + //_ => return Err(format!("missing info definition").into()) + //}, + //Some("bind") => match exp.tail()? { + //Some(keys) => self.keys = EventMap::from_dsl(&mut &keys)?, + //_ => return Err(format!("missing keys definition").into()) + //}, + //Some("view") => match exp.tail()? { + //Some(tail) => self.view = tail.src()?.unwrap_or_default().into(), + //_ => return Err(format!("missing view definition").into()) + //}, + //dsl => return Err(format!("unexpected: {dsl:?}").into()) + //}, + //_ => return Err(format!("unexpected: {dsl:?}").into()) + //})); + Ok(self) + } + fn load_binding (&mut self, dsl: impl Dsl) -> Usually<&mut Self> { + todo!(); + Ok(self) + } +} /// Various possible dialog modes. impl App { pub fn update_clock (&self) { @@ -231,6 +230,12 @@ maybe_has!(Scene: |self: App| { MaybeHas::::get(&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() } } +fn unquote (x: &str) -> &str { + let mut chars = x.chars(); + chars.next(); + //chars.next_back(); + chars.as_str() +} #[derive(Debug, Clone, Default)] pub enum Dialog { diff --git a/crates/app/app_ctrl.rs b/crates/app/app_ctrl.rs index 3bcabf55..a1240359 100644 --- a/crates/app/app_ctrl.rs +++ b/crates/app/app_ctrl.rs @@ -2,15 +2,8 @@ use crate::*; #[derive(Debug)] pub enum AppCommand {} handle!(TuiIn:|self: App, input|{ - 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.mode.as_ref() + panic!("{input:?}"); + //Ok(if let Some(binding) = self.profile.as_ref() //.map(|c|c.keys.dispatch(input.event())).flatten() //{ //let binding = binding.clone(); @@ -102,14 +95,12 @@ dsl_exp!(|app: App| -> AppCommand { ["clock" / command: ClockCommand] => todo!(), ["sampler" / command: SamplerCommand] => todo!(), ["pool" / command: PoolCommand] => todo!(), - ["pool" / editor: MidiEditCommand] => todo!(), }); dsl_exp!(|app: App| -> DialogCommand {}); dsl_exp!(|app: App| -> ArrangementCommand {}); dsl_exp!(|app: App| -> ClockCommand {}); dsl_exp!(|app: App| -> SamplerCommand {}); dsl_exp!(|app: App| -> PoolCommand {}); -dsl_exp!(|app: App| -> MidiEditCommand {}); //dsl_bind!(AppCommand: App { //enqueue = |app, clip: Option>>| { todo!() }; //history = |app, delta: isize| { todo!() }; diff --git a/crates/app/app_dsl.rs b/crates/app/app_dsl.rs index fe15da92..745da9a6 100644 --- a/crates/app/app_dsl.rs +++ b/crates/app/app_dsl.rs @@ -1,23 +1,8 @@ use crate::*; -/// Namespace. Currently linearly searched. pub struct DslNs<'t, T: 't>(pub &'t [(&'t str, T)]); -/// Namespace where keys are symbols. -pub trait DslSymNs<'t, T: 't>: 't { - const NS: DslNs<'t, fn (&'t Self)->T>; - fn from_sym (&'t self, dsl: D) -> Usually { - if let Some(dsl) = dsl.sym()? { - for (sym, get) in Self::NS.0 { - if dsl == *sym { - return Ok(get(self)) - } - } - } - return Err(format!("not found: sym: {dsl:?}").into()) - } -} - +pub trait DslSymNs<'t, T: 't>: 't { const NS: DslNs<'t, fn (&'t Self)->T>; } #[macro_export] macro_rules! dsl_sym ( (|$state:ident:$State:ty| -> $type:ty {$($lit:literal => $exp:expr),* $(,)?})=>{ impl<'t> DslSymNs<'t, $type> for $State { diff --git a/crates/app/app_view.rs b/crates/app/app_view.rs index 5ea8b571..3e9d15c8 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 modes = state.config.modes.clone(); + let views = state.config.views.clone(); Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() { + for (index, (id, profile)) in views.read().unwrap().iter().enumerate() { let bg = if index == 0 { Rgb(64,64,64) } else { Rgb(32,32,32) }; - 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 name = profile.name.as_ref().map(|x|unquote(unquote(x.as_ref()))); + let info = profile.info.as_ref().map(|x|unquote(unquote(x.as_ref()))); 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 ec6c35e7..529eb63a 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -52,7 +52,6 @@ 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![]; @@ -73,10 +72,11 @@ 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: Config::init()?, color: ItemTheme::random(), dialog: Dialog::Menu(0), project: Arrangement { diff --git a/deps/tengri b/deps/tengri index 7fd6c916..24ac52d8 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 7fd6c91643cbcfece56ebc14500c6a1ab775fc9e +Subproject commit 24ac52d8079140b2b9f363d1568597bb5600a166 diff --git a/tek.edn b/tek.edn deleted file mode 100644 index 5cdd6c8d..00000000 --- a/tek.edn +++ /dev/null @@ -1,138 +0,0 @@ -(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))