@devaloop/devalang 0.0.1-beta.1 → 0.0.1-beta.3

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 (207) hide show
  1. package/.devalang +9 -10
  2. package/Cargo.toml +84 -80
  3. package/README.md +10 -7
  4. package/docs/CHANGELOG.md +83 -0
  5. package/docs/ROADMAP.md +6 -2
  6. package/docs/TODO.md +3 -14
  7. package/examples/bus.deva +10 -0
  8. package/examples/chain.deva +19 -0
  9. package/examples/effect.deva +2 -0
  10. package/examples/filter.deva +11 -0
  11. package/examples/lfo.deva +9 -0
  12. package/examples/plugin.deva +10 -10
  13. package/examples/routing.deva +23 -0
  14. package/examples/synth.deva +11 -1
  15. package/examples/synth_types.deva +17 -0
  16. package/out-tsc/bin/project-version.json +6 -0
  17. package/out-tsc/core/functions/index.d.ts +5 -0
  18. package/out-tsc/core/functions/index.js +11 -0
  19. package/out-tsc/pkg/devalang_core.d.ts +2 -0
  20. package/out-tsc/pkg/devalang_core.js +17 -2
  21. package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +1 -0
  22. package/out-tsc/scripts/version/copy-to-binary.d.ts +1 -0
  23. package/out-tsc/scripts/version/copy-to-binary.js +79 -0
  24. package/package.json +23 -10
  25. package/project-version.json +3 -3
  26. package/rust/bindings/Cargo.toml +9 -0
  27. package/rust/bindings/src/lib.rs +86 -0
  28. package/rust/cli/addon/commands.rs +35 -0
  29. package/rust/cli/addon/download.rs +234 -0
  30. package/rust/cli/addon/install.rs +33 -0
  31. package/rust/cli/addon/list.rs +224 -0
  32. package/rust/cli/addon/metadata.rs +124 -0
  33. package/rust/cli/addon/mod.rs +8 -0
  34. package/rust/cli/addon/remove.rs +271 -0
  35. package/rust/cli/addon/update.rs +305 -0
  36. package/rust/cli/{install/addon.rs → addon/utils.rs} +34 -43
  37. package/rust/cli/build/commands.rs +153 -103
  38. package/rust/cli/build/mod.rs +2 -2
  39. package/rust/cli/build/process.rs +165 -146
  40. package/rust/cli/check/mod.rs +208 -208
  41. package/rust/cli/discover/commands.rs +53 -31
  42. package/rust/cli/discover/config.rs +2 -4
  43. package/rust/cli/discover/install.rs +139 -28
  44. package/rust/cli/discover/metadata.rs +3 -3
  45. package/rust/cli/login/commands.rs +124 -124
  46. package/rust/cli/me/commands.rs +52 -0
  47. package/rust/cli/me/mod.rs +1 -0
  48. package/rust/cli/mod.rs +2 -2
  49. package/rust/cli/parser.rs +76 -70
  50. package/rust/cli/play/commands.rs +375 -324
  51. package/rust/cli/play/mod.rs +5 -5
  52. package/rust/cli/play/process.rs +159 -150
  53. package/rust/cli/play/realtime.rs +91 -91
  54. package/rust/cli/telemetry/commands.rs +22 -22
  55. package/rust/cli/telemetry/event_creator.rs +80 -80
  56. package/rust/cli/telemetry/mod.rs +3 -3
  57. package/rust/cli/telemetry/send.rs +51 -51
  58. package/rust/cli/template/commands.rs +69 -69
  59. package/rust/config/driver.rs +112 -103
  60. package/rust/config/mod.rs +3 -3
  61. package/rust/config/ops.rs +26 -26
  62. package/rust/config/settings.rs +101 -101
  63. package/rust/core/audio/engine/driver.rs +237 -0
  64. package/rust/core/audio/engine/export.rs +169 -0
  65. package/rust/core/audio/engine/helpers.rs +178 -170
  66. package/rust/core/audio/engine/mod.rs +56 -7
  67. package/rust/core/audio/engine/notes/dsp.rs +88 -0
  68. package/rust/core/audio/engine/notes/mod.rs +53 -0
  69. package/rust/core/audio/engine/notes/params.rs +294 -0
  70. package/rust/core/audio/engine/sample/insert.rs +300 -0
  71. package/rust/core/audio/engine/sample/mod.rs +40 -0
  72. package/rust/core/audio/engine/sample/padding.rs +170 -0
  73. package/rust/core/audio/evaluator/condition.rs +61 -0
  74. package/rust/core/audio/evaluator/mod.rs +9 -0
  75. package/rust/core/audio/{evaluator.rs → evaluator/numeric.rs} +152 -310
  76. package/rust/core/audio/evaluator/rhs.rs +16 -0
  77. package/rust/core/audio/evaluator/string_expr.rs +94 -0
  78. package/rust/core/audio/interpreter/driver.rs +574 -542
  79. package/rust/core/audio/interpreter/mod.rs +2 -14
  80. package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +179 -0
  81. package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +398 -0
  82. package/rust/core/audio/interpreter/statements/arrow_call/methods/effects.rs +323 -0
  83. package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +3 -0
  84. package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +371 -0
  85. package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -0
  86. package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -0
  87. package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -0
  88. package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -0
  89. package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -0
  90. package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -0
  91. package/rust/core/audio/interpreter/{automate.rs → statements/automate.rs} +2 -4
  92. package/rust/core/audio/interpreter/{call.rs → statements/call.rs} +36 -5
  93. package/rust/core/audio/interpreter/{condition.rs → statements/condition.rs} +72 -71
  94. package/rust/core/audio/interpreter/{function.rs → statements/function.rs} +24 -26
  95. package/rust/core/audio/interpreter/{let_.rs → statements/let_.rs} +36 -38
  96. package/rust/core/audio/interpreter/{load.rs → statements/load.rs} +17 -19
  97. package/rust/core/audio/interpreter/{loop_.rs → statements/loop_.rs} +115 -114
  98. package/rust/core/audio/interpreter/statements/mod.rs +12 -0
  99. package/rust/core/audio/interpreter/{sleep.rs → statements/sleep.rs} +28 -28
  100. package/rust/core/audio/interpreter/{spawn.rs → statements/spawn.rs} +54 -4
  101. package/rust/core/audio/interpreter/{tempo.rs → statements/tempo.rs} +40 -40
  102. package/rust/core/audio/interpreter/{trigger.rs → statements/trigger.rs} +242 -239
  103. package/rust/core/audio/loader/trigger.rs +98 -97
  104. package/rust/core/audio/mod.rs +6 -7
  105. package/rust/core/audio/special/easing.rs +189 -189
  106. package/rust/core/audio/special/env.rs +45 -45
  107. package/rust/core/audio/special/math.rs +134 -134
  108. package/rust/core/audio/special/modulator.rs +143 -143
  109. package/rust/core/builder/mod.rs +129 -86
  110. package/rust/core/debugger/{module.rs → logs.rs} +52 -55
  111. package/rust/core/debugger/mod.rs +30 -30
  112. package/rust/core/debugger/store.rs +38 -40
  113. package/rust/core/error/mod.rs +269 -269
  114. package/rust/core/lexer/driver.rs +2 -4
  115. package/rust/core/mod.rs +9 -10
  116. package/rust/core/parser/driver/block.rs +111 -0
  117. package/rust/core/parser/driver/cursor.rs +82 -0
  118. package/rust/core/parser/driver/driver_impl.rs +159 -0
  119. package/rust/core/parser/driver/mod.rs +6 -0
  120. package/rust/core/parser/driver/parse_array.rs +120 -0
  121. package/rust/core/parser/driver/parse_map.rs +247 -0
  122. package/rust/core/parser/driver/parser.rs +160 -0
  123. package/rust/core/parser/handler/arrow_call.rs +90 -15
  124. package/rust/core/parser/handler/at.rs +279 -279
  125. package/rust/core/parser/handler/bank.rs +104 -104
  126. package/rust/core/parser/handler/condition.rs +83 -83
  127. package/rust/core/parser/handler/dot.rs +148 -148
  128. package/rust/core/parser/handler/identifier/automate.rs +254 -254
  129. package/rust/core/parser/handler/identifier/call.rs +91 -91
  130. package/rust/core/parser/handler/identifier/emit.rs +70 -70
  131. package/rust/core/parser/handler/identifier/function.rs +113 -113
  132. package/rust/core/parser/handler/identifier/group.rs +89 -89
  133. package/rust/core/parser/handler/identifier/let_.rs +173 -173
  134. package/rust/core/parser/handler/identifier/mod.rs +55 -55
  135. package/rust/core/parser/handler/identifier/on.rs +107 -107
  136. package/rust/core/parser/handler/identifier/print.rs +49 -49
  137. package/rust/core/parser/handler/identifier/sleep.rs +96 -43
  138. package/rust/core/parser/handler/identifier/spawn.rs +91 -91
  139. package/rust/core/parser/handler/identifier/synth.rs +39 -3
  140. package/rust/core/parser/handler/loop_.rs +194 -194
  141. package/rust/core/parser/handler/pattern.rs +25 -2
  142. package/rust/core/parser/handler/tempo.rs +105 -57
  143. package/rust/core/parser/statement.rs +10 -11
  144. package/rust/core/plugin/loader.rs +137 -137
  145. package/rust/core/plugin/runner/mod.rs +11 -0
  146. package/rust/core/plugin/{runner.rs → runner/non_wasm.rs} +206 -72
  147. package/rust/core/plugin/runner/wasm32.rs +44 -0
  148. package/rust/core/preprocessor/loader/inject.rs +313 -0
  149. package/rust/core/preprocessor/loader/loader_helpers.rs +110 -0
  150. package/rust/core/preprocessor/loader/mod.rs +235 -0
  151. package/rust/core/preprocessor/module.rs +55 -60
  152. package/rust/core/preprocessor/{processor.rs → processor/handlers.rs} +107 -114
  153. package/rust/core/preprocessor/processor/mod.rs +1 -0
  154. package/rust/core/preprocessor/resolver/function.rs +69 -69
  155. package/rust/core/preprocessor/resolver/group.rs +122 -94
  156. package/rust/core/preprocessor/resolver/pattern.rs +14 -2
  157. package/rust/core/store/global.rs +57 -61
  158. package/rust/core/store/mod.rs +1 -5
  159. package/rust/lib.rs +323 -308
  160. package/rust/macros/Cargo.toml +14 -0
  161. package/rust/macros/src/lib.rs +52 -0
  162. package/rust/main.rs +336 -143
  163. package/rust/types/Cargo.toml +1 -1
  164. package/rust/types/src/addons.rs +57 -55
  165. package/rust/types/src/config.rs +82 -74
  166. package/rust/types/src/lib.rs +15 -12
  167. package/rust/types/src/plugin.rs +20 -0
  168. package/rust/types/src/store.rs +139 -0
  169. package/rust/types/src/telemetry.rs +85 -85
  170. package/rust/utils/Cargo.toml +5 -2
  171. package/rust/utils/src/file.rs +477 -94
  172. package/rust/utils/src/first_usage.rs +97 -97
  173. package/rust/utils/src/lib.rs +9 -9
  174. package/rust/utils/src/logger.rs +200 -200
  175. package/rust/utils/src/path.rs +158 -88
  176. package/rust/utils/src/signature.rs +41 -41
  177. package/rust/utils/src/spinner.rs +20 -20
  178. package/rust/utils/src/version.rs +58 -27
  179. package/rust/utils/src/watcher.rs +46 -46
  180. package/rust/web/api.rs +5 -5
  181. package/rust/web/auth.rs +5 -0
  182. package/rust/web/cdn.rs +34 -34
  183. package/rust/web/forge.rs +5 -0
  184. package/rust/web/mod.rs +2 -0
  185. package/tests/integration.rs +21 -21
  186. package/typescript/core/functions/index.ts +11 -0
  187. package/typescript/pkg/devalang_core.ts +20 -4
  188. package/typescript/scripts/version/copy-to-binary.ts +82 -0
  189. package/rust/cli/bank/api.rs +0 -122
  190. package/rust/cli/bank/commands.rs +0 -275
  191. package/rust/cli/bank/mod.rs +0 -29
  192. package/rust/cli/install/bank.rs +0 -53
  193. package/rust/cli/install/commands.rs +0 -35
  194. package/rust/cli/install/mod.rs +0 -4
  195. package/rust/cli/install/plugin.rs +0 -61
  196. package/rust/core/audio/engine/sample.rs +0 -366
  197. package/rust/core/audio/engine/synth.rs +0 -325
  198. package/rust/core/audio/interpreter/arrow_call.rs +0 -311
  199. package/rust/core/audio/renderer.rs +0 -54
  200. package/rust/core/parser/driver.rs +0 -584
  201. package/rust/core/preprocessor/loader.rs +0 -637
  202. package/rust/core/store/export.rs +0 -28
  203. package/rust/core/store/function.rs +0 -40
  204. package/rust/core/store/import.rs +0 -28
  205. package/rust/core/store/variable.rs +0 -51
  206. package/rust/core/utils/mod.rs +0 -1
  207. package/rust/core/utils/path.rs +0 -37
@@ -1,45 +1,45 @@
1
- use devalang_types::Value;
2
-
3
- use crate::core::store::variable::VariableTable;
4
- use std::sync::OnceLock;
5
- use std::time::{SystemTime, UNIX_EPOCH};
6
-
7
- static SESSION_SEED: OnceLock<f32> = OnceLock::new();
8
-
9
- pub fn get_session_seed() -> f32 {
10
- *SESSION_SEED.get_or_init(|| {
11
- let now = SystemTime::now()
12
- .duration_since(UNIX_EPOCH)
13
- .unwrap_or_default();
14
- // Build a stable 0..1 seed from nanos
15
- let nanos = now.subsec_nanos();
16
- ((nanos as f32) / 1_000_000_000.0).clamp(0.0, 1.0)
17
- })
18
- }
19
-
20
- // Resolve special environment variables like $env.bpm, $env.beat, $env.position
21
- // For now, $env.position is treated as an alias of beat.
22
- pub fn resolve_env_atom(atom: &str, bpm: f32, beat: f32) -> Option<f32> {
23
- match atom {
24
- "$env.bpm" => Some(bpm),
25
- "$env.beat" => Some(beat),
26
- "$env.position" => Some(beat),
27
- // Optional seed for deterministic randomness
28
- "$env.seed" => Some(get_session_seed()),
29
- _ => None,
30
- }
31
- }
32
-
33
- // Utility: resolve an identifier or numeric literal to f32 using the variable table
34
- pub fn resolve_atom_or_var(atom: &str, vars: &VariableTable, bpm: f32, beat: f32) -> Option<f32> {
35
- if let Some(v) = resolve_env_atom(atom, bpm, beat) {
36
- return Some(v);
37
- }
38
- if let Ok(n) = atom.parse::<f32>() {
39
- return Some(n);
40
- }
41
- if let Some(Value::Number(n)) = vars.get(atom) {
42
- return Some(*n);
43
- }
44
- None
45
- }
1
+ use devalang_types::Value;
2
+
3
+ use devalang_types::VariableTable;
4
+ use std::sync::OnceLock;
5
+ use std::time::{SystemTime, UNIX_EPOCH};
6
+
7
+ static SESSION_SEED: OnceLock<f32> = OnceLock::new();
8
+
9
+ pub fn get_session_seed() -> f32 {
10
+ *SESSION_SEED.get_or_init(|| {
11
+ let now = SystemTime::now()
12
+ .duration_since(UNIX_EPOCH)
13
+ .unwrap_or_default();
14
+ // Build a stable 0..1 seed from nanos
15
+ let nanos = now.subsec_nanos();
16
+ ((nanos as f32) / 1_000_000_000.0).clamp(0.0, 1.0)
17
+ })
18
+ }
19
+
20
+ // Resolve special environment variables like $env.bpm, $env.beat, $env.position
21
+ // For now, $env.position is treated as an alias of beat.
22
+ pub fn resolve_env_atom(atom: &str, bpm: f32, beat: f32) -> Option<f32> {
23
+ match atom {
24
+ "$env.bpm" => Some(bpm),
25
+ "$env.beat" => Some(beat),
26
+ "$env.position" => Some(beat),
27
+ // Optional seed for deterministic randomness
28
+ "$env.seed" => Some(get_session_seed()),
29
+ _ => None,
30
+ }
31
+ }
32
+
33
+ // Utility: resolve an identifier or numeric literal to f32 using the variable table
34
+ pub fn resolve_atom_or_var(atom: &str, vars: &VariableTable, bpm: f32, beat: f32) -> Option<f32> {
35
+ if let Some(v) = resolve_env_atom(atom, bpm, beat) {
36
+ return Some(v);
37
+ }
38
+ if let Ok(n) = atom.parse::<f32>() {
39
+ return Some(n);
40
+ }
41
+ if let Some(Value::Number(n)) = vars.get(atom) {
42
+ return Some(*n);
43
+ }
44
+ None
45
+ }
@@ -1,134 +1,134 @@
1
- use crate::core::store::variable::VariableTable;
2
- use devalang_utils::logger::{LogLevel, Logger};
3
-
4
- // Parse comma-separated arguments at top level (no nested parentheses split)
5
- fn parse_top_level_args(s: &str) -> Vec<&str> {
6
- let mut args = Vec::new();
7
- let mut depth = 0i32;
8
- let mut start = 0usize;
9
- for (i, ch) in s.char_indices() {
10
- match ch {
11
- '(' => depth += 1,
12
- ')' => depth -= 1,
13
- ',' if depth == 0 => {
14
- args.push(s[start..i].trim());
15
- start = i + 1;
16
- }
17
- _ => {}
18
- }
19
- }
20
- let last = s[start..].trim();
21
- if !last.is_empty() {
22
- args.push(last);
23
- }
24
- args
25
- }
26
-
27
- fn eval_math_func(func: &str, args: &[f32], fallback_seed: f32) -> Option<f32> {
28
- match func {
29
- "sin" => args.first().copied().map(f32::sin),
30
- "cos" => args.first().copied().map(f32::cos),
31
- "random" => {
32
- // deterministic pseudo-random based on provided seed or a fallback session seed
33
- let seed = args.first().copied().unwrap_or(fallback_seed);
34
- let x = (seed * 12.9898).sin() * 43_758.547;
35
- Some((x.fract() * 2.0 - 1.0).clamp(-1.0, 1.0))
36
- }
37
- "lerp" => {
38
- if args.len() >= 3 {
39
- Some(args[0] + (args[1] - args[0]) * args[2])
40
- } else {
41
- None
42
- }
43
- }
44
- _ => None,
45
- }
46
- }
47
-
48
- // Find and evaluate the first $math.<fn>(...) occurrence in the string, replacing it with a number.
49
- // Supports multi-argument functions by splitting on top-level commas.
50
- pub fn find_and_eval_first_math_call<EvalFn>(
51
- s: &str,
52
- eval: EvalFn,
53
- vars: &VariableTable,
54
- bpm: f32,
55
- beat: f32,
56
- ) -> Option<String>
57
- where
58
- EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
59
- {
60
- let logger = Logger::new();
61
-
62
- let start = s.find("$math.")?;
63
- let open_rel = match s[start..].find('(') {
64
- Some(i) => i,
65
- None => {
66
- logger.log_message(
67
- LogLevel::Error,
68
- &format!("Malformed $math call: missing '(' in '{}'", s),
69
- );
70
- return None;
71
- }
72
- };
73
- let open = start + open_rel;
74
- if open <= start + 6 {
75
- logger.log_message(
76
- LogLevel::Error,
77
- &format!("Malformed $math call: missing function name in '{}'", s),
78
- );
79
- return None;
80
- }
81
- let func = &s[start + 6..open];
82
-
83
- // Find matching close parenthesis, handling nesting
84
- let mut depth: i32 = 0;
85
- let mut close_abs: Option<usize> = None;
86
- for (i, ch) in s[open..].char_indices() {
87
- match ch {
88
- '(' => depth += 1,
89
- ')' => {
90
- depth -= 1;
91
- if depth == 0 {
92
- close_abs = Some(open + i);
93
- break;
94
- }
95
- }
96
- _ => {}
97
- }
98
- }
99
- let close = match close_abs {
100
- Some(c) => c,
101
- None => {
102
- logger.log_message(
103
- LogLevel::Error,
104
- &format!("Malformed $math call: missing closing ')' in '{}'", s),
105
- );
106
- return None;
107
- }
108
- };
109
-
110
- let inner = &s[open + 1..close];
111
- let raw_args = parse_top_level_args(inner);
112
- let mut args: Vec<f32> = Vec::with_capacity(raw_args.len());
113
- for a in raw_args {
114
- if let Some(v) = eval(a, vars, bpm, beat) {
115
- args.push(v);
116
- } else {
117
- logger.log_message(
118
- LogLevel::Error,
119
- &format!("Failed to evaluate argument '{}' for $math.{}", a, func),
120
- );
121
- return None;
122
- }
123
- }
124
-
125
- // If no explicit seed is provided, use $env.seed via fallback
126
- let fallback_seed = eval("$env.seed", vars, bpm, beat).unwrap_or(0.0);
127
- let result = eval_math_func(func, &args, fallback_seed)?;
128
-
129
- let mut replaced = String::new();
130
- replaced.push_str(&s[..start]);
131
- replaced.push_str(&result.to_string());
132
- replaced.push_str(&s[close + 1..]);
133
- Some(replaced)
134
- }
1
+ use devalang_types::VariableTable;
2
+ use devalang_utils::logger::{LogLevel, Logger};
3
+
4
+ // Parse comma-separated arguments at top level (no nested parentheses split)
5
+ fn parse_top_level_args(s: &str) -> Vec<&str> {
6
+ let mut args = Vec::new();
7
+ let mut depth = 0i32;
8
+ let mut start = 0usize;
9
+ for (i, ch) in s.char_indices() {
10
+ match ch {
11
+ '(' => depth += 1,
12
+ ')' => depth -= 1,
13
+ ',' if depth == 0 => {
14
+ args.push(s[start..i].trim());
15
+ start = i + 1;
16
+ }
17
+ _ => {}
18
+ }
19
+ }
20
+ let last = s[start..].trim();
21
+ if !last.is_empty() {
22
+ args.push(last);
23
+ }
24
+ args
25
+ }
26
+
27
+ fn eval_math_func(func: &str, args: &[f32], fallback_seed: f32) -> Option<f32> {
28
+ match func {
29
+ "sin" => args.first().copied().map(f32::sin),
30
+ "cos" => args.first().copied().map(f32::cos),
31
+ "random" => {
32
+ // deterministic pseudo-random based on provided seed or a fallback session seed
33
+ let seed = args.first().copied().unwrap_or(fallback_seed);
34
+ let x = (seed * 12.9898).sin() * 43_758.547;
35
+ Some((x.fract() * 2.0 - 1.0).clamp(-1.0, 1.0))
36
+ }
37
+ "lerp" => {
38
+ if args.len() >= 3 {
39
+ Some(args[0] + (args[1] - args[0]) * args[2])
40
+ } else {
41
+ None
42
+ }
43
+ }
44
+ _ => None,
45
+ }
46
+ }
47
+
48
+ // Find and evaluate the first $math.<fn>(...) occurrence in the string, replacing it with a number.
49
+ // Supports multi-argument functions by splitting on top-level commas.
50
+ pub fn find_and_eval_first_math_call<EvalFn>(
51
+ s: &str,
52
+ eval: EvalFn,
53
+ vars: &VariableTable,
54
+ bpm: f32,
55
+ beat: f32,
56
+ ) -> Option<String>
57
+ where
58
+ EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
59
+ {
60
+ let logger = Logger::new();
61
+
62
+ let start = s.find("$math.")?;
63
+ let open_rel = match s[start..].find('(') {
64
+ Some(i) => i,
65
+ None => {
66
+ logger.log_message(
67
+ LogLevel::Error,
68
+ &format!("Malformed $math call: missing '(' in '{}'", s),
69
+ );
70
+ return None;
71
+ }
72
+ };
73
+ let open = start + open_rel;
74
+ if open <= start + 6 {
75
+ logger.log_message(
76
+ LogLevel::Error,
77
+ &format!("Malformed $math call: missing function name in '{}'", s),
78
+ );
79
+ return None;
80
+ }
81
+ let func = &s[start + 6..open];
82
+
83
+ // Find matching close parenthesis, handling nesting
84
+ let mut depth: i32 = 0;
85
+ let mut close_abs: Option<usize> = None;
86
+ for (i, ch) in s[open..].char_indices() {
87
+ match ch {
88
+ '(' => depth += 1,
89
+ ')' => {
90
+ depth -= 1;
91
+ if depth == 0 {
92
+ close_abs = Some(open + i);
93
+ break;
94
+ }
95
+ }
96
+ _ => {}
97
+ }
98
+ }
99
+ let close = match close_abs {
100
+ Some(c) => c,
101
+ None => {
102
+ logger.log_message(
103
+ LogLevel::Error,
104
+ &format!("Malformed $math call: missing closing ')' in '{}'", s),
105
+ );
106
+ return None;
107
+ }
108
+ };
109
+
110
+ let inner = &s[open + 1..close];
111
+ let raw_args = parse_top_level_args(inner);
112
+ let mut args: Vec<f32> = Vec::with_capacity(raw_args.len());
113
+ for a in raw_args {
114
+ if let Some(v) = eval(a, vars, bpm, beat) {
115
+ args.push(v);
116
+ } else {
117
+ logger.log_message(
118
+ LogLevel::Error,
119
+ &format!("Failed to evaluate argument '{}' for $math.{}", a, func),
120
+ );
121
+ return None;
122
+ }
123
+ }
124
+
125
+ // If no explicit seed is provided, use $env.seed via fallback
126
+ let fallback_seed = eval("$env.seed", vars, bpm, beat).unwrap_or(0.0);
127
+ let result = eval_math_func(func, &args, fallback_seed)?;
128
+
129
+ let mut replaced = String::new();
130
+ replaced.push_str(&s[..start]);
131
+ replaced.push_str(&result.to_string());
132
+ replaced.push_str(&s[close + 1..]);
133
+ Some(replaced)
134
+ }
@@ -1,143 +1,143 @@
1
- use crate::core::store::variable::VariableTable;
2
-
3
- fn lfo_sine(rate_per_beat: f32, beat: f32) -> f32 {
4
- // Output in [-1,1]
5
- (2.0 * std::f32::consts::PI * rate_per_beat * beat).sin()
6
- }
7
-
8
- fn lfo_triangle(rate_per_beat: f32, beat: f32) -> f32 {
9
- // Triangle in [-1,1]
10
- let phase = (rate_per_beat * beat).fract();
11
- // Map [0,1]->[-1,1] tri
12
- 4.0 * (phase - 0.5).abs() - 1.0
13
- }
14
-
15
- fn adsr_envelope_value_t(attack: f32, decay: f32, sustain: f32, release: f32, t: f32) -> f32 {
16
- let a = attack.max(0.0);
17
- let d = decay.max(0.0);
18
- let r = release.max(0.0);
19
- let s = sustain.clamp(0.0, 1.0);
20
-
21
- // Normalize phases so that the whole ADSR spans t in [0,1]
22
- let total = (a + d + r).max(1e-6);
23
- let ap = a / total;
24
- let dp = d / total;
25
- let rp = r / total;
26
-
27
- if t < ap {
28
- // attack (0->1)
29
- if ap > 0.0 { t / ap } else { 1.0 }
30
- } else if t < ap + dp {
31
- // decay (1->sustain)
32
- let u = (t - ap) / dp.max(1e-6);
33
- 1.0 - (1.0 - s) * u
34
- } else if t < 1.0 - rp {
35
- // sustain
36
- s
37
- } else {
38
- // release (sustain->0)
39
- let u = (t - (1.0 - rp)) / rp.max(1e-6);
40
- s * (1.0 - u)
41
- }
42
- }
43
-
44
- fn eval_mod_func(func: &str, args: &[f32], beat: f32) -> Option<f32> {
45
- match func {
46
- "lfo.sine" => {
47
- let rate = args.first().copied().unwrap_or(1.0);
48
- Some(lfo_sine(rate, beat))
49
- }
50
- "lfo.tri" | "lfo.triangle" => {
51
- let rate = args.first().copied().unwrap_or(1.0);
52
- Some(lfo_triangle(rate, beat))
53
- }
54
- // ADSR envelope normalized over t in [0,1]
55
- // $mod.envelope(attack, decay, sustain, release, t)
56
- "envelope" | "mod.envelope" => {
57
- if args.len() >= 5 {
58
- Some(adsr_envelope_value_t(
59
- args[0],
60
- args[1],
61
- args[2],
62
- args[3],
63
- args[4].clamp(0.0, 1.0),
64
- ))
65
- } else {
66
- None
67
- }
68
- }
69
- _ => None,
70
- }
71
- }
72
-
73
- fn parse_top_level_args(s: &str) -> Vec<&str> {
74
- let mut args = Vec::new();
75
- let mut depth = 0i32;
76
- let mut start = 0usize;
77
- for (i, ch) in s.char_indices() {
78
- match ch {
79
- '(' => depth += 1,
80
- ')' => depth -= 1,
81
- ',' if depth == 0 => {
82
- args.push(s[start..i].trim());
83
- start = i + 1;
84
- }
85
- _ => {}
86
- }
87
- }
88
- let last = s[start..].trim();
89
- if !last.is_empty() {
90
- args.push(last);
91
- }
92
- args
93
- }
94
-
95
- // Find and evaluate the first $mod.<fn>(...) occurrence in the string.
96
- pub fn find_and_eval_first_mod_call<EvalFn>(
97
- s: &str,
98
- eval: EvalFn,
99
- vars: &VariableTable,
100
- bpm: f32,
101
- beat: f32,
102
- ) -> Option<String>
103
- where
104
- EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
105
- {
106
- let start = s.find("$mod.")?;
107
- let open_rel = s[start..].find('(')?;
108
- let open = start + open_rel;
109
- let func = &s[start + 5..open];
110
-
111
- // matching close
112
- let mut depth: i32 = 0;
113
- let mut close_abs: Option<usize> = None;
114
- for (i, ch) in s[open..].char_indices() {
115
- match ch {
116
- '(' => depth += 1,
117
- ')' => {
118
- depth -= 1;
119
- if depth == 0 {
120
- close_abs = Some(open + i);
121
- break;
122
- }
123
- }
124
- _ => {}
125
- }
126
- }
127
- let close = close_abs?;
128
-
129
- let inner = &s[open + 1..close];
130
- let raw_args = parse_top_level_args(inner);
131
- let mut args: Vec<f32> = Vec::with_capacity(raw_args.len());
132
- for a in raw_args {
133
- args.push(eval(a, vars, bpm, beat)?);
134
- }
135
-
136
- let result = eval_mod_func(func, &args, beat)?;
137
-
138
- let mut replaced = String::new();
139
- replaced.push_str(&s[..start]);
140
- replaced.push_str(&result.to_string());
141
- replaced.push_str(&s[close + 1..]);
142
- Some(replaced)
143
- }
1
+ use devalang_types::VariableTable;
2
+
3
+ fn lfo_sine(rate_per_beat: f32, beat: f32) -> f32 {
4
+ // Output in [-1,1]
5
+ (2.0 * std::f32::consts::PI * rate_per_beat * beat).sin()
6
+ }
7
+
8
+ fn lfo_triangle(rate_per_beat: f32, beat: f32) -> f32 {
9
+ // Triangle in [-1,1]
10
+ let phase = (rate_per_beat * beat).fract();
11
+ // Map [0,1]->[-1,1] tri
12
+ 4.0 * (phase - 0.5).abs() - 1.0
13
+ }
14
+
15
+ fn adsr_envelope_value_t(attack: f32, decay: f32, sustain: f32, release: f32, t: f32) -> f32 {
16
+ let a = attack.max(0.0);
17
+ let d = decay.max(0.0);
18
+ let r = release.max(0.0);
19
+ let s = sustain.clamp(0.0, 1.0);
20
+
21
+ // Normalize phases so that the whole ADSR spans t in [0,1]
22
+ let total = (a + d + r).max(1e-6);
23
+ let ap = a / total;
24
+ let dp = d / total;
25
+ let rp = r / total;
26
+
27
+ if t < ap {
28
+ // attack (0->1)
29
+ if ap > 0.0 { t / ap } else { 1.0 }
30
+ } else if t < ap + dp {
31
+ // decay (1->sustain)
32
+ let u = (t - ap) / dp.max(1e-6);
33
+ 1.0 - (1.0 - s) * u
34
+ } else if t < 1.0 - rp {
35
+ // sustain
36
+ s
37
+ } else {
38
+ // release (sustain->0)
39
+ let u = (t - (1.0 - rp)) / rp.max(1e-6);
40
+ s * (1.0 - u)
41
+ }
42
+ }
43
+
44
+ fn eval_mod_func(func: &str, args: &[f32], beat: f32) -> Option<f32> {
45
+ match func {
46
+ "lfo.sine" => {
47
+ let rate = args.first().copied().unwrap_or(1.0);
48
+ Some(lfo_sine(rate, beat))
49
+ }
50
+ "lfo.tri" | "lfo.triangle" => {
51
+ let rate = args.first().copied().unwrap_or(1.0);
52
+ Some(lfo_triangle(rate, beat))
53
+ }
54
+ // ADSR envelope normalized over t in [0,1]
55
+ // $mod.envelope(attack, decay, sustain, release, t)
56
+ "envelope" | "mod.envelope" => {
57
+ if args.len() >= 5 {
58
+ Some(adsr_envelope_value_t(
59
+ args[0],
60
+ args[1],
61
+ args[2],
62
+ args[3],
63
+ args[4].clamp(0.0, 1.0),
64
+ ))
65
+ } else {
66
+ None
67
+ }
68
+ }
69
+ _ => None,
70
+ }
71
+ }
72
+
73
+ fn parse_top_level_args(s: &str) -> Vec<&str> {
74
+ let mut args = Vec::new();
75
+ let mut depth = 0i32;
76
+ let mut start = 0usize;
77
+ for (i, ch) in s.char_indices() {
78
+ match ch {
79
+ '(' => depth += 1,
80
+ ')' => depth -= 1,
81
+ ',' if depth == 0 => {
82
+ args.push(s[start..i].trim());
83
+ start = i + 1;
84
+ }
85
+ _ => {}
86
+ }
87
+ }
88
+ let last = s[start..].trim();
89
+ if !last.is_empty() {
90
+ args.push(last);
91
+ }
92
+ args
93
+ }
94
+
95
+ // Find and evaluate the first $mod.<fn>(...) occurrence in the string.
96
+ pub fn find_and_eval_first_mod_call<EvalFn>(
97
+ s: &str,
98
+ eval: EvalFn,
99
+ vars: &VariableTable,
100
+ bpm: f32,
101
+ beat: f32,
102
+ ) -> Option<String>
103
+ where
104
+ EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
105
+ {
106
+ let start = s.find("$mod.")?;
107
+ let open_rel = s[start..].find('(')?;
108
+ let open = start + open_rel;
109
+ let func = &s[start + 5..open];
110
+
111
+ // matching close
112
+ let mut depth: i32 = 0;
113
+ let mut close_abs: Option<usize> = None;
114
+ for (i, ch) in s[open..].char_indices() {
115
+ match ch {
116
+ '(' => depth += 1,
117
+ ')' => {
118
+ depth -= 1;
119
+ if depth == 0 {
120
+ close_abs = Some(open + i);
121
+ break;
122
+ }
123
+ }
124
+ _ => {}
125
+ }
126
+ }
127
+ let close = close_abs?;
128
+
129
+ let inner = &s[open + 1..close];
130
+ let raw_args = parse_top_level_args(inner);
131
+ let mut args: Vec<f32> = Vec::with_capacity(raw_args.len());
132
+ for a in raw_args {
133
+ args.push(eval(a, vars, bpm, beat)?);
134
+ }
135
+
136
+ let result = eval_mod_func(func, &args, beat)?;
137
+
138
+ let mut replaced = String::new();
139
+ replaced.push_str(&s[..start]);
140
+ replaced.push_str(&result.to_string());
141
+ replaced.push_str(&s[close + 1..]);
142
+ Some(replaced)
143
+ }