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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/.devalang +9 -10
  2. package/Cargo.toml +5 -4
  3. package/README.md +7 -5
  4. package/docs/CHANGELOG.md +42 -0
  5. package/docs/ROADMAP.md +5 -1
  6. package/docs/TODO.md +3 -14
  7. package/examples/bus.deva +10 -0
  8. package/examples/effect.deva +2 -0
  9. package/examples/filter.deva +11 -0
  10. package/examples/lfo.deva +9 -0
  11. package/examples/synth.deva +11 -1
  12. package/examples/synth_types.deva +17 -0
  13. package/out-tsc/core/functions/index.d.ts +5 -0
  14. package/out-tsc/core/functions/index.js +11 -0
  15. package/out-tsc/pkg/devalang_core.d.ts +2 -0
  16. package/out-tsc/pkg/devalang_core.js +17 -2
  17. package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +8 -7
  18. package/package.json +1 -1
  19. package/project-version.json +3 -3
  20. package/rust/cli/bank/api.rs +122 -122
  21. package/rust/cli/bank/commands.rs +33 -2
  22. package/rust/cli/bank/mod.rs +29 -29
  23. package/rust/cli/build/commands.rs +53 -3
  24. package/rust/cli/build/mod.rs +2 -2
  25. package/rust/cli/build/process.rs +26 -7
  26. package/rust/cli/check/mod.rs +2 -2
  27. package/rust/cli/discover/commands.rs +253 -253
  28. package/rust/cli/discover/config.rs +111 -111
  29. package/rust/cli/discover/fs.rs +19 -19
  30. package/rust/cli/discover/install.rs +103 -103
  31. package/rust/cli/discover/metadata.rs +48 -48
  32. package/rust/cli/discover/mod.rs +5 -5
  33. package/rust/cli/install/addon.rs +118 -118
  34. package/rust/cli/install/bank.rs +22 -3
  35. package/rust/cli/install/commands.rs +35 -35
  36. package/rust/cli/install/mod.rs +4 -4
  37. package/rust/cli/install/plugin.rs +80 -61
  38. package/rust/cli/login/commands.rs +124 -124
  39. package/rust/cli/mod.rs +12 -12
  40. package/rust/cli/parser.rs +46 -1
  41. package/rust/cli/play/commands.rs +71 -20
  42. package/rust/cli/play/mod.rs +5 -5
  43. package/rust/cli/play/process.rs +14 -5
  44. package/rust/cli/play/realtime.rs +91 -91
  45. package/rust/cli/telemetry/commands.rs +22 -22
  46. package/rust/cli/telemetry/event_creator.rs +80 -80
  47. package/rust/cli/telemetry/mod.rs +3 -3
  48. package/rust/cli/telemetry/send.rs +51 -51
  49. package/rust/cli/template/commands.rs +69 -69
  50. package/rust/config/driver.rs +112 -103
  51. package/rust/config/mod.rs +3 -3
  52. package/rust/config/ops.rs +26 -26
  53. package/rust/config/settings.rs +101 -101
  54. package/rust/core/audio/engine/driver.rs +220 -0
  55. package/rust/core/audio/engine/export.rs +169 -0
  56. package/rust/core/audio/engine/helpers.rs +178 -170
  57. package/rust/core/audio/engine/mod.rs +51 -2
  58. package/rust/core/audio/engine/notes/dsp.rs +85 -0
  59. package/rust/core/audio/engine/notes/mod.rs +44 -0
  60. package/rust/core/audio/engine/notes/params.rs +294 -0
  61. package/rust/core/audio/engine/sample/insert.rs +199 -0
  62. package/rust/core/audio/engine/sample/mod.rs +40 -0
  63. package/rust/core/audio/engine/sample/padding.rs +170 -0
  64. package/rust/core/audio/evaluator/condition.rs +61 -0
  65. package/rust/core/audio/evaluator/mod.rs +9 -0
  66. package/rust/core/audio/{evaluator.rs → evaluator/numeric.rs} +1 -159
  67. package/rust/core/audio/evaluator/rhs.rs +16 -0
  68. package/rust/core/audio/evaluator/string_expr.rs +94 -0
  69. package/rust/core/audio/interpreter/driver.rs +55 -23
  70. package/rust/core/audio/interpreter/mod.rs +1 -13
  71. package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +175 -0
  72. package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +384 -0
  73. package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +2 -0
  74. package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +316 -0
  75. package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -0
  76. package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -0
  77. package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -0
  78. package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -0
  79. package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -0
  80. package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -0
  81. package/rust/core/audio/interpreter/{automate.rs → statements/automate.rs} +16 -18
  82. package/rust/core/audio/interpreter/{call.rs → statements/call.rs} +5 -4
  83. package/rust/core/audio/interpreter/{condition.rs → statements/condition.rs} +2 -1
  84. package/rust/core/audio/interpreter/{function.rs → statements/function.rs} +2 -4
  85. package/rust/core/audio/interpreter/{let_.rs → statements/let_.rs} +2 -4
  86. package/rust/core/audio/interpreter/{load.rs → statements/load.rs} +2 -4
  87. package/rust/core/audio/interpreter/{loop_.rs → statements/loop_.rs} +2 -1
  88. package/rust/core/audio/interpreter/statements/mod.rs +12 -0
  89. package/rust/core/audio/interpreter/{sleep.rs → statements/sleep.rs} +28 -28
  90. package/rust/core/audio/interpreter/{spawn.rs → statements/spawn.rs} +3 -2
  91. package/rust/core/audio/interpreter/{tempo.rs → statements/tempo.rs} +40 -40
  92. package/rust/core/audio/interpreter/{trigger.rs → statements/trigger.rs} +1 -1
  93. package/rust/core/audio/loader/trigger.rs +2 -1
  94. package/rust/core/audio/mod.rs +6 -7
  95. package/rust/core/audio/player.rs +70 -70
  96. package/rust/core/audio/special/easing.rs +189 -189
  97. package/rust/core/audio/special/env.rs +45 -45
  98. package/rust/core/audio/special/math.rs +134 -134
  99. package/rust/core/audio/special/mod.rs +9 -9
  100. package/rust/core/audio/special/modulator.rs +143 -143
  101. package/rust/core/builder/mod.rs +45 -2
  102. package/rust/core/debugger/lexer.rs +27 -27
  103. package/rust/core/debugger/{module.rs → logs.rs} +3 -6
  104. package/rust/core/debugger/mod.rs +30 -30
  105. package/rust/core/debugger/preprocessor.rs +27 -27
  106. package/rust/core/debugger/store.rs +2 -4
  107. package/rust/core/error/mod.rs +269 -269
  108. package/rust/core/lexer/driver.rs +59 -61
  109. package/rust/core/lexer/handler/arrow.rs +82 -82
  110. package/rust/core/lexer/handler/at.rs +21 -21
  111. package/rust/core/lexer/handler/brace.rs +41 -41
  112. package/rust/core/lexer/handler/colon.rs +21 -21
  113. package/rust/core/lexer/handler/comment.rs +30 -30
  114. package/rust/core/lexer/handler/dot.rs +21 -21
  115. package/rust/core/lexer/handler/driver.rs +337 -337
  116. package/rust/core/lexer/handler/identifier.rs +47 -47
  117. package/rust/core/lexer/handler/indent.rs +66 -66
  118. package/rust/core/lexer/handler/mod.rs +15 -15
  119. package/rust/core/lexer/handler/newline.rs +23 -23
  120. package/rust/core/lexer/handler/number.rs +31 -31
  121. package/rust/core/lexer/handler/operator.rs +46 -46
  122. package/rust/core/lexer/handler/parenthesis.rs +41 -41
  123. package/rust/core/lexer/handler/slash.rs +21 -21
  124. package/rust/core/lexer/handler/string.rs +63 -63
  125. package/rust/core/lexer/mod.rs +3 -3
  126. package/rust/core/mod.rs +0 -1
  127. package/rust/core/parser/driver/block.rs +111 -0
  128. package/rust/core/parser/driver/cursor.rs +82 -0
  129. package/rust/core/parser/driver/driver_impl.rs +139 -0
  130. package/rust/core/parser/driver/mod.rs +6 -0
  131. package/rust/core/parser/driver/parse_array.rs +120 -0
  132. package/rust/core/parser/driver/parse_map.rs +223 -0
  133. package/rust/core/parser/driver/parser.rs +160 -0
  134. package/rust/core/parser/handler/arrow_call.rs +28 -4
  135. package/rust/core/parser/handler/at.rs +279 -279
  136. package/rust/core/parser/handler/bank.rs +104 -104
  137. package/rust/core/parser/handler/condition.rs +83 -83
  138. package/rust/core/parser/handler/dot.rs +148 -148
  139. package/rust/core/parser/handler/identifier/automate.rs +254 -254
  140. package/rust/core/parser/handler/identifier/call.rs +91 -91
  141. package/rust/core/parser/handler/identifier/emit.rs +70 -70
  142. package/rust/core/parser/handler/identifier/function.rs +113 -113
  143. package/rust/core/parser/handler/identifier/group.rs +89 -89
  144. package/rust/core/parser/handler/identifier/let_.rs +173 -173
  145. package/rust/core/parser/handler/identifier/mod.rs +55 -55
  146. package/rust/core/parser/handler/identifier/on.rs +107 -107
  147. package/rust/core/parser/handler/identifier/print.rs +49 -49
  148. package/rust/core/parser/handler/identifier/sleep.rs +96 -43
  149. package/rust/core/parser/handler/identifier/spawn.rs +91 -91
  150. package/rust/core/parser/handler/identifier/synth.rs +135 -135
  151. package/rust/core/parser/handler/loop_.rs +194 -194
  152. package/rust/core/parser/handler/mod.rs +9 -9
  153. package/rust/core/parser/handler/pattern.rs +1 -1
  154. package/rust/core/parser/handler/tempo.rs +105 -57
  155. package/rust/core/parser/statement.rs +10 -11
  156. package/rust/core/plugin/loader.rs +1 -1
  157. package/rust/core/plugin/mod.rs +2 -2
  158. package/rust/core/plugin/runner/mod.rs +11 -0
  159. package/rust/core/plugin/{runner.rs → runner/non_wasm.rs} +297 -347
  160. package/rust/core/plugin/runner/wasm32.rs +43 -0
  161. package/rust/core/preprocessor/loader/inject.rs +278 -0
  162. package/rust/core/preprocessor/loader/loader_helpers.rs +110 -0
  163. package/rust/core/preprocessor/loader/mod.rs +235 -0
  164. package/rust/core/preprocessor/module.rs +2 -7
  165. package/rust/core/preprocessor/{processor.rs → processor/handlers.rs} +6 -13
  166. package/rust/core/preprocessor/processor/mod.rs +1 -0
  167. package/rust/core/preprocessor/resolver/bank.rs +49 -49
  168. package/rust/core/preprocessor/resolver/call.rs +124 -124
  169. package/rust/core/preprocessor/resolver/condition.rs +95 -95
  170. package/rust/core/preprocessor/resolver/driver.rs +324 -324
  171. package/rust/core/preprocessor/resolver/function.rs +2 -2
  172. package/rust/core/preprocessor/resolver/group.rs +46 -18
  173. package/rust/core/preprocessor/resolver/let_.rs +32 -32
  174. package/rust/core/preprocessor/resolver/loop_.rs +318 -318
  175. package/rust/core/preprocessor/resolver/mod.rs +16 -16
  176. package/rust/core/preprocessor/resolver/pattern.rs +83 -83
  177. package/rust/core/preprocessor/resolver/spawn.rs +99 -99
  178. package/rust/core/preprocessor/resolver/synth.rs +54 -54
  179. package/rust/core/preprocessor/resolver/tempo.rs +48 -48
  180. package/rust/core/preprocessor/resolver/trigger.rs +116 -116
  181. package/rust/core/preprocessor/resolver/value.rs +176 -176
  182. package/rust/core/store/global.rs +2 -6
  183. package/rust/core/store/mod.rs +1 -5
  184. package/rust/lib.rs +18 -3
  185. package/rust/main.rs +27 -3
  186. package/rust/types/Cargo.toml +1 -1
  187. package/rust/types/src/addons.rs +55 -55
  188. package/rust/types/src/config.rs +84 -74
  189. package/rust/types/src/lib.rs +15 -12
  190. package/rust/types/src/plugin.rs +20 -0
  191. package/rust/types/src/store.rs +139 -0
  192. package/rust/types/src/telemetry.rs +85 -85
  193. package/rust/utils/Cargo.toml +2 -2
  194. package/rust/utils/src/file.rs +94 -94
  195. package/rust/utils/src/first_usage.rs +97 -97
  196. package/rust/utils/src/lib.rs +9 -9
  197. package/rust/utils/src/logger.rs +200 -200
  198. package/rust/utils/src/path.rs +129 -88
  199. package/rust/utils/src/signature.rs +41 -41
  200. package/rust/utils/src/spinner.rs +20 -20
  201. package/rust/utils/src/version.rs +27 -27
  202. package/rust/utils/src/watcher.rs +46 -46
  203. package/rust/web/api.rs +5 -5
  204. package/rust/web/cdn.rs +34 -34
  205. package/rust/web/mod.rs +3 -3
  206. package/tests/integration.rs +21 -21
  207. package/typescript/core/functions/index.ts +11 -0
  208. package/typescript/pkg/devalang_core.ts +20 -4
  209. package/rust/core/audio/engine/sample.rs +0 -366
  210. package/rust/core/audio/engine/synth.rs +0 -325
  211. package/rust/core/audio/interpreter/arrow_call.rs +0 -311
  212. package/rust/core/audio/renderer.rs +0 -54
  213. package/rust/core/parser/driver.rs +0 -584
  214. package/rust/core/preprocessor/loader.rs +0 -637
  215. package/rust/core/store/export.rs +0 -28
  216. package/rust/core/store/function.rs +0 -40
  217. package/rust/core/store/import.rs +0 -28
  218. package/rust/core/store/variable.rs +0 -51
  219. package/rust/core/utils/mod.rs +0 -1
  220. package/rust/core/utils/path.rs +0 -37
@@ -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,9 +1,9 @@
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
+ 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,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
+ }
@@ -1,4 +1,4 @@
1
- use crate::core::audio::renderer::render_audio_with_modules;
1
+ use crate::core::audio::engine::render_audio_with_modules;
2
2
  use crate::core::parser::statement::Statement;
3
3
  use crate::core::store::global::GlobalStore;
4
4
  use devalang_utils::logger::Logger;
@@ -48,6 +48,8 @@ impl Builder {
48
48
  modules: &HashMap<String, Vec<Statement>>,
49
49
  normalized_output_dir: &str,
50
50
  global_store: &mut GlobalStore,
51
+ audio_format: Option<String>,
52
+ sample_rate: Option<u32>,
51
53
  ) {
52
54
  let logger = Logger::new();
53
55
 
@@ -69,7 +71,7 @@ impl Builder {
69
71
  normalized_output_dir, formatted_module_name
70
72
  );
71
73
 
72
- match audio_engine.generate_wav_file(&output_path) {
74
+ match audio_engine.generate_wav_file(&output_path, audio_format.clone(), sample_rate) {
73
75
  Ok(_) => {}
74
76
  Err(msg) => {
75
77
  logger.log_error_with_stacktrace(
@@ -83,4 +85,45 @@ impl Builder {
83
85
  }
84
86
  }
85
87
  }
88
+
89
+ pub fn build_midi(
90
+ &self,
91
+ modules: &HashMap<String, Vec<Statement>>,
92
+ normalized_output_dir: &str,
93
+ global_store: &mut GlobalStore,
94
+ ) {
95
+ let logger = Logger::new();
96
+
97
+ let audio_engines =
98
+ render_audio_with_modules(modules.clone(), normalized_output_dir, global_store);
99
+
100
+ create_dir_all(format!("{}/midi", normalized_output_dir))
101
+ .expect("Failed to create MIDI directory");
102
+
103
+ for (module_name, mut audio_engine) in audio_engines {
104
+ let formatted_module_name = module_name
105
+ .split('/')
106
+ .next_back()
107
+ .unwrap_or(&module_name)
108
+ .replace(".deva", "");
109
+
110
+ let output_path = format!(
111
+ "{}/midi/{}.mid",
112
+ normalized_output_dir, formatted_module_name
113
+ );
114
+
115
+ match audio_engine.generate_midi_file(&output_path, None, None) {
116
+ Ok(_) => {}
117
+ Err(msg) => {
118
+ logger.log_error_with_stacktrace(
119
+ &format!(
120
+ "Unable to generate MIDI file for module '{}': {}",
121
+ formatted_module_name, msg
122
+ ),
123
+ &module_name,
124
+ );
125
+ }
126
+ }
127
+ }
128
+ }
86
129
  }