@devaloop/devalang 0.0.1-alpha.14 → 0.0.1-alpha.16

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 (177) hide show
  1. package/.devalang +10 -8
  2. package/.github/workflows/ci.yml +92 -0
  3. package/Cargo.toml +60 -58
  4. package/README.md +32 -15
  5. package/docs/CHANGELOG.md +93 -1
  6. package/docs/CONTRIBUTING.md +101 -1
  7. package/docs/ROADMAP.md +2 -2
  8. package/docs/TODO.md +1 -1
  9. package/examples/automation.deva +42 -0
  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 +39 -25
  14. package/examples/loop.deva +5 -11
  15. package/examples/pattern.deva +8 -0
  16. package/examples/plugin.deva +16 -0
  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 -456
  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 -8
  38. package/rust/common/cdn.rs +3 -6
  39. package/rust/common/mod.rs +3 -3
  40. package/rust/common/sso.rs +3 -6
  41. package/rust/config/driver.rs +118 -94
  42. package/rust/config/loader.rs +165 -156
  43. package/rust/config/mod.rs +4 -2
  44. package/rust/config/settings.rs +91 -0
  45. package/rust/config/stats.rs +257 -0
  46. package/rust/core/audio/engine.rs +696 -518
  47. package/rust/core/audio/evaluator.rs +263 -31
  48. package/rust/core/audio/interpreter/arrow_call.rs +198 -161
  49. package/rust/core/audio/interpreter/automate.rs +18 -0
  50. package/rust/core/audio/interpreter/call.rs +98 -95
  51. package/rust/core/audio/interpreter/condition.rs +70 -71
  52. package/rust/core/audio/interpreter/driver.rs +487 -198
  53. package/rust/core/audio/interpreter/function.rs +26 -21
  54. package/rust/core/audio/interpreter/let_.rs +38 -19
  55. package/rust/core/audio/interpreter/load.rs +18 -18
  56. package/rust/core/audio/interpreter/loop_.rs +113 -73
  57. package/rust/core/audio/interpreter/mod.rs +14 -13
  58. package/rust/core/audio/interpreter/sleep.rs +27 -30
  59. package/rust/core/audio/interpreter/spawn.rs +105 -102
  60. package/rust/core/audio/interpreter/tempo.rs +19 -16
  61. package/rust/core/audio/interpreter/trigger.rs +239 -210
  62. package/rust/core/audio/loader/mod.rs +1 -1
  63. package/rust/core/audio/loader/trigger.rs +100 -97
  64. package/rust/core/audio/mod.rs +7 -6
  65. package/rust/core/audio/player.rs +64 -64
  66. package/rust/core/audio/renderer.rs +56 -53
  67. package/rust/core/audio/special/easing.rs +189 -0
  68. package/rust/core/audio/special/env.rs +43 -0
  69. package/rust/core/audio/special/math.rs +102 -0
  70. package/rust/core/audio/special/mod.rs +9 -0
  71. package/rust/core/audio/special/modulator.rs +143 -0
  72. package/rust/core/builder/mod.rs +80 -85
  73. package/rust/core/debugger/lexer.rs +27 -27
  74. package/rust/core/debugger/mod.rs +24 -23
  75. package/rust/core/debugger/module.rs +55 -47
  76. package/rust/core/debugger/preprocessor.rs +27 -27
  77. package/rust/core/debugger/store.rs +40 -39
  78. package/rust/core/error/mod.rs +80 -66
  79. package/rust/core/lexer/handler/arrow.rs +82 -31
  80. package/rust/core/lexer/handler/at.rs +21 -21
  81. package/rust/core/lexer/handler/brace.rs +41 -41
  82. package/rust/core/lexer/handler/colon.rs +21 -21
  83. package/rust/core/lexer/handler/comment.rs +30 -30
  84. package/rust/core/lexer/handler/dot.rs +21 -21
  85. package/rust/core/lexer/handler/driver.rs +337 -263
  86. package/rust/core/lexer/handler/identifier.rs +46 -42
  87. package/rust/core/lexer/handler/indent.rs +66 -66
  88. package/rust/core/lexer/handler/mod.rs +16 -16
  89. package/rust/core/lexer/handler/newline.rs +23 -23
  90. package/rust/core/lexer/handler/number.rs +31 -31
  91. package/rust/core/lexer/handler/operator.rs +46 -44
  92. package/rust/core/lexer/handler/parenthesis.rs +41 -41
  93. package/rust/core/lexer/handler/slash.rs +21 -21
  94. package/rust/core/lexer/handler/string.rs +63 -63
  95. package/rust/core/lexer/mod.rs +54 -51
  96. package/rust/core/lexer/token.rs +97 -91
  97. package/rust/core/mod.rs +11 -11
  98. package/rust/core/parser/driver.rs +513 -408
  99. package/rust/core/parser/handler/arrow_call.rs +233 -211
  100. package/rust/core/parser/handler/at.rs +245 -162
  101. package/rust/core/parser/handler/bank.rs +94 -69
  102. package/rust/core/parser/handler/condition.rs +80 -74
  103. package/rust/core/parser/handler/dot.rs +143 -135
  104. package/rust/core/parser/handler/identifier/automate.rs +257 -0
  105. package/rust/core/parser/handler/identifier/call.rs +91 -88
  106. package/rust/core/parser/handler/identifier/emit.rs +66 -0
  107. package/rust/core/parser/handler/identifier/function.rs +100 -92
  108. package/rust/core/parser/handler/identifier/group.rs +85 -75
  109. package/rust/core/parser/handler/identifier/let_.rs +158 -127
  110. package/rust/core/parser/handler/identifier/mod.rs +54 -52
  111. package/rust/core/parser/handler/identifier/on.rs +98 -0
  112. package/rust/core/parser/handler/identifier/print.rs +52 -0
  113. package/rust/core/parser/handler/identifier/sleep.rs +36 -33
  114. package/rust/core/parser/handler/identifier/spawn.rs +91 -88
  115. package/rust/core/parser/handler/identifier/synth.rs +65 -65
  116. package/rust/core/parser/handler/loop_.rs +170 -72
  117. package/rust/core/parser/handler/mod.rs +8 -8
  118. package/rust/core/parser/handler/tempo.rs +53 -47
  119. package/rust/core/parser/mod.rs +4 -4
  120. package/rust/core/parser/statement.rs +142 -108
  121. package/rust/core/plugin/loader.rs +123 -48
  122. package/rust/core/plugin/mod.rs +2 -1
  123. package/rust/core/plugin/runner.rs +296 -0
  124. package/rust/core/preprocessor/loader.rs +515 -326
  125. package/rust/core/preprocessor/mod.rs +4 -4
  126. package/rust/core/preprocessor/module.rs +60 -58
  127. package/rust/core/preprocessor/processor.rs +99 -101
  128. package/rust/core/preprocessor/resolver/bank.rs +51 -49
  129. package/rust/core/preprocessor/resolver/call.rs +100 -100
  130. package/rust/core/preprocessor/resolver/condition.rs +97 -97
  131. package/rust/core/preprocessor/resolver/driver.rs +310 -278
  132. package/rust/core/preprocessor/resolver/function.rs +69 -78
  133. package/rust/core/preprocessor/resolver/group.rs +96 -91
  134. package/rust/core/preprocessor/resolver/let_.rs +32 -28
  135. package/rust/core/preprocessor/resolver/loop_.rs +320 -91
  136. package/rust/core/preprocessor/resolver/mod.rs +15 -15
  137. package/rust/core/preprocessor/resolver/spawn.rs +76 -92
  138. package/rust/core/preprocessor/resolver/synth.rs +56 -50
  139. package/rust/core/preprocessor/resolver/tempo.rs +50 -49
  140. package/rust/core/preprocessor/resolver/trigger.rs +113 -116
  141. package/rust/core/preprocessor/resolver/value.rs +81 -87
  142. package/rust/core/shared/bank.rs +1 -1
  143. package/rust/core/shared/duration.rs +9 -9
  144. package/rust/core/shared/mod.rs +3 -3
  145. package/rust/core/shared/value.rs +35 -32
  146. package/rust/core/store/function.rs +34 -34
  147. package/rust/core/store/global.rs +55 -38
  148. package/rust/core/store/mod.rs +5 -5
  149. package/rust/core/store/variable.rs +37 -34
  150. package/rust/core/utils/mod.rs +2 -2
  151. package/rust/core/utils/path.rs +37 -31
  152. package/rust/core/utils/validation.rs +35 -37
  153. package/rust/installer/addon.rs +84 -80
  154. package/rust/installer/bank.rs +62 -65
  155. package/rust/installer/mod.rs +5 -5
  156. package/rust/installer/plugin.rs +54 -55
  157. package/rust/installer/utils.rs +56 -56
  158. package/rust/lib.rs +156 -164
  159. package/rust/main.rs +250 -145
  160. package/rust/utils/error.rs +200 -0
  161. package/rust/utils/file.rs +38 -35
  162. package/rust/utils/first_usage.rs +76 -0
  163. package/rust/utils/logger.rs +195 -139
  164. package/rust/utils/mod.rs +9 -50
  165. package/rust/utils/signature.rs +19 -17
  166. package/rust/utils/spinner.rs +22 -19
  167. package/rust/utils/telemetry.rs +292 -0
  168. package/rust/utils/watcher.rs +34 -33
  169. package/templates/minimal/README.md +97 -121
  170. package/templates/welcome/README.md +97 -121
  171. package/typescript/bin/index.ts +19 -5
  172. package/typescript/index.ts +3 -1
  173. package/typescript/scripts/postbuild.ts +10 -6
  174. package/typescript/scripts/postinstall.ts +56 -0
  175. package/typescript/scripts/version/bump.ts +0 -1
  176. package/typescript/scripts/version/index.ts +0 -1
  177. package/out-tsc/bin/devalang.exe +0 -0
@@ -1,31 +1,263 @@
1
- use crate::core::{ shared::value::Value, store::variable::VariableTable };
2
-
3
- pub fn evaluate_condition_string(expr: &str, vars: &VariableTable) -> bool {
4
- let tokens: Vec<&str> = expr.split_whitespace().collect();
5
- if tokens.len() != 3 {
6
- return false;
7
- }
8
-
9
- let left = tokens[0];
10
- let op = tokens[1];
11
- let right = tokens[2];
12
-
13
- let left_val = match vars.get(left) {
14
- Some(Value::Number(n)) => *n,
15
- _ => {
16
- return false;
17
- }
18
- };
19
-
20
- let right_val: f32 = right.parse().unwrap_or(0.0);
21
-
22
- match op {
23
- ">" => left_val > right_val,
24
- "<" => left_val < right_val,
25
- ">=" => left_val >= right_val,
26
- "<=" => left_val <= right_val,
27
- "==" => (left_val - right_val).abs() < f32::EPSILON,
28
- "!=" => (left_val - right_val).abs() > f32::EPSILON,
29
- _ => false,
30
- }
31
- }
1
+ use crate::core::audio::special::{
2
+ find_and_eval_first_easing_call, find_and_eval_first_math_call, find_and_eval_first_mod_call,
3
+ resolve_env_atom,
4
+ };
5
+ use crate::core::{shared::value::Value, store::variable::VariableTable};
6
+
7
+ pub fn evaluate_condition_string(expr: &str, vars: &VariableTable) -> bool {
8
+ let tokens: Vec<&str> = expr.split_whitespace().collect();
9
+ if tokens.len() != 3 {
10
+ return false;
11
+ }
12
+
13
+ let left = tokens[0];
14
+ let op = tokens[1];
15
+ let right = tokens[2];
16
+
17
+ let left_val = match vars.get(left) {
18
+ Some(Value::Number(n)) => *n,
19
+ _ => {
20
+ return false;
21
+ }
22
+ };
23
+
24
+ let right_val: f32 = right.parse().unwrap_or(0.0);
25
+
26
+ match op {
27
+ ">" => left_val > right_val,
28
+ "<" => left_val < right_val,
29
+ ">=" => left_val >= right_val,
30
+ "<=" => left_val <= right_val,
31
+ "==" => (left_val - right_val).abs() < f32::EPSILON,
32
+ "!=" => (left_val - right_val).abs() > f32::EPSILON,
33
+ _ => false,
34
+ }
35
+ }
36
+
37
+ // Very small expression evaluator for `$env.*`, `$math.*` and variables.
38
+ // Supports: +, -, *, / and simple parentheses, left-to-right (no precedence), and $math.(sin|cos)(expr)
39
+ pub fn evaluate_numeric_expression(
40
+ expr: &str,
41
+ vars: &VariableTable,
42
+ env_bpm: f32,
43
+ env_beat: f32,
44
+ ) -> Option<f32> {
45
+ let expr = expr.replace(" ", "");
46
+
47
+ // Helper to resolve an atom to a number
48
+ fn resolve_atom(atom: &str, vars: &VariableTable, bpm: f32, beat: f32) -> Option<f32> {
49
+ if let Some(v) = resolve_env_atom(atom, bpm, beat) {
50
+ return Some(v);
51
+ }
52
+ if let Ok(n) = atom.parse::<f32>() {
53
+ return Some(n);
54
+ }
55
+ if let Some(Value::Number(n)) = vars.get(atom) {
56
+ return Some(*n);
57
+ }
58
+ None
59
+ }
60
+
61
+ // Shunting-like, simplified: first evaluate any $math.func(...) calls anywhere in the expression,
62
+ // then fold remaining parentheses and evaluate left-to-right.
63
+ fn eval(expr: &str, vars: &VariableTable, bpm: f32, beat: f32) -> Option<f32> {
64
+ // 1) Replace $math.* calls progressively
65
+ let mut s = expr.to_string();
66
+ // Evaluate modulators first (they may feed easing/math)
67
+ while let Some(next) =
68
+ find_and_eval_first_mod_call(&s, evaluate_numeric_expression, vars, bpm, beat)
69
+ {
70
+ s = next;
71
+ }
72
+ // Then easing functions
73
+ while let Some(next) =
74
+ find_and_eval_first_easing_call(&s, evaluate_numeric_expression, vars, bpm, beat)
75
+ {
76
+ s = next;
77
+ }
78
+ // Finally math transforms
79
+ while let Some(next) =
80
+ find_and_eval_first_math_call(&s, evaluate_numeric_expression, vars, bpm, beat)
81
+ {
82
+ s = next;
83
+ }
84
+
85
+ // 2) Evaluate remaining (pure) parentheses starting from innermost
86
+ if let Some(open) = s.rfind('(') {
87
+ if let Some(close_rel) = s[open..].find(')') {
88
+ // index relatif
89
+ let close = open + close_rel;
90
+ let inner = &s[open + 1..close];
91
+ let val = eval(inner, vars, bpm, beat)?;
92
+ let mut replaced = String::new();
93
+ replaced.push_str(&s[..open]);
94
+ replaced.push_str(&val.to_string());
95
+ replaced.push_str(&s[close + 1..]);
96
+ return eval(&replaced, vars, bpm, beat);
97
+ }
98
+ }
99
+
100
+ // Tokenize by operators left-to-right
101
+ let mut parts: Vec<String> = Vec::new();
102
+ let mut cur = String::new();
103
+ for ch in s.chars() {
104
+ if "+-*/".contains(ch) {
105
+ if !cur.is_empty() {
106
+ parts.push(cur.clone());
107
+ cur.clear();
108
+ }
109
+ parts.push(ch.to_string());
110
+ } else {
111
+ cur.push(ch);
112
+ }
113
+ }
114
+ if !cur.is_empty() {
115
+ parts.push(cur);
116
+ }
117
+ if parts.is_empty() {
118
+ return None;
119
+ }
120
+
121
+ // Resolve atoms and compute
122
+ let mut acc: Option<f32> = None;
123
+ let mut op: Option<char> = None;
124
+ for part in parts {
125
+ if part.len() == 1 && "+-*/".contains(part.chars().next().unwrap()) {
126
+ op = part.chars().next();
127
+ continue;
128
+ }
129
+ let val = if let Some(v) = resolve_atom(&part, vars, bpm, beat) {
130
+ v
131
+ } else if part.starts_with("$env.") {
132
+ // $env atom not handled by resolve_atom (when composed), try recursive eval
133
+ eval(&part, vars, bpm, beat)?
134
+ } else {
135
+ return None;
136
+ };
137
+
138
+ acc = Some(match (acc, op) {
139
+ (None, _) => val,
140
+ (Some(a), Some('+')) => a + val,
141
+ (Some(a), Some('-')) => a - val,
142
+ (Some(a), Some('*')) => a * val,
143
+ (Some(a), Some('/')) => {
144
+ if val != 0.0 {
145
+ a / val
146
+ } else {
147
+ return Some(f32::INFINITY);
148
+ }
149
+ }
150
+ (Some(_), None) => val,
151
+ _ => return None,
152
+ });
153
+ }
154
+
155
+ acc
156
+ }
157
+
158
+ eval(&expr, vars, env_bpm, env_beat)
159
+ }
160
+
161
+ pub fn evaluate_rhs_into_value(
162
+ raw: &str,
163
+ vars: &VariableTable,
164
+ env_bpm: f32,
165
+ env_beat: f32,
166
+ ) -> Value {
167
+ if let Some(num) = evaluate_numeric_expression(raw, vars, env_bpm, env_beat) {
168
+ Value::Number(num)
169
+ } else {
170
+ Value::String(raw.to_string())
171
+ }
172
+ }
173
+
174
+ // Evaluate a simple string concatenation expression like: "hello " + name + "!" + $env.beat
175
+ // - Splits on + outside quotes
176
+ // - Terms can be string literals (double quotes), variables (Number/String/Boolean), or numeric env/math expressions
177
+ // Returns None if parsing fails (fallback to raw print)
178
+ pub fn evaluate_string_expression(
179
+ expr: &str,
180
+ vars: &VariableTable,
181
+ env_bpm: f32,
182
+ env_beat: f32,
183
+ ) -> Option<String> {
184
+ // Quick reject if no '+' present
185
+ if !expr.contains('+') {
186
+ return None;
187
+ }
188
+
189
+ // Split by '+' outside of quotes
190
+ let mut parts: Vec<String> = Vec::new();
191
+ let mut cur = String::new();
192
+ let mut in_quotes = false;
193
+ let mut escape = false;
194
+ for ch in expr.chars() {
195
+ if escape {
196
+ cur.push(ch);
197
+ escape = false;
198
+ continue;
199
+ }
200
+ if ch == '\\' {
201
+ // escape next char
202
+ escape = true;
203
+ continue;
204
+ }
205
+ if ch == '"' {
206
+ in_quotes = !in_quotes;
207
+ cur.push(ch);
208
+ continue;
209
+ }
210
+ if ch == '+' && !in_quotes {
211
+ parts.push(cur.to_string());
212
+ cur.clear();
213
+ continue;
214
+ }
215
+ cur.push(ch);
216
+ }
217
+ if !cur.is_empty() {
218
+ parts.push(cur.to_string());
219
+ }
220
+ if parts.is_empty() {
221
+ return None;
222
+ }
223
+
224
+ // Resolve each part into a string
225
+ fn strip_quotes(s: &str) -> Option<String> {
226
+ let st = s.trim();
227
+ if st.len() >= 2 && st.starts_with('"') && st.ends_with('"') {
228
+ Some(st[1..st.len() - 1].to_string())
229
+ } else {
230
+ None
231
+ }
232
+ }
233
+
234
+ let mut out = String::new();
235
+ for p in parts {
236
+ if p.is_empty() {
237
+ continue;
238
+ }
239
+ if let Some(lit) = strip_quotes(&p) {
240
+ out.push_str(&lit);
241
+ continue;
242
+ }
243
+ // Try variables first
244
+ if let Some(val) = vars.get(&p) {
245
+ match val {
246
+ crate::core::shared::value::Value::String(s) => out.push_str(s),
247
+ crate::core::shared::value::Value::Number(n) => out.push_str(&format!("{}", n)),
248
+ crate::core::shared::value::Value::Boolean(b) => out.push_str(&format!("{}", b)),
249
+ other => out.push_str(&format!("{:?}", other)),
250
+ }
251
+ continue;
252
+ }
253
+ // Try env/math/numeric expression for this term
254
+ if let Some(n) = evaluate_numeric_expression(&p, vars, env_bpm, env_beat) {
255
+ out.push_str(&format!("{}", n));
256
+ continue;
257
+ }
258
+ // Bareword not resolved: include as-is (safe fallback)
259
+ out.push_str(&p);
260
+ }
261
+
262
+ Some(out)
263
+ }
@@ -1,161 +1,198 @@
1
- use crate::core::{
2
- audio::engine::AudioEngine,
3
- parser::statement::{ Statement, StatementKind },
4
- shared::value::Value,
5
- store::variable::VariableTable,
6
- };
7
-
8
- use std::collections::HashMap;
9
-
10
- pub fn interprete_call_arrow_statement(
11
- stmt: &Statement,
12
- audio_engine: &mut AudioEngine,
13
- variable_table: &VariableTable,
14
- base_bpm: f32,
15
- base_duration: f32,
16
- max_end_time: &mut f32,
17
- mut cursor_time: Option<&mut f32>,
18
- update_cursor: bool
19
- ) -> (f32, f32) {
20
- let cursor_copy = cursor_time
21
- .as_ref()
22
- .map(|c| **c)
23
- .unwrap_or(0.0);
24
-
25
- if let StatementKind::ArrowCall { target, method, args } = &stmt.kind {
26
- let Some(Value::Statement(synth_stmt)) = variable_table.get(target) else {
27
- println!("❌ Synth '{}' not found in variable table", target);
28
- return (*max_end_time, cursor_copy);
29
- };
30
-
31
- let Value::Map(synth_map) = &synth_stmt.value else {
32
- println!("❌ Invalid synth statement for '{}', expected a map.", target);
33
- return (*max_end_time, cursor_copy);
34
- };
35
-
36
- let Some(Value::String(entity)) = synth_map.get("entity") else {
37
- println!("❌ Missing 'entity' key in synth '{}'.", target);
38
- return (*max_end_time, cursor_copy);
39
- };
40
-
41
- if entity != "synth" {
42
- println!("❌ '{}' is not a synth, entity is '{}'.", target, entity);
43
- return (*max_end_time, cursor_copy);
44
- }
45
-
46
- let Some(Value::Map(value_map)) = synth_map.get("value") else {
47
- println!("❌ Missing 'value' map in synth '{}'.", target);
48
- return (*max_end_time, cursor_copy);
49
- };
50
-
51
- let Some(Value::String(waveform)) = value_map.get("waveform") else {
52
- println!("❌ Missing or invalid 'waveform' in synth '{}'.", target);
53
- return (*max_end_time, cursor_copy);
54
- };
55
-
56
- let Some(Value::Map(params)) = value_map.get("parameters") else {
57
- println!("❌ Missing or invalid 'parameters' in synth '{}'.", target);
58
- return (*max_end_time, cursor_copy);
59
- };
60
-
61
- // Synth parameters
62
- let synth_params = params.clone();
63
- let amp = extract_f32(&synth_params, "amp", base_bpm).unwrap_or(1.0);
64
-
65
- if method == "note" {
66
- let filtered_args: Vec<_> = args
67
- .iter()
68
- .filter(|arg| !matches!(arg, Value::Unknown))
69
- .collect();
70
-
71
- let Some(Value::Identifier(note_name)) = filtered_args.get(0).map(|v| (*v).clone()) else {
72
- println!("❌ Invalid or missing argument for 'note' method on '{}'.", target);
73
- return (*max_end_time, cursor_copy);
74
- };
75
-
76
- let mut note_params = HashMap::new();
77
- if let Some(arg1) = filtered_args.get(1) {
78
- match (*arg1).clone() {
79
- Value::Map(map) => {
80
- for (key, value) in map {
81
- note_params.insert(key, value);
82
- }
83
- }
84
- _ => {}
85
- }
86
- }
87
-
88
- // Note parameters and calculations
89
- let amp_note = extract_f32(&note_params, "amp", base_bpm).unwrap_or(amp);
90
- let duration_ms = extract_f32(&note_params, "duration", base_bpm)
91
- .unwrap_or(base_duration * 1000.0);
92
-
93
- let duration_secs = duration_ms / 1000.0;
94
- let final_freq = note_to_freq(&note_name);
95
- let start_time = cursor_copy;
96
- let end_time = start_time + duration_secs;
97
-
98
- audio_engine.insert_note(
99
- waveform.clone(),
100
- final_freq,
101
- amp_note,
102
- start_time * 1000.0,
103
- duration_ms,
104
- synth_params,
105
- note_params
106
- );
107
-
108
- *max_end_time = (*max_end_time).max(end_time);
109
-
110
- if update_cursor {
111
- if let Some(c) = cursor_time.as_mut() {
112
- **c = end_time;
113
- }
114
- }
115
-
116
- return (*max_end_time, end_time);
117
- } else {
118
- println!("❌ Unknown method '{}' on synth '{}'.", method, target);
119
- }
120
- }
121
-
122
- (*max_end_time, cursor_copy)
123
- }
124
-
125
- fn extract_f32(map: &HashMap<String, Value>, key: &str, base_bpm: f32) -> Option<f32> {
126
- map.get(key).and_then(|v| {
127
- match v {
128
- Value::Number(n) => Some(*n),
129
- Value::Beat(beat_str) => {
130
- let parts: Vec<&str> = beat_str.split('/').collect();
131
- if parts.len() == 2 {
132
- let numerator = parts[0].parse::<f32>().ok()?;
133
- let denominator = parts[1].parse::<f32>().ok()?;
134
-
135
- Some((numerator / denominator) * ((60.0 / base_bpm) * 1000.0))
136
- } else {
137
- None
138
- }
139
- }
140
- _ => None,
141
- }
142
- })
143
- }
144
-
145
- fn note_to_freq(note: &str) -> f32 {
146
- let notes = vec!["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
147
-
148
- if note.len() < 2 || note.len() > 3 {
149
- return 440.0;
150
- }
151
-
152
- let (name, octave_str) = note.split_at(note.len() - 1);
153
- let semitone = notes
154
- .iter()
155
- .position(|&n| n == name)
156
- .unwrap_or(9) as i32;
157
- let octave = octave_str.parse::<i32>().unwrap_or(4);
158
- let midi_note = (octave + 1) * 12 + semitone;
159
-
160
- 440.0 * (2.0_f32).powf(((midi_note as f32) - 69.0) / 12.0)
161
- }
1
+ use crate::core::{
2
+ audio::engine::AudioEngine,
3
+ parser::statement::{Statement, StatementKind},
4
+ shared::value::Value,
5
+ store::variable::VariableTable,
6
+ };
7
+
8
+ use std::collections::HashMap;
9
+
10
+ pub fn interprete_call_arrow_statement(
11
+ stmt: &Statement,
12
+ audio_engine: &mut AudioEngine,
13
+ variable_table: &VariableTable,
14
+ base_bpm: f32,
15
+ base_duration: f32,
16
+ max_end_time: &mut f32,
17
+ mut cursor_time: Option<&mut f32>,
18
+ update_cursor: bool,
19
+ ) -> (f32, f32) {
20
+ let cursor_copy = cursor_time.as_ref().map(|c| **c).unwrap_or(0.0);
21
+
22
+ if let StatementKind::ArrowCall {
23
+ target,
24
+ method,
25
+ args,
26
+ } = &stmt.kind
27
+ {
28
+ let Some(Value::Statement(synth_stmt)) = variable_table.get(target) else {
29
+ println!("❌ Synth '{}' not found in variable table", target);
30
+ return (*max_end_time, cursor_copy);
31
+ };
32
+
33
+ let Value::Map(synth_map) = &synth_stmt.value else {
34
+ println!(
35
+ "❌ Invalid synth statement for '{}', expected a map.",
36
+ target
37
+ );
38
+ return (*max_end_time, cursor_copy);
39
+ };
40
+
41
+ let Some(Value::String(entity)) = synth_map.get("entity") else {
42
+ println!("❌ Missing 'entity' key in synth '{}'.", target);
43
+ return (*max_end_time, cursor_copy);
44
+ };
45
+
46
+ if entity != "synth" {
47
+ println!("❌ '{}' is not a synth, entity is '{}'.", target, entity);
48
+ return (*max_end_time, cursor_copy);
49
+ }
50
+
51
+ let Some(Value::Map(value_map)) = synth_map.get("value") else {
52
+ println!("❌ Missing 'value' map in synth '{}'.", target);
53
+ return (*max_end_time, cursor_copy);
54
+ };
55
+
56
+ let Some(Value::String(waveform)) = value_map.get("waveform") else {
57
+ println!("❌ Missing or invalid 'waveform' in synth '{}'.", target);
58
+ return (*max_end_time, cursor_copy);
59
+ };
60
+
61
+ let Some(Value::Map(params)) = value_map.get("parameters") else {
62
+ println!("❌ Missing or invalid 'parameters' in synth '{}'.", target);
63
+ return (*max_end_time, cursor_copy);
64
+ };
65
+
66
+ // Synth parameters
67
+ let synth_params = params.clone();
68
+ let amp = extract_f32(&synth_params, "amp", base_bpm).unwrap_or(1.0);
69
+
70
+ if method == "note" {
71
+ let filtered_args: Vec<_> = args
72
+ .iter()
73
+ .filter(|arg| !matches!(arg, Value::Unknown))
74
+ .collect();
75
+
76
+ let Some(Value::Identifier(note_name)) = filtered_args.get(0).map(|v| (*v).clone())
77
+ else {
78
+ println!(
79
+ "❌ Invalid or missing argument for 'note' method on '{}'.",
80
+ target
81
+ );
82
+ return (*max_end_time, cursor_copy);
83
+ };
84
+
85
+ let mut note_params = HashMap::new();
86
+ if let Some(arg1) = filtered_args.get(1) {
87
+ match (*arg1).clone() {
88
+ Value::Map(map) => {
89
+ for (key, value) in map {
90
+ note_params.insert(key, value);
91
+ }
92
+ }
93
+ _ => {}
94
+ }
95
+ }
96
+
97
+ // Note parameters and calculations
98
+ let amp_note = extract_f32(&note_params, "amp", base_bpm).unwrap_or(amp);
99
+ let duration_ms =
100
+ extract_f32(&note_params, "duration", base_bpm).unwrap_or(base_duration * 1000.0);
101
+
102
+ let duration_secs = duration_ms / 1000.0;
103
+ let final_freq = note_to_freq(&note_name);
104
+ let start_time = cursor_copy;
105
+ let end_time = start_time + duration_secs;
106
+
107
+ // Fetch automation map if present:
108
+ // - Global (per-synth): key "<target>__automation" => map with key "params"
109
+ // - Per-note: note parameter "automate" => map
110
+ let auto_key = format!("{}__automation", target);
111
+ let synth_automation = match variable_table.get(&auto_key) {
112
+ Some(Value::Map(map)) => match map.get("params") {
113
+ Some(Value::Map(p)) => Some(p.clone()),
114
+ _ => None,
115
+ },
116
+ _ => None,
117
+ };
118
+
119
+ let note_automation = match note_params.get("automate") {
120
+ Some(Value::Map(m)) => Some(m.clone()),
121
+ _ => None,
122
+ };
123
+
124
+ // Merge: per-note overrides synth automation per key (volume/pan/pitch)
125
+ let automation = match (synth_automation, note_automation) {
126
+ (Some(mut a), Some(n)) => {
127
+ for (k, v) in n {
128
+ a.insert(k, v);
129
+ }
130
+ Some(a)
131
+ }
132
+ (None, Some(n)) => Some(n),
133
+ (Some(a), None) => Some(a),
134
+ _ => None,
135
+ };
136
+
137
+ audio_engine.insert_note(
138
+ waveform.clone(),
139
+ final_freq,
140
+ amp_note,
141
+ start_time * 1000.0,
142
+ duration_ms,
143
+ synth_params,
144
+ note_params,
145
+ automation,
146
+ );
147
+
148
+ *max_end_time = (*max_end_time).max(end_time);
149
+
150
+ if update_cursor {
151
+ if let Some(c) = cursor_time.as_mut() {
152
+ **c = end_time;
153
+ }
154
+ }
155
+
156
+ return (*max_end_time, end_time);
157
+ } else {
158
+ println!("❌ Unknown method '{}' on synth '{}'.", method, target);
159
+ }
160
+ }
161
+
162
+ (*max_end_time, cursor_copy)
163
+ }
164
+
165
+ fn extract_f32(map: &HashMap<String, Value>, key: &str, base_bpm: f32) -> Option<f32> {
166
+ map.get(key).and_then(|v| match v {
167
+ Value::Number(n) => Some(*n),
168
+ Value::Beat(beat_str) => {
169
+ let parts: Vec<&str> = beat_str.split('/').collect();
170
+ if parts.len() == 2 {
171
+ let numerator = parts[0].parse::<f32>().ok()?;
172
+ let denominator = parts[1].parse::<f32>().ok()?;
173
+
174
+ Some((numerator / denominator) * ((60.0 / base_bpm) * 1000.0))
175
+ } else {
176
+ None
177
+ }
178
+ }
179
+ _ => None,
180
+ })
181
+ }
182
+
183
+ fn note_to_freq(note: &str) -> f32 {
184
+ let notes = vec![
185
+ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
186
+ ];
187
+
188
+ if note.len() < 2 || note.len() > 3 {
189
+ return 440.0;
190
+ }
191
+
192
+ let (name, octave_str) = note.split_at(note.len() - 1);
193
+ let semitone = notes.iter().position(|&n| n == name).unwrap_or(9) as i32;
194
+ let octave = octave_str.parse::<i32>().unwrap_or(4);
195
+ let midi_note = (octave + 1) * 12 + semitone;
196
+
197
+ 440.0 * (2.0_f32).powf(((midi_note as f32) - 69.0) / 12.0)
198
+ }