@devaloop/devalang 0.0.1-alpha.9 → 0.0.1-beta.2

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 (322) hide show
  1. package/.cargo/config.toml +2 -0
  2. package/.devalang +6 -1
  3. package/.github/workflows/ci.yml +103 -0
  4. package/Cargo.toml +81 -48
  5. package/README.md +137 -154
  6. package/docs/CHANGELOG.md +428 -1
  7. package/docs/CONTRIBUTING.md +101 -0
  8. package/docs/ROADMAP.md +14 -7
  9. package/docs/TODO.md +16 -15
  10. package/examples/automation.deva +42 -0
  11. package/examples/bank.deva +7 -0
  12. package/examples/bus.deva +10 -0
  13. package/examples/duration.deva +9 -0
  14. package/examples/effect.deva +2 -0
  15. package/examples/events.deva +12 -0
  16. package/examples/filter.deva +11 -0
  17. package/examples/function.deva +15 -0
  18. package/examples/index.deva +57 -12
  19. package/examples/lfo.deva +9 -0
  20. package/examples/loop.deva +5 -12
  21. package/examples/pattern.deva +8 -0
  22. package/examples/plugin.deva +16 -0
  23. package/examples/synth.deva +11 -1
  24. package/examples/synth_types.deva +17 -0
  25. package/examples/variables.deva +1 -1
  26. package/out-tsc/bin/index.d.ts +2 -0
  27. package/out-tsc/bin/index.js +51 -7
  28. package/out-tsc/core/functions/index.d.ts +42 -0
  29. package/out-tsc/core/functions/index.js +87 -0
  30. package/out-tsc/core/index.d.ts +6 -0
  31. package/out-tsc/core/index.js +22 -0
  32. package/out-tsc/core/types/index.d.ts +4 -0
  33. package/out-tsc/core/types/index.js +20 -0
  34. package/out-tsc/core/types/plugin.d.ts +18 -0
  35. package/out-tsc/core/types/plugin.js +2 -0
  36. package/out-tsc/core/types/result.d.ts +27 -0
  37. package/out-tsc/core/types/result.js +2 -0
  38. package/out-tsc/core/types/statement.d.ts +106 -0
  39. package/out-tsc/core/types/statement.js +2 -0
  40. package/out-tsc/core/types/value.d.ts +43 -0
  41. package/out-tsc/core/types/value.js +2 -0
  42. package/out-tsc/index.d.ts +7 -0
  43. package/out-tsc/index.js +42 -1
  44. package/out-tsc/pkg/devalang_core.d.ts +15 -0
  45. package/out-tsc/pkg/devalang_core.js +65 -0
  46. package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +34 -0
  47. package/out-tsc/scripts/copy-wasm-dts.d.ts +1 -0
  48. package/out-tsc/scripts/copy-wasm-dts.js +73 -0
  49. package/out-tsc/scripts/postinstall.d.ts +1 -0
  50. package/out-tsc/scripts/postinstall.js +83 -0
  51. package/out-tsc/scripts/version/bump.d.ts +1 -0
  52. package/out-tsc/scripts/version/fetch.d.ts +1 -0
  53. package/out-tsc/scripts/version/index.d.ts +1 -0
  54. package/out-tsc/scripts/version/sync.d.ts +1 -0
  55. package/package.json +28 -7
  56. package/project-version.json +4 -4
  57. package/rust/cli/bank/api.rs +122 -0
  58. package/rust/cli/bank/commands.rs +306 -0
  59. package/rust/cli/bank/mod.rs +29 -0
  60. package/rust/cli/build/commands.rs +153 -0
  61. package/rust/cli/build/mod.rs +2 -0
  62. package/rust/cli/build/process.rs +165 -0
  63. package/rust/cli/check/mod.rs +208 -0
  64. package/rust/cli/discover/commands.rs +253 -0
  65. package/rust/cli/discover/config.rs +111 -0
  66. package/rust/cli/discover/fs.rs +19 -0
  67. package/rust/cli/discover/install.rs +103 -0
  68. package/rust/cli/discover/metadata.rs +48 -0
  69. package/rust/cli/discover/mod.rs +5 -0
  70. package/rust/cli/{init.rs → init/commands.rs} +32 -23
  71. package/rust/cli/init/mod.rs +1 -0
  72. package/rust/cli/install/addon.rs +118 -0
  73. package/rust/cli/install/bank.rs +72 -0
  74. package/rust/cli/install/commands.rs +35 -0
  75. package/rust/cli/install/mod.rs +4 -0
  76. package/rust/cli/install/plugin.rs +80 -0
  77. package/rust/cli/login/commands.rs +124 -0
  78. package/rust/cli/login/mod.rs +1 -0
  79. package/rust/cli/mod.rs +9 -202
  80. package/rust/cli/parser.rs +359 -0
  81. package/rust/cli/play/commands.rs +375 -0
  82. package/rust/cli/play/io.rs +17 -0
  83. package/rust/cli/play/mod.rs +5 -0
  84. package/rust/cli/play/process.rs +159 -0
  85. package/rust/cli/play/realtime.rs +91 -0
  86. package/rust/cli/play/utils.rs +23 -0
  87. package/rust/cli/telemetry/commands.rs +22 -0
  88. package/rust/cli/telemetry/event_creator.rs +80 -0
  89. package/rust/cli/telemetry/mod.rs +3 -0
  90. package/rust/cli/telemetry/send.rs +51 -0
  91. package/rust/cli/{template.rs → template/commands.rs} +17 -5
  92. package/rust/cli/template/mod.rs +1 -0
  93. package/rust/cli/update/commands.rs +6 -0
  94. package/rust/cli/update/mod.rs +1 -0
  95. package/rust/config/driver.rs +112 -0
  96. package/rust/config/mod.rs +3 -16
  97. package/rust/config/ops.rs +26 -0
  98. package/rust/config/settings.rs +101 -0
  99. package/rust/core/audio/engine/driver.rs +220 -0
  100. package/rust/core/audio/engine/export.rs +169 -0
  101. package/rust/core/audio/engine/helpers.rs +178 -0
  102. package/rust/core/audio/engine/mod.rs +56 -0
  103. package/rust/core/audio/engine/notes/dsp.rs +85 -0
  104. package/rust/core/audio/engine/notes/mod.rs +44 -0
  105. package/rust/core/audio/engine/notes/params.rs +294 -0
  106. package/rust/core/audio/engine/sample/insert.rs +199 -0
  107. package/rust/core/audio/engine/sample/mod.rs +40 -0
  108. package/rust/core/audio/engine/sample/padding.rs +170 -0
  109. package/rust/core/audio/evaluator/condition.rs +61 -0
  110. package/rust/core/audio/evaluator/mod.rs +9 -0
  111. package/rust/core/audio/evaluator/numeric.rs +152 -0
  112. package/rust/core/audio/evaluator/rhs.rs +16 -0
  113. package/rust/core/audio/evaluator/string_expr.rs +94 -0
  114. package/rust/core/audio/interpreter/driver.rs +574 -216
  115. package/rust/core/audio/interpreter/mod.rs +2 -12
  116. package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +175 -0
  117. package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +384 -0
  118. package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +2 -0
  119. package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +316 -0
  120. package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -0
  121. package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -0
  122. package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -0
  123. package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -0
  124. package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -0
  125. package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -0
  126. package/rust/core/audio/interpreter/statements/automate.rs +16 -0
  127. package/rust/core/audio/interpreter/statements/call.rs +295 -0
  128. package/rust/core/audio/interpreter/{condition.rs → statements/condition.rs} +72 -69
  129. package/rust/core/audio/interpreter/statements/function.rs +24 -0
  130. package/rust/core/audio/interpreter/statements/let_.rs +36 -0
  131. package/rust/core/audio/interpreter/statements/load.rs +17 -0
  132. package/rust/core/audio/interpreter/statements/loop_.rs +115 -0
  133. package/rust/core/audio/interpreter/statements/mod.rs +12 -0
  134. package/rust/core/audio/interpreter/statements/sleep.rs +28 -0
  135. package/rust/core/audio/interpreter/statements/spawn.rs +253 -0
  136. package/rust/core/audio/interpreter/statements/tempo.rs +40 -0
  137. package/rust/core/audio/interpreter/statements/trigger.rs +239 -0
  138. package/rust/core/audio/loader/mod.rs +1 -1
  139. package/rust/core/audio/loader/trigger.rs +98 -52
  140. package/rust/core/audio/mod.rs +2 -2
  141. package/rust/core/audio/player.rs +28 -12
  142. package/rust/core/audio/special/easing.rs +189 -0
  143. package/rust/core/audio/special/env.rs +45 -0
  144. package/rust/core/audio/special/math.rs +134 -0
  145. package/rust/core/audio/special/mod.rs +9 -0
  146. package/rust/core/audio/special/modulator.rs +143 -0
  147. package/rust/core/builder/mod.rs +129 -80
  148. package/rust/core/debugger/lexer.rs +4 -4
  149. package/rust/core/debugger/logs.rs +52 -0
  150. package/rust/core/debugger/mod.rs +11 -2
  151. package/rust/core/debugger/preprocessor.rs +4 -4
  152. package/rust/core/debugger/store.rs +38 -25
  153. package/rust/core/error/mod.rs +221 -12
  154. package/rust/core/lexer/driver.rs +59 -0
  155. package/rust/core/lexer/handler/arrow.rs +62 -11
  156. package/rust/core/lexer/handler/at.rs +5 -5
  157. package/rust/core/lexer/handler/brace.rs +11 -11
  158. package/rust/core/lexer/handler/colon.rs +5 -5
  159. package/rust/core/lexer/handler/comment.rs +3 -3
  160. package/rust/core/lexer/handler/dot.rs +6 -6
  161. package/rust/core/lexer/handler/driver.rs +143 -32
  162. package/rust/core/lexer/handler/identifier.rs +11 -5
  163. package/rust/core/lexer/handler/indent.rs +18 -4
  164. package/rust/core/lexer/handler/mod.rs +6 -5
  165. package/rust/core/lexer/handler/newline.rs +3 -3
  166. package/rust/core/lexer/handler/number.rs +5 -5
  167. package/rust/core/lexer/handler/operator.rs +5 -3
  168. package/rust/core/lexer/handler/parenthesis.rs +41 -0
  169. package/rust/core/lexer/handler/slash.rs +21 -0
  170. package/rust/core/lexer/handler/string.rs +3 -3
  171. package/rust/core/lexer/mod.rs +1 -49
  172. package/rust/core/lexer/token.rs +17 -12
  173. package/rust/core/mod.rs +9 -10
  174. package/rust/core/parser/driver/block.rs +111 -0
  175. package/rust/core/parser/driver/cursor.rs +82 -0
  176. package/rust/core/parser/driver/driver_impl.rs +139 -0
  177. package/rust/core/parser/driver/mod.rs +6 -0
  178. package/rust/core/parser/driver/parse_array.rs +120 -0
  179. package/rust/core/parser/driver/parse_map.rs +223 -0
  180. package/rust/core/parser/driver/parser.rs +160 -0
  181. package/rust/core/parser/handler/arrow_call.rs +277 -126
  182. package/rust/core/parser/handler/at.rs +142 -25
  183. package/rust/core/parser/handler/bank.rs +83 -20
  184. package/rust/core/parser/handler/condition.rs +14 -5
  185. package/rust/core/parser/handler/dot.rs +111 -75
  186. package/rust/core/parser/handler/identifier/automate.rs +254 -0
  187. package/rust/core/parser/handler/identifier/call.rs +74 -24
  188. package/rust/core/parser/handler/identifier/emit.rs +70 -0
  189. package/rust/core/parser/handler/identifier/function.rs +113 -0
  190. package/rust/core/parser/handler/identifier/group.rs +28 -14
  191. package/rust/core/parser/handler/identifier/let_.rs +61 -21
  192. package/rust/core/parser/handler/identifier/mod.rs +24 -20
  193. package/rust/core/parser/handler/identifier/on.rs +107 -0
  194. package/rust/core/parser/handler/identifier/print.rs +49 -0
  195. package/rust/core/parser/handler/identifier/sleep.rs +77 -14
  196. package/rust/core/parser/handler/identifier/spawn.rs +81 -31
  197. package/rust/core/parser/handler/identifier/synth.rs +102 -32
  198. package/rust/core/parser/handler/loop_.rs +144 -22
  199. package/rust/core/parser/handler/mod.rs +6 -5
  200. package/rust/core/parser/handler/pattern.rs +74 -0
  201. package/rust/core/parser/handler/tempo.rs +67 -9
  202. package/rust/core/parser/mod.rs +3 -4
  203. package/rust/core/parser/statement.rs +6 -92
  204. package/rust/core/plugin/loader.rs +137 -0
  205. package/rust/core/plugin/mod.rs +2 -0
  206. package/rust/core/plugin/runner/mod.rs +11 -0
  207. package/rust/core/plugin/runner/non_wasm.rs +297 -0
  208. package/rust/core/plugin/runner/wasm32.rs +43 -0
  209. package/rust/core/preprocessor/loader/inject.rs +278 -0
  210. package/rust/core/preprocessor/loader/loader_helpers.rs +110 -0
  211. package/rust/core/preprocessor/loader/mod.rs +235 -0
  212. package/rust/core/preprocessor/mod.rs +4 -4
  213. package/rust/core/preprocessor/module.rs +55 -50
  214. package/rust/core/preprocessor/processor/handlers.rs +107 -0
  215. package/rust/core/preprocessor/processor/mod.rs +1 -0
  216. package/rust/core/preprocessor/resolver/bank.rs +14 -12
  217. package/rust/core/preprocessor/resolver/call.rs +106 -105
  218. package/rust/core/preprocessor/resolver/condition.rs +13 -10
  219. package/rust/core/preprocessor/resolver/driver.rs +145 -48
  220. package/rust/core/preprocessor/resolver/function.rs +69 -0
  221. package/rust/core/preprocessor/resolver/group.rs +122 -61
  222. package/rust/core/preprocessor/resolver/let_.rs +13 -12
  223. package/rust/core/preprocessor/resolver/loop_.rs +240 -13
  224. package/rust/core/preprocessor/resolver/mod.rs +8 -6
  225. package/rust/core/preprocessor/resolver/pattern.rs +83 -0
  226. package/rust/core/preprocessor/resolver/spawn.rs +83 -42
  227. package/rust/core/preprocessor/resolver/synth.rs +15 -11
  228. package/rust/core/preprocessor/resolver/tempo.rs +13 -14
  229. package/rust/core/preprocessor/resolver/trigger.rs +32 -28
  230. package/rust/core/preprocessor/resolver/value.rs +111 -13
  231. package/rust/core/store/global.rs +57 -39
  232. package/rust/core/store/mod.rs +0 -3
  233. package/rust/lib.rs +323 -117
  234. package/rust/main.rs +388 -65
  235. package/rust/types/Cargo.toml +11 -0
  236. package/rust/types/src/addons.rs +55 -0
  237. package/rust/types/src/ast.rs +202 -0
  238. package/rust/types/src/config.rs +84 -0
  239. package/rust/types/src/lib.rs +15 -0
  240. package/rust/types/src/plugin.rs +20 -0
  241. package/rust/types/src/store.rs +139 -0
  242. package/rust/types/src/telemetry.rs +85 -0
  243. package/rust/utils/Cargo.toml +26 -0
  244. package/rust/utils/src/error.rs +186 -0
  245. package/rust/utils/src/file.rs +94 -0
  246. package/rust/utils/src/first_usage.rs +97 -0
  247. package/rust/utils/{mod.rs → src/lib.rs} +6 -3
  248. package/rust/utils/{logger.rs → src/logger.rs} +94 -17
  249. package/rust/utils/src/path.rs +129 -0
  250. package/rust/utils/src/signature.rs +41 -0
  251. package/rust/utils/{spinner.rs → src/spinner.rs} +7 -8
  252. package/rust/utils/src/version.rs +27 -0
  253. package/rust/utils/{watcher.rs → src/watcher.rs} +17 -4
  254. package/rust/web/api.rs +5 -0
  255. package/rust/web/cdn.rs +34 -0
  256. package/rust/web/mod.rs +3 -0
  257. package/rust/web/sso.rs +5 -0
  258. package/templates/minimal/README.md +143 -127
  259. package/templates/welcome/README.md +143 -127
  260. package/templates/welcome/src/index.deva +56 -8
  261. package/templates/welcome/src/variables.deva +2 -4
  262. package/tests/integration.rs +21 -0
  263. package/tests/rust/cli_check_build.rs +21 -0
  264. package/tests/rust/cli_help.rs +12 -0
  265. package/tests/rust/cli_template_list.rs +10 -0
  266. package/tests/rust/cli_version.rs +11 -0
  267. package/tests/typescript/index.spec.ts +136 -0
  268. package/tests/typescript/playhead.spec.ts +36 -0
  269. package/tests/typescript/render_e2e.spec.ts +77 -0
  270. package/tsconfig.json +12 -10
  271. package/typescript/bin/index.ts +19 -5
  272. package/typescript/core/functions/index.ts +94 -0
  273. package/typescript/core/index.ts +6 -0
  274. package/typescript/core/types/index.ts +4 -0
  275. package/typescript/core/types/plugin.ts +19 -0
  276. package/typescript/core/types/result.ts +29 -0
  277. package/typescript/core/types/statement.ts +47 -0
  278. package/typescript/core/types/value.ts +29 -0
  279. package/typescript/index.ts +8 -1
  280. package/typescript/pkg/devalang_core.d.ts +4 -0
  281. package/typescript/pkg/devalang_core.ts +65 -0
  282. package/typescript/scripts/copy-wasm-dts.ts +41 -0
  283. package/typescript/scripts/postinstall.ts +85 -0
  284. package/typescript/scripts/version/bump.ts +0 -1
  285. package/typescript/scripts/version/index.ts +0 -1
  286. package/docs/COMMANDS.md +0 -85
  287. package/docs/CONFIG.md +0 -30
  288. package/docs/SYNTAX.md +0 -210
  289. package/out-tsc/bin/devalang.exe +0 -0
  290. package/out-tsc/scripts/postbuild.js +0 -11
  291. package/rust/cli/build.rs +0 -137
  292. package/rust/cli/check.rs +0 -117
  293. package/rust/cli/play.rs +0 -193
  294. package/rust/config/loader.rs +0 -13
  295. package/rust/core/audio/engine.rs +0 -203
  296. package/rust/core/audio/evaluator.rs +0 -31
  297. package/rust/core/audio/interpreter/arrow_call.rs +0 -129
  298. package/rust/core/audio/interpreter/call.rs +0 -64
  299. package/rust/core/audio/interpreter/let_.rs +0 -19
  300. package/rust/core/audio/interpreter/load.rs +0 -18
  301. package/rust/core/audio/interpreter/loop_.rs +0 -67
  302. package/rust/core/audio/interpreter/sleep.rs +0 -36
  303. package/rust/core/audio/interpreter/spawn.rs +0 -66
  304. package/rust/core/audio/interpreter/tempo.rs +0 -16
  305. package/rust/core/audio/interpreter/trigger.rs +0 -69
  306. package/rust/core/audio/renderer.rs +0 -54
  307. package/rust/core/parser/driver.rs +0 -331
  308. package/rust/core/preprocessor/loader.rs +0 -193
  309. package/rust/core/preprocessor/processor.rs +0 -76
  310. package/rust/core/shared/duration.rs +0 -8
  311. package/rust/core/shared/mod.rs +0 -2
  312. package/rust/core/shared/value.rs +0 -18
  313. package/rust/core/store/export.rs +0 -28
  314. package/rust/core/store/import.rs +0 -28
  315. package/rust/core/store/variable.rs +0 -28
  316. package/rust/core/utils/mod.rs +0 -2
  317. package/rust/core/utils/path.rs +0 -31
  318. package/rust/core/utils/validation.rs +0 -37
  319. package/rust/utils/file.rs +0 -35
  320. package/rust/utils/signature.rs +0 -17
  321. package/rust/utils/version.rs +0 -15
  322. package/typescript/scripts/postbuild.ts +0 -8
@@ -0,0 +1,239 @@
1
+ use std::collections::HashMap;
2
+
3
+ use crate::core::{
4
+ audio::{engine::AudioEngine, loader::trigger::load_trigger},
5
+ parser::statement::{Statement, StatementKind},
6
+ };
7
+ use devalang_types::store::VariableTable;
8
+ use devalang_types::{Duration, Value};
9
+ use devalang_utils::logger::Logger;
10
+
11
+ pub fn interprete_trigger_statement(
12
+ stmt: &Statement,
13
+ audio_engine: &mut AudioEngine,
14
+ variable_table: &VariableTable,
15
+ base_duration: f32,
16
+ cursor_time: f32,
17
+ max_end_time: f32,
18
+ ) -> Option<(f32, f32, AudioEngine)> {
19
+ if let StatementKind::Trigger {
20
+ entity,
21
+ duration,
22
+ effects,
23
+ } = &stmt.kind
24
+ {
25
+ let mut trigger_val = Value::String(entity.clone());
26
+ let mut trigger_src = String::new();
27
+
28
+ match variable_table.variables.get(entity) {
29
+ Some(Value::Identifier(id)) => {
30
+ // Get real value from global variable table
31
+ if let Some(global_table) = &variable_table.parent {
32
+ if let Some(val) = global_table.get(id) {
33
+ trigger_val = val.clone();
34
+ } else {
35
+ eprintln!(
36
+ "❌ Trigger entity '{}' not found in global variable table",
37
+ id
38
+ );
39
+ return None;
40
+ }
41
+ } else if let Some(val) = variable_table.get(id) {
42
+ trigger_val = val.clone();
43
+ } else {
44
+ eprintln!("❌ Trigger entity '{}' not found in variable table", id);
45
+ return None;
46
+ }
47
+ }
48
+ Some(Value::Sample(sample_src)) => {
49
+ // If the entity is a sample, we use its path directly
50
+ trigger_src = sample_src.clone();
51
+ }
52
+ Some(Value::Map(map)) => {
53
+ // If the entity is a map, we assume it contains an "entity" key
54
+ if let Some(Value::String(src)) = map.get("entity") {
55
+ trigger_val = Value::String(src.clone());
56
+ } else if let Some(Value::Identifier(src)) = map.get("entity") {
57
+ trigger_val = Value::Identifier(src.clone());
58
+ } else {
59
+ eprintln!(
60
+ "❌ Trigger map must contain an 'entity' key with a string or identifier value."
61
+ );
62
+ return None;
63
+ }
64
+ }
65
+ _ => {
66
+ trigger_val = Value::String(entity.clone());
67
+ }
68
+ }
69
+
70
+ // If trigger could not be resolved to a known mapping or explicit path, abort early
71
+ if let Value::String(s) = &trigger_val {
72
+ let is_protocol = s.starts_with("devalang://");
73
+ let is_var = variable_table.get(s).is_some()
74
+ || variable_table
75
+ .parent
76
+ .as_ref()
77
+ .and_then(|p| p.get(s))
78
+ .is_some();
79
+ let looks_like_path = s.contains('/')
80
+ || s.ends_with(".wav")
81
+ || s.ends_with(".mp3")
82
+ || s.ends_with(".ogg");
83
+ if !is_protocol && !is_var && !looks_like_path {
84
+ let logger = Logger::new();
85
+ logger.log_error_with_stacktrace(
86
+ &format!("unknown trigger: {}", s),
87
+ &format!("{}:{}:{}", audio_engine.module_name, stmt.line, stmt.column),
88
+ );
89
+ return None;
90
+ }
91
+ }
92
+
93
+ let duration_secs = match duration {
94
+ Duration::Number(n) => *n,
95
+
96
+ Duration::Identifier(id) => {
97
+ if id == "auto" {
98
+ 1.0
99
+ } else {
100
+ match variable_table.get(id) {
101
+ Some(Value::Number(n)) => *n,
102
+ Some(Value::Identifier(other)) if other == "auto" => 1.0,
103
+ Some(other) => {
104
+ eprintln!(
105
+ "❌ Invalid duration reference '{}': expected number, got {:?}",
106
+ id, other
107
+ );
108
+ return None;
109
+ }
110
+ None => {
111
+ eprintln!("❌ Duration identifier '{}' not found", id);
112
+ return None;
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ Duration::Beat(beat_str) => {
119
+ let parts: Vec<&str> = beat_str.split('/').collect();
120
+ if parts.len() != 2 {
121
+ eprintln!("❌ Invalid beat duration format: {}", beat_str);
122
+ return None;
123
+ }
124
+
125
+ let numerator: f32 = parts[0].parse().unwrap_or(1.0);
126
+ let denominator: f32 = parts[1].parse().unwrap_or(4.0);
127
+
128
+ let beats = (numerator / denominator) * 4.0;
129
+
130
+ beats * base_duration
131
+ }
132
+
133
+ Duration::Auto => base_duration,
134
+ };
135
+
136
+ let final_variable_table = if let Some(parent) = &variable_table.parent {
137
+ VariableTable {
138
+ variables: parent.variables.clone(),
139
+ parent: None,
140
+ }
141
+ } else {
142
+ variable_table.clone()
143
+ };
144
+
145
+ let (src, sample_length) = load_trigger(
146
+ &trigger_val,
147
+ duration,
148
+ effects,
149
+ base_duration,
150
+ final_variable_table.clone(),
151
+ );
152
+
153
+ if trigger_src.is_empty() {
154
+ trigger_src = src;
155
+ }
156
+
157
+ let effects = extract_effects(stmt.value.clone());
158
+ let one_shot = effects
159
+ .as_ref()
160
+ .and_then(|map| map.get("one_shot"))
161
+ .and_then(|v| match v {
162
+ Value::Identifier(id) if id == "true" => Some(true),
163
+ Value::String(s) if s == "true" => Some(true),
164
+ _ => None,
165
+ })
166
+ .unwrap_or(false);
167
+
168
+ let play_length = if one_shot {
169
+ sample_length // play entire sample
170
+ } else {
171
+ duration_secs.min(sample_length)
172
+ };
173
+
174
+ let trigger_src = match trigger_val.get("entity") {
175
+ Some(Value::String(src)) => src.clone(),
176
+ Some(Value::Identifier(id)) => id.clone(),
177
+ Some(Value::Statement(stmt)) => {
178
+ if let StatementKind::Trigger { entity, .. } = &stmt.kind {
179
+ entity.clone()
180
+ } else {
181
+ eprintln!("❌ Invalid trigger statement in map: expected 'Trigger' kind");
182
+ return None;
183
+ }
184
+ }
185
+ _ => trigger_src,
186
+ };
187
+
188
+ if let Some(effects_map) = effects {
189
+ audio_engine.insert_sample(
190
+ &trigger_src,
191
+ cursor_time,
192
+ play_length,
193
+ Some(effects_map),
194
+ &final_variable_table,
195
+ );
196
+ } else {
197
+ audio_engine.insert_sample(
198
+ &trigger_src,
199
+ cursor_time,
200
+ play_length,
201
+ None,
202
+ &final_variable_table,
203
+ );
204
+ }
205
+
206
+ let new_cursor_time = cursor_time + duration_secs; // advance by beat duration
207
+ let new_max_end_time = (cursor_time + play_length).max(max_end_time);
208
+
209
+ let updated_engine = audio_engine.clone();
210
+
211
+ return Some((new_cursor_time, new_max_end_time, updated_engine));
212
+ }
213
+
214
+ None
215
+ }
216
+
217
+ fn extract_effects(value: Value) -> Option<HashMap<String, Value>> {
218
+ if let Value::Map(map) = value {
219
+ let mut effects = HashMap::new();
220
+
221
+ for (key, val) in map {
222
+ if key == "effects" {
223
+ if let Value::Map(effect_map) = val {
224
+ for (effect_key, effect_val) in effect_map {
225
+ effects.insert(effect_key, effect_val);
226
+ }
227
+ } else {
228
+ return None; // effects must be a map
229
+ }
230
+ } else {
231
+ return Some(effects);
232
+ }
233
+ }
234
+
235
+ Some(effects)
236
+ } else {
237
+ None
238
+ }
239
+ }
@@ -1 +1 @@
1
- pub mod trigger;
1
+ pub mod trigger;
@@ -1,52 +1,98 @@
1
- use crate::core::{ shared::{ duration::Duration, value::Value }, store::variable::VariableTable };
2
-
3
- pub fn load_trigger(
4
- trigger: &Value,
5
- duration: &Duration,
6
- base_duration: f32,
7
- variable_table: VariableTable
8
- ) -> (String, f32) {
9
- let mut trigger_path = String::new();
10
- let mut duration_as_secs = 0.0;
11
-
12
- match trigger {
13
- Value::String(src) => {
14
- trigger_path = src.to_string();
15
- }
16
- _ => {
17
- eprintln!("❌ Invalid trigger type. Expected a text variable.");
18
- }
19
- }
20
-
21
- match duration {
22
- Duration::Identifier(duration_identifier) => {
23
- if duration_identifier == "auto" {
24
- duration_as_secs = base_duration;
25
- } else if let Some(Value::Number(num)) = variable_table.get(duration_identifier) {
26
- duration_as_secs = *num;
27
- } else if let Some(Value::String(num_str)) = variable_table.get(duration_identifier) {
28
- duration_as_secs = num_str.parse::<f32>().unwrap_or(base_duration);
29
- } else if
30
- let Some(Value::Identifier(num_str)) = variable_table.get(duration_identifier)
31
- {
32
- duration_as_secs = num_str.parse::<f32>().unwrap_or(base_duration);
33
- } else {
34
- eprintln!("❌ Invalid duration identifier: {}", duration_identifier);
35
- }
36
- }
37
-
38
- Duration::Number(num) => {
39
- duration_as_secs = *num;
40
- }
41
-
42
- Duration::Auto => {
43
- duration_as_secs = base_duration;
44
- }
45
-
46
- _ => {
47
- eprintln!("❌ Invalid duration type. Expected an identifier.");
48
- }
49
- }
50
-
51
- (trigger_path, duration_as_secs)
52
- }
1
+ use crate::core::parser::statement::StatementKind;
2
+ use devalang_types::store::VariableTable;
3
+ use devalang_types::{Duration, Value};
4
+
5
+ pub fn load_trigger(
6
+ trigger: &Value,
7
+ duration: &Duration,
8
+ _effects: &Option<Value>,
9
+ base_duration: f32,
10
+ variable_table: VariableTable,
11
+ ) -> (String, f32) {
12
+ let mut trigger_path = String::new();
13
+ let mut duration_as_secs = 0.0;
14
+
15
+ match trigger {
16
+ Value::String(src) => {
17
+ trigger_path = src.to_string();
18
+ }
19
+ Value::Identifier(src) => {
20
+ trigger_path = src.to_string();
21
+ }
22
+
23
+ Value::Map(map) => {
24
+ if let Some(Value::String(src)) = map.get("entity") {
25
+ trigger_path = format!("devalang://bank/{}", src);
26
+ } else if let Some(Value::Identifier(src)) = map.get("entity") {
27
+ trigger_path = format!("devalang://bank/{}", src);
28
+ } else {
29
+ eprintln!(
30
+ "❌ Trigger map must contain an 'entity' key with a string or identifier value."
31
+ );
32
+ }
33
+ }
34
+ Value::Sample(src) => {
35
+ trigger_path = src.to_string();
36
+ }
37
+ Value::Statement(stmt) => {
38
+ if let StatementKind::Trigger {
39
+ entity,
40
+ duration: _,
41
+ effects: _,
42
+ } = &stmt.kind
43
+ {
44
+ trigger_path = entity.clone();
45
+ } else {
46
+ eprintln!(
47
+ "❌ Trigger statement must be of type 'Trigger', found: {:?}",
48
+ stmt.kind
49
+ );
50
+ }
51
+ }
52
+ _ => {
53
+ eprintln!(
54
+ "❌ Invalid trigger type. Expected a string or identifier variable, found: {:?}",
55
+ trigger
56
+ );
57
+ }
58
+ }
59
+
60
+ match duration {
61
+ Duration::Identifier(duration_identifier) => {
62
+ if duration_identifier == "auto" {
63
+ duration_as_secs = base_duration;
64
+ } else if let Some(Value::Number(num)) = variable_table.get(duration_identifier) {
65
+ duration_as_secs = *num;
66
+ } else if let Some(Value::String(num_str)) = variable_table.get(duration_identifier) {
67
+ duration_as_secs = num_str.parse::<f32>().unwrap_or(base_duration);
68
+ } else if let Some(Value::Identifier(num_str)) = variable_table.get(duration_identifier)
69
+ {
70
+ duration_as_secs = num_str.parse::<f32>().unwrap_or(base_duration);
71
+ } else {
72
+ eprintln!("❌ Invalid duration identifier: {}", duration_identifier);
73
+ }
74
+ }
75
+
76
+ Duration::Number(num) => {
77
+ duration_as_secs = *num;
78
+ }
79
+
80
+ Duration::Auto => {
81
+ duration_as_secs = base_duration;
82
+ }
83
+
84
+ Duration::Beat(beat_str) => {
85
+ let parts: Vec<&str> = beat_str.split('/').collect();
86
+
87
+ if parts.len() == 2 {
88
+ let numerator: f32 = parts[0].parse().unwrap_or(1.0);
89
+ let denominator: f32 = parts[1].parse().unwrap_or(1.0);
90
+ duration_as_secs = (numerator / denominator) * base_duration;
91
+ } else {
92
+ eprintln!("❌ Invalid beat duration format: {}", beat_str);
93
+ }
94
+ }
95
+ }
96
+
97
+ (trigger_path, duration_as_secs)
98
+ }
@@ -1,6 +1,6 @@
1
1
  pub mod engine;
2
+ pub mod evaluator;
2
3
  pub mod interpreter;
3
4
  pub mod loader;
4
5
  pub mod player;
5
- pub mod renderer;
6
- pub mod evaluator;
6
+ pub mod special;
@@ -1,5 +1,5 @@
1
- use rodio::{ Decoder, OutputStream, OutputStreamHandle, Sink, Source };
2
- use std::{ fs::File, io::BufReader };
1
+ use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink, Source};
2
+ use std::{fs::File, io::BufReader};
3
3
 
4
4
  pub struct AudioPlayer {
5
5
  _stream: OutputStream,
@@ -8,6 +8,12 @@ pub struct AudioPlayer {
8
8
  last_path: Option<String>,
9
9
  }
10
10
 
11
+ impl Default for AudioPlayer {
12
+ fn default() -> Self {
13
+ Self::new()
14
+ }
15
+ }
16
+
11
17
  impl AudioPlayer {
12
18
  pub fn new() -> Self {
13
19
  let (stream, handle) = OutputStream::try_default().unwrap();
@@ -21,23 +27,33 @@ impl AudioPlayer {
21
27
  }
22
28
  }
23
29
 
24
- fn load_source(&self, path: &str) -> impl Source<Item = f32> + Send + 'static {
25
- let file = File::open(path).unwrap();
26
- let reader = BufReader::new(file);
27
-
28
- Decoder::new(reader).unwrap().convert_samples()
30
+ fn load_source(&self, path: &str) -> Option<impl Source<Item = f32> + Send + 'static> {
31
+ if let Ok(file) = File::open(path) {
32
+ let reader = BufReader::new(file);
33
+ match Decoder::new(reader) {
34
+ Ok(decoder) => Some(decoder.convert_samples()),
35
+ Err(e) => {
36
+ eprintln!("❌ Failed to decode audio file '{}': {}", path, e);
37
+ None
38
+ }
39
+ }
40
+ } else {
41
+ eprintln!("❌ Could not open audio file: {}", path);
42
+ None
43
+ }
29
44
  }
30
45
 
31
46
  pub fn play_file_once(&mut self, path: &str) {
32
47
  self.sink.stop();
33
48
  self.sink = Sink::try_new(&self.handle).unwrap();
34
-
35
49
  self.sink.set_volume(1.0);
36
50
 
37
- let source = self.load_source(path);
38
-
39
- self.sink.append(source);
40
- self.last_path = Some(path.to_string());
51
+ if let Some(source) = self.load_source(path) {
52
+ self.sink.append(source);
53
+ self.last_path = Some(path.to_string());
54
+ } else {
55
+ eprintln!("⚠️ Skipping playback: failed to load '{}'", path);
56
+ }
41
57
  }
42
58
 
43
59
  pub fn replay_last(&mut self) {
@@ -0,0 +1,189 @@
1
+ use devalang_types::VariableTable;
2
+
3
+ // Basic easing functions operating on t in [0,1]
4
+ fn easing_value(func: &str, t: f32) -> Option<f32> {
5
+ let x = t.clamp(0.0, 1.0);
6
+ match func {
7
+ "linear" => Some(x),
8
+ "easeInQuad" => Some(x * x),
9
+ "easeOutQuad" => Some(x * (2.0 - x)),
10
+ "easeInOutQuad" => {
11
+ if x < 0.5 {
12
+ Some(2.0 * x * x)
13
+ } else {
14
+ Some(-1.0 + (4.0 - 2.0 * x) * x)
15
+ }
16
+ }
17
+ // Cubic
18
+ "easeInCubic" => Some(x * x * x),
19
+ "easeOutCubic" => Some(1.0 - (1.0 - x).powi(3)),
20
+ "easeInOutCubic" => {
21
+ if x < 0.5 {
22
+ Some(4.0 * x * x * x)
23
+ } else {
24
+ Some(1.0 - (-2.0 * x + 2.0).powi(3) / 2.0)
25
+ }
26
+ }
27
+ // Quartic
28
+ "easeInQuart" => Some(x.powi(4)),
29
+ "easeOutQuart" => Some(1.0 - (1.0 - x).powi(4)),
30
+ "easeInOutQuart" => {
31
+ if x < 0.5 {
32
+ Some(8.0 * x.powi(4))
33
+ } else {
34
+ Some(1.0 - (-2.0 * x + 2.0).powi(4) / 2.0)
35
+ }
36
+ }
37
+ // Exponential
38
+ "easeInExpo" => Some(if x <= 0.0 {
39
+ 0.0
40
+ } else {
41
+ 2.0_f32.powf(10.0 * x - 10.0)
42
+ }),
43
+ "easeOutExpo" => Some(if x >= 1.0 {
44
+ 1.0
45
+ } else {
46
+ 1.0 - 2.0_f32.powf(-10.0 * x)
47
+ }),
48
+ "easeInOutExpo" => Some(if x <= 0.0 {
49
+ 0.0
50
+ } else if x >= 1.0 {
51
+ 1.0
52
+ } else if x < 0.5 {
53
+ 2.0_f32.powf(20.0 * x - 10.0) / 2.0
54
+ } else {
55
+ (2.0 - 2.0_f32.powf(-20.0 * x + 10.0)) / 2.0
56
+ }),
57
+ // Back (overshoot c ~ 1.70158)
58
+ "easeInBack" => {
59
+ let c = 1.70158;
60
+ Some((c + 1.0) * x * x * x - c * x * x)
61
+ }
62
+ "easeOutBack" => {
63
+ let c = 1.70158;
64
+ let y = 1.0 - x;
65
+ Some(1.0 - ((c + 1.0) * y * y * y - c * y * y))
66
+ }
67
+ "easeInOutBack" => {
68
+ let c1 = 1.70158;
69
+ let c2 = c1 * 1.525;
70
+ let x2 = x * 2.0;
71
+ if x2 < 1.0 {
72
+ Some((x2 * x2 * ((c2 + 1.0) * x2 - c2)) / 2.0)
73
+ } else {
74
+ let x2 = x2 - 2.0;
75
+ Some((x2 * x2 * ((c2 + 1.0) * x2 + c2)) / 2.0 + 1.0)
76
+ }
77
+ }
78
+ // Elastic
79
+ "easeInElastic" => {
80
+ if x == 0.0 {
81
+ Some(0.0)
82
+ } else if x == 1.0 {
83
+ Some(1.0)
84
+ } else {
85
+ let c = 2.0 * std::f32::consts::PI / 3.0;
86
+ Some(-(2.0_f32.powf(10.0 * x - 10.0)) * ((x * 10.0 - 10.75) * c).sin())
87
+ }
88
+ }
89
+ "easeOutElastic" => {
90
+ if x == 0.0 {
91
+ Some(0.0)
92
+ } else if x == 1.0 {
93
+ Some(1.0)
94
+ } else {
95
+ let c = 2.0 * std::f32::consts::PI / 3.0;
96
+ Some(2.0_f32.powf(-10.0 * x) * ((x * 10.0 - 0.75) * c).sin() + 1.0)
97
+ }
98
+ }
99
+ "easeInOutElastic" => {
100
+ if x == 0.0 {
101
+ Some(0.0)
102
+ } else if x == 1.0 {
103
+ Some(1.0)
104
+ } else {
105
+ let c = 2.0 * std::f32::consts::PI / 4.5;
106
+ if x < 0.5 {
107
+ Some(-(2.0_f32.powf(20.0 * x - 10.0)) * ((20.0 * x - 11.125) * c).sin() / 2.0)
108
+ } else {
109
+ Some(
110
+ 2.0_f32.powf(-20.0 * x + 10.0) * ((20.0 * x - 11.125) * c).sin() / 2.0
111
+ + 1.0,
112
+ )
113
+ }
114
+ }
115
+ }
116
+ // Bounce helpers
117
+ "easeInBounce" => Some(1.0 - bounce_out(1.0 - x)),
118
+ "easeOutBounce" => Some(bounce_out(x)),
119
+ "easeInOutBounce" => Some(if x < 0.5 {
120
+ (1.0 - bounce_out(1.0 - 2.0 * x)) / 2.0
121
+ } else {
122
+ (1.0 + bounce_out(2.0 * x - 1.0)) / 2.0
123
+ }),
124
+ _ => None,
125
+ }
126
+ }
127
+
128
+ fn bounce_out(x: f32) -> f32 {
129
+ let n1 = 7.5625;
130
+ let d1 = 2.75;
131
+ if x < 1.0 / d1 {
132
+ n1 * x * x
133
+ } else if x < 2.0 / d1 {
134
+ let x = x - 1.5 / d1;
135
+ n1 * x * x + 0.75
136
+ } else if x < 2.5 / d1 {
137
+ let x = x - 2.25 / d1;
138
+ n1 * x * x + 0.9375
139
+ } else {
140
+ let x = x - 2.625 / d1;
141
+ n1 * x * x + 0.984375
142
+ }
143
+ }
144
+
145
+ // Find and evaluate the first $easing.<fn>(...) occurrence in the string.
146
+ // Accepts a single argument expression producing t in [0,1].
147
+ pub fn find_and_eval_first_easing_call<EvalFn>(
148
+ s: &str,
149
+ eval: EvalFn,
150
+ vars: &VariableTable,
151
+ bpm: f32,
152
+ beat: f32,
153
+ ) -> Option<String>
154
+ where
155
+ EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
156
+ {
157
+ let start = s.find("$easing.")?;
158
+ let open_rel = s[start..].find('(')?;
159
+ let open = start + open_rel;
160
+ let func = &s[start + 9..open];
161
+
162
+ // Find matching close parenthesis
163
+ let mut depth: i32 = 0;
164
+ let mut close_abs: Option<usize> = None;
165
+ for (i, ch) in s[open..].char_indices() {
166
+ match ch {
167
+ '(' => depth += 1,
168
+ ')' => {
169
+ depth -= 1;
170
+ if depth == 0 {
171
+ close_abs = Some(open + i);
172
+ break;
173
+ }
174
+ }
175
+ _ => {}
176
+ }
177
+ }
178
+ let close = close_abs?;
179
+
180
+ let inner = &s[open + 1..close];
181
+ let t = eval(inner, vars, bpm, beat)?;
182
+ let result = easing_value(func, t)?;
183
+
184
+ let mut replaced = String::new();
185
+ replaced.push_str(&s[..start]);
186
+ replaced.push_str(&result.to_string());
187
+ replaced.push_str(&s[close + 1..]);
188
+ Some(replaced)
189
+ }