diff --git a/crates/forth/src/ops.rs b/crates/forth/src/ops.rs index 2be7eb8..0c5401b 100644 --- a/crates/forth/src/ops.rs +++ b/crates/forth/src/ops.rs @@ -88,4 +88,5 @@ pub enum Op { DivEnd, StackStart, EmitN, + ClearCmd, } diff --git a/crates/forth/src/types.rs b/crates/forth/src/types.rs index 58d0c1a..8830bbb 100644 --- a/crates/forth/src/types.rs +++ b/crates/forth/src/types.rs @@ -135,7 +135,14 @@ impl CmdRegister { } pub(super) fn snapshot(&self) -> Option<(Value, Vec<(String, Value)>)> { - self.sound.as_ref().map(|s| (s.clone(), self.params.clone())) + self.sound + .as_ref() + .map(|s| (s.clone(), self.params.clone())) + } + + pub(super) fn clear(&mut self) { + self.sound = None; + self.params.clear(); } } diff --git a/crates/forth/src/vm.rs b/crates/forth/src/vm.rs index 63834ed..6b36bee 100644 --- a/crates/forth/src/vm.rs +++ b/crates/forth/src/vm.rs @@ -742,6 +742,10 @@ impl Forth { scope_stack.push(new_scope); } + Op::ClearCmd => { + cmd.clear(); + } + Op::EmitN => { let n = stack.pop().ok_or("stack underflow")?.as_int()?; if n < 0 { diff --git a/crates/forth/src/words.rs b/crates/forth/src/words.rs index 3fb77e2..ec85f29 100644 --- a/crates/forth/src/words.rs +++ b/crates/forth/src/words.rs @@ -1534,6 +1534,54 @@ pub const WORDS: &[Word] = &[ example: "0.02 chorusdelay", compile: Param, }, + Word { + name: "eqlo", + category: "EQ", + stack: "(f --)", + desc: "Set low shelf gain (dB)", + example: "3 eqlo", + compile: Param, + }, + Word { + name: "eqmid", + category: "EQ", + stack: "(f --)", + desc: "Set mid peak gain (dB)", + example: "-2 eqmid", + compile: Param, + }, + Word { + name: "eqhi", + category: "EQ", + stack: "(f --)", + desc: "Set high shelf gain (dB)", + example: "1 eqhi", + compile: Param, + }, + Word { + name: "tilt", + category: "EQ", + stack: "(f --)", + desc: "Set tilt EQ (-1 dark, 1 bright)", + example: "-0.5 tilt", + compile: Param, + }, + Word { + name: "width", + category: "Stereo", + stack: "(f --)", + desc: "Set stereo width (0 mono, 1 normal, 2 wide)", + example: "0 width", + compile: Param, + }, + Word { + name: "haas", + category: "Stereo", + stack: "(f --)", + desc: "Set Haas delay in ms (spatial placement)", + example: "8 haas", + compile: Param, + }, Word { name: "comb", category: "Filter", @@ -1766,6 +1814,14 @@ pub const WORDS: &[Word] = &[ example: "1 reset", compile: Param, }, + Word { + name: "clear", + category: "Sound", + stack: "(--)", + desc: "Clear sound register (sound and all params)", + example: "\"kick\" s 0.5 gain . clear \"hat\" s .", + compile: Simple, + }, // Quotation execution Word { name: "apply", @@ -1871,6 +1927,7 @@ pub(super) fn simple_op(name: &str) -> Option { "stack" => Op::StackStart, "~" => Op::DivEnd, ".!" => Op::EmitN, + "clear" => Op::ClearCmd, _ => return None, }) } diff --git a/crates/project/src/file.rs b/crates/project/src/file.rs index 9dec2c7..60ab66c 100644 --- a/crates/project/src/file.rs +++ b/crates/project/src/file.rs @@ -35,11 +35,13 @@ impl From<&Project> for ProjectFile { impl From for Project { fn from(file: ProjectFile) -> Self { - Self { + let mut project = Self { banks: file.banks, sample_paths: file.sample_paths, tempo: file.tempo, - } + }; + project.normalize(); + project } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ae97746..c70d923 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -282,4 +282,14 @@ impl Project { pub fn pattern_at_mut(&mut self, bank: usize, pattern: usize) -> &mut Pattern { &mut self.banks[bank].patterns[pattern] } + + pub fn normalize(&mut self) { + self.banks.resize_with(MAX_BANKS, Bank::default); + for bank in &mut self.banks { + bank.patterns.resize_with(MAX_PATTERNS, Pattern::default); + for pattern in &mut bank.patterns { + pattern.steps.resize_with(MAX_STEPS, Step::default); + } + } + } } diff --git a/src/app.rs b/src/app.rs index fe36ad7..b08063f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -53,7 +53,8 @@ impl App { let variables = Arc::new(Mutex::new(HashMap::new())); let dict = Arc::new(Mutex::new(HashMap::new())); let rng = Arc::new(Mutex::new(StdRng::seed_from_u64(0))); - let script_engine = ScriptEngine::new(Arc::clone(&variables), Arc::clone(&dict), Arc::clone(&rng)); + let script_engine = + ScriptEngine::new(Arc::clone(&variables), Arc::clone(&dict), Arc::clone(&rng)); let live_keys = Arc::new(LiveKeyState::new()); Self { @@ -251,7 +252,9 @@ impl App { }) .collect(); self.editor_ctx.editor.set_candidates(candidates); - self.editor_ctx.editor.set_completion_enabled(self.ui.show_completion); + self.editor_ctx + .editor + .set_completion_enabled(self.ui.show_completion); } } @@ -333,7 +336,8 @@ impl App { { step.command = None; } - self.ui.flash(&format!("Script error: {e}"), 300, FlashKind::Error); + self.ui + .flash(&format!("Script error: {e}"), 300, FlashKind::Error); } } } @@ -409,21 +413,14 @@ impl App { let is_playing = snapshot.is_playing(bank, pattern); let pattern_data = self.project_state.project.pattern_at(bank, pattern); - let existing = self - .playback - .staged_changes - .iter() - .position(|c| { - c.change.pattern_id().bank == bank && c.change.pattern_id().pattern == pattern - }); + let existing = self.playback.staged_changes.iter().position(|c| { + c.change.pattern_id().bank == bank && c.change.pattern_id().pattern == pattern + }); if let Some(idx) = existing { self.playback.staged_changes.remove(idx); - self.ui.set_status(format!( - "B{:02}:P{:02} unstaged", - bank + 1, - pattern + 1 - )); + self.ui + .set_status(format!("B{:02}:P{:02} unstaged", bank + 1, pattern + 1)); } else if is_playing { self.playback.staged_changes.push(StagedChange { change: PatternChange::Stop { bank, pattern }, @@ -467,7 +464,8 @@ impl App { } let count = self.playback.staged_changes.len(); self.playback.staged_changes.clear(); - self.ui.set_status(format!("Cleared {count} staged changes")); + self.ui + .set_status(format!("Cleared {count} staged changes")); } pub fn select_edit_pattern(&mut self, pattern: usize) { @@ -705,8 +703,11 @@ impl App { } self.project_state.mark_dirty(bank, pattern); self.load_step_to_editor(); - self.ui - .flash(&format!("Linked to step {:02}", copied.step + 1), 150, FlashKind::Success); + self.ui.flash( + &format!("Linked to step {:02}", copied.step + 1), + 150, + FlashKind::Success, + ); } pub fn harden_step(&mut self) { @@ -896,7 +897,8 @@ impl App { // If current step is a shallow copy, navigate to source step let pattern = &self.project_state.project.banks[self.editor_ctx.bank].patterns [self.editor_ctx.pattern]; - if let Some(source) = pattern.steps[self.editor_ctx.step].source { + if let Some(source) = pattern.step(self.editor_ctx.step).and_then(|s| s.source) + { self.editor_ctx.step = source; } self.load_step_to_editor(); diff --git a/src/views/highlight.rs b/src/views/highlight.rs index b376cf0..28f10fc 100644 --- a/src/views/highlight.rs +++ b/src/views/highlight.rs @@ -51,11 +51,57 @@ const OPERATORS: &[&str] = &[ "or", "not", "ceil", "floor", "round", "mtof", "ftom", ]; const KEYWORDS: &[&str] = &[ - "if", "else", "then", "emit", "rand", "rrand", "seed", "cycle", "choose", "chance", "[", "]", - "zoom", "scale!", "stack", "echo", "necho", "for", "div", "each", "at", "pop", "adsr", "ad", - "?", "!?", "<<", ">>", "|", "@", "!", "pcycle", "tempo!", "prob", "sometimes", "often", - "rarely", "almostAlways", "almostNever", "always", "never", "coin", "fill", "iter", "every", - "gt", "lt", ":", ";", "apply", + "if", + "else", + "then", + "emit", + "rand", + "rrand", + "seed", + "cycle", + "choose", + "chance", + "[", + "]", + "zoom", + "scale!", + "stack", + "echo", + "necho", + "for", + "div", + "each", + "at", + "pop", + "adsr", + "ad", + "?", + "!?", + "<<", + ">>", + "|", + "@", + "!", + "pcycle", + "tempo!", + "prob", + "sometimes", + "often", + "rarely", + "almostAlways", + "almostNever", + "always", + "never", + "coin", + "fill", + "iter", + "every", + "gt", + "lt", + ":", + ";", + "apply", + "clear", ]; const SOUND: &[&str] = &["sound", "s"]; const CONTEXT: &[&str] = &[ @@ -168,6 +214,23 @@ const PARAMS: &[&str] = &[ "n", "cut", "reset", + "eqlo", + "eqmid", + "eqhi", + "tilt", + "width", + "haas", + "llpf", + "llpq", + "lhpf", + "lhpq", + "lbpf", + "lbpq", + "sub", + "suboct", + "subwave", + "bank", + "loop", ]; const INTERVALS: &[&str] = &[ "P1", "unison", "m2", "M2", "m3", "M3", "P4", "aug4", "dim5", "tritone", "P5", "m6", "M6", @@ -176,34 +239,27 @@ const INTERVALS: &[&str] = &[ ]; const NOTES: &[&str] = &[ - "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", - "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", - "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", - "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", - "g0", "g1", "g2", "g3", "g4", "g5", "g6", "g7", "g8", "g9", - "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", - "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", - "cs0", "cs1", "cs2", "cs3", "cs4", "cs5", "cs6", "cs7", "cs8", "cs9", - "ds0", "ds1", "ds2", "ds3", "ds4", "ds5", "ds6", "ds7", "ds8", "ds9", - "es0", "es1", "es2", "es3", "es4", "es5", "es6", "es7", "es8", "es9", - "fs0", "fs1", "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", "fs8", "fs9", - "gs0", "gs1", "gs2", "gs3", "gs4", "gs5", "gs6", "gs7", "gs8", "gs9", - "as0", "as1", "as2", "as3", "as4", "as5", "as6", "as7", "as8", "as9", - "bs0", "bs1", "bs2", "bs3", "bs4", "bs5", "bs6", "bs7", "bs8", "bs9", - "cb0", "cb1", "cb2", "cb3", "cb4", "cb5", "cb6", "cb7", "cb8", "cb9", - "db0", "db1", "db2", "db3", "db4", "db5", "db6", "db7", "db8", "db9", - "eb0", "eb1", "eb2", "eb3", "eb4", "eb5", "eb6", "eb7", "eb8", "eb9", - "fb0", "fb1", "fb2", "fb3", "fb4", "fb5", "fb6", "fb7", "fb8", "fb9", - "gb0", "gb1", "gb2", "gb3", "gb4", "gb5", "gb6", "gb7", "gb8", "gb9", - "ab0", "ab1", "ab2", "ab3", "ab4", "ab5", "ab6", "ab7", "ab8", "ab9", - "bb0", "bb1", "bb2", "bb3", "bb4", "bb5", "bb6", "bb7", "bb8", "bb9", - "c#0", "c#1", "c#2", "c#3", "c#4", "c#5", "c#6", "c#7", "c#8", "c#9", - "d#0", "d#1", "d#2", "d#3", "d#4", "d#5", "d#6", "d#7", "d#8", "d#9", - "e#0", "e#1", "e#2", "e#3", "e#4", "e#5", "e#6", "e#7", "e#8", "e#9", - "f#0", "f#1", "f#2", "f#3", "f#4", "f#5", "f#6", "f#7", "f#8", "f#9", - "g#0", "g#1", "g#2", "g#3", "g#4", "g#5", "g#6", "g#7", "g#8", "g#9", - "a#0", "a#1", "a#2", "a#3", "a#4", "a#5", "a#6", "a#7", "a#8", "a#9", - "b#0", "b#1", "b#2", "b#3", "b#4", "b#5", "b#6", "b#7", "b#8", "b#9", + "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "d0", "d1", "d2", "d3", "d4", "d5", + "d6", "d7", "d8", "d9", "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "f0", "f1", + "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "g0", "g1", "g2", "g3", "g4", "g5", "g6", "g7", + "g8", "g9", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "b0", "b1", "b2", "b3", + "b4", "b5", "b6", "b7", "b8", "b9", "cs0", "cs1", "cs2", "cs3", "cs4", "cs5", "cs6", "cs7", + "cs8", "cs9", "ds0", "ds1", "ds2", "ds3", "ds4", "ds5", "ds6", "ds7", "ds8", "ds9", "es0", + "es1", "es2", "es3", "es4", "es5", "es6", "es7", "es8", "es9", "fs0", "fs1", "fs2", "fs3", + "fs4", "fs5", "fs6", "fs7", "fs8", "fs9", "gs0", "gs1", "gs2", "gs3", "gs4", "gs5", "gs6", + "gs7", "gs8", "gs9", "as0", "as1", "as2", "as3", "as4", "as5", "as6", "as7", "as8", "as9", + "bs0", "bs1", "bs2", "bs3", "bs4", "bs5", "bs6", "bs7", "bs8", "bs9", "cb0", "cb1", "cb2", + "cb3", "cb4", "cb5", "cb6", "cb7", "cb8", "cb9", "db0", "db1", "db2", "db3", "db4", "db5", + "db6", "db7", "db8", "db9", "eb0", "eb1", "eb2", "eb3", "eb4", "eb5", "eb6", "eb7", "eb8", + "eb9", "fb0", "fb1", "fb2", "fb3", "fb4", "fb5", "fb6", "fb7", "fb8", "fb9", "gb0", "gb1", + "gb2", "gb3", "gb4", "gb5", "gb6", "gb7", "gb8", "gb9", "ab0", "ab1", "ab2", "ab3", "ab4", + "ab5", "ab6", "ab7", "ab8", "ab9", "bb0", "bb1", "bb2", "bb3", "bb4", "bb5", "bb6", "bb7", + "bb8", "bb9", "c#0", "c#1", "c#2", "c#3", "c#4", "c#5", "c#6", "c#7", "c#8", "c#9", "d#0", + "d#1", "d#2", "d#3", "d#4", "d#5", "d#6", "d#7", "d#8", "d#9", "e#0", "e#1", "e#2", "e#3", + "e#4", "e#5", "e#6", "e#7", "e#8", "e#9", "f#0", "f#1", "f#2", "f#3", "f#4", "f#5", "f#6", + "f#7", "f#8", "f#9", "g#0", "g#1", "g#2", "g#3", "g#4", "g#5", "g#6", "g#7", "g#8", "g#9", + "a#0", "a#1", "a#2", "a#3", "a#4", "a#5", "a#6", "a#7", "a#8", "a#9", "b#0", "b#1", "b#2", + "b#3", "b#4", "b#5", "b#6", "b#7", "b#8", "b#9", ]; pub fn tokenize_line(line: &str) -> Vec {