@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.
- package/.devalang +10 -8
- package/.github/workflows/ci.yml +92 -0
- package/Cargo.toml +60 -58
- package/README.md +32 -15
- package/docs/CHANGELOG.md +93 -1
- package/docs/CONTRIBUTING.md +101 -1
- package/docs/ROADMAP.md +2 -2
- package/docs/TODO.md +1 -1
- package/examples/automation.deva +42 -0
- package/examples/bank.deva +4 -4
- package/examples/events.deva +12 -0
- package/examples/function.deva +4 -4
- package/examples/index.deva +39 -25
- package/examples/loop.deva +5 -11
- package/examples/pattern.deva +8 -0
- package/examples/plugin.deva +16 -0
- package/examples/variables.deva +1 -1
- package/out-tsc/bin/index.js +51 -7
- package/out-tsc/index.js +3 -1
- package/out-tsc/scripts/postbuild.js +9 -10
- package/out-tsc/scripts/postinstall.js +49 -0
- package/package.json +12 -4
- package/project-version.json +3 -3
- package/rust/cli/bank.rs +462 -456
- package/rust/cli/build.rs +252 -199
- package/rust/cli/check.rs +221 -180
- package/rust/cli/driver.rs +297 -292
- package/rust/cli/generator.rs +1 -0
- package/rust/cli/init.rs +87 -79
- package/rust/cli/install.rs +35 -32
- package/rust/cli/login.rs +127 -134
- package/rust/cli/mod.rs +13 -11
- package/rust/cli/play.rs +1123 -218
- package/rust/cli/telemetry.rs +19 -0
- package/rust/cli/template.rs +69 -57
- package/rust/cli/update.rs +6 -4
- package/rust/common/api.rs +5 -8
- package/rust/common/cdn.rs +3 -6
- package/rust/common/mod.rs +3 -3
- package/rust/common/sso.rs +3 -6
- package/rust/config/driver.rs +118 -94
- package/rust/config/loader.rs +165 -156
- package/rust/config/mod.rs +4 -2
- package/rust/config/settings.rs +91 -0
- package/rust/config/stats.rs +257 -0
- package/rust/core/audio/engine.rs +696 -518
- package/rust/core/audio/evaluator.rs +263 -31
- package/rust/core/audio/interpreter/arrow_call.rs +198 -161
- package/rust/core/audio/interpreter/automate.rs +18 -0
- package/rust/core/audio/interpreter/call.rs +98 -95
- package/rust/core/audio/interpreter/condition.rs +70 -71
- package/rust/core/audio/interpreter/driver.rs +487 -198
- package/rust/core/audio/interpreter/function.rs +26 -21
- package/rust/core/audio/interpreter/let_.rs +38 -19
- package/rust/core/audio/interpreter/load.rs +18 -18
- package/rust/core/audio/interpreter/loop_.rs +113 -73
- package/rust/core/audio/interpreter/mod.rs +14 -13
- package/rust/core/audio/interpreter/sleep.rs +27 -30
- package/rust/core/audio/interpreter/spawn.rs +105 -102
- package/rust/core/audio/interpreter/tempo.rs +19 -16
- package/rust/core/audio/interpreter/trigger.rs +239 -210
- package/rust/core/audio/loader/mod.rs +1 -1
- package/rust/core/audio/loader/trigger.rs +100 -97
- package/rust/core/audio/mod.rs +7 -6
- package/rust/core/audio/player.rs +64 -64
- package/rust/core/audio/renderer.rs +56 -53
- package/rust/core/audio/special/easing.rs +189 -0
- package/rust/core/audio/special/env.rs +43 -0
- package/rust/core/audio/special/math.rs +102 -0
- package/rust/core/audio/special/mod.rs +9 -0
- package/rust/core/audio/special/modulator.rs +143 -0
- package/rust/core/builder/mod.rs +80 -85
- package/rust/core/debugger/lexer.rs +27 -27
- package/rust/core/debugger/mod.rs +24 -23
- package/rust/core/debugger/module.rs +55 -47
- package/rust/core/debugger/preprocessor.rs +27 -27
- package/rust/core/debugger/store.rs +40 -39
- package/rust/core/error/mod.rs +80 -66
- package/rust/core/lexer/handler/arrow.rs +82 -31
- package/rust/core/lexer/handler/at.rs +21 -21
- package/rust/core/lexer/handler/brace.rs +41 -41
- package/rust/core/lexer/handler/colon.rs +21 -21
- package/rust/core/lexer/handler/comment.rs +30 -30
- package/rust/core/lexer/handler/dot.rs +21 -21
- package/rust/core/lexer/handler/driver.rs +337 -263
- package/rust/core/lexer/handler/identifier.rs +46 -42
- package/rust/core/lexer/handler/indent.rs +66 -66
- package/rust/core/lexer/handler/mod.rs +16 -16
- package/rust/core/lexer/handler/newline.rs +23 -23
- package/rust/core/lexer/handler/number.rs +31 -31
- package/rust/core/lexer/handler/operator.rs +46 -44
- package/rust/core/lexer/handler/parenthesis.rs +41 -41
- package/rust/core/lexer/handler/slash.rs +21 -21
- package/rust/core/lexer/handler/string.rs +63 -63
- package/rust/core/lexer/mod.rs +54 -51
- package/rust/core/lexer/token.rs +97 -91
- package/rust/core/mod.rs +11 -11
- package/rust/core/parser/driver.rs +513 -408
- package/rust/core/parser/handler/arrow_call.rs +233 -211
- package/rust/core/parser/handler/at.rs +245 -162
- package/rust/core/parser/handler/bank.rs +94 -69
- package/rust/core/parser/handler/condition.rs +80 -74
- package/rust/core/parser/handler/dot.rs +143 -135
- package/rust/core/parser/handler/identifier/automate.rs +257 -0
- package/rust/core/parser/handler/identifier/call.rs +91 -88
- package/rust/core/parser/handler/identifier/emit.rs +66 -0
- package/rust/core/parser/handler/identifier/function.rs +100 -92
- package/rust/core/parser/handler/identifier/group.rs +85 -75
- package/rust/core/parser/handler/identifier/let_.rs +158 -127
- package/rust/core/parser/handler/identifier/mod.rs +54 -52
- package/rust/core/parser/handler/identifier/on.rs +98 -0
- package/rust/core/parser/handler/identifier/print.rs +52 -0
- package/rust/core/parser/handler/identifier/sleep.rs +36 -33
- package/rust/core/parser/handler/identifier/spawn.rs +91 -88
- package/rust/core/parser/handler/identifier/synth.rs +65 -65
- package/rust/core/parser/handler/loop_.rs +170 -72
- package/rust/core/parser/handler/mod.rs +8 -8
- package/rust/core/parser/handler/tempo.rs +53 -47
- package/rust/core/parser/mod.rs +4 -4
- package/rust/core/parser/statement.rs +142 -108
- package/rust/core/plugin/loader.rs +123 -48
- package/rust/core/plugin/mod.rs +2 -1
- package/rust/core/plugin/runner.rs +296 -0
- package/rust/core/preprocessor/loader.rs +515 -326
- package/rust/core/preprocessor/mod.rs +4 -4
- package/rust/core/preprocessor/module.rs +60 -58
- package/rust/core/preprocessor/processor.rs +99 -101
- package/rust/core/preprocessor/resolver/bank.rs +51 -49
- package/rust/core/preprocessor/resolver/call.rs +100 -100
- package/rust/core/preprocessor/resolver/condition.rs +97 -97
- package/rust/core/preprocessor/resolver/driver.rs +310 -278
- package/rust/core/preprocessor/resolver/function.rs +69 -78
- package/rust/core/preprocessor/resolver/group.rs +96 -91
- package/rust/core/preprocessor/resolver/let_.rs +32 -28
- package/rust/core/preprocessor/resolver/loop_.rs +320 -91
- package/rust/core/preprocessor/resolver/mod.rs +15 -15
- package/rust/core/preprocessor/resolver/spawn.rs +76 -92
- package/rust/core/preprocessor/resolver/synth.rs +56 -50
- package/rust/core/preprocessor/resolver/tempo.rs +50 -49
- package/rust/core/preprocessor/resolver/trigger.rs +113 -116
- package/rust/core/preprocessor/resolver/value.rs +81 -87
- package/rust/core/shared/bank.rs +1 -1
- package/rust/core/shared/duration.rs +9 -9
- package/rust/core/shared/mod.rs +3 -3
- package/rust/core/shared/value.rs +35 -32
- package/rust/core/store/function.rs +34 -34
- package/rust/core/store/global.rs +55 -38
- package/rust/core/store/mod.rs +5 -5
- package/rust/core/store/variable.rs +37 -34
- package/rust/core/utils/mod.rs +2 -2
- package/rust/core/utils/path.rs +37 -31
- package/rust/core/utils/validation.rs +35 -37
- package/rust/installer/addon.rs +84 -80
- package/rust/installer/bank.rs +62 -65
- package/rust/installer/mod.rs +5 -5
- package/rust/installer/plugin.rs +54 -55
- package/rust/installer/utils.rs +56 -56
- package/rust/lib.rs +156 -164
- package/rust/main.rs +250 -145
- package/rust/utils/error.rs +200 -0
- package/rust/utils/file.rs +38 -35
- package/rust/utils/first_usage.rs +76 -0
- package/rust/utils/logger.rs +195 -139
- package/rust/utils/mod.rs +9 -50
- package/rust/utils/signature.rs +19 -17
- package/rust/utils/spinner.rs +22 -19
- package/rust/utils/telemetry.rs +292 -0
- package/rust/utils/watcher.rs +34 -33
- package/templates/minimal/README.md +97 -121
- package/templates/welcome/README.md +97 -121
- package/typescript/bin/index.ts +19 -5
- package/typescript/index.ts +3 -1
- package/typescript/scripts/postbuild.ts +10 -6
- package/typescript/scripts/postinstall.ts +56 -0
- package/typescript/scripts/version/bump.ts +0 -1
- package/typescript/scripts/version/index.ts +0 -1
- package/out-tsc/bin/devalang.exe +0 -0
|
@@ -1,31 +1,263 @@
|
|
|
1
|
-
use crate::core::
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
let
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
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::{
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return (*max_end_time, cursor_copy);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
println!("❌ '
|
|
43
|
-
return (*max_end_time, cursor_copy);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
println!("❌
|
|
48
|
-
return (*max_end_time, cursor_copy);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let Some(Value::
|
|
52
|
-
println!("❌ Missing
|
|
53
|
-
return (*max_end_time, cursor_copy);
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
let Some(Value::
|
|
57
|
-
println!("❌ Missing or invalid '
|
|
58
|
-
return (*max_end_time, cursor_copy);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
let
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
let
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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(¬e_params, "amp", base_bpm).unwrap_or(amp);
|
|
99
|
+
let duration_ms =
|
|
100
|
+
extract_f32(¬e_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(¬e_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
|
+
}
|