@devaloop/devalang 0.0.1-alpha.15 → 0.0.1-alpha.16-hotfix.0

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 (173) hide show
  1. package/.devalang +2 -0
  2. package/.github/workflows/ci.yml +92 -0
  3. package/Cargo.toml +60 -58
  4. package/README.md +1 -1
  5. package/docs/CHANGELOG.md +34 -1
  6. package/docs/CONTRIBUTING.md +101 -1
  7. package/docs/ROADMAP.md +1 -1
  8. package/docs/TODO.md +1 -1
  9. package/examples/automation.deva +1 -3
  10. package/examples/bank.deva +4 -4
  11. package/examples/events.deva +12 -0
  12. package/examples/function.deva +4 -4
  13. package/examples/index.deva +3 -5
  14. package/examples/loop.deva +5 -11
  15. package/examples/pattern.deva +8 -0
  16. package/examples/plugin.deva +12 -11
  17. package/examples/variables.deva +1 -1
  18. package/out-tsc/bin/index.js +51 -7
  19. package/out-tsc/index.js +3 -1
  20. package/out-tsc/scripts/postbuild.js +9 -10
  21. package/out-tsc/scripts/postinstall.js +49 -0
  22. package/package.json +12 -4
  23. package/project-version.json +3 -3
  24. package/rust/cli/bank.rs +462 -455
  25. package/rust/cli/build.rs +252 -199
  26. package/rust/cli/check.rs +221 -180
  27. package/rust/cli/driver.rs +297 -292
  28. package/rust/cli/generator.rs +1 -0
  29. package/rust/cli/init.rs +87 -79
  30. package/rust/cli/install.rs +35 -32
  31. package/rust/cli/login.rs +127 -134
  32. package/rust/cli/mod.rs +13 -11
  33. package/rust/cli/play.rs +1123 -218
  34. package/rust/cli/telemetry.rs +19 -0
  35. package/rust/cli/template.rs +69 -57
  36. package/rust/cli/update.rs +6 -4
  37. package/rust/common/api.rs +5 -5
  38. package/rust/common/mod.rs +3 -3
  39. package/rust/config/driver.rs +118 -94
  40. package/rust/config/loader.rs +165 -156
  41. package/rust/config/mod.rs +4 -2
  42. package/rust/config/settings.rs +91 -0
  43. package/rust/config/stats.rs +257 -0
  44. package/rust/core/audio/engine.rs +696 -659
  45. package/rust/core/audio/evaluator.rs +263 -132
  46. package/rust/core/audio/interpreter/arrow_call.rs +198 -187
  47. package/rust/core/audio/interpreter/call.rs +98 -95
  48. package/rust/core/audio/interpreter/condition.rs +70 -71
  49. package/rust/core/audio/interpreter/driver.rs +487 -231
  50. package/rust/core/audio/interpreter/function.rs +26 -21
  51. package/rust/core/audio/interpreter/let_.rs +38 -26
  52. package/rust/core/audio/interpreter/load.rs +18 -18
  53. package/rust/core/audio/interpreter/loop_.rs +113 -106
  54. package/rust/core/audio/interpreter/mod.rs +14 -14
  55. package/rust/core/audio/interpreter/sleep.rs +27 -28
  56. package/rust/core/audio/interpreter/spawn.rs +105 -102
  57. package/rust/core/audio/interpreter/tempo.rs +19 -16
  58. package/rust/core/audio/interpreter/trigger.rs +239 -210
  59. package/rust/core/audio/loader/mod.rs +1 -1
  60. package/rust/core/audio/loader/trigger.rs +100 -94
  61. package/rust/core/audio/mod.rs +7 -7
  62. package/rust/core/audio/player.rs +64 -64
  63. package/rust/core/audio/renderer.rs +56 -53
  64. package/rust/core/audio/special/easing.rs +189 -120
  65. package/rust/core/audio/special/env.rs +43 -41
  66. package/rust/core/audio/special/math.rs +102 -92
  67. package/rust/core/audio/special/mod.rs +9 -9
  68. package/rust/core/audio/special/modulator.rs +143 -120
  69. package/rust/core/builder/mod.rs +80 -85
  70. package/rust/core/debugger/lexer.rs +27 -27
  71. package/rust/core/debugger/mod.rs +24 -23
  72. package/rust/core/debugger/module.rs +55 -47
  73. package/rust/core/debugger/preprocessor.rs +27 -27
  74. package/rust/core/debugger/store.rs +40 -39
  75. package/rust/core/error/mod.rs +80 -69
  76. package/rust/core/lexer/handler/arrow.rs +82 -82
  77. package/rust/core/lexer/handler/at.rs +21 -21
  78. package/rust/core/lexer/handler/brace.rs +41 -41
  79. package/rust/core/lexer/handler/colon.rs +21 -21
  80. package/rust/core/lexer/handler/comment.rs +30 -30
  81. package/rust/core/lexer/handler/dot.rs +21 -21
  82. package/rust/core/lexer/handler/driver.rs +337 -292
  83. package/rust/core/lexer/handler/identifier.rs +46 -43
  84. package/rust/core/lexer/handler/indent.rs +66 -66
  85. package/rust/core/lexer/handler/mod.rs +16 -16
  86. package/rust/core/lexer/handler/newline.rs +23 -23
  87. package/rust/core/lexer/handler/number.rs +31 -31
  88. package/rust/core/lexer/handler/operator.rs +46 -46
  89. package/rust/core/lexer/handler/parenthesis.rs +41 -41
  90. package/rust/core/lexer/handler/slash.rs +21 -21
  91. package/rust/core/lexer/handler/string.rs +63 -63
  92. package/rust/core/lexer/mod.rs +54 -51
  93. package/rust/core/lexer/token.rs +97 -94
  94. package/rust/core/mod.rs +11 -11
  95. package/rust/core/parser/driver.rs +513 -490
  96. package/rust/core/parser/handler/arrow_call.rs +233 -227
  97. package/rust/core/parser/handler/at.rs +245 -162
  98. package/rust/core/parser/handler/bank.rs +94 -69
  99. package/rust/core/parser/handler/condition.rs +80 -74
  100. package/rust/core/parser/handler/dot.rs +143 -135
  101. package/rust/core/parser/handler/identifier/automate.rs +257 -194
  102. package/rust/core/parser/handler/identifier/call.rs +91 -88
  103. package/rust/core/parser/handler/identifier/emit.rs +66 -0
  104. package/rust/core/parser/handler/identifier/function.rs +100 -91
  105. package/rust/core/parser/handler/identifier/group.rs +85 -75
  106. package/rust/core/parser/handler/identifier/let_.rs +158 -143
  107. package/rust/core/parser/handler/identifier/mod.rs +54 -56
  108. package/rust/core/parser/handler/identifier/on.rs +98 -0
  109. package/rust/core/parser/handler/identifier/print.rs +52 -29
  110. package/rust/core/parser/handler/identifier/sleep.rs +36 -33
  111. package/rust/core/parser/handler/identifier/spawn.rs +91 -88
  112. package/rust/core/parser/handler/identifier/synth.rs +65 -63
  113. package/rust/core/parser/handler/loop_.rs +170 -89
  114. package/rust/core/parser/handler/mod.rs +8 -8
  115. package/rust/core/parser/handler/tempo.rs +53 -47
  116. package/rust/core/parser/mod.rs +4 -4
  117. package/rust/core/parser/statement.rs +142 -113
  118. package/rust/core/plugin/loader.rs +123 -48
  119. package/rust/core/plugin/mod.rs +2 -1
  120. package/rust/core/plugin/runner.rs +296 -0
  121. package/rust/core/preprocessor/loader.rs +515 -326
  122. package/rust/core/preprocessor/mod.rs +4 -4
  123. package/rust/core/preprocessor/module.rs +60 -58
  124. package/rust/core/preprocessor/processor.rs +99 -101
  125. package/rust/core/preprocessor/resolver/bank.rs +51 -48
  126. package/rust/core/preprocessor/resolver/call.rs +100 -101
  127. package/rust/core/preprocessor/resolver/condition.rs +97 -97
  128. package/rust/core/preprocessor/resolver/driver.rs +310 -280
  129. package/rust/core/preprocessor/resolver/function.rs +69 -68
  130. package/rust/core/preprocessor/resolver/group.rs +96 -91
  131. package/rust/core/preprocessor/resolver/let_.rs +32 -28
  132. package/rust/core/preprocessor/resolver/loop_.rs +320 -121
  133. package/rust/core/preprocessor/resolver/mod.rs +15 -15
  134. package/rust/core/preprocessor/resolver/spawn.rs +76 -73
  135. package/rust/core/preprocessor/resolver/synth.rs +56 -50
  136. package/rust/core/preprocessor/resolver/tempo.rs +50 -49
  137. package/rust/core/preprocessor/resolver/trigger.rs +113 -115
  138. package/rust/core/preprocessor/resolver/value.rs +81 -81
  139. package/rust/core/shared/duration.rs +9 -9
  140. package/rust/core/shared/mod.rs +3 -3
  141. package/rust/core/shared/value.rs +35 -32
  142. package/rust/core/store/function.rs +34 -34
  143. package/rust/core/store/global.rs +55 -38
  144. package/rust/core/store/mod.rs +5 -5
  145. package/rust/core/store/variable.rs +37 -34
  146. package/rust/core/utils/mod.rs +2 -2
  147. package/rust/core/utils/path.rs +37 -31
  148. package/rust/core/utils/validation.rs +35 -36
  149. package/rust/installer/addon.rs +84 -80
  150. package/rust/installer/bank.rs +62 -65
  151. package/rust/installer/mod.rs +5 -5
  152. package/rust/installer/plugin.rs +54 -55
  153. package/rust/installer/utils.rs +56 -56
  154. package/rust/lib.rs +156 -164
  155. package/rust/main.rs +250 -144
  156. package/rust/utils/error.rs +200 -51
  157. package/rust/utils/file.rs +38 -35
  158. package/rust/utils/first_usage.rs +76 -0
  159. package/rust/utils/logger.rs +195 -143
  160. package/rust/utils/mod.rs +9 -7
  161. package/rust/utils/signature.rs +19 -17
  162. package/rust/utils/spinner.rs +22 -19
  163. package/rust/utils/telemetry.rs +292 -0
  164. package/rust/utils/watcher.rs +34 -33
  165. package/templates/minimal/README.md +97 -121
  166. package/templates/welcome/README.md +97 -121
  167. package/typescript/bin/index.ts +19 -5
  168. package/typescript/index.ts +3 -1
  169. package/typescript/scripts/postbuild.ts +10 -6
  170. package/typescript/scripts/postinstall.ts +56 -0
  171. package/typescript/scripts/version/bump.ts +0 -1
  172. package/typescript/scripts/version/index.ts +0 -1
  173. package/out-tsc/bin/devalang.exe +0 -0
@@ -1,231 +1,487 @@
1
- use rayon::prelude::*;
2
-
3
- use crate::{core::{
4
- audio::{
5
- engine::AudioEngine,
6
- interpreter::{
7
- arrow_call::interprete_call_arrow_statement,
8
- call::interprete_call_statement,
9
- function::interprete_function_statement,
10
- let_::interprete_let_statement,
11
- load::interprete_load_statement,
12
- loop_::interprete_loop_statement,
13
- sleep::interprete_sleep_statement,
14
- spawn::interprete_spawn_statement,
15
- tempo::interprete_tempo_statement,
16
- trigger::interprete_trigger_statement,
17
- },
18
- }, parser::statement::{ Statement, StatementKind }, shared::value::Value, store::{ function::FunctionTable, global::GlobalStore, variable::VariableTable }
19
- }, utils::logger::{LogLevel, Logger}};
20
-
21
- pub fn run_audio_program(
22
- statements: &Vec<Statement>,
23
- audio_engine: &mut AudioEngine,
24
- _entry: String,
25
- _output: String,
26
- _module_variables: VariableTable,
27
- _module_functions: FunctionTable,
28
- global_store: &mut GlobalStore
29
- ) -> (f32, f32) {
30
- let base_bpm = 120.0;
31
- let base_duration = 60.0 / base_bpm;
32
-
33
- let (max_end_time, cursor_time) = execute_audio_block(
34
- audio_engine,
35
- global_store,
36
- global_store.variables.clone(),
37
- global_store.functions.clone(),
38
- &statements,
39
- base_bpm,
40
- base_duration,
41
- 0.0,
42
- 0.0
43
- );
44
-
45
- (max_end_time, cursor_time)
46
- }
47
-
48
- pub fn execute_audio_block(
49
- audio_engine: &mut AudioEngine,
50
- global_store: &GlobalStore,
51
- mut variable_table: VariableTable,
52
- mut functions_table: FunctionTable,
53
- statements: &[Statement],
54
- mut base_bpm: f32,
55
- mut base_duration: f32,
56
- mut max_end_time: f32,
57
- mut cursor_time: f32
58
- ) -> (f32, f32) {
59
- let (spawns, others): (Vec<_>, Vec<_>) = statements
60
- .iter()
61
- .partition(|stmt| matches!(stmt.kind, StatementKind::Spawn { .. }));
62
-
63
- // Execute sequential statements first
64
- for stmt in others {
65
- match &stmt.kind {
66
- StatementKind::Load { .. } => {
67
- if let Some(new_table) = interprete_load_statement(&stmt, &mut variable_table) {
68
- variable_table = new_table;
69
- }
70
- }
71
- StatementKind::Let { .. } => {
72
- if let Some(new_table) = interprete_let_statement(&stmt, &mut variable_table) {
73
- variable_table = new_table;
74
- }
75
- }
76
- StatementKind::Function { .. } => {
77
- if let Some(new_functions) =
78
- interprete_function_statement(&stmt, &mut functions_table)
79
- {
80
- functions_table = new_functions;
81
- }
82
- }
83
- StatementKind::Tempo => {
84
- if let Some((new_bpm, new_duration)) = interprete_tempo_statement(&stmt) {
85
- base_bpm = new_bpm;
86
- base_duration = new_duration;
87
- }
88
- }
89
- StatementKind::Trigger { .. } => {
90
- if
91
- let Some((new_cursor, new_max, _)) = interprete_trigger_statement(
92
- &stmt,
93
- audio_engine,
94
- &variable_table,
95
- base_duration,
96
- cursor_time,
97
- max_end_time
98
- )
99
- {
100
- cursor_time = new_cursor;
101
- max_end_time = new_max;
102
- }
103
- }
104
- StatementKind::Sleep => {
105
- let (new_cursor, new_max) = interprete_sleep_statement(
106
- &stmt,
107
- cursor_time,
108
- max_end_time
109
- );
110
- cursor_time = new_cursor;
111
- max_end_time = new_max;
112
- }
113
- StatementKind::Loop => {
114
- let (new_max, new_cursor) = interprete_loop_statement(
115
- &stmt,
116
- audio_engine,
117
- global_store,
118
- &variable_table,
119
- &functions_table,
120
- base_bpm,
121
- base_duration,
122
- max_end_time,
123
- cursor_time
124
- );
125
- cursor_time = new_cursor;
126
- max_end_time = new_max;
127
- }
128
- StatementKind::Call { .. } => {
129
- let (new_max, _) = interprete_call_statement(
130
- &stmt,
131
- audio_engine,
132
- &variable_table,
133
- &functions_table,
134
- global_store,
135
- base_bpm,
136
- base_duration,
137
- max_end_time,
138
- cursor_time,
139
- );
140
- cursor_time = new_max;
141
- max_end_time = new_max;
142
- }
143
- StatementKind::ArrowCall { .. } => {
144
- let (new_max, new_cursor) = interprete_call_arrow_statement(
145
- &stmt,
146
- audio_engine,
147
- &variable_table,
148
- base_bpm,
149
- base_duration,
150
- &mut max_end_time,
151
- Some(&mut cursor_time),
152
- true
153
- );
154
-
155
- cursor_time = new_cursor;
156
-
157
- if new_max > max_end_time {
158
- max_end_time = new_max;
159
- }
160
- }
161
- StatementKind::Automate { .. } => {
162
- if let Some(new_table) = crate::core::audio::interpreter::automate::interprete_automate_statement(&stmt, &mut variable_table) {
163
- variable_table = new_table;
164
- }
165
- }
166
- StatementKind::Print => {
167
- // Print debug output; if the string contains special expressions, evaluate them.
168
- let logger = Logger::new();
169
- match &stmt.value {
170
- Value::String(s) => {
171
- let bpm = if let Some(Value::Number(n)) = variable_table.get("bpm") { *n } else { 120.0 };
172
- let beat = if let Some(Value::Number(n)) = variable_table.get("beat") { *n } else { 0.0 };
173
- // If the string is exactly a variable name, print its value
174
- if let Some(val) = variable_table.get(&s) {
175
- logger.log_message(LogLevel::Print, &format!("{:?}", val));
176
- } else if s.contains("$env") || s.contains("$math") || s.parse::<f32>().is_ok() {
177
- let v = crate::core::audio::evaluator::evaluate_rhs_into_value(s, &variable_table, bpm, beat);
178
- match v { Value::Number(n) => logger.log_message(LogLevel::Print, &format!("{}", n)), _ => logger.log_message(LogLevel::Print, s) }
179
- } else {
180
- logger.log_message(LogLevel::Print, s)
181
- }
182
- }
183
- Value::Identifier(name) => {
184
- if let Some(val) = variable_table.get(name) {
185
- match val {
186
- Value::Number(n) => logger.log_message(LogLevel::Print, &format!("{}", n)),
187
- Value::String(s) => logger.log_message(LogLevel::Print, s),
188
- Value::Boolean(b) => logger.log_message(LogLevel::Print, &format!("{}", b)),
189
- other => logger.log_message(LogLevel::Print, &format!("{:?}", other)),
190
- }
191
- } else {
192
- logger.log_message(LogLevel::Print, name)
193
- }
194
- }
195
- v => logger.log_message(LogLevel::Print, &format!("{:?}", v)),
196
- }
197
- }
198
- _ => {}
199
- }
200
- }
201
-
202
- // Execute parallel spawns (collect results)
203
- let spawn_results: Vec<(AudioEngine, f32)> = spawns
204
- .par_iter()
205
- .map(|stmt| {
206
- let mut local_engine = AudioEngine::new(audio_engine.module_name.clone());
207
- let (spawn_max, _) = interprete_spawn_statement(
208
- stmt,
209
- &mut local_engine,
210
- &variable_table,
211
- &functions_table,
212
- global_store,
213
- base_bpm,
214
- base_duration,
215
- 0.0,
216
- 0.0
217
- );
218
- (local_engine, spawn_max)
219
- })
220
- .collect();
221
-
222
- // Finally, merge results from all spawns
223
- for (local_engine, spawn_max) in spawn_results {
224
- audio_engine.merge_with(local_engine);
225
- if spawn_max > max_end_time {
226
- max_end_time = spawn_max;
227
- }
228
- }
229
-
230
- (max_end_time.max(cursor_time), cursor_time)
231
- }
1
+ use rayon::prelude::*;
2
+
3
+ use crate::{
4
+ core::{
5
+ audio::{
6
+ engine::AudioEngine,
7
+ interpreter::{
8
+ arrow_call::interprete_call_arrow_statement, call::interprete_call_statement,
9
+ function::interprete_function_statement, let_::interprete_let_statement,
10
+ load::interprete_load_statement, loop_::interprete_loop_statement,
11
+ sleep::interprete_sleep_statement, spawn::interprete_spawn_statement,
12
+ tempo::interprete_tempo_statement, trigger::interprete_trigger_statement,
13
+ },
14
+ },
15
+ parser::statement::{Statement, StatementKind},
16
+ shared::value::Value,
17
+ store::{function::FunctionTable, global::GlobalStore, variable::VariableTable},
18
+ },
19
+ utils::logger::{LogLevel, Logger},
20
+ };
21
+
22
+ pub fn run_audio_program(
23
+ statements: &Vec<Statement>,
24
+ audio_engine: &mut AudioEngine,
25
+ _entry: String,
26
+ _output: String,
27
+ _module_variables: VariableTable,
28
+ _module_functions: FunctionTable,
29
+ global_store: &mut GlobalStore,
30
+ ) -> (f32, f32) {
31
+ let base_bpm = 120.0;
32
+ let base_duration = 60.0 / base_bpm;
33
+
34
+ let (max_end_time, cursor_time) = execute_audio_block(
35
+ audio_engine,
36
+ global_store,
37
+ global_store.variables.clone(),
38
+ global_store.functions.clone(),
39
+ &statements,
40
+ base_bpm,
41
+ base_duration,
42
+ 0.0,
43
+ 0.0,
44
+ );
45
+
46
+ (max_end_time, cursor_time)
47
+ }
48
+
49
+ pub fn execute_audio_block(
50
+ audio_engine: &mut AudioEngine,
51
+ global_store: &GlobalStore,
52
+ mut variable_table: VariableTable,
53
+ mut functions_table: FunctionTable,
54
+ statements: &[Statement],
55
+ mut base_bpm: f32,
56
+ mut base_duration: f32,
57
+ mut max_end_time: f32,
58
+ mut cursor_time: f32,
59
+ ) -> (f32, f32) {
60
+ // Track nested depth of execute_audio_block to avoid scheduling periodic events multiple times
61
+ let current_depth = match variable_table.get("__depth") {
62
+ Some(Value::Number(n)) => *n,
63
+ _ => 0.0,
64
+ };
65
+ variable_table.set("__depth".to_string(), Value::Number(current_depth + 1.0));
66
+ let (spawns, others): (Vec<_>, Vec<_>) = statements
67
+ .iter()
68
+ .partition(|stmt| matches!(stmt.kind, StatementKind::Spawn { .. }));
69
+
70
+ // Execute sequential statements first
71
+ for stmt in others {
72
+ match &stmt.kind {
73
+ StatementKind::Load { .. } => {
74
+ if let Some(new_table) = interprete_load_statement(&stmt, &mut variable_table) {
75
+ variable_table = new_table;
76
+ }
77
+ }
78
+ StatementKind::On { .. } => {
79
+ // already registered in global store during parsing; nothing to do at runtime
80
+ }
81
+ StatementKind::Emit { event, payload: _ } => {
82
+ if let Some(handlers) = global_store.get_event_handlers(event) {
83
+ for h in handlers {
84
+ if let StatementKind::On {
85
+ event: _,
86
+ args,
87
+ body,
88
+ } = &h.kind
89
+ {
90
+ // Create a derived variable table with event context
91
+ let mut vt = variable_table.clone();
92
+ let mut ctx = std::collections::HashMap::new();
93
+ ctx.insert("name".to_string(), Value::String(event.clone()));
94
+ if let Some(arg_list) = args.clone() {
95
+ ctx.insert("args".to_string(), Value::Array(arg_list));
96
+ }
97
+ // Attach payload if any on the Emit statement value
98
+ ctx.insert("payload".to_string(), stmt.value.clone());
99
+ vt.set("event".to_string(), Value::Map(ctx));
100
+ // Mark we're inside an event handler to avoid re-scheduling periodic events recursively
101
+ vt.set("__in_event".to_string(), Value::Boolean(true));
102
+
103
+ let (_max, _cursor) = execute_audio_block(
104
+ audio_engine,
105
+ global_store,
106
+ vt,
107
+ functions_table.clone(),
108
+ body,
109
+ base_bpm,
110
+ base_duration,
111
+ max_end_time,
112
+ cursor_time,
113
+ );
114
+ }
115
+ }
116
+ }
117
+ }
118
+ StatementKind::Let { .. } => {
119
+ if let Some(new_table) = interprete_let_statement(&stmt, &mut variable_table) {
120
+ variable_table = new_table;
121
+ }
122
+ }
123
+ StatementKind::Function { .. } => {
124
+ if let Some(new_functions) =
125
+ interprete_function_statement(&stmt, &mut functions_table)
126
+ {
127
+ functions_table = new_functions;
128
+ }
129
+ }
130
+ StatementKind::Tempo => {
131
+ if let Some((new_bpm, new_duration)) = interprete_tempo_statement(&stmt) {
132
+ base_bpm = new_bpm;
133
+ base_duration = new_duration;
134
+ }
135
+ }
136
+ StatementKind::Trigger { .. } => {
137
+ if let Some((new_cursor, new_max, _)) = interprete_trigger_statement(
138
+ &stmt,
139
+ audio_engine,
140
+ &variable_table,
141
+ base_duration,
142
+ cursor_time,
143
+ max_end_time,
144
+ ) {
145
+ cursor_time = new_cursor;
146
+ max_end_time = new_max;
147
+ }
148
+ }
149
+ StatementKind::Sleep => {
150
+ let (new_cursor, new_max) =
151
+ interprete_sleep_statement(&stmt, cursor_time, max_end_time);
152
+ cursor_time = new_cursor;
153
+ max_end_time = new_max;
154
+ }
155
+ StatementKind::Loop => {
156
+ let (new_max, new_cursor) = interprete_loop_statement(
157
+ &stmt,
158
+ audio_engine,
159
+ global_store,
160
+ &variable_table,
161
+ &functions_table,
162
+ base_bpm,
163
+ base_duration,
164
+ max_end_time,
165
+ cursor_time,
166
+ );
167
+ cursor_time = new_cursor;
168
+ max_end_time = new_max;
169
+ }
170
+ StatementKind::Call { .. } => {
171
+ let (new_max, _) = interprete_call_statement(
172
+ &stmt,
173
+ audio_engine,
174
+ &variable_table,
175
+ &functions_table,
176
+ global_store,
177
+ base_bpm,
178
+ base_duration,
179
+ max_end_time,
180
+ cursor_time,
181
+ );
182
+ cursor_time = new_max;
183
+ max_end_time = new_max;
184
+ }
185
+ StatementKind::ArrowCall { .. } => {
186
+ let (new_max, new_cursor) = interprete_call_arrow_statement(
187
+ &stmt,
188
+ audio_engine,
189
+ &variable_table,
190
+ base_bpm,
191
+ base_duration,
192
+ &mut max_end_time,
193
+ Some(&mut cursor_time),
194
+ true,
195
+ );
196
+
197
+ cursor_time = new_cursor;
198
+
199
+ if new_max > max_end_time {
200
+ max_end_time = new_max;
201
+ }
202
+ }
203
+ StatementKind::Automate { .. } => {
204
+ if let Some(new_table) =
205
+ crate::core::audio::interpreter::automate::interprete_automate_statement(
206
+ &stmt,
207
+ &mut variable_table,
208
+ )
209
+ {
210
+ variable_table = new_table;
211
+ }
212
+ }
213
+ StatementKind::Print => {
214
+ // Only print in real-time mode (during playback), not during offline render.
215
+ let is_realtime = matches!(variable_table.get("__rt"), Some(Value::Boolean(true)));
216
+ if is_realtime {
217
+ let logger = Logger::new();
218
+ match &stmt.value {
219
+ Value::String(s) => {
220
+ let bpm = if let Some(Value::Number(n)) = variable_table.get("bpm") {
221
+ *n
222
+ } else {
223
+ 120.0
224
+ };
225
+ let beat = if let Some(Value::Number(n)) = variable_table.get("beat") {
226
+ *n
227
+ } else {
228
+ 0.0
229
+ };
230
+ // First try JS-like string concatenation: "str " + var + 1 + $env.*
231
+ if let Some(res) =
232
+ crate::core::audio::evaluator::evaluate_string_expression(
233
+ s,
234
+ &variable_table,
235
+ bpm,
236
+ beat,
237
+ )
238
+ {
239
+ logger.log_message(LogLevel::Print, &res);
240
+ } else if let Some(val) = variable_table.get(&s) {
241
+ logger.log_message(LogLevel::Print, &format!("{:?}", val));
242
+ } else if s.contains("$env")
243
+ || s.contains("$math")
244
+ || s.parse::<f32>().is_ok()
245
+ {
246
+ let v = crate::core::audio::evaluator::evaluate_rhs_into_value(
247
+ s,
248
+ &variable_table,
249
+ bpm,
250
+ beat,
251
+ );
252
+ match v {
253
+ Value::Number(n) => {
254
+ logger.log_message(LogLevel::Print, &format!("{}", n))
255
+ }
256
+ _ => logger.log_message(LogLevel::Print, s),
257
+ }
258
+ } else {
259
+ logger.log_message(LogLevel::Print, s)
260
+ }
261
+ }
262
+ Value::Number(n) => {
263
+ logger.log_message(LogLevel::Print, &format!("{}", n));
264
+ }
265
+ Value::Identifier(name) => {
266
+ if let Some(val) = variable_table.get(name) {
267
+ match val {
268
+ Value::Number(n) => {
269
+ logger.log_message(LogLevel::Print, &format!("{}", n))
270
+ }
271
+ Value::String(s) => logger.log_message(LogLevel::Print, s),
272
+ Value::Boolean(b) => {
273
+ logger.log_message(LogLevel::Print, &format!("{}", b))
274
+ }
275
+ other => {
276
+ logger.log_message(LogLevel::Print, &format!("{:?}", other))
277
+ }
278
+ }
279
+ } else {
280
+ logger.log_message(LogLevel::Print, name)
281
+ }
282
+ }
283
+ v => logger.log_message(LogLevel::Print, &format!("{:?}", v)),
284
+ }
285
+ }
286
+ }
287
+ _ => {}
288
+ }
289
+ }
290
+
291
+ // Execute parallel spawns (collect results)
292
+ let spawn_results: Vec<(AudioEngine, f32)> = spawns
293
+ .par_iter()
294
+ .map(|stmt| {
295
+ let mut local_engine = AudioEngine::new(audio_engine.module_name.clone());
296
+ let (spawn_max, _) = interprete_spawn_statement(
297
+ stmt,
298
+ &mut local_engine,
299
+ &variable_table,
300
+ &functions_table,
301
+ global_store,
302
+ base_bpm,
303
+ base_duration,
304
+ 0.0,
305
+ 0.0,
306
+ );
307
+ (local_engine, spawn_max)
308
+ })
309
+ .collect();
310
+
311
+ // Finally, merge results from all spawns
312
+ for (local_engine, spawn_max) in spawn_results {
313
+ audio_engine.merge_with(local_engine);
314
+ if spawn_max > max_end_time {
315
+ max_end_time = spawn_max;
316
+ }
317
+ }
318
+
319
+ // ─────────────────────────────────────────────────────────────
320
+ // Built-in periodic events (e.g., on beat(n), on bar(n))
321
+ // Emit handlers across the timeline up to max_end_time.
322
+ // If no audio was scheduled (max_end_time == 0.0), skip.
323
+ // Don't schedule periodic events if we're already inside an event handler
324
+ let in_event = matches!(variable_table.get("__in_event"), Some(Value::Boolean(true)));
325
+ let depth_is_root = matches!(variable_table.get("__depth"), Some(Value::Number(n)) if (*n - 1.0).abs() < f32::EPSILON);
326
+ if max_end_time > 0.0 && !in_event && depth_is_root {
327
+ if !global_store.events.is_empty() {
328
+ // Beat-based handlers (support "beat" and "$beat")
329
+ for ev_key in ["beat", "$beat"] {
330
+ if let Some(handlers) = global_store.get_event_handlers(ev_key) {
331
+ let mut seen: std::collections::HashSet<(usize, usize, usize)> =
332
+ std::collections::HashSet::new();
333
+ // Default every 1 beat if args missing
334
+ for h in handlers {
335
+ let key = (h.line, h.column, h.indent);
336
+ if !seen.insert(key) {
337
+ continue;
338
+ }
339
+ if let StatementKind::On { event, args, body } = &h.kind {
340
+ let every: f32 = args
341
+ .as_ref()
342
+ .and_then(|v| v.get(0))
343
+ .and_then(|x| match x {
344
+ Value::Number(n) => Some(*n),
345
+ Value::Identifier(s) => {
346
+ // Try to resolve from variables first, fallback to parsing the literal
347
+ match variable_table.get(s) {
348
+ Some(Value::Number(n)) => Some(*n),
349
+ _ => s.parse::<f32>().ok(),
350
+ }
351
+ }
352
+ _ => None,
353
+ })
354
+ .unwrap_or(1.0)
355
+ .max(0.0001);
356
+ let step = base_duration * every;
357
+ // Start from first full bar boundary after t=0
358
+ let mut t = step;
359
+ while t <= max_end_time {
360
+ // Prepare event context
361
+ let mut vt = variable_table.clone();
362
+ let mut ctx = std::collections::HashMap::new();
363
+ ctx.insert("name".to_string(), Value::String(event.clone()));
364
+ if let Some(a) = args.clone() {
365
+ ctx.insert("args".to_string(), Value::Array(a));
366
+ }
367
+ vt.set("event".to_string(), Value::Map(ctx));
368
+ vt.set("beat".to_string(), Value::Number(t / base_duration));
369
+ // Prevent nested scheduling
370
+ vt.set("__in_event".to_string(), Value::Boolean(true));
371
+
372
+ let (_m, _c) = execute_audio_block(
373
+ audio_engine,
374
+ global_store,
375
+ vt,
376
+ functions_table.clone(),
377
+ body,
378
+ base_bpm,
379
+ base_duration,
380
+ max_end_time,
381
+ t,
382
+ );
383
+
384
+ t += step;
385
+ }
386
+ }
387
+ }
388
+ }
389
+ }
390
+
391
+ // Bar-based handlers (default 4/4 time => 4 beats per bar); support "bar" and "$bar"
392
+ for ev_key in ["bar", "$bar"] {
393
+ if let Some(handlers) = global_store.get_event_handlers(ev_key) {
394
+ let mut seen: std::collections::HashSet<(usize, usize, usize)> =
395
+ std::collections::HashSet::new();
396
+ for h in handlers {
397
+ let key = (h.line, h.column, h.indent);
398
+ if !seen.insert(key) {
399
+ continue;
400
+ }
401
+ if let StatementKind::On { event, args, body } = &h.kind {
402
+ let bar_beats = 4.0f32; // TODO: time signature support
403
+ let first_only = args.as_ref().and_then(|v| v.get(0)).is_none();
404
+
405
+ let every_bar: f32 = if first_only {
406
+ 1.0
407
+ } else {
408
+ args.as_ref()
409
+ .and_then(|v| v.get(0))
410
+ .and_then(|x| match x {
411
+ Value::Number(n) => Some(*n),
412
+ Value::Identifier(s) => match variable_table.get(s) {
413
+ Some(Value::Number(n)) => Some(*n),
414
+ _ => s.parse::<f32>().ok(),
415
+ },
416
+ _ => None,
417
+ })
418
+ .unwrap_or(1.0)
419
+ .max(0.0001)
420
+ };
421
+
422
+ let step = base_duration * bar_beats * every_bar;
423
+
424
+ if first_only {
425
+ let t = step; // first full bar after t=0
426
+ if t <= max_end_time {
427
+ let mut vt = variable_table.clone();
428
+ let mut ctx = std::collections::HashMap::new();
429
+ ctx.insert("name".to_string(), Value::String(event.clone()));
430
+ if let Some(a) = args.clone() {
431
+ ctx.insert("args".to_string(), Value::Array(a));
432
+ }
433
+ vt.set("event".to_string(), Value::Map(ctx));
434
+ vt.set("beat".to_string(), Value::Number(t / base_duration));
435
+ // Prevent nested scheduling
436
+ vt.set("__in_event".to_string(), Value::Boolean(true));
437
+
438
+ let (_m, _c) = execute_audio_block(
439
+ audio_engine,
440
+ global_store,
441
+ vt,
442
+ functions_table.clone(),
443
+ body,
444
+ base_bpm,
445
+ base_duration,
446
+ max_end_time,
447
+ t,
448
+ );
449
+ }
450
+ } else {
451
+ let mut t = step; // start from first full bar after t=0
452
+ while t <= max_end_time {
453
+ let mut vt = variable_table.clone();
454
+ let mut ctx = std::collections::HashMap::new();
455
+ ctx.insert("name".to_string(), Value::String(event.clone()));
456
+ if let Some(a) = args.clone() {
457
+ ctx.insert("args".to_string(), Value::Array(a));
458
+ }
459
+ vt.set("event".to_string(), Value::Map(ctx));
460
+ vt.set("beat".to_string(), Value::Number(t / base_duration));
461
+ // Prevent nested scheduling
462
+ vt.set("__in_event".to_string(), Value::Boolean(true));
463
+
464
+ let (_m, _c) = execute_audio_block(
465
+ audio_engine,
466
+ global_store,
467
+ vt,
468
+ functions_table.clone(),
469
+ body,
470
+ base_bpm,
471
+ base_duration,
472
+ max_end_time,
473
+ t,
474
+ );
475
+
476
+ t += step;
477
+ }
478
+ }
479
+ }
480
+ }
481
+ }
482
+ }
483
+ }
484
+ }
485
+
486
+ (max_end_time.max(cursor_time), cursor_time)
487
+ }