@devaloop/devalang 0.0.1-alpha.15 → 0.0.1-alpha.16-hotfix.0

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 (173) hide show
  1. package/.devalang +2 -0
  2. package/.github/workflows/ci.yml +92 -0
  3. package/Cargo.toml +60 -58
  4. package/README.md +1 -1
  5. package/docs/CHANGELOG.md +34 -1
  6. package/docs/CONTRIBUTING.md +101 -1
  7. package/docs/ROADMAP.md +1 -1
  8. package/docs/TODO.md +1 -1
  9. package/examples/automation.deva +1 -3
  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 +3 -5
  14. package/examples/loop.deva +5 -11
  15. package/examples/pattern.deva +8 -0
  16. package/examples/plugin.deva +12 -11
  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 -455
  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 -5
  38. package/rust/common/mod.rs +3 -3
  39. package/rust/config/driver.rs +118 -94
  40. package/rust/config/loader.rs +165 -156
  41. package/rust/config/mod.rs +4 -2
  42. package/rust/config/settings.rs +91 -0
  43. package/rust/config/stats.rs +257 -0
  44. package/rust/core/audio/engine.rs +696 -659
  45. package/rust/core/audio/evaluator.rs +263 -132
  46. package/rust/core/audio/interpreter/arrow_call.rs +198 -187
  47. package/rust/core/audio/interpreter/call.rs +98 -95
  48. package/rust/core/audio/interpreter/condition.rs +70 -71
  49. package/rust/core/audio/interpreter/driver.rs +487 -231
  50. package/rust/core/audio/interpreter/function.rs +26 -21
  51. package/rust/core/audio/interpreter/let_.rs +38 -26
  52. package/rust/core/audio/interpreter/load.rs +18 -18
  53. package/rust/core/audio/interpreter/loop_.rs +113 -106
  54. package/rust/core/audio/interpreter/mod.rs +14 -14
  55. package/rust/core/audio/interpreter/sleep.rs +27 -28
  56. package/rust/core/audio/interpreter/spawn.rs +105 -102
  57. package/rust/core/audio/interpreter/tempo.rs +19 -16
  58. package/rust/core/audio/interpreter/trigger.rs +239 -210
  59. package/rust/core/audio/loader/mod.rs +1 -1
  60. package/rust/core/audio/loader/trigger.rs +100 -94
  61. package/rust/core/audio/mod.rs +7 -7
  62. package/rust/core/audio/player.rs +64 -64
  63. package/rust/core/audio/renderer.rs +56 -53
  64. package/rust/core/audio/special/easing.rs +189 -120
  65. package/rust/core/audio/special/env.rs +43 -41
  66. package/rust/core/audio/special/math.rs +102 -92
  67. package/rust/core/audio/special/mod.rs +9 -9
  68. package/rust/core/audio/special/modulator.rs +143 -120
  69. package/rust/core/builder/mod.rs +80 -85
  70. package/rust/core/debugger/lexer.rs +27 -27
  71. package/rust/core/debugger/mod.rs +24 -23
  72. package/rust/core/debugger/module.rs +55 -47
  73. package/rust/core/debugger/preprocessor.rs +27 -27
  74. package/rust/core/debugger/store.rs +40 -39
  75. package/rust/core/error/mod.rs +80 -69
  76. package/rust/core/lexer/handler/arrow.rs +82 -82
  77. package/rust/core/lexer/handler/at.rs +21 -21
  78. package/rust/core/lexer/handler/brace.rs +41 -41
  79. package/rust/core/lexer/handler/colon.rs +21 -21
  80. package/rust/core/lexer/handler/comment.rs +30 -30
  81. package/rust/core/lexer/handler/dot.rs +21 -21
  82. package/rust/core/lexer/handler/driver.rs +337 -292
  83. package/rust/core/lexer/handler/identifier.rs +46 -43
  84. package/rust/core/lexer/handler/indent.rs +66 -66
  85. package/rust/core/lexer/handler/mod.rs +16 -16
  86. package/rust/core/lexer/handler/newline.rs +23 -23
  87. package/rust/core/lexer/handler/number.rs +31 -31
  88. package/rust/core/lexer/handler/operator.rs +46 -46
  89. package/rust/core/lexer/handler/parenthesis.rs +41 -41
  90. package/rust/core/lexer/handler/slash.rs +21 -21
  91. package/rust/core/lexer/handler/string.rs +63 -63
  92. package/rust/core/lexer/mod.rs +54 -51
  93. package/rust/core/lexer/token.rs +97 -94
  94. package/rust/core/mod.rs +11 -11
  95. package/rust/core/parser/driver.rs +513 -490
  96. package/rust/core/parser/handler/arrow_call.rs +233 -227
  97. package/rust/core/parser/handler/at.rs +245 -162
  98. package/rust/core/parser/handler/bank.rs +94 -69
  99. package/rust/core/parser/handler/condition.rs +80 -74
  100. package/rust/core/parser/handler/dot.rs +143 -135
  101. package/rust/core/parser/handler/identifier/automate.rs +257 -194
  102. package/rust/core/parser/handler/identifier/call.rs +91 -88
  103. package/rust/core/parser/handler/identifier/emit.rs +66 -0
  104. package/rust/core/parser/handler/identifier/function.rs +100 -91
  105. package/rust/core/parser/handler/identifier/group.rs +85 -75
  106. package/rust/core/parser/handler/identifier/let_.rs +158 -143
  107. package/rust/core/parser/handler/identifier/mod.rs +54 -56
  108. package/rust/core/parser/handler/identifier/on.rs +98 -0
  109. package/rust/core/parser/handler/identifier/print.rs +52 -29
  110. package/rust/core/parser/handler/identifier/sleep.rs +36 -33
  111. package/rust/core/parser/handler/identifier/spawn.rs +91 -88
  112. package/rust/core/parser/handler/identifier/synth.rs +65 -63
  113. package/rust/core/parser/handler/loop_.rs +170 -89
  114. package/rust/core/parser/handler/mod.rs +8 -8
  115. package/rust/core/parser/handler/tempo.rs +53 -47
  116. package/rust/core/parser/mod.rs +4 -4
  117. package/rust/core/parser/statement.rs +142 -113
  118. package/rust/core/plugin/loader.rs +123 -48
  119. package/rust/core/plugin/mod.rs +2 -1
  120. package/rust/core/plugin/runner.rs +296 -0
  121. package/rust/core/preprocessor/loader.rs +515 -326
  122. package/rust/core/preprocessor/mod.rs +4 -4
  123. package/rust/core/preprocessor/module.rs +60 -58
  124. package/rust/core/preprocessor/processor.rs +99 -101
  125. package/rust/core/preprocessor/resolver/bank.rs +51 -48
  126. package/rust/core/preprocessor/resolver/call.rs +100 -101
  127. package/rust/core/preprocessor/resolver/condition.rs +97 -97
  128. package/rust/core/preprocessor/resolver/driver.rs +310 -280
  129. package/rust/core/preprocessor/resolver/function.rs +69 -68
  130. package/rust/core/preprocessor/resolver/group.rs +96 -91
  131. package/rust/core/preprocessor/resolver/let_.rs +32 -28
  132. package/rust/core/preprocessor/resolver/loop_.rs +320 -121
  133. package/rust/core/preprocessor/resolver/mod.rs +15 -15
  134. package/rust/core/preprocessor/resolver/spawn.rs +76 -73
  135. package/rust/core/preprocessor/resolver/synth.rs +56 -50
  136. package/rust/core/preprocessor/resolver/tempo.rs +50 -49
  137. package/rust/core/preprocessor/resolver/trigger.rs +113 -115
  138. package/rust/core/preprocessor/resolver/value.rs +81 -81
  139. package/rust/core/shared/duration.rs +9 -9
  140. package/rust/core/shared/mod.rs +3 -3
  141. package/rust/core/shared/value.rs +35 -32
  142. package/rust/core/store/function.rs +34 -34
  143. package/rust/core/store/global.rs +55 -38
  144. package/rust/core/store/mod.rs +5 -5
  145. package/rust/core/store/variable.rs +37 -34
  146. package/rust/core/utils/mod.rs +2 -2
  147. package/rust/core/utils/path.rs +37 -31
  148. package/rust/core/utils/validation.rs +35 -36
  149. package/rust/installer/addon.rs +84 -80
  150. package/rust/installer/bank.rs +62 -65
  151. package/rust/installer/mod.rs +5 -5
  152. package/rust/installer/plugin.rs +54 -55
  153. package/rust/installer/utils.rs +56 -56
  154. package/rust/lib.rs +156 -164
  155. package/rust/main.rs +250 -144
  156. package/rust/utils/error.rs +200 -51
  157. package/rust/utils/file.rs +38 -35
  158. package/rust/utils/first_usage.rs +76 -0
  159. package/rust/utils/logger.rs +195 -143
  160. package/rust/utils/mod.rs +9 -7
  161. package/rust/utils/signature.rs +19 -17
  162. package/rust/utils/spinner.rs +22 -19
  163. package/rust/utils/telemetry.rs +292 -0
  164. package/rust/utils/watcher.rs +34 -33
  165. package/templates/minimal/README.md +97 -121
  166. package/templates/welcome/README.md +97 -121
  167. package/typescript/bin/index.ts +19 -5
  168. package/typescript/index.ts +3 -1
  169. package/typescript/scripts/postbuild.ts +10 -6
  170. package/typescript/scripts/postinstall.ts +56 -0
  171. package/typescript/scripts/version/bump.ts +0 -1
  172. package/typescript/scripts/version/index.ts +0 -1
  173. package/out-tsc/bin/devalang.exe +0 -0
@@ -1,92 +1,102 @@
1
- use crate::core::store::variable::VariableTable;
2
-
3
- // Parse comma-separated arguments at top level (no nested parentheses split)
4
- fn parse_top_level_args(s: &str) -> Vec<&str> {
5
- let mut args = Vec::new();
6
- let mut depth = 0i32;
7
- let mut start = 0usize;
8
- for (i, ch) in s.char_indices() {
9
- match ch {
10
- '(' => depth += 1,
11
- ')' => depth -= 1,
12
- ',' if depth == 0 => {
13
- args.push(s[start..i].trim());
14
- start = i + 1;
15
- }
16
- _ => {}
17
- }
18
- }
19
- let last = s[start..].trim();
20
- if !last.is_empty() { args.push(last); }
21
- args
22
- }
23
-
24
- fn eval_math_func(func: &str, args: &[f32], fallback_seed: f32) -> Option<f32> {
25
- match func {
26
- "sin" => args.get(0).copied().map(f32::sin),
27
- "cos" => args.get(0).copied().map(f32::cos),
28
- "random" => {
29
- // deterministic pseudo-random based on provided seed or a fallback session seed
30
- let seed = args.get(0).copied().unwrap_or(fallback_seed);
31
- let x = (seed * 12.9898).sin() * 43758.5453;
32
- Some((x.fract() * 2.0 - 1.0).clamp(-1.0, 1.0))
33
- }
34
- "lerp" => {
35
- if args.len() >= 3 { Some(args[0] + (args[1] - args[0]) * args[2]) } else { None }
36
- }
37
- _ => None,
38
- }
39
- }
40
-
41
- // Find and evaluate the first $math.<fn>(...) occurrence in the string, replacing it with a number.
42
- // Supports multi-argument functions by splitting on top-level commas.
43
- pub fn find_and_eval_first_math_call<EvalFn>(
44
- s: &str,
45
- eval: EvalFn,
46
- vars: &VariableTable,
47
- bpm: f32,
48
- beat: f32,
49
- ) -> Option<String>
50
- where
51
- EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
52
- {
53
- let start = s.find("$math.")?;
54
- let open_rel = s[start..].find('(')?;
55
- let open = start + open_rel;
56
- let func = &s[start + 6..open];
57
-
58
- // Find matching close parenthesis, handling nesting
59
- let mut depth: i32 = 0;
60
- let mut close_abs: Option<usize> = None;
61
- for (i, ch) in s[open..].char_indices() {
62
- match ch {
63
- '(' => depth += 1,
64
- ')' => {
65
- depth -= 1;
66
- if depth == 0 {
67
- close_abs = Some(open + i);
68
- break;
69
- }
70
- }
71
- _ => {}
72
- }
73
- }
74
- let close = close_abs?;
75
-
76
- let inner = &s[open + 1..close];
77
- let raw_args = parse_top_level_args(inner);
78
- let mut args: Vec<f32> = Vec::with_capacity(raw_args.len());
79
- for a in raw_args {
80
- if let Some(v) = eval(a, vars, bpm, beat) { args.push(v); } else { return None; }
81
- }
82
-
83
- // If no explicit seed is provided, use $env.seed via fallback
84
- let fallback_seed = eval("$env.seed", vars, bpm, beat).unwrap_or(0.0);
85
- let result = eval_math_func(func, &args, fallback_seed)?;
86
-
87
- let mut replaced = String::new();
88
- replaced.push_str(&s[..start]);
89
- replaced.push_str(&result.to_string());
90
- replaced.push_str(&s[close + 1..]);
91
- Some(replaced)
92
- }
1
+ use crate::core::store::variable::VariableTable;
2
+
3
+ // Parse comma-separated arguments at top level (no nested parentheses split)
4
+ fn parse_top_level_args(s: &str) -> Vec<&str> {
5
+ let mut args = Vec::new();
6
+ let mut depth = 0i32;
7
+ let mut start = 0usize;
8
+ for (i, ch) in s.char_indices() {
9
+ match ch {
10
+ '(' => depth += 1,
11
+ ')' => depth -= 1,
12
+ ',' if depth == 0 => {
13
+ args.push(s[start..i].trim());
14
+ start = i + 1;
15
+ }
16
+ _ => {}
17
+ }
18
+ }
19
+ let last = s[start..].trim();
20
+ if !last.is_empty() {
21
+ args.push(last);
22
+ }
23
+ args
24
+ }
25
+
26
+ fn eval_math_func(func: &str, args: &[f32], fallback_seed: f32) -> Option<f32> {
27
+ match func {
28
+ "sin" => args.get(0).copied().map(f32::sin),
29
+ "cos" => args.get(0).copied().map(f32::cos),
30
+ "random" => {
31
+ // deterministic pseudo-random based on provided seed or a fallback session seed
32
+ let seed = args.get(0).copied().unwrap_or(fallback_seed);
33
+ let x = (seed * 12.9898).sin() * 43758.5453;
34
+ Some((x.fract() * 2.0 - 1.0).clamp(-1.0, 1.0))
35
+ }
36
+ "lerp" => {
37
+ if args.len() >= 3 {
38
+ Some(args[0] + (args[1] - args[0]) * args[2])
39
+ } else {
40
+ None
41
+ }
42
+ }
43
+ _ => None,
44
+ }
45
+ }
46
+
47
+ // Find and evaluate the first $math.<fn>(...) occurrence in the string, replacing it with a number.
48
+ // Supports multi-argument functions by splitting on top-level commas.
49
+ pub fn find_and_eval_first_math_call<EvalFn>(
50
+ s: &str,
51
+ eval: EvalFn,
52
+ vars: &VariableTable,
53
+ bpm: f32,
54
+ beat: f32,
55
+ ) -> Option<String>
56
+ where
57
+ EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
58
+ {
59
+ let start = s.find("$math.")?;
60
+ let open_rel = s[start..].find('(')?;
61
+ let open = start + open_rel;
62
+ let func = &s[start + 6..open];
63
+
64
+ // Find matching close parenthesis, handling nesting
65
+ let mut depth: i32 = 0;
66
+ let mut close_abs: Option<usize> = None;
67
+ for (i, ch) in s[open..].char_indices() {
68
+ match ch {
69
+ '(' => depth += 1,
70
+ ')' => {
71
+ depth -= 1;
72
+ if depth == 0 {
73
+ close_abs = Some(open + i);
74
+ break;
75
+ }
76
+ }
77
+ _ => {}
78
+ }
79
+ }
80
+ let close = close_abs?;
81
+
82
+ let inner = &s[open + 1..close];
83
+ let raw_args = parse_top_level_args(inner);
84
+ let mut args: Vec<f32> = Vec::with_capacity(raw_args.len());
85
+ for a in raw_args {
86
+ if let Some(v) = eval(a, vars, bpm, beat) {
87
+ args.push(v);
88
+ } else {
89
+ return None;
90
+ }
91
+ }
92
+
93
+ // If no explicit seed is provided, use $env.seed via fallback
94
+ let fallback_seed = eval("$env.seed", vars, bpm, beat).unwrap_or(0.0);
95
+ let result = eval_math_func(func, &args, fallback_seed)?;
96
+
97
+ let mut replaced = String::new();
98
+ replaced.push_str(&s[..start]);
99
+ replaced.push_str(&result.to_string());
100
+ replaced.push_str(&s[close + 1..]);
101
+ Some(replaced)
102
+ }
@@ -1,9 +1,9 @@
1
- pub mod env;
2
- pub mod math;
3
- pub mod easing;
4
- pub mod modulator;
5
-
6
- pub use env::resolve_env_atom;
7
- pub use math::find_and_eval_first_math_call;
8
- pub use easing::find_and_eval_first_easing_call;
9
- pub use modulator::find_and_eval_first_mod_call;
1
+ pub mod easing;
2
+ pub mod env;
3
+ pub mod math;
4
+ pub mod modulator;
5
+
6
+ pub use easing::find_and_eval_first_easing_call;
7
+ pub use env::resolve_env_atom;
8
+ pub use math::find_and_eval_first_math_call;
9
+ pub use modulator::find_and_eval_first_mod_call;
@@ -1,120 +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; let dp = d / total; let rp = r / total;
24
-
25
- if t < ap {
26
- // attack (0->1)
27
- if ap > 0.0 { t / ap } else { 1.0 }
28
- } else if t < ap + dp {
29
- // decay (1->sustain)
30
- let u = (t - ap) / dp.max(1e-6);
31
- 1.0 - (1.0 - s) * u
32
- } else if t < 1.0 - rp {
33
- // sustain
34
- s
35
- } else {
36
- // release (sustain->0)
37
- let u = (t - (1.0 - rp)) / rp.max(1e-6);
38
- s * (1.0 - u)
39
- }
40
- }
41
-
42
- fn eval_mod_func(func: &str, args: &[f32], beat: f32) -> Option<f32> {
43
- match func {
44
- "lfo.sine" => {
45
- let rate = args.get(0).copied().unwrap_or(1.0);
46
- Some(lfo_sine(rate, beat))
47
- }
48
- "lfo.tri" | "lfo.triangle" => {
49
- let rate = args.get(0).copied().unwrap_or(1.0);
50
- Some(lfo_triangle(rate, beat))
51
- }
52
- // ADSR envelope normalized over t in [0,1]
53
- // $mod.envelope(attack, decay, sustain, release, t)
54
- "envelope" | "mod.envelope" => {
55
- if args.len() >= 5 {
56
- Some(adsr_envelope_value_t(args[0], args[1], args[2], args[3], args[4].clamp(0.0, 1.0)))
57
- } else { None }
58
- }
59
- _ => None,
60
- }
61
- }
62
-
63
- fn parse_top_level_args(s: &str) -> Vec<&str> {
64
- let mut args = Vec::new();
65
- let mut depth = 0i32;
66
- let mut start = 0usize;
67
- for (i, ch) in s.char_indices() {
68
- match ch {
69
- '(' => depth += 1,
70
- ')' => depth -= 1,
71
- ',' if depth == 0 => { args.push(s[start..i].trim()); start = i + 1; }
72
- _ => {}
73
- }
74
- }
75
- let last = s[start..].trim();
76
- if !last.is_empty() { args.push(last); }
77
- args
78
- }
79
-
80
- // Find and evaluate the first $mod.<fn>(...) occurrence in the string.
81
- pub fn find_and_eval_first_mod_call<EvalFn>(
82
- s: &str,
83
- eval: EvalFn,
84
- vars: &VariableTable,
85
- bpm: f32,
86
- beat: f32,
87
- ) -> Option<String>
88
- where
89
- EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
90
- {
91
- let start = s.find("$mod.")?;
92
- let open_rel = s[start..].find('(')?;
93
- let open = start + open_rel;
94
- let func = &s[start + 5..open];
95
-
96
- // matching close
97
- let mut depth: i32 = 0;
98
- let mut close_abs: Option<usize> = None;
99
- for (i, ch) in s[open..].char_indices() {
100
- match ch {
101
- '(' => depth += 1,
102
- ')' => { depth -= 1; if depth == 0 { close_abs = Some(open + i); break; } }
103
- _ => {}
104
- }
105
- }
106
- let close = close_abs?;
107
-
108
- let inner = &s[open + 1..close];
109
- let raw_args = parse_top_level_args(inner);
110
- let mut args: Vec<f32> = Vec::with_capacity(raw_args.len());
111
- for a in raw_args { args.push(eval(a, vars, bpm, beat)?); }
112
-
113
- let result = eval_mod_func(func, &args, beat)?;
114
-
115
- let mut replaced = String::new();
116
- replaced.push_str(&s[..start]);
117
- replaced.push_str(&result.to_string());
118
- replaced.push_str(&s[close + 1..]);
119
- Some(replaced)
120
- }
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.get(0).copied().unwrap_or(1.0);
48
+ Some(lfo_sine(rate, beat))
49
+ }
50
+ "lfo.tri" | "lfo.triangle" => {
51
+ let rate = args.get(0).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,85 +1,80 @@
1
- use crate::core::audio::renderer::render_audio_with_modules;
2
- use crate::core::parser::statement::Statement;
3
- use crate::core::store::global::GlobalStore;
4
- use std::{ collections::HashMap, fs::create_dir_all };
5
- use std::io::Write;
6
- use crate::utils::logger::Logger;
7
-
8
- pub struct Builder {}
9
-
10
- impl Builder {
11
- pub fn new() -> Self {
12
- Builder {}
13
- }
14
-
15
- pub fn build_ast(
16
- &self,
17
- modules: &HashMap<String, Vec<Statement>>,
18
- out_dir: &str,
19
- compress: bool
20
- ) {
21
- for (name, statements) in modules {
22
- let formatted_name = name.split("/").last().unwrap_or(name);
23
- let formatted_name = formatted_name.replace(".deva", "");
24
-
25
- create_dir_all(format!("{}/ast", out_dir)).expect("Failed to create AST directory");
26
-
27
- let file_path = format!("{}/ast/{}.json", out_dir, formatted_name);
28
- let mut file = std::fs::File::create(file_path).expect("Failed to create AST file");
29
- let content = if compress {
30
- serde_json::to_string(&statements).expect("Failed to serialize AST")
31
- } else {
32
- serde_json::to_string_pretty(&statements).expect("Failed to serialize AST")
33
- };
34
-
35
- file.write_all(content.as_bytes()).expect("Failed to write AST to file");
36
- }
37
- }
38
-
39
- pub fn build_audio(
40
- &self,
41
- modules: &HashMap<String, Vec<Statement>>,
42
- normalized_output_dir: &str,
43
- global_store: &mut GlobalStore
44
- ) {
45
- let logger = Logger::new();
46
-
47
- let audio_engines = render_audio_with_modules(
48
- modules.clone(),
49
- &normalized_output_dir,
50
- global_store
51
- );
52
-
53
- create_dir_all(format!("{}/audio", normalized_output_dir)).expect(
54
- "Failed to create audio directory"
55
- );
56
-
57
- for (module_name, mut audio_engine) in audio_engines {
58
- let formatted_module_name = module_name
59
- .split('/')
60
- .last()
61
- .unwrap_or(&module_name)
62
- .replace(".deva", "");
63
-
64
- let output_path = format!(
65
- "{}/audio/{}.wav",
66
- normalized_output_dir,
67
- formatted_module_name
68
- );
69
-
70
- match audio_engine.generate_wav_file(&output_path) {
71
- Ok(_) => {}
72
- Err(msg) => {
73
- logger.log_error_with_stacktrace(
74
- &format!(
75
- "Unable to generate WAV file for module '{}': {}",
76
- formatted_module_name,
77
- msg
78
- ),
79
- &module_name
80
- );
81
- }
82
- }
83
- }
84
- }
85
- }
1
+ use crate::core::audio::renderer::render_audio_with_modules;
2
+ use crate::core::parser::statement::Statement;
3
+ use crate::core::store::global::GlobalStore;
4
+ use crate::utils::logger::Logger;
5
+ use std::io::Write;
6
+ use std::{collections::HashMap, fs::create_dir_all};
7
+
8
+ pub struct Builder {}
9
+
10
+ impl Builder {
11
+ pub fn new() -> Self {
12
+ Builder {}
13
+ }
14
+
15
+ pub fn build_ast(
16
+ &self,
17
+ modules: &HashMap<String, Vec<Statement>>,
18
+ out_dir: &str,
19
+ compress: bool,
20
+ ) {
21
+ for (name, statements) in modules {
22
+ let formatted_name = name.split("/").last().unwrap_or(name);
23
+ let formatted_name = formatted_name.replace(".deva", "");
24
+
25
+ create_dir_all(format!("{}/ast", out_dir)).expect("Failed to create AST directory");
26
+
27
+ let file_path = format!("{}/ast/{}.json", out_dir, formatted_name);
28
+ let mut file = std::fs::File::create(file_path).expect("Failed to create AST file");
29
+ let content = if compress {
30
+ serde_json::to_string(&statements).expect("Failed to serialize AST")
31
+ } else {
32
+ serde_json::to_string_pretty(&statements).expect("Failed to serialize AST")
33
+ };
34
+
35
+ file.write_all(content.as_bytes())
36
+ .expect("Failed to write AST to file");
37
+ }
38
+ }
39
+
40
+ pub fn build_audio(
41
+ &self,
42
+ modules: &HashMap<String, Vec<Statement>>,
43
+ normalized_output_dir: &str,
44
+ global_store: &mut GlobalStore,
45
+ ) {
46
+ let logger = Logger::new();
47
+
48
+ let audio_engines =
49
+ render_audio_with_modules(modules.clone(), &normalized_output_dir, global_store);
50
+
51
+ create_dir_all(format!("{}/audio", normalized_output_dir))
52
+ .expect("Failed to create audio directory");
53
+
54
+ for (module_name, mut audio_engine) in audio_engines {
55
+ let formatted_module_name = module_name
56
+ .split('/')
57
+ .last()
58
+ .unwrap_or(&module_name)
59
+ .replace(".deva", "");
60
+
61
+ let output_path = format!(
62
+ "{}/audio/{}.wav",
63
+ normalized_output_dir, formatted_module_name
64
+ );
65
+
66
+ match audio_engine.generate_wav_file(&output_path) {
67
+ Ok(_) => {}
68
+ Err(msg) => {
69
+ logger.log_error_with_stacktrace(
70
+ &format!(
71
+ "Unable to generate WAV file for module '{}': {}",
72
+ formatted_module_name, msg
73
+ ),
74
+ &module_name,
75
+ );
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }