diff --git a/.cargo/config.toml b/.cargo/config.toml index e0222a5..a7e674b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,9 @@ # Uncomment to use local doux for development paths = ["/Users/bubo/doux"] +[env] +MACOSX_DEPLOYMENT_TARGET = "12.0" + [alias] xtask = "run --package xtask --release --" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 96ca212..16b7527 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ on: env: CARGO_TERM_COLOR: always + MACOSX_DEPLOYMENT_TARGET: "12.0" concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/Cargo.toml b/Cargo.toml index 35ca3d0..7da55ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ doux = { git = "https://github.com/sova-org/doux", features = ["native", "soundf rusty_link = "0.4" ratatui = "0.30" crossterm = "0.29" -cpal = { version = "0.17", features = ["jack"], optional = true } +cpal = { version = "0.17", optional = true } clap = { version = "4", features = ["derive"], optional = true } rand = "0.8" serde = { version = "1", features = ["derive"] } @@ -83,6 +83,9 @@ rustc-hash = { version = "2", optional = true } image = { version = "0.25", default-features = false, features = ["png"], optional = true } +[target.'cfg(target_os = "linux")'.dependencies] +cpal = { version = "0.17", optional = true, features = ["jack"] } + [target.'cfg(windows)'.build-dependencies] winres = "0.1" @@ -109,3 +112,4 @@ icon = ["assets/Cagire.icns", "assets/Cagire.ico", "assets/Cagire.png"] copyright = "Copyright (c) 2025 Raphaël Forment" category = "Music" short_description = "Forth-based music sequencer" +minimum_system_version = "12.0" diff --git a/crates/forth/src/compiler.rs b/crates/forth/src/compiler.rs index cac8b45..2a12303 100644 --- a/crates/forth/src/compiler.rs +++ b/crates/forth/src/compiler.rs @@ -31,7 +31,7 @@ fn tokenize(input: &str) -> Vec { continue; } - if c == '(' || c == ')' { + if c == '{' || c == '}' { chars.next(); continue; } @@ -133,7 +133,7 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result, String> { Token::Str(s, span) => ops.push(Op::PushStr(Arc::from(s.as_str()), Some(*span))), Token::Word(w, span) => { let word = w.as_str(); - if word == "{" { + if word == "(" { let (quote_ops, consumed, end_span) = compile_quotation(&tokens[i + 1..], dict)?; i += consumed; @@ -142,8 +142,8 @@ fn compile(tokens: &[Token], dict: &Dictionary) -> Result, String> { end: end_span.end, }; ops.push(Op::Quotation(Arc::from(quote_ops), Some(body_span))); - } else if word == "}" { - return Err("unexpected }".into()); + } else if word == ")" { + return Err("unexpected )".into()); } else if word == "[" { let (bracket_ops, consumed, end_span) = compile_bracket(&tokens[i + 1..], dict)?; @@ -203,8 +203,8 @@ fn compile_quotation( for (i, tok) in tokens.iter().enumerate() { if let Token::Word(w, _) = tok { match w.as_str() { - "{" => depth += 1, - "}" => { + "(" => depth += 1, + ")" => { depth -= 1; if depth == 0 { end_idx = Some(i); @@ -216,7 +216,7 @@ fn compile_quotation( } } - let end_idx = end_idx.ok_or("missing }")?; + let end_idx = end_idx.ok_or("missing )")?; let end_span = match &tokens[end_idx] { Token::Word(_, span) => *span, _ => unreachable!(), diff --git a/crates/forth/src/words/core.rs b/crates/forth/src/words/core.rs index 6c8cf9a..452230c 100644 --- a/crates/forth/src/words/core.rs +++ b/crates/forth/src/words/core.rs @@ -502,7 +502,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Logic", stack: "(true-quot false-quot bool --)", desc: "Execute true-quot if true, else false-quot", - example: "{ 1 } { 2 } coin ifelse", + example: "( 1 ) ( 2 ) coin ifelse", compile: Simple, varargs: false, }, @@ -512,7 +512,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Logic", stack: "(..quots n --)", desc: "Execute nth quotation (0-indexed)", - example: "{ 1 } { 2 } { 3 } 2 select => 3", + example: "( 1 ) ( 2 ) ( 3 ) 2 select => 3", compile: Simple, varargs: true, }, @@ -522,7 +522,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Logic", stack: "(quot bool --)", desc: "Execute quotation if true", - example: "{ 2 distort } 0.5 chance ?", + example: "( 2 distort ) 0.5 chance ?", compile: Simple, varargs: false, }, @@ -532,7 +532,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Logic", stack: "(quot bool --)", desc: "Execute quotation if false", - example: "{ 1 distort } 0.5 chance !?", + example: "( 1 distort ) 0.5 chance !?", compile: Simple, varargs: false, }, @@ -542,7 +542,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Logic", stack: "(quot --)", desc: "Execute quotation unconditionally", - example: "{ 2 * } apply", + example: "( 2 * ) apply", compile: Simple, varargs: false, }, @@ -553,7 +553,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Control", stack: "(n quot --)", desc: "Execute quotation n times, @i holds current index", - example: "4 { @i . } times => 0 1 2 3", + example: "4 ( @i . ) times => 0 1 2 3", compile: Simple, varargs: false, }, diff --git a/crates/forth/src/words/sequencing.rs b/crates/forth/src/words/sequencing.rs index c1dda6c..ed6003c 100644 --- a/crates/forth/src/words/sequencing.rs +++ b/crates/forth/src/words/sequencing.rs @@ -60,7 +60,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Probability", stack: "(quot prob --)", desc: "Execute quotation with probability (0.0-1.0)", - example: "{ 2 distort } 0.75 chance", + example: "( 2 distort ) 0.75 chance", compile: Simple, varargs: false, }, @@ -70,7 +70,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Probability", stack: "(quot pct --)", desc: "Execute quotation with probability (0-100)", - example: "{ 2 distort } 75 prob", + example: "( 2 distort ) 75 prob", compile: Simple, varargs: false, }, @@ -150,7 +150,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Probability", stack: "(quot --)", desc: "Always execute quotation", - example: "{ 2 distort } always", + example: "( 2 distort ) always", compile: Probability(1.0), varargs: false, }, @@ -160,7 +160,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Probability", stack: "(quot --)", desc: "Never execute quotation", - example: "{ 2 distort } never", + example: "( 2 distort ) never", compile: Probability(0.0), varargs: false, }, @@ -170,7 +170,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Probability", stack: "(quot --)", desc: "Execute quotation 75% of the time", - example: "{ 2 distort } often", + example: "( 2 distort ) often", compile: Probability(0.75), varargs: false, }, @@ -180,7 +180,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Probability", stack: "(quot --)", desc: "Execute quotation 50% of the time", - example: "{ 2 distort } sometimes", + example: "( 2 distort ) sometimes", compile: Probability(0.5), varargs: false, }, @@ -190,7 +190,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Probability", stack: "(quot --)", desc: "Execute quotation 25% of the time", - example: "{ 2 distort } rarely", + example: "( 2 distort ) rarely", compile: Probability(0.25), varargs: false, }, @@ -200,7 +200,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Probability", stack: "(quot --)", desc: "Execute quotation 10% of the time", - example: "{ 2 distort } almostNever", + example: "( 2 distort ) almostNever", compile: Probability(0.1), varargs: false, }, @@ -210,7 +210,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Probability", stack: "(quot --)", desc: "Execute quotation 90% of the time", - example: "{ 2 distort } almostAlways", + example: "( 2 distort ) almostAlways", compile: Probability(0.9), varargs: false, }, @@ -221,7 +221,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Time", stack: "(quot n --)", desc: "Execute quotation every nth iteration", - example: "{ 2 distort } 4 every", + example: "( 2 distort ) 4 every", compile: Simple, varargs: false, }, @@ -231,7 +231,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Time", stack: "(quot n --)", desc: "Execute quotation on all iterations except every nth", - example: "{ 2 distort } 4 except", + example: "( 2 distort ) 4 except", compile: Simple, varargs: false, }, @@ -241,7 +241,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Time", stack: "(quot n offset --)", desc: "Execute quotation every nth iteration with phase offset", - example: "{ snare } 4 2 every+ => fires at iter 2, 6, 10...", + example: "( snare ) 4 2 every+ => fires at iter 2, 6, 10...", compile: Simple, varargs: false, }, @@ -251,7 +251,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Time", stack: "(quot n offset --)", desc: "Skip quotation every nth iteration with phase offset", - example: "{ snare } 4 2 except+ => skips at iter 2, 6, 10...", + example: "( snare ) 4 2 except+ => skips at iter 2, 6, 10...", compile: Simple, varargs: false, }, @@ -261,7 +261,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Time", stack: "(quot k n --)", desc: "Execute quotation using Euclidean distribution over step runs", - example: "{ 2 distort } 3 8 bjork", + example: "( 2 distort ) 3 8 bjork", compile: Simple, varargs: false, }, @@ -271,7 +271,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Time", stack: "(quot k n --)", desc: "Execute quotation using Euclidean distribution over pattern iterations", - example: "{ 2 distort } 3 8 pbjork", + example: "( 2 distort ) 3 8 pbjork", compile: Simple, varargs: false, }, @@ -456,7 +456,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Desktop", stack: "(-- bool)", desc: "1 when mouse button held, 0 otherwise", - example: "mdown { \"crash\" s . } ?", + example: "mdown ( \"crash\" s . ) ?", compile: Context("mdown"), varargs: false, }, @@ -487,7 +487,7 @@ pub(super) const WORDS: &[Word] = &[ category: "Generator", stack: "(quot n -- results...)", desc: "Execute quotation n times, push all results", - example: "{ 1 6 rand } 4 gen => 4 random values", + example: "( 1 6 rand ) 4 gen => 4 random values", compile: Simple, varargs: true, }, diff --git a/demos/02.cagire b/demos/02.cagire index 44ff314..4c88c11 100644 --- a/demos/02.cagire +++ b/demos/02.cagire @@ -7,11 +7,11 @@ "steps": [ { "i": 0, - "script": "0 8 12 rand ..\nc3 c4 g3 g2 4 pcycle key!\n0 1 2 choose 2\n6 12 rand pentatonic\n{ inv } rarely\n{ inv } sometimes arp note\ngrain sound 2 8 rand decay \n2 vib 0.125 2 / vibmod\n0.01 1.0 exprand pan\n2 release\n0.8 verb 1.0 verbdiff\n0.2 chorus\n1 morph\n0.0 1.0 rand \n0.0 1.0 rand timbre\n0.5 gain\n0.8 sustain\n2 8 rand release\n." + "script": "0 8 12 rand ..\nc3 c4 g3 g2 4 pcycle key!\n0 1 2 choose 2\n6 12 rand pentatonic\n( inv ) rarely\n( inv ) sometimes arp note\ngrain sound 2 8 rand decay \n2 vib 0.125 2 / vibmod\n0.01 1.0 exprand pan\n2 release\n0.8 verb 1.0 verbdiff\n0.2 chorus\n1 morph\n0.0 1.0 rand \n0.0 1.0 rand timbre\n0.5 gain\n0.8 sustain\n2 8 rand release\n." }, { "i": 4, - "script": "0 12 20 rand ..\nc3 c4 g3 g2 4 pcycle key!\n0 1 2 choose 2\n6 12 rand pentatonic\n{ inv } rarely\n{ inv } sometimes arp note\ngrain sound 2 8 rand decay \n2 vib 0.125 2 / vibmod\n0.01 1.0 exprand pan\n10 16 rand release\n0.8 verb 1.0 verbdiff\n0.2 chorus\n1 morph\n0.0 1.0 rand 0.0 1.0 rand timbre\n0.5 gain\n{ . } 2 every" + "script": "0 12 20 rand ..\nc3 c4 g3 g2 4 pcycle key!\n0 1 2 choose 2\n6 12 rand pentatonic\n( inv ) rarely\n( inv ) sometimes arp note\ngrain sound 2 8 rand decay \n2 vib 0.125 2 / vibmod\n0.01 1.0 exprand pan\n10 16 rand release\n0.8 verb 1.0 verbdiff\n0.2 chorus\n1 morph\n0.0 1.0 rand 0.0 1.0 rand timbre\n0.5 gain\n( . ) 2 every" } ], "length": 16, diff --git a/docs/forth/about_forth.md b/docs/forth/about_forth.md index 905d6c3..c42322b 100644 --- a/docs/forth/about_forth.md +++ b/docs/forth/about_forth.md @@ -54,7 +54,7 @@ Four basic types of values can live on the stack: - **Integers**: `42`, `-7`, `0` - **Floats**: `0.5`, `3.14`, `-1.0` - **Strings**: `"kick"`, `"hello"` -- **Quotations**: `{ dup + }` (code as data) +- **Quotations**: `( dup + )` (code as data) Floats can omit the leading zero: `.25` is the same as `0.25`, and `-.5` is `-0.5`. diff --git a/docs/forth/control_flow.md b/docs/forth/control_flow.md index 1d20c12..5733df9 100644 --- a/docs/forth/control_flow.md +++ b/docs/forth/control_flow.md @@ -29,22 +29,22 @@ These are compiled directly into branch instructions. For that reason, these wor When you already have a quotation, `?` executes it if the condition is truthy: ```forth -{ 0.4 verb } coin ? +( 0.4 verb ) coin ? saw s c4 note 0.5 gain . ;; reverb on half the hits ``` `!?` is the opposite — executes when falsy: ```forth -{ 0.2 gain } coin !? +( 0.2 gain ) coin !? saw s c4 note . ;; quiet on half the hits ``` These pair well with `chance`, `prob`, and the other probability words: ```forth -{ 0.5 verb } 0.3 chance ? ;; occasional reverb wash -{ 12 + } fill ? ;; octave up during fills +( 0.5 verb ) 0.3 chance ? ;; occasional reverb wash +( 12 + ) fill ? ;; octave up during fills ``` ## ifelse @@ -52,14 +52,14 @@ These pair well with `chance`, `prob`, and the other probability words: Two quotations, one condition. The true branch comes first: ```forth -{ c3 note } { c4 note } coin ifelse +( c3 note ) ( c4 note ) coin ifelse saw s 0.6 gain . ;; bass or lead, coin flip ``` Reads naturally: "c3 or c4, depending on the coin." ```forth -{ 0.8 gain } { 0.3 gain } fill ifelse +( 0.8 gain ) ( 0.3 gain ) fill ifelse tri s c4 note 0.2 decay . ;; loud during fills, quiet otherwise ``` @@ -68,7 +68,7 @@ tri s c4 note 0.2 decay . ;; loud during fills, quiet otherwise Choose the nth option from a list of quotations: ```forth -{ c4 } { e4 } { g4 } { b4 } iter 4 mod select +( c4 ) ( e4 ) ( g4 ) ( b4 ) iter 4 mod select note sine s 0.5 decay . ``` @@ -79,7 +79,7 @@ Four notes cycling through a major seventh chord, one per pattern iteration. The When you have a quotation and want to execute it unconditionally, use `apply`: ```forth -{ dup + } apply ;; doubles the top value +( dup + ) apply ;; doubles the top value ``` This is simpler than `?` when there is no condition to check. It pops the quotation and runs it. @@ -115,14 +115,14 @@ saw s c4 note . Repeat a quotation n times. The variable `@i` is automatically set to the current iteration index (starting from 0): ```forth -3 { c4 @i 4 * + note } times +3 ( c4 @i 4 * + note ) times sine s 0.4 gain 0.5 verb . ;; c4, e4, g#4 — a chord ``` Subdivide with `at`: ```forth -4 { @i 4 / at sine s c4 note 0.3 gain . } times +4 ( @i 4 / at sine s c4 note 0.3 gain . ) times ``` Four evenly spaced notes within the step. @@ -130,11 +130,11 @@ Four evenly spaced notes within the step. Vary intensity per iteration: ```forth -8 { +8 ( @i 8 / at @i 4 mod 0 = if 0.7 else 0.2 then gain tri s c5 note 0.1 decay . -} times +) times ``` Eight notes per step. Every fourth one louder. diff --git a/docs/forth/oddities.md b/docs/forth/oddities.md index 2f6fa22..34f13d2 100644 --- a/docs/forth/oddities.md +++ b/docs/forth/oddities.md @@ -22,10 +22,10 @@ Everything after `;;` until the end of the line is ignored. Classic Forth has no quotations. Code is not a value you can pass around. -Cagire has first-class quotations using curly braces: +Cagire has first-class quotations using parentheses: ```forth -{ dup + } +( dup + ) ``` This pushes a block of code onto the stack. You can store it, pass it to other words, and execute it later. Quotations enable conditionals, probability, and cycling. @@ -41,14 +41,14 @@ x 0 > IF 1 ELSE -1 THEN Cagire supports this syntax but also provides quotation-based conditionals: ```forth -{ 1 } { -1 } x 0 > ifelse +( 1 ) ( -1 ) x 0 > ifelse ``` The words `?` and `!?` execute a quotation based on a condition: ```forth -{ "kick" s . } coin ? ;; execute if coin is 1 -{ "snare" s . } coin !? ;; execute if coin is 0 +( "kick" s . ) coin ? ;; execute if coin is 1 +( "snare" s . ) coin !? ;; execute if coin is 0 ``` ## Strings @@ -116,21 +116,21 @@ Classic Forth has `DO ... LOOP`: Cagire uses a quotation-based loop with `times`: ```forth -4 { @i . } times ;; prints 0 1 2 3 +4 ( @i . ) times ;; prints 0 1 2 3 ``` The loop counter is stored in the variable `i`, accessed with `@i`. This fits Cagire's style where control flow uses quotations. ```forth -4 { @i 4 / at hat s . } times ;; hat at 0, 0.25, 0.5, 0.75 -4 { c4 @i + note sine s . } times ;; ascending notes +4 ( @i 4 / at hat s . ) times ;; hat at 0, 0.25, 0.5, 0.75 +4 ( c4 @i + note sine s . ) times ;; ascending notes ``` For generating sequences without side effects, use `..` or `gen`: ```forth 1 5 .. ;; pushes 1 2 3 4 5 -{ dup * } 4 gen ;; pushes 0 1 4 9 (squares) +( dup * ) 4 gen ;; pushes 0 1 4 9 (squares) ``` ## The Command Register @@ -167,11 +167,11 @@ These have no equivalent in classic Forth. They connect your script to the seque Classic Forth is deterministic. Cagire has built-in randomness: ```forth -{ "snare" s . } 50 prob ;; 50% chance -{ "clap" s . } 0.25 chance ;; 25% chance -{ "hat" s . } often ;; 75% chance -{ "rim" s . } sometimes ;; 50% chance -{ "tom" s . } rarely ;; 25% chance +( "snare" s . ) 50 prob ;; 50% chance +( "clap" s . ) 0.25 chance ;; 25% chance +( "hat" s . ) often ;; 75% chance +( "rim" s . ) sometimes ;; 50% chance +( "tom" s . ) rarely ;; 25% chance ``` These words take a quotation and execute it probabilistically. @@ -181,9 +181,9 @@ These words take a quotation and execute it probabilistically. Execute a quotation on specific iterations: ```forth -{ "snare" s . } 4 every ;; every 4th pattern iteration -{ "hat" s . } 3 8 bjork ;; Euclidean: 3 hits across 8 step runs -{ "hat" s . } 5 8 pbjork ;; Euclidean: 5 hits across 8 pattern iterations +( "snare" s . ) 4 every ;; every 4th pattern iteration +( "hat" s . ) 3 8 bjork ;; Euclidean: 3 hits across 8 step runs +( "hat" s . ) 5 8 pbjork ;; Euclidean: 5 hits across 8 pattern iterations ``` `every` checks the pattern iteration count. On iteration 0, 4, 8, 12... the quotation runs. On all other iterations it is skipped. @@ -203,7 +203,7 @@ Each time the step runs, a different note is selected. The `3` tells `cycle` how You can also use quotations if you need to execute code: ```forth -{ c4 note } { e4 note } { g4 note } 3 cycle +( c4 note ) ( e4 note ) ( g4 note ) 3 cycle ``` When the selected value is a quotation, it gets executed. When it is a plain value, it gets pushed onto the stack. diff --git a/docs/tutorials/at.md b/docs/tutorials/at.md index 8a713fa..c5510f1 100644 --- a/docs/tutorials/at.md +++ b/docs/tutorials/at.md @@ -75,7 +75,7 @@ Euclidean distribution via `euclid`: Random timing via `gen`: ```forth -{ 0.0 1.0 rand } 4 gen at hat s . ;; 4 hats at random positions +( 0.0 1.0 rand ) 4 gen at hat s . ;; 4 hats at random positions ``` Geometric spacing via `geom..`: @@ -89,10 +89,10 @@ Geometric spacing via `geom..`: Wrap `at` expressions in quotations for conditional timing: ```forth -{ 0 0.25 0.5 0.75 at } 2 every ;; 16th-note hats every other bar +( 0 0.25 0.5 0.75 at ) 2 every ;; 16th-note hats every other bar hat s . -{ 0 0.5 at } 0.5 chance ;; 50% chance of double-hit +( 0 0.5 at ) 0.5 chance ;; 50% chance of double-hit kick s . ``` diff --git a/docs/tutorials/generators.md b/docs/tutorials/generators.md index 1b110d2..1a56018 100644 --- a/docs/tutorials/generators.md +++ b/docs/tutorials/generators.md @@ -40,15 +40,15 @@ That gives you 110, 220, 440, 880, 1760 (reversed), ready to feed into `freq`. `gen` executes a quotation n times and collects all results. The quotation must push exactly one value per call: ```forth -{ 1 6 rand } 4 gen ;; 4 random values between 1 and 6 -{ coin } 8 gen ;; 8 random 0s and 1s +( 1 6 rand ) 4 gen ;; 4 random values between 1 and 6 +( coin ) 8 gen ;; 8 random 0s and 1s ``` Contrast with `times`, which executes for side effects and does not collect. `times` sets `@i` to the current index: ```forth -4 { @i } times ;; 0 1 2 3 (pushes @i each iteration) -4 { @i 60 + note sine s . } times ;; plays 4 notes, collects nothing +4 ( @i ) times ;; 0 1 2 3 (pushes @i each iteration) +4 ( @i 60 + note sine s . ) times ;; plays 4 notes, collects nothing ``` The distinction: `gen` is for building data. `times` is for doing things. @@ -109,7 +109,7 @@ c4 e4 g4 b4 4 shuffle ;; random permutation each time Useful for computing averages or accumulating values: ```forth -{ 1 6 rand } 4 gen 4 sum ;; sum of 4 dice rolls +( 1 6 rand ) 4 gen 4 sum ;; sum of 4 dice rolls ``` ## Replication diff --git a/docs/tutorials/harmony.md b/docs/tutorials/harmony.md index 53abdcc..5f4ba82 100644 --- a/docs/tutorials/harmony.md +++ b/docs/tutorials/harmony.md @@ -263,7 +263,7 @@ c4 mtof freq sine s . A chord progression cycling every pattern iteration: ```forth -{ c3 maj7 } { f3 maj7 } { g3 dom7 } { c3 maj7 } 4 pcycle +( c3 maj7 ) ( f3 maj7 ) ( g3 dom7 ) ( c3 maj7 ) 4 pcycle note sine s . ``` @@ -290,7 +290,7 @@ Chord voicings with random inversion: ```forth e3 min9 -{ } { 1 oct } 2 choose +( ) ( 1 oct ) 2 choose note modal s . ``` diff --git a/docs/tutorials/randomness.md b/docs/tutorials/randomness.md index 1a4f600..0bbd920 100644 --- a/docs/tutorials/randomness.md +++ b/docs/tutorials/randomness.md @@ -31,8 +31,8 @@ These are useful for parameters where perception is logarithmic, like frequency The probability words take a quotation and execute it with some chance. `chance` takes a float from 0.0 to 1.0, `prob` takes a percentage from 0 to 100: ```forth -{ hat s . } 0.25 chance ;; 25% chance -{ hat s . } 75 prob ;; 75% chance +( hat s . ) 0.25 chance ;; 25% chance +( hat s . ) 75 prob ;; 75% chance ``` Named probability words save you from remembering numbers: @@ -48,9 +48,9 @@ Named probability words save you from remembering numbers: | `never` | 0% | ```forth -{ hat s . } often ;; 75% -{ snare s . } sometimes ;; 50% -{ clap s . } rarely ;; 25% +( hat s . ) often ;; 75% +( snare s . ) sometimes ;; 50% +( clap s . ) rarely ;; 25% ``` `always` and `never` are useful when you want to temporarily mute or unmute a voice without deleting code. Change `sometimes` to `never` to silence it, `always` to bring it back. @@ -58,8 +58,8 @@ Named probability words save you from remembering numbers: Use `?` and `!?` with `coin` for quick coin-flip decisions: ```forth -{ hat s . } coin ? ;; execute if coin is 1 -{ rim s . } coin !? ;; execute if coin is 0 +( hat s . ) coin ? ;; execute if coin is 1 +( rim s . ) coin !? ;; execute if coin is 0 ``` ## Selection @@ -74,7 +74,7 @@ kick snare hat 3 choose s . ;; random drum hit When a chosen item is a quotation, it gets executed: ```forth -{ 0.1 decay } { 0.5 decay } { 0.9 decay } 3 choose +( 0.1 decay ) ( 0.5 decay ) ( 0.9 decay ) 3 choose sine s . ``` @@ -115,7 +115,7 @@ The difference matters when patterns have different lengths. `cycle` counts per- Quotations work here too: ```forth -{ c4 note } { e4 note } { g4 note } 3 cycle +( c4 note ) ( e4 note ) ( g4 note ) 3 cycle sine s . ``` @@ -130,20 +130,20 @@ sine s . `every` runs a quotation once every n pattern iterations: ```forth -{ crash s . } 4 every ;; crash cymbal every 4th iteration +( crash s . ) 4 every ;; crash cymbal every 4th iteration ``` `except` is the inverse -- it runs a quotation on all iterations *except* every nth: ```forth -{ 2 distort } 4 except ;; distort on all iterations except every 4th +( 2 distort ) 4 except ;; distort on all iterations except every 4th ``` `bjork` and `pbjork` use Bjorklund's algorithm to distribute k hits across n positions as evenly as possible. Classic Euclidean rhythms: ```forth -{ hat s . } 3 8 bjork ;; tresillo: x..x..x. (by step runs) -{ hat s . } 5 8 pbjork ;; cinquillo: x.xx.xx. (by pattern iterations) +( hat s . ) 3 8 bjork ;; tresillo: x..x..x. (by step runs) +( hat s . ) 5 8 pbjork ;; cinquillo: x.xx.xx. (by pattern iterations) ``` `bjork` counts by step runs (how many times this particular step has played). `pbjork` counts by pattern iterations. Some classic patterns: @@ -172,7 +172,7 @@ The real power comes from mixing techniques. A hi-hat pattern with ghost notes: ```forth hat s -{ 0.3 0.6 rand gain } { 0.8 gain } 2 cycle +( 0.3 0.6 rand gain ) ( 0.8 gain ) 2 cycle . ``` @@ -181,18 +181,18 @@ Full volume on even runs, random quiet on odd runs. A bass line that changes every 4 bars: ```forth -{ c2 note } { e2 note } { g2 note } { a2 note } 4 pcycle -{ 0.5 decay } often +( c2 note ) ( e2 note ) ( g2 note ) ( a2 note ) 4 pcycle +( 0.5 decay ) often sine s . ``` Layered percussion with different densities: ```forth -{ kick s . } always -{ snare s . } 2 every -{ hat s . } 5 8 bjork -{ rim s . } rarely +( kick s . ) always +( snare s . ) 2 every +( hat s . ) 5 8 bjork +( rim s . ) rarely ``` A melodic step with weighted note selection and random timbre: diff --git a/docs/tutorials/variables.md b/docs/tutorials/variables.md index 24c01ec..7271656 100644 --- a/docs/tutorials/variables.md +++ b/docs/tutorials/variables.md @@ -53,7 +53,7 @@ Reset on some condition: ```forth @n 1 + !n -{ 0 !n } @n 16 > ? ;; reset after 16 +( 0 !n ) @n 16 > ? ;; reset after 16 ``` ## When Changes Take Effect diff --git a/scripts/build-all.sh b/scripts/build-all.sh index cee18d3..b66d47b 100755 --- a/scripts/build-all.sh +++ b/scripts/build-all.sh @@ -1,11 +1,13 @@ #!/usr/bin/env bash set -euo pipefail +export MACOSX_DEPLOYMENT_TARGET="12.0" + cd "$(git rev-parse --show-toplevel)" PLUGIN_NAME="cagire-plugins" LIB_NAME="cagire_plugins" # cargo converts hyphens to underscores -OUT="target/releases" +OUT="releases" PLATFORMS=( "aarch64-apple-darwin" @@ -312,13 +314,16 @@ copy_artifacts() { # macOS .app bundle if [[ "$os" == "macos" ]]; then local app_src="$rd/bundle/osx/Cagire.app" - if [[ -d "$app_src" ]]; then - local app_dst="$OUT/Cagire-${arch}.app" - rm -rf "$app_dst" - cp -R "$app_src" "$app_dst" - echo " Cagire.app -> $app_dst" - scripts/make-dmg.sh "$app_dst" "$OUT" + if [[ ! -d "$app_src" ]]; then + echo " ERROR: .app bundle not found at $app_src" + echo " Did 'cargo bundle' succeed?" + return 1 fi + local app_dst="$OUT/Cagire-${arch}.app" + rm -rf "$app_dst" + cp -R "$app_src" "$app_dst" + echo " Cagire.app -> $app_dst" + scripts/make-dmg.sh "$app_dst" "$OUT" fi fi diff --git a/tests/forth/comparison.rs b/tests/forth/comparison.rs index 055c363..2960a29 100644 --- a/tests/forth/comparison.rs +++ b/tests/forth/comparison.rs @@ -187,30 +187,30 @@ fn nor_ff() { #[test] fn ifelse_true() { - expect_int("{ 42 } { 99 } 1 ifelse", 42); + expect_int("( 42 ) ( 99 ) 1 ifelse", 42); } #[test] fn ifelse_false() { - expect_int("{ 42 } { 99 } 0 ifelse", 99); + expect_int("( 42 ) ( 99 ) 0 ifelse", 99); } #[test] fn select_first() { - expect_int("{ 10 } { 20 } { 30 } 0 select", 10); + expect_int("( 10 ) ( 20 ) ( 30 ) 0 select", 10); } #[test] fn select_second() { - expect_int("{ 10 } { 20 } { 30 } 1 select", 20); + expect_int("( 10 ) ( 20 ) ( 30 ) 1 select", 20); } #[test] fn select_third() { - expect_int("{ 10 } { 20 } { 30 } 2 select", 30); + expect_int("( 10 ) ( 20 ) ( 30 ) 2 select", 30); } #[test] fn select_preserves_stack() { - expect_int("5 { 10 } { 20 } 0 select +", 15); + expect_int("5 ( 10 ) ( 20 ) 0 select +", 15); } diff --git a/tests/forth/context.rs b/tests/forth/context.rs index 5486464..3f49f32 100644 --- a/tests/forth/context.rs +++ b/tests/forth/context.rs @@ -59,14 +59,14 @@ fn iter() { #[test] fn every_true_on_zero() { let ctx = ctx_with(|c| c.iter = 0); - let f = run_ctx("{ 100 } 4 every", &ctx); + let f = run_ctx("( 100 ) 4 every", &ctx); assert_eq!(stack_int(&f), 100); } #[test] fn every_true_on_multiple() { let ctx = ctx_with(|c| c.iter = 8); - let f = run_ctx("{ 100 } 4 every", &ctx); + let f = run_ctx("( 100 ) 4 every", &ctx); assert_eq!(stack_int(&f), 100); } @@ -74,14 +74,14 @@ fn every_true_on_multiple() { fn every_false_between() { for i in 1..4 { let ctx = ctx_with(|c| c.iter = i); - let f = run_ctx("{ 100 } 4 every", &ctx); + let f = run_ctx("( 100 ) 4 every", &ctx); assert!(f.stack().is_empty(), "iter={} should not execute quotation", i); } } #[test] fn every_zero_count() { - expect_error("{ 1 } 0 every", "every count must be > 0"); + expect_error("( 1 ) 0 every", "every count must be > 0"); } #[test] @@ -105,7 +105,7 @@ fn bjork_tresillo() { // Bresenham(3,8) hits at positions 0, 2, 5 for runs in 0..8 { let ctx = ctx_with(|c| c.runs = runs); - let f = run_ctx("{ 100 } 3 8 bjork", &ctx); + let f = run_ctx("( 100 ) 3 8 bjork", &ctx); let hit = ((runs + 1) * 3) / 8 != (runs * 3) / 8; if hit { assert_eq!(stack_int(&f), 100, "runs={} should hit", runs); @@ -121,7 +121,7 @@ fn bjork_hit_count() { let mut hit_count = 0; for runs in 0..8 { let ctx = ctx_with(|c| c.runs = runs); - let f = run_ctx("{ 100 } 3 8 bjork", &ctx); + let f = run_ctx("( 100 ) 3 8 bjork", &ctx); if !f.stack().is_empty() { hit_count += 1; } @@ -132,20 +132,20 @@ fn bjork_hit_count() { #[test] fn bjork_all_hits() { let ctx = ctx_with(|c| c.runs = 0); - let f = run_ctx("{ 100 } 8 8 bjork", &ctx); + let f = run_ctx("( 100 ) 8 8 bjork", &ctx); assert_eq!(stack_int(&f), 100); } #[test] fn bjork_zero_hits() { let ctx = ctx_with(|c| c.runs = 0); - let f = run_ctx("{ 100 } 0 8 bjork", &ctx); + let f = run_ctx("( 100 ) 0 8 bjork", &ctx); assert!(f.stack().is_empty()); } #[test] fn bjork_invalid() { - expect_error("{ 1 } 3 0 bjork", "bjork"); + expect_error("( 1 ) 3 0 bjork", "bjork"); } // pbjork (iter-based) @@ -155,7 +155,7 @@ fn pbjork_cinquillo() { let mut hit_count = 0; for iter in 0..8 { let ctx = ctx_with(|c| c.iter = iter); - let f = run_ctx("{ 100 } 5 8 pbjork", &ctx); + let f = run_ctx("( 100 ) 5 8 pbjork", &ctx); if !f.stack().is_empty() { hit_count += 1; } @@ -167,7 +167,7 @@ fn pbjork_cinquillo() { fn pbjork_wraps() { let ctx0 = ctx_with(|c| c.iter = 0); let ctx8 = ctx_with(|c| c.iter = 8); - let f0 = run_ctx("{ 100 } 3 8 pbjork", &ctx0); - let f8 = run_ctx("{ 100 } 3 8 pbjork", &ctx8); + let f0 = run_ctx("( 100 ) 3 8 pbjork", &ctx0); + let f8 = run_ctx("( 100 ) 3 8 pbjork", &ctx8); assert_eq!(f0.stack().is_empty(), f8.stack().is_empty()); } diff --git a/tests/forth/definitions.rs b/tests/forth/definitions.rs index 26eb5f4..25fa25e 100644 --- a/tests/forth/definitions.rs +++ b/tests/forth/definitions.rs @@ -68,12 +68,12 @@ fn unexpected_semicolon_errors() { #[test] fn apply_executes_quotation() { - expect_int("5 { 2 * } apply", 10); + expect_int("5 ( 2 * ) apply", 10); } #[test] fn apply_with_stack_ops() { - expect_int("3 4 { + } apply", 7); + expect_int("3 4 ( + ) apply", 7); } #[test] @@ -88,12 +88,12 @@ fn apply_non_quotation_errors() { #[test] fn apply_nested() { - expect_int("2 { { 3 * } apply } apply", 6); + expect_int("2 ( ( 3 * ) apply ) apply", 6); } #[test] fn define_word_containing_quotation() { - expect_int(": dbl { 2 * } apply ; 7 dbl", 14); + expect_int(": dbl ( 2 * ) apply ; 7 dbl", 14); } #[test] diff --git a/tests/forth/generator.rs b/tests/forth/generator.rs index cdf7c5e..96f5a59 100644 --- a/tests/forth/generator.rs +++ b/tests/forth/generator.rs @@ -33,7 +33,7 @@ fn range_underflow() { #[test] fn gen_basic() { - expect_stack("{ 42 } 3 gen", &[int(42), int(42), int(42)]); + expect_stack("( 42 ) 3 gen", &[int(42), int(42), int(42)]); } #[test] @@ -42,7 +42,7 @@ fn gen_with_computation() { // 0 → dup(0,0) 1+(0,1) → pop 1, stack [0] // 0 → dup(0,0) 1+(0,1) → pop 1, stack [0] // So we get [0, 1, 1, 1] - the 0 stays, we collect three 1s - expect_stack("0 { dup 1 + } 3 gen", &[int(0), int(1), int(1), int(1)]); + expect_stack("0 ( dup 1 + ) 3 gen", &[int(0), int(1), int(1), int(1)]); } #[test] @@ -50,12 +50,12 @@ fn gen_chained() { // Start with 1, each iteration: dup, multiply by 2 // 1 → dup(1,1) 2*(1,2) → pop 2, stack [1] // 1 → dup(1,1) 2*(1,2) → pop 2, stack [1] - expect_stack("1 { dup 2 * } 3 gen", &[int(1), int(2), int(2), int(2)]); + expect_stack("1 ( dup 2 * ) 3 gen", &[int(1), int(2), int(2), int(2)]); } #[test] fn gen_zero() { - expect_stack("{ 1 } 0 gen", &[]); + expect_stack("( 1 ) 0 gen", &[]); } #[test] @@ -65,17 +65,17 @@ fn gen_underflow() { #[test] fn gen_not_a_number() { - expect_error("{ 1 } gen", "expected number"); + expect_error("( 1 ) gen", "expected number"); } #[test] fn gen_negative() { - expect_error("{ 1 } -1 gen", "gen count must be >= 0"); + expect_error("( 1 ) -1 gen", "gen count must be >= 0"); } #[test] fn gen_empty_quot_error() { - expect_error("{ } 3 gen", "quotation must produce"); + expect_error("( ) 3 gen", "quotation must produce"); } #[test] diff --git a/tests/forth/list_words.rs b/tests/forth/list_words.rs index c03a6be..56f4a83 100644 --- a/tests/forth/list_words.rs +++ b/tests/forth/list_words.rs @@ -46,20 +46,20 @@ fn pcycle_by_iter() { #[test] fn cycle_with_quotations() { let ctx = ctx_with(|c| c.runs = 0); - let f = run_ctx("5 { dup } { 2 * } 2 cycle", &ctx); + let f = run_ctx("5 ( dup ) ( 2 * ) 2 cycle", &ctx); let stack = f.stack(); assert_eq!(stack.len(), 2); assert_eq!(stack_int(&f), 5); let ctx = ctx_with(|c| c.runs = 1); - let f = run_ctx("5 { dup } { 2 * } 2 cycle", &ctx); + let f = run_ctx("5 ( dup ) ( 2 * ) 2 cycle", &ctx); assert_eq!(stack_int(&f), 10); } #[test] fn cycle_executes_quotation() { let ctx = ctx_with(|c| c.runs = 0); - let f = run_ctx("10 { 3 + } { 5 + } 2 cycle", &ctx); + let f = run_ctx("10 ( 3 + ) ( 5 + ) 2 cycle", &ctx); assert_eq!(stack_int(&f), 13); } @@ -108,11 +108,11 @@ fn bracket_cycle() { #[test] fn bracket_with_quotations() { let ctx = ctx_with(|c| c.runs = 0); - let f = run_ctx("5 [ { 3 + } { 5 + } ] cycle", &ctx); + let f = run_ctx("5 [ ( 3 + ) ( 5 + ) ] cycle", &ctx); assert_eq!(stack_int(&f), 8); let ctx = ctx_with(|c| c.runs = 1); - let f = run_ctx("5 [ { 3 + } { 5 + } ] cycle", &ctx); + let f = run_ctx("5 [ ( 3 + ) ( 5 + ) ] cycle", &ctx); assert_eq!(stack_int(&f), 10); } @@ -172,7 +172,7 @@ fn index_negative_wraps() { #[test] fn index_with_quotation() { - expect_int("5 [ { 3 + } { 5 + } ] 0 index", 8); + expect_int("5 [ ( 3 + ) ( 5 + ) ] 0 index", 8); } #[test] diff --git a/tests/forth/quotations.rs b/tests/forth/quotations.rs index 2287300..05a2e83 100644 --- a/tests/forth/quotations.rs +++ b/tests/forth/quotations.rs @@ -4,26 +4,26 @@ use super::harness::*; fn quotation_on_stack() { // Quotation should be pushable to stack let f = forth(); - let result = f.evaluate("{ 1 2 + }", &default_ctx()); + let result = f.evaluate("( 1 2 + )", &default_ctx()); assert!(result.is_ok()); } #[test] fn when_true_executes() { - let f = run("{ 42 } 1 ?"); + let f = run("( 42 ) 1 ?"); assert_eq!(stack_int(&f), 42); } #[test] fn when_false_skips() { - let f = run("99 { 42 } 0 ?"); + let f = run("99 ( 42 ) 0 ?"); // Stack should still have 99, quotation not executed assert_eq!(stack_int(&f), 99); } #[test] fn when_with_arithmetic() { - let f = run("10 { 5 + } 1 ?"); + let f = run("10 ( 5 + ) 1 ?"); assert_eq!(stack_int(&f), 15); } @@ -31,48 +31,48 @@ fn when_with_arithmetic() { fn when_with_every() { // iter=0, every 2 executes quotation let ctx = ctx_with(|c| c.iter = 0); - let f = run_ctx("{ 100 } 2 every", &ctx); + let f = run_ctx("( 100 ) 2 every", &ctx); assert_eq!(stack_int(&f), 100); // iter=1, every 2 skips quotation let ctx = ctx_with(|c| c.iter = 1); - let f = run_ctx("50 { 100 } 2 every", &ctx); + let f = run_ctx("50 ( 100 ) 2 every", &ctx); assert_eq!(stack_int(&f), 50); // quotation not executed } #[test] fn when_with_chance_deterministic() { // 1.0 chance always executes quotation - let f = run("{ 42 } 1.0 chance"); + let f = run("( 42 ) 1.0 chance"); assert_eq!(stack_int(&f), 42); // 0.0 chance never executes quotation - let f = run("99 { 42 } 0.0 chance"); + let f = run("99 ( 42 ) 0.0 chance"); assert_eq!(stack_int(&f), 99); } #[test] fn nested_quotations() { - let f = run("{ { 42 } 1 ? } 1 ?"); + let f = run("( ( 42 ) 1 ? ) 1 ?"); assert_eq!(stack_int(&f), 42); } #[test] fn quotation_with_param() { - let outputs = expect_outputs(r#""kick" s { 2 distort } 1 ? ."#, 1); + let outputs = expect_outputs(r#""kick" s ( 2 distort ) 1 ? ."#, 1); assert!(outputs[0].contains("distort/2")); } #[test] fn quotation_skips_param() { - let outputs = expect_outputs(r#""kick" s { 2 distort } 0 ? ."#, 1); + let outputs = expect_outputs(r#""kick" s ( 2 distort ) 0 ? ."#, 1); assert!(!outputs[0].contains("distort")); } #[test] fn quotation_with_emit() { // When true, . should fire - let outputs = expect_outputs(r#""kick" s { . } 1 ?"#, 1); + let outputs = expect_outputs(r#""kick" s ( . ) 1 ?"#, 1); assert!(outputs[0].contains("kick")); } @@ -81,7 +81,7 @@ fn quotation_skips_emit() { // When false, . should not fire let f = forth(); let outputs = f - .evaluate(r#""kick" s { . } 0 ?"#, &default_ctx()) + .evaluate(r#""kick" s ( . ) 0 ?"#, &default_ctx()) .unwrap(); // No output since . was skipped and no implicit emit assert_eq!(outputs.len(), 0); @@ -94,22 +94,22 @@ fn missing_quotation_error() { #[test] fn unclosed_quotation_error() { - expect_error("{ 1 2", "missing }"); + expect_error("( 1 2", "missing )"); } #[test] fn unexpected_close_error() { - expect_error("1 2 }", "unexpected }"); + expect_error("1 2 )", "unexpected )"); } #[test] fn every_with_quotation_integration() { - // { 2 distort } 2 every — on even iterations, distort is applied + // ( 2 distort ) 2 every — on even iterations, distort is applied for iter in 0..4 { let ctx = ctx_with(|c| c.iter = iter); let f = forth(); let outputs = f - .evaluate(r#""kick" s { 2 distort } 2 every ."#, &ctx) + .evaluate(r#""kick" s ( 2 distort ) 2 every ."#, &ctx) .unwrap(); if iter % 2 == 0 { assert!( @@ -134,7 +134,7 @@ fn bjork_with_sound() { let ctx = ctx_with(|c| c.runs = 2); // position 2 is a hit for (3,8) let f = forth(); let outputs = f - .evaluate(r#""kick" s { 2 distort } 3 8 bjork ."#, &ctx) + .evaluate(r#""kick" s ( 2 distort ) 3 8 bjork ."#, &ctx) .unwrap(); assert!(outputs[0].contains("distort/2")); } @@ -143,13 +143,13 @@ fn bjork_with_sound() { #[test] fn unless_false_executes() { - let f = run("{ 42 } 0 !?"); + let f = run("( 42 ) 0 !?"); assert_eq!(stack_int(&f), 42); } #[test] fn unless_true_skips() { - let f = run("99 { 42 } 1 !?"); + let f = run("99 ( 42 ) 1 !?"); assert_eq!(stack_int(&f), 99); } @@ -161,7 +161,7 @@ fn when_and_unless_complementary() { let f = forth(); let outputs = f .evaluate( - r#""kick" s { 2 distort } iter 2 mod 0 = ? { 4 distort } iter 2 mod 0 = !? ."#, + r#""kick" s ( 2 distort ) iter 2 mod 0 = ? ( 4 distort ) iter 2 mod 0 = !? ."#, &ctx, ) .unwrap(); diff --git a/tests/forth/randomness.rs b/tests/forth/randomness.rs index 0cf191c..ff9cfa2 100644 --- a/tests/forth/randomness.rs +++ b/tests/forth/randomness.rs @@ -38,14 +38,14 @@ fn coin_binary() { #[test] fn chance_zero() { // 0.0 probability should never execute the quotation - let f = run("99 { 42 } 0.0 chance"); + let f = run("99 ( 42 ) 0.0 chance"); assert_eq!(stack_int(&f), 99); // quotation not executed, 99 still on stack } #[test] fn chance_one() { // 1.0 probability should always execute the quotation - let f = run("{ 42 } 1.0 chance"); + let f = run("( 42 ) 1.0 chance"); assert_eq!(stack_int(&f), 42); } @@ -281,7 +281,7 @@ fn wchoose_negative_weight() { #[test] fn wchoose_quotation() { let f = forth_seeded(42); - f.evaluate("{ 10 } 0.0 { 20 } 1.0 2 wchoose", &default_ctx()) + f.evaluate("( 10 ) 0.0 ( 20 ) 1.0 2 wchoose", &default_ctx()) .unwrap(); assert_eq!(stack_int(&f), 20); } diff --git a/tests/forth/temporal.rs b/tests/forth/temporal.rs index 5b463f9..1830099 100644 --- a/tests/forth/temporal.rs +++ b/tests/forth/temporal.rs @@ -99,7 +99,7 @@ fn cycle_picks_by_runs() { for runs in 0..4 { let ctx = ctx_with(|c| c.runs = runs); let f = forth(); - let outputs = f.evaluate(r#""kick" s { . } { } 2 cycle"#, &ctx).unwrap(); + let outputs = f.evaluate(r#""kick" s ( . ) ( ) 2 cycle"#, &ctx).unwrap(); if runs % 2 == 0 { assert_eq!(outputs.len(), 1, "runs={}: emit should be picked", runs); } else { @@ -113,7 +113,7 @@ fn pcycle_picks_by_iter() { for iter in 0..4 { let ctx = ctx_with(|c| c.iter = iter); let f = forth(); - let outputs = f.evaluate(r#""kick" s { . } { } 2 pcycle"#, &ctx).unwrap(); + let outputs = f.evaluate(r#""kick" s ( . ) ( ) 2 pcycle"#, &ctx).unwrap(); if iter % 2 == 0 { assert_eq!(outputs.len(), 1, "iter={}: emit should be picked", iter); } else { @@ -128,7 +128,7 @@ fn cycle_with_sounds() { let ctx = ctx_with(|c| c.runs = runs); let f = forth(); let outputs = f.evaluate( - r#"{ "kick" s . } { "hat" s . } { "snare" s . } 3 cycle"#, + r#"( "kick" s . ) ( "hat" s . ) ( "snare" s . ) 3 cycle"#, &ctx ).unwrap(); assert_eq!(outputs.len(), 1, "runs={}: expected 1 output", runs); @@ -346,7 +346,7 @@ fn every_offset_fires_at_offset() { for iter in 0..8 { let ctx = ctx_with(|c| c.iter = iter); let f = forth(); - let outputs = f.evaluate(r#""kick" s { . } 4 2 every+"#, &ctx).unwrap(); + let outputs = f.evaluate(r#""kick" s ( . ) 4 2 every+"#, &ctx).unwrap(); if iter % 4 == 2 { assert_eq!(outputs.len(), 1, "iter={}: should fire", iter); } else { @@ -361,7 +361,7 @@ fn every_offset_wraps_large_offset() { for iter in 0..8 { let ctx = ctx_with(|c| c.iter = iter); let f = forth(); - let outputs = f.evaluate(r#""kick" s { . } 4 6 every+"#, &ctx).unwrap(); + let outputs = f.evaluate(r#""kick" s ( . ) 4 6 every+"#, &ctx).unwrap(); if iter % 4 == 2 { assert_eq!(outputs.len(), 1, "iter={}: should fire (wrapped offset)", iter); } else { @@ -375,7 +375,7 @@ fn except_offset_inverse() { for iter in 0..8 { let ctx = ctx_with(|c| c.iter = iter); let f = forth(); - let outputs = f.evaluate(r#""kick" s { . } 4 2 except+"#, &ctx).unwrap(); + let outputs = f.evaluate(r#""kick" s ( . ) 4 2 except+"#, &ctx).unwrap(); if iter % 4 != 2 { assert_eq!(outputs.len(), 1, "iter={}: should fire", iter); } else { @@ -389,8 +389,8 @@ fn every_offset_zero_is_same_as_every() { for iter in 0..8 { let ctx = ctx_with(|c| c.iter = iter); let f = forth(); - let a = f.evaluate(r#""kick" s { . } 3 every"#, &ctx).unwrap(); - let b = f.evaluate(r#""kick" s { . } 3 0 every+"#, &ctx).unwrap(); + let a = f.evaluate(r#""kick" s ( . ) 3 every"#, &ctx).unwrap(); + let b = f.evaluate(r#""kick" s ( . ) 3 0 every+"#, &ctx).unwrap(); assert_eq!(a.len(), b.len(), "iter={}: every and every+ 0 should match", iter); } }