@devaloop/devalang 0.0.1-alpha.8 → 0.0.1-beta.1

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