@devaloop/devalang 0.0.1-alpha.7 → 0.0.1-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/Cargo.toml +1 -1
  2. package/README.md +29 -10
  3. package/docs/CHANGELOG.md +22 -2
  4. package/docs/ROADMAP.md +2 -2
  5. package/docs/SYNTAX.md +41 -7
  6. package/docs/TODO.md +3 -3
  7. package/examples/condition.deva +24 -0
  8. package/examples/index.deva +4 -5
  9. package/examples/variables.deva +1 -1
  10. package/out-tsc/bin/devalang.exe +0 -0
  11. package/package.json +1 -1
  12. package/project-version.json +3 -3
  13. package/rust/cli/build.rs +6 -1
  14. package/rust/core/audio/evaluator.rs +31 -0
  15. package/rust/core/audio/interpreter/call.rs +42 -0
  16. package/rust/core/audio/interpreter/condition.rs +65 -0
  17. package/rust/core/audio/interpreter/driver.rs +204 -0
  18. package/rust/core/audio/interpreter/let_.rs +19 -0
  19. package/rust/core/audio/interpreter/load.rs +18 -0
  20. package/rust/core/audio/interpreter/loop_.rs +59 -0
  21. package/rust/core/audio/interpreter/mod.rs +11 -0
  22. package/rust/core/audio/interpreter/sleep.rs +36 -0
  23. package/rust/core/audio/interpreter/spawn.rs +65 -0
  24. package/rust/core/audio/interpreter/tempo.rs +16 -0
  25. package/rust/core/audio/interpreter/trigger.rs +69 -0
  26. package/rust/core/audio/loader/mod.rs +1 -0
  27. package/rust/core/audio/{loader.rs → loader/trigger.rs} +3 -1
  28. package/rust/core/audio/mod.rs +2 -1
  29. package/rust/core/audio/{render.rs → renderer.rs} +6 -2
  30. package/rust/core/builder/mod.rs +1 -1
  31. package/rust/core/debugger/lexer.rs +1 -1
  32. package/rust/core/debugger/mod.rs +1 -0
  33. package/rust/core/debugger/store.rs +25 -0
  34. package/rust/core/error/mod.rs +1 -1
  35. package/rust/core/lexer/handler/driver.rs +215 -0
  36. package/rust/core/lexer/handler/identifier.rs +2 -0
  37. package/rust/core/lexer/handler/mod.rs +3 -227
  38. package/rust/core/lexer/handler/operator.rs +44 -0
  39. package/rust/core/lexer/mod.rs +1 -1
  40. package/rust/core/lexer/token.rs +36 -9
  41. package/rust/core/parser/driver.rs +312 -0
  42. package/rust/core/parser/handler/at.rs +3 -7
  43. package/rust/core/parser/handler/bank.rs +5 -2
  44. package/rust/core/parser/handler/condition.rs +74 -0
  45. package/rust/core/parser/handler/dot.rs +1 -1
  46. package/rust/core/parser/handler/identifier.rs +38 -36
  47. package/rust/core/parser/handler/loop_.rs +1 -1
  48. package/rust/core/parser/handler/mod.rs +2 -1
  49. package/rust/core/parser/handler/tempo.rs +1 -1
  50. package/rust/core/parser/mod.rs +3 -237
  51. package/rust/core/parser/statement.rs +29 -36
  52. package/rust/core/preprocessor/loader.rs +7 -6
  53. package/rust/core/preprocessor/processor.rs +1 -1
  54. package/rust/core/preprocessor/resolver/call.rs +53 -0
  55. package/rust/core/preprocessor/resolver/condition.rs +66 -0
  56. package/rust/core/preprocessor/resolver/driver.rs +182 -0
  57. package/rust/core/preprocessor/resolver/group.rs +89 -84
  58. package/rust/core/preprocessor/resolver/mod.rs +5 -153
  59. package/rust/core/preprocessor/resolver/spawn.rs +53 -0
  60. package/rust/core/audio/interpreter.rs +0 -317
  61. package/rust/core/lexer/handler/equal.rs +0 -32
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "devalang"
3
- version = "0.0.1-alpha.7"
3
+ version = "0.0.1-alpha.8"
4
4
  authors = ["Devaloop <contact@devaloop.com>"]
5
5
  description = "Write music like code. Devalang is a domain-specific language (DSL) for sound designers and music hackers. Compose, automate, and control sound — in plain text."
6
6
  license = "MIT"
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  ## 🎼 Devalang, by **Devaloop Labs**
20
20
 
21
- 🎶 Compose music with code — simple, structured, sonic.
21
+ 🎶 Compose music with code — structured, expressive, and fast.
22
22
 
23
23
  Devalang is a tiny domain-specific language (DSL) for music makers, sound designers, and audio hackers.
24
24
  Compose loops, control samples, render and play audio — all in clean, readable text.
@@ -27,11 +27,13 @@ Compose loops, control samples, render and play audio — all in clean, readable
27
27
 
28
28
  From studio sketches to live sets, Devalang gives you rhythmic control — with the elegance of code.
29
29
 
30
- > 🚧 **v0.0.1-alpha.5 Notice** 🚧
30
+ > 🚧 **v0.0.1-alpha.8 Notice** 🚧
31
31
  >
32
32
  > NEW: Devalang VSCode extension is now available !
33
33
  > [Get it here](https://marketplace.visualstudio.com/items?itemName=devaloop.devalang-vscode).
34
34
  >
35
+ > NEW: Devalang supports conditional statements (`if`, `else`, `else if`) for more dynamic compositions !
36
+ >
35
37
  > Currently, Devalang CLI is only available for **Windows**.
36
38
  > Linux and macOS binaries will be added in future releases via cross-platform builds.
37
39
 
@@ -45,6 +47,15 @@ From studio sketches to live sets, Devalang gives you rhythmic control — with
45
47
  - [🎨 Prettier Plugin](https://www.npmjs.com/package/@devaloop/prettier-plugin-devalang)
46
48
  - [🌐 Project Website](https://devalang.com)
47
49
 
50
+ ## ❓ Why Devalang?
51
+
52
+ - 🎹 Prototype audio ideas without opening a DAW
53
+ - 💻 Integrate sound into code-based workflows
54
+ - 🎛️ Control audio parameters through readable syntax
55
+ - 🧪 Build musical logic with variables and conditions
56
+
57
+ > Producer, coder, or both — Devalang gives you musical structure, instantly.
58
+
48
59
  ## 🚀 Features
49
60
 
50
61
  - 🎵 **Audio Engine**: Integrated audio playback and rendering
@@ -53,6 +64,8 @@ From studio sketches to live sets, Devalang gives you rhythmic control — with
53
64
  - 🔢 **Basic data types**: strings, numbers, booleans, maps, arrays
54
65
  - 👁️ **Watch mode** for `build`, `check` and `play` commands
55
66
  - 📂 **Project templates** for quick setup
67
+ - 🎛️ **Custom samples**: easily load and trigger your own audio files
68
+ - 🔄 **Looping and grouping**: create complex patterns with ease
56
69
 
57
70
  ## 📆 Installation
58
71
 
@@ -63,13 +76,13 @@ From studio sketches to live sets, Devalang gives you rhythmic control — with
63
76
  Install the package globally (NPM)
64
77
 
65
78
  ```bash
66
- npm install -g @devaloop/devalang
79
+ npm install -g @devaloop/devalang@latest
67
80
  ```
68
81
 
69
82
  Usage without install (NPX)
70
83
 
71
84
  ```bash
72
- npx @devaloop/devalang <command>
85
+ npx @devaloop/devalang@latest
73
86
  ```
74
87
 
75
88
  ### For contributors
@@ -78,10 +91,11 @@ npx @devaloop/devalang <command>
78
91
  > - ⚠️ Requires [Rust 1.70+](https://www.rust-lang.org/learn/get-started#installing-rust)
79
92
 
80
93
  ```bash
81
- > git clone https://github.com/devaloop-labs/devalang.git
82
- > cd devalang
83
- > npm install
84
- > cargo install --path .
94
+ git clone https://github.com/devaloop-labs/devalang.git
95
+
96
+ cd devalang
97
+
98
+ npm install
85
99
  ```
86
100
 
87
101
  Development usage (you can customize arguments in package.json)
@@ -89,6 +103,7 @@ Development usage (you can customize arguments in package.json)
89
103
  ```bash
90
104
  # For syntax checking test
91
105
  npm run rust:dev:check
106
+
92
107
  # For building test
93
108
  npm run rust:dev:build
94
109
  ```
@@ -182,9 +197,12 @@ group myGroup:
182
197
  call myGroup
183
198
 
184
199
  # Will be executed in parallel (concurrently)
200
+ # ⚠️ Note: `spawn` runs the entire group in parallel, but the group’s internal logic remains sequential unless it uses `spawn` internally.
185
201
  # spawn myGroup
186
202
  ```
187
203
 
204
+ > 🧠 Note: `call` and `spawn` only work with `group` blocks. They do not apply to individual samples or other statements.
205
+
188
206
  ```deva
189
207
  # variables.deva
190
208
 
@@ -197,14 +215,15 @@ let kickDuration = 500
197
215
 
198
216
  ## 🧯 Known issues
199
217
 
200
- - No support yet for `if`, `else`, `else if`, `pattern`, `function`, ... statements
218
+ - No smart modules yet, all groups, variables, and samples must be explicitly imported where used
219
+ - No support yet for `pattern`, `function`, ... statements
201
220
  - No support yet for cross-platform builds (Linux, macOS)
202
221
 
203
222
  ## 🧪 Roadmap Highlights
204
223
 
205
224
  For more info, see [docs/ROADMAP.md](./docs/ROADMAP.md)
206
225
 
207
- - ⏳ Other statements (e.g `if`, `function`, ...)
226
+ - ⏳ Other statements (e.g `function`, `pattern`, ...)
208
227
  - ⏳ Cross-platform support (Linux, macOS)
209
228
  - ⏳ More built-in instruments (e.g. snare, hi-hat, etc.)
210
229
 
package/docs/CHANGELOG.md CHANGED
@@ -4,11 +4,31 @@
4
4
 
5
5
  # Changelog
6
6
 
7
- ## Version 0.0.1-alpha.6 (2025-07-11)
7
+ ## Version 0.0.1-alpha.8 (2025-07-12)
8
+
9
+ ### Syntax
10
+
11
+ - Implemented `if` directive to conditionally execute blocks of code.
12
+ - Implemented `else` directive to provide an alternative block of code when the `if` condition is not met.
13
+ - Implemented `else if` directive to provide additional conditions for the `if` directive.
14
+
15
+ ### Core Components
16
+
17
+ - Implemented evaluator for audio statements, to execute conditional statements.
18
+ - Fixed `group` resolution and export issues.
19
+ - Implemented `Global Store` debugger to inspect variables by module for build command.
20
+ - Organized `TokenKind` and `StatementKind` enums for better clarity and maintainability.
21
+ - Refactored audio interpreter to handle the new syntax and directives.
22
+ - Refactored lexer to handle new directives and improve tokenization.
23
+ - Refactored parser to handle new directives and improve parsing logic.
24
+ - Added support for `call` and `spawn` execution of imported groups.
25
+ - Enforced scoped resolution of groups in `spawn` and `call` (must be imported in current module).
26
+
27
+ ## Version 0.0.1-alpha.7 (2025-07-11)
8
28
 
9
29
  ## Examples
10
30
 
11
- - Added example for `group` directive in `examples/group.devalang`.
31
+ - Added examples in `examples` folder (group, loop, variables, index).
12
32
 
13
33
  ## Structure
14
34
 
package/docs/ROADMAP.md CHANGED
@@ -24,8 +24,8 @@ Devalang is a work in progress. Here’s what we’re planning next:
24
24
 
25
25
  ## Upcoming
26
26
 
27
+ - ⏳ **Smart modules**: Let Devalang detect and use groups, samples, and variables without needing to import them manually.
27
28
  - ⏳ **VSCode extension**: Create a VSCode extension for syntax highlighting and code completion.
28
- - ⏳ **Other statements**: Implement `if`, `else`, and other control structures.
29
- - ⏳ **Pattern and group statements**: Add support for `@pattern` and `@group` to organize code.
29
+ - ⏳ **Other statements**: Implement `pattern`, `function`, and other control structures.
30
30
  - ⏳ **Functions**: Add support for defining and calling functions.
31
31
  - ⏳ **Testing**: Expand test coverage for all features.
package/docs/SYNTAX.md CHANGED
@@ -58,7 +58,7 @@ let boolean = false
58
58
  Maps are key-value pairs defined using curly braces. Keys are strings, and values can be of any type (string, number, boolean, map, or array).
59
59
 
60
60
  ```deva
61
- let map = {myKey: 99}
61
+ let map = { myKey: 99 }
62
62
  ```
63
63
 
64
64
  ### Array
@@ -93,13 +93,17 @@ bank 808
93
93
 
94
94
  Modules can be imported and exported to share variables between different files.
95
95
 
96
+ > ⚠️ The import/export system is still experimental and may change in the future.
97
+ >
98
+ > You must explicitly declare imports and exports in each file — Devalang does not automatically detect or resolve them.
99
+
96
100
  Exporting variables from a module :
97
101
 
98
102
  ```deva
99
103
  # exported.deva
100
104
 
101
105
  let exportedIterator = 10
102
- let exportedParams = {drive: 50, decay: 30}
106
+ let exportedParams = { drive: 50, decay: 30 }
103
107
 
104
108
  @export { exportedIterator, exportedParams }
105
109
  ```
@@ -115,7 +119,7 @@ loop exportedIterator:
115
119
  .kick auto exportedParams
116
120
  ```
117
121
 
118
- ### Triggers
122
+ ### Loading Samples
119
123
 
120
124
  You can load your own samples and use them in your music.
121
125
 
@@ -129,7 +133,7 @@ Trigger usage : `.<name> <duration> <params>`
129
133
  .mySample auto {reverb: 50, drive: 25}
130
134
  ```
131
135
 
132
- ### Let variables
136
+ ### Variables
133
137
 
134
138
  Variables are defined using the `let` keyword, followed by the variable name and its value. The value can be of any type (string, number, boolean, map, or array).
135
139
 
@@ -137,7 +141,7 @@ Variables are defined using the `let` keyword, followed by the variable name and
137
141
  let number = 0
138
142
  let boolean = true
139
143
  let string = "string"
140
- let map = {myKey: 200}
144
+ let map = { myKey: 200 }
141
145
  let array = [0, 1, 2]
142
146
  ```
143
147
 
@@ -154,14 +158,40 @@ loop 10:
154
158
 
155
159
  Groups are defined using the `group` keyword, followed by the group name. The body of the group is indented.
156
160
 
161
+ Groups allow you to organize your code into reusable blocks. They can be called or spawned later in the code.
162
+
157
163
  ```deva
158
164
  group myGroup:
159
165
  # ...
160
166
  ```
161
167
 
168
+ ### Conditions
169
+
170
+ Conditions are defined using the `if` keyword, followed by a condition. The body of the condition is indented.
171
+
172
+ ```deva
173
+ if myCondition:
174
+ # ...
175
+ ```
176
+
177
+ You can also use `else` and `else if` for alternative branches:
178
+
179
+ ```deva
180
+ if myCondition:
181
+ # ...
182
+ else if anotherCondition:
183
+ # ...
184
+ else:
185
+ # ...
186
+ ```
187
+
162
188
  ### Calling Groups (Sequential Execution)
163
189
 
164
- Groups can be called using the `call` keyword, which executes the group in sequence.
190
+ Groups can be called using the `call` keyword, which executes only the group in sequence.
191
+
192
+ > ⚠️ `call` only works on `group` declarations. It does not apply to other statements.
193
+
194
+ This executes the entire group in the current execution thread, following a sequential order.
165
195
 
166
196
  ```deva
167
197
  call myGroup
@@ -169,7 +199,11 @@ call myGroup
169
199
 
170
200
  ### Spawning Groups (Parallel Execution)
171
201
 
172
- Groups can be spawned using the `spawn` keyword, which executes the group in parallel.
202
+ Groups can be spawned using the `spawn` keyword, which executes only the group in parallel.
203
+
204
+ > ⚠️ spawn also only works on group declarations. It does not make the group’s content parallel unless it explicitly uses spawn inside.
205
+
206
+ This runs the entire group in a separate execution thread, allowing it to play alongside other actions.
173
207
 
174
208
  ```deva
175
209
  spawn myGroup
package/docs/TODO.md CHANGED
@@ -54,9 +54,9 @@ This is a list of tasks and features to be implemented in Devalang. Note that th
54
54
  - [x] bank
55
55
  - [x] loop
56
56
  - [x] let
57
- - [ ] if
58
- - [ ] else
59
- - [ ] else if
57
+ - [x] if
58
+ - [x] else
59
+ - [x] else if
60
60
 
61
61
  ## Triggers
62
62
 
@@ -0,0 +1,24 @@
1
+ # This file demonstrates the use of conditional blocks in Devalang.
2
+
3
+ @import { myGroup } from "./examples/group.deva"
4
+
5
+ @load "./examples/samples/kick-808.wav" as kickCustom
6
+ @load "./examples/samples/hat-808.wav" as hatCustom
7
+
8
+ group conditionBlock:
9
+ if tempo > 120:
10
+ # Will be executed if the condition is true
11
+ .kickCustom
12
+ else if tempo > 155:
13
+ # Will be executed if the condition is false
14
+ .hatCustom
15
+ else:
16
+ # Following lines will be executed if the condition is neither true or false
17
+
18
+ # This will call myGroup sequentially (in the main thread)
19
+ call myGroup
20
+
21
+ # This will spawn a group in parallel to the main thread (kick + hat simultaneously)
22
+ # spawn myGroup
23
+
24
+ @export { conditionBlock }
@@ -1,8 +1,10 @@
1
1
  # This file demonstrates the use of main features in Devalang.
2
2
 
3
3
  @import { duration, default_bank, params, loopCount, tempo } from "./examples/variables.deva"
4
+ @import { conditionBlock } from "./examples/condition.deva"
4
5
  @import { myGroup } from "./examples/group.deva"
5
6
 
7
+
6
8
  @load "./examples/samples/kick-808.wav" as kickCustom
7
9
  @load "./examples/samples/hat-808.wav" as hatCustom
8
10
 
@@ -10,8 +12,5 @@ bpm tempo
10
12
 
11
13
  bank default_bank
12
14
 
13
- # Will be executed line by line (sequentially)
14
- call myGroup
15
-
16
- # Will be executed in parallel (concurrently)
17
- # spawn myGroup
15
+ # This will call the group sequentially (line by line)
16
+ call conditionBlock
@@ -4,6 +4,6 @@ let duration = auto
4
4
  let default_bank = 808
5
5
  let params = {decay:10, delay:30}
6
6
  let loopCount = 5
7
- let tempo = 155
7
+ let tempo = 115
8
8
 
9
9
  @export { duration, default_bank, params, loopCount, tempo }
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@devaloop/devalang",
3
3
  "private": false,
4
- "version": "0.0.1-alpha.7",
4
+ "version": "0.0.1-alpha.8",
5
5
  "description": "Write music like code. Devalang is a domain-specific language (DSL) for sound designers and music hackers. Compose, automate, and control sound — in plain text.",
6
6
  "main": "out-tsc/index.js",
7
7
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.0.1-alpha.7",
2
+ "version": "0.0.1-alpha.8",
3
3
  "channel": "alpha",
4
- "lastCommit": "81db490b7d9cc92f0512b49cf3dbaeb74cf57de1",
5
- "build": 6
4
+ "lastCommit": "fdb440173acc82227067864903aabc83e1d1570c",
5
+ "build": 7
6
6
  }
package/rust/cli/build.rs CHANGED
@@ -2,7 +2,11 @@ use crate::{
2
2
  config::Config,
3
3
  core::{
4
4
  builder::Builder,
5
- debugger::{ lexer::write_lexer_log_file, preprocessor::write_preprocessor_log_file },
5
+ debugger::{
6
+ lexer::write_lexer_log_file,
7
+ preprocessor::write_preprocessor_log_file,
8
+ store::write_store_log_file,
9
+ },
6
10
  preprocessor::loader::ModuleLoader,
7
11
  store::global::GlobalStore,
8
12
  utils::path::{ find_entry_file, normalize_path },
@@ -113,6 +117,7 @@ fn begin_build(entry: String, output: String) {
113
117
  "resolved_statements.log",
114
118
  modules_statements.clone()
115
119
  );
120
+ write_store_log_file(&normalized_output_dir, "global_store.log", global_store.modules.clone());
116
121
 
117
122
  // SECTION Building AST and Audio
118
123
  let builder = Builder::new();
@@ -0,0 +1,31 @@
1
+ use crate::core::{ shared::value::Value, store::variable::VariableTable };
2
+
3
+ pub fn evaluate_condition_string(expr: &str, vars: &VariableTable) -> bool {
4
+ let tokens: Vec<&str> = expr.split_whitespace().collect();
5
+ if tokens.len() != 3 {
6
+ return false;
7
+ }
8
+
9
+ let left = tokens[0];
10
+ let op = tokens[1];
11
+ let right = tokens[2];
12
+
13
+ let left_val = match vars.get(left) {
14
+ Some(Value::Number(n)) => *n,
15
+ _ => {
16
+ return false;
17
+ }
18
+ };
19
+
20
+ let right_val: f32 = right.parse().unwrap_or(0.0);
21
+
22
+ match op {
23
+ ">" => left_val > right_val,
24
+ "<" => left_val < right_val,
25
+ ">=" => left_val >= right_val,
26
+ "<=" => left_val <= right_val,
27
+ "==" => (left_val - right_val).abs() < f32::EPSILON,
28
+ "!=" => (left_val - right_val).abs() > f32::EPSILON,
29
+ _ => false,
30
+ }
31
+ }
@@ -0,0 +1,42 @@
1
+ use crate::core::{
2
+ audio::{ engine::AudioEngine, interpreter::{ driver::execute_audio_block } },
3
+ parser::statement::{ Statement, StatementKind },
4
+ shared::{ duration::Duration, value::Value },
5
+ store::variable::VariableTable,
6
+ };
7
+
8
+ pub fn interprete_call_statement(
9
+ stmt: &Statement,
10
+ audio_engine: AudioEngine,
11
+ variable_table: VariableTable,
12
+ base_bpm: f32,
13
+ base_duration: f32,
14
+ max_end_time: f32,
15
+ cursor_time: f32
16
+ ) -> (AudioEngine, f32, f32) {
17
+ if let Value::String(identifier) = &stmt.value {
18
+ if let Some(Value::Map(map)) = variable_table.clone().get(identifier) {
19
+ if let Some(Value::Block(block)) = map.get("body") {
20
+ let (eng, _, end_time) = execute_audio_block(
21
+ audio_engine,
22
+ variable_table,
23
+ block.clone(),
24
+ base_bpm,
25
+ base_duration,
26
+ max_end_time,
27
+ cursor_time
28
+ );
29
+
30
+ return (eng, max_end_time.max(end_time), end_time);
31
+ } else {
32
+ eprintln!("❌ 'body' must be a block");
33
+ }
34
+ } else {
35
+ eprintln!("❌ Call target '{}' not found or invalid", identifier);
36
+ }
37
+ } else {
38
+ eprintln!("❌ Invalid call statement: expected string identifier");
39
+ }
40
+
41
+ (audio_engine, max_end_time, cursor_time)
42
+ }
@@ -0,0 +1,65 @@
1
+ use crate::core::{
2
+ audio::{ engine::AudioEngine, evaluator::evaluate_condition_string, interpreter::driver::execute_audio_block },
3
+ parser::statement::Statement,
4
+ shared::value::Value,
5
+ store::variable::VariableTable,
6
+ };
7
+
8
+ pub fn interprete_condition_statement(
9
+ stmt: &Statement,
10
+ audio_engine: AudioEngine,
11
+ variable_table: VariableTable,
12
+ base_bpm: f32,
13
+ base_duration: f32,
14
+ max_end_time: f32,
15
+ cursor_time: f32
16
+ ) -> (AudioEngine, f32, f32) {
17
+ let mut engine = audio_engine.clone();
18
+ let mut vars = variable_table.clone();
19
+ let mut cur_time = cursor_time;
20
+ let mut max_time = max_end_time;
21
+
22
+ let mut current = stmt.value.clone();
23
+
24
+ loop {
25
+ let Value::Map(map) = current else {
26
+ break;
27
+ };
28
+
29
+ let should_execute = match map.get("condition") {
30
+ Some(Value::Boolean(b)) => *b,
31
+ Some(Value::String(expr)) => evaluate_condition_string(expr, &vars),
32
+ Some(_) => false,
33
+ None => true,
34
+ };
35
+
36
+ if should_execute {
37
+ if let Some(Value::Block(block)) = map.get("body") {
38
+ let (new_engine, _, new_max) = execute_audio_block(
39
+ engine,
40
+ vars,
41
+ block.clone(),
42
+ base_bpm,
43
+ base_duration,
44
+ max_time,
45
+ cur_time
46
+ );
47
+ return (new_engine, new_max, new_max);
48
+ } else {
49
+ break;
50
+ }
51
+ }
52
+
53
+ // Advance to the next condition
54
+ match map.get("next") {
55
+ Some(Value::Map(next_map)) => {
56
+ current = Value::Map(next_map.clone());
57
+ }
58
+ _ => {
59
+ break;
60
+ }
61
+ }
62
+ }
63
+
64
+ (audio_engine, max_end_time, cursor_time)
65
+ }