@devaloop/devalang 0.0.1-alpha.8 → 0.0.1-beta.1

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 (277) hide show
  1. package/.cargo/config.toml +2 -0
  2. package/.devalang +10 -4
  3. package/.github/workflows/ci.yml +103 -0
  4. package/Cargo.toml +80 -48
  5. package/README.md +135 -158
  6. package/docs/CHANGELOG.md +413 -1
  7. package/docs/CONTRIBUTING.md +101 -0
  8. package/docs/ROADMAP.md +10 -7
  9. package/docs/TODO.md +21 -9
  10. package/examples/automation.deva +42 -0
  11. package/examples/bank.deva +7 -0
  12. package/examples/condition.deva +8 -12
  13. package/examples/duration.deva +9 -0
  14. package/examples/events.deva +12 -0
  15. package/examples/function.deva +15 -0
  16. package/examples/group.deva +3 -3
  17. package/examples/index.deva +57 -10
  18. package/examples/loop.deva +7 -12
  19. package/examples/pattern.deva +8 -0
  20. package/examples/plugin.deva +16 -0
  21. package/examples/synth.deva +14 -0
  22. package/examples/variables.deva +2 -2
  23. package/out-tsc/bin/index.d.ts +2 -0
  24. package/out-tsc/bin/index.js +51 -7
  25. package/out-tsc/core/functions/index.d.ts +37 -0
  26. package/out-tsc/core/functions/index.js +76 -0
  27. package/out-tsc/core/index.d.ts +6 -0
  28. package/out-tsc/core/index.js +22 -0
  29. package/out-tsc/core/types/index.d.ts +4 -0
  30. package/out-tsc/core/types/index.js +20 -0
  31. package/out-tsc/core/types/plugin.d.ts +18 -0
  32. package/out-tsc/core/types/plugin.js +2 -0
  33. package/out-tsc/core/types/result.d.ts +27 -0
  34. package/out-tsc/core/types/result.js +2 -0
  35. package/out-tsc/core/types/statement.d.ts +106 -0
  36. package/out-tsc/core/types/statement.js +2 -0
  37. package/out-tsc/core/types/value.d.ts +43 -0
  38. package/out-tsc/core/types/value.js +2 -0
  39. package/out-tsc/index.d.ts +7 -0
  40. package/out-tsc/index.js +42 -1
  41. package/out-tsc/pkg/devalang_core.d.ts +13 -0
  42. package/out-tsc/pkg/devalang_core.js +50 -0
  43. package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +33 -0
  44. package/out-tsc/scripts/copy-wasm-dts.d.ts +1 -0
  45. package/out-tsc/scripts/copy-wasm-dts.js +73 -0
  46. package/out-tsc/scripts/postinstall.d.ts +1 -0
  47. package/out-tsc/scripts/postinstall.js +83 -0
  48. package/out-tsc/scripts/version/bump.d.ts +1 -0
  49. package/out-tsc/scripts/version/fetch.d.ts +1 -0
  50. package/out-tsc/scripts/version/fetch.js +1 -5
  51. package/out-tsc/scripts/version/index.d.ts +1 -0
  52. package/out-tsc/scripts/version/sync.d.ts +1 -0
  53. package/package.json +28 -7
  54. package/project-version.json +4 -4
  55. package/rust/cli/bank/api.rs +122 -0
  56. package/rust/cli/bank/commands.rs +275 -0
  57. package/rust/cli/bank/mod.rs +29 -0
  58. package/rust/cli/build/commands.rs +103 -0
  59. package/rust/cli/build/mod.rs +2 -0
  60. package/rust/cli/build/process.rs +146 -0
  61. package/rust/cli/check/mod.rs +208 -0
  62. package/rust/cli/discover/commands.rs +253 -0
  63. package/rust/cli/discover/config.rs +111 -0
  64. package/rust/cli/discover/fs.rs +19 -0
  65. package/rust/cli/discover/install.rs +103 -0
  66. package/rust/cli/discover/metadata.rs +48 -0
  67. package/rust/cli/discover/mod.rs +5 -0
  68. package/rust/cli/{init.rs → init/commands.rs} +32 -23
  69. package/rust/cli/init/mod.rs +1 -0
  70. package/rust/cli/install/addon.rs +118 -0
  71. package/rust/cli/install/bank.rs +53 -0
  72. package/rust/cli/install/commands.rs +35 -0
  73. package/rust/cli/install/mod.rs +4 -0
  74. package/rust/cli/install/plugin.rs +61 -0
  75. package/rust/cli/login/commands.rs +124 -0
  76. package/rust/cli/login/mod.rs +1 -0
  77. package/rust/cli/mod.rs +12 -205
  78. package/rust/cli/parser.rs +314 -0
  79. package/rust/cli/play/commands.rs +324 -0
  80. package/rust/cli/play/io.rs +17 -0
  81. package/rust/cli/play/mod.rs +5 -0
  82. package/rust/cli/play/process.rs +150 -0
  83. package/rust/cli/play/realtime.rs +91 -0
  84. package/rust/cli/play/utils.rs +23 -0
  85. package/rust/cli/telemetry/commands.rs +22 -0
  86. package/rust/cli/telemetry/event_creator.rs +80 -0
  87. package/rust/cli/telemetry/mod.rs +3 -0
  88. package/rust/cli/telemetry/send.rs +51 -0
  89. package/rust/cli/{template.rs → template/commands.rs} +69 -57
  90. package/rust/cli/template/mod.rs +1 -0
  91. package/rust/cli/update/commands.rs +6 -0
  92. package/rust/cli/update/mod.rs +1 -0
  93. package/rust/config/driver.rs +103 -0
  94. package/rust/config/mod.rs +3 -16
  95. package/rust/config/ops.rs +26 -0
  96. package/rust/config/settings.rs +101 -0
  97. package/rust/core/audio/engine/helpers.rs +170 -0
  98. package/rust/core/audio/engine/mod.rs +7 -0
  99. package/rust/core/audio/engine/sample.rs +366 -0
  100. package/rust/core/audio/engine/synth.rs +325 -0
  101. package/rust/core/audio/evaluator.rs +310 -31
  102. package/rust/core/audio/interpreter/arrow_call.rs +311 -0
  103. package/rust/core/audio/interpreter/automate.rs +18 -0
  104. package/rust/core/audio/interpreter/call.rs +294 -42
  105. package/rust/core/audio/interpreter/condition.rs +71 -65
  106. package/rust/core/audio/interpreter/driver.rs +542 -204
  107. package/rust/core/audio/interpreter/function.rs +26 -0
  108. package/rust/core/audio/interpreter/let_.rs +38 -19
  109. package/rust/core/audio/interpreter/load.rs +19 -18
  110. package/rust/core/audio/interpreter/loop_.rs +114 -59
  111. package/rust/core/audio/interpreter/mod.rs +14 -11
  112. package/rust/core/audio/interpreter/sleep.rs +28 -36
  113. package/rust/core/audio/interpreter/spawn.rs +252 -65
  114. package/rust/core/audio/interpreter/tempo.rs +40 -16
  115. package/rust/core/audio/interpreter/trigger.rs +239 -69
  116. package/rust/core/audio/loader/mod.rs +1 -1
  117. package/rust/core/audio/loader/trigger.rs +97 -52
  118. package/rust/core/audio/mod.rs +7 -6
  119. package/rust/core/audio/player.rs +70 -54
  120. package/rust/core/audio/renderer.rs +54 -57
  121. package/rust/core/audio/special/easing.rs +189 -0
  122. package/rust/core/audio/special/env.rs +45 -0
  123. package/rust/core/audio/special/math.rs +134 -0
  124. package/rust/core/audio/special/mod.rs +9 -0
  125. package/rust/core/audio/special/modulator.rs +143 -0
  126. package/rust/core/builder/mod.rs +86 -80
  127. package/rust/core/debugger/lexer.rs +27 -27
  128. package/rust/core/debugger/mod.rs +30 -21
  129. package/rust/core/debugger/module.rs +55 -0
  130. package/rust/core/debugger/preprocessor.rs +27 -27
  131. package/rust/core/debugger/store.rs +40 -25
  132. package/rust/core/error/mod.rs +269 -60
  133. package/rust/core/lexer/driver.rs +61 -0
  134. package/rust/core/lexer/handler/arrow.rs +82 -0
  135. package/rust/core/lexer/handler/at.rs +21 -21
  136. package/rust/core/lexer/handler/brace.rs +41 -41
  137. package/rust/core/lexer/handler/colon.rs +21 -21
  138. package/rust/core/lexer/handler/comment.rs +30 -30
  139. package/rust/core/lexer/handler/dot.rs +21 -21
  140. package/rust/core/lexer/handler/driver.rs +337 -215
  141. package/rust/core/lexer/handler/identifier.rs +47 -40
  142. package/rust/core/lexer/handler/indent.rs +66 -52
  143. package/rust/core/lexer/handler/mod.rs +15 -13
  144. package/rust/core/lexer/handler/newline.rs +23 -23
  145. package/rust/core/lexer/handler/number.rs +31 -31
  146. package/rust/core/lexer/handler/operator.rs +46 -44
  147. package/rust/core/lexer/handler/parenthesis.rs +41 -0
  148. package/rust/core/lexer/handler/slash.rs +21 -0
  149. package/rust/core/lexer/handler/string.rs +63 -63
  150. package/rust/core/lexer/mod.rs +3 -30
  151. package/rust/core/lexer/token.rs +21 -12
  152. package/rust/core/mod.rs +10 -10
  153. package/rust/core/parser/driver.rs +584 -312
  154. package/rust/core/parser/handler/arrow_call.rs +253 -0
  155. package/rust/core/parser/handler/at.rs +279 -162
  156. package/rust/core/parser/handler/bank.rs +104 -41
  157. package/rust/core/parser/handler/condition.rs +83 -74
  158. package/rust/core/parser/handler/dot.rs +148 -112
  159. package/rust/core/parser/handler/identifier/automate.rs +254 -0
  160. package/rust/core/parser/handler/identifier/call.rs +91 -0
  161. package/rust/core/parser/handler/identifier/emit.rs +70 -0
  162. package/rust/core/parser/handler/identifier/function.rs +113 -0
  163. package/rust/core/parser/handler/identifier/group.rs +89 -0
  164. package/rust/core/parser/handler/identifier/let_.rs +173 -0
  165. package/rust/core/parser/handler/identifier/mod.rs +55 -0
  166. package/rust/core/parser/handler/identifier/on.rs +107 -0
  167. package/rust/core/parser/handler/identifier/print.rs +49 -0
  168. package/rust/core/parser/handler/identifier/sleep.rs +43 -0
  169. package/rust/core/parser/handler/identifier/spawn.rs +91 -0
  170. package/rust/core/parser/handler/identifier/synth.rs +135 -0
  171. package/rust/core/parser/handler/loop_.rs +194 -66
  172. package/rust/core/parser/handler/mod.rs +9 -7
  173. package/rust/core/parser/handler/pattern.rs +74 -0
  174. package/rust/core/parser/handler/tempo.rs +57 -47
  175. package/rust/core/parser/mod.rs +3 -4
  176. package/rust/core/parser/statement.rs +11 -88
  177. package/rust/core/plugin/loader.rs +137 -0
  178. package/rust/core/plugin/mod.rs +2 -0
  179. package/rust/core/plugin/runner.rs +347 -0
  180. package/rust/core/preprocessor/loader.rs +637 -179
  181. package/rust/core/preprocessor/mod.rs +4 -4
  182. package/rust/core/preprocessor/module.rs +60 -53
  183. package/rust/core/preprocessor/processor.rs +114 -67
  184. package/rust/core/preprocessor/resolver/bank.rs +49 -47
  185. package/rust/core/preprocessor/resolver/call.rs +124 -53
  186. package/rust/core/preprocessor/resolver/condition.rs +95 -66
  187. package/rust/core/preprocessor/resolver/driver.rs +324 -182
  188. package/rust/core/preprocessor/resolver/function.rs +69 -0
  189. package/rust/core/preprocessor/resolver/group.rs +94 -118
  190. package/rust/core/preprocessor/resolver/let_.rs +32 -0
  191. package/rust/core/preprocessor/resolver/loop_.rs +318 -145
  192. package/rust/core/preprocessor/resolver/mod.rs +16 -10
  193. package/rust/core/preprocessor/resolver/pattern.rs +83 -0
  194. package/rust/core/preprocessor/resolver/spawn.rs +99 -53
  195. package/rust/core/preprocessor/resolver/synth.rs +54 -0
  196. package/rust/core/preprocessor/resolver/tempo.rs +48 -49
  197. package/rust/core/preprocessor/resolver/trigger.rs +116 -111
  198. package/rust/core/preprocessor/resolver/value.rs +176 -0
  199. package/rust/core/store/export.rs +28 -28
  200. package/rust/core/store/function.rs +40 -0
  201. package/rust/core/store/global.rs +61 -39
  202. package/rust/core/store/import.rs +28 -28
  203. package/rust/core/store/mod.rs +5 -4
  204. package/rust/core/store/variable.rs +51 -28
  205. package/rust/core/utils/mod.rs +1 -2
  206. package/rust/core/utils/path.rs +37 -46
  207. package/rust/lib.rs +308 -117
  208. package/rust/main.rs +364 -65
  209. package/rust/types/Cargo.toml +11 -0
  210. package/rust/types/src/addons.rs +55 -0
  211. package/rust/types/src/ast.rs +202 -0
  212. package/rust/types/src/config.rs +74 -0
  213. package/rust/types/src/lib.rs +12 -0
  214. package/rust/types/src/telemetry.rs +85 -0
  215. package/rust/utils/Cargo.toml +26 -0
  216. package/rust/utils/src/error.rs +186 -0
  217. package/rust/utils/src/file.rs +94 -0
  218. package/rust/utils/src/first_usage.rs +97 -0
  219. package/rust/utils/{mod.rs → src/lib.rs} +9 -6
  220. package/rust/utils/{logger.rs → src/logger.rs} +200 -123
  221. package/rust/utils/src/path.rs +88 -0
  222. package/rust/utils/src/signature.rs +41 -0
  223. package/rust/utils/{spinner.rs → src/spinner.rs} +20 -21
  224. package/rust/utils/src/version.rs +27 -0
  225. package/rust/utils/{watcher.rs → src/watcher.rs} +46 -33
  226. package/rust/web/api.rs +5 -0
  227. package/rust/web/cdn.rs +34 -0
  228. package/rust/web/mod.rs +3 -0
  229. package/rust/web/sso.rs +5 -0
  230. package/templates/minimal/README.md +143 -127
  231. package/templates/welcome/README.md +143 -127
  232. package/templates/welcome/src/index.deva +56 -8
  233. package/templates/welcome/src/variables.deva +2 -4
  234. package/tests/integration.rs +21 -0
  235. package/tests/rust/cli_check_build.rs +21 -0
  236. package/tests/rust/cli_help.rs +12 -0
  237. package/tests/rust/cli_template_list.rs +10 -0
  238. package/tests/rust/cli_version.rs +11 -0
  239. package/tests/typescript/index.spec.ts +136 -0
  240. package/tests/typescript/playhead.spec.ts +36 -0
  241. package/tests/typescript/render_e2e.spec.ts +77 -0
  242. package/tsconfig.json +12 -10
  243. package/typescript/bin/index.ts +19 -5
  244. package/typescript/core/functions/index.ts +83 -0
  245. package/typescript/core/index.ts +6 -0
  246. package/typescript/core/types/index.ts +4 -0
  247. package/typescript/core/types/plugin.ts +19 -0
  248. package/typescript/core/types/result.ts +29 -0
  249. package/typescript/core/types/statement.ts +47 -0
  250. package/typescript/core/types/value.ts +29 -0
  251. package/typescript/index.ts +8 -1
  252. package/typescript/pkg/devalang_core.d.ts +4 -0
  253. package/typescript/pkg/devalang_core.ts +49 -0
  254. package/typescript/scripts/copy-wasm-dts.ts +41 -0
  255. package/typescript/scripts/postinstall.ts +85 -0
  256. package/typescript/scripts/version/bump.ts +0 -1
  257. package/typescript/scripts/version/fetch.ts +1 -6
  258. package/typescript/scripts/version/index.ts +0 -1
  259. package/docs/COMMANDS.md +0 -85
  260. package/docs/CONFIG.md +0 -30
  261. package/docs/SYNTAX.md +0 -210
  262. package/out-tsc/bin/devalang.exe +0 -0
  263. package/out-tsc/scripts/postbuild.js +0 -11
  264. package/rust/cli/build.rs +0 -137
  265. package/rust/cli/check.rs +0 -117
  266. package/rust/cli/play.rs +0 -193
  267. package/rust/config/loader.rs +0 -13
  268. package/rust/core/audio/engine.rs +0 -126
  269. package/rust/core/parser/handler/identifier.rs +0 -262
  270. package/rust/core/shared/duration.rs +0 -8
  271. package/rust/core/shared/mod.rs +0 -2
  272. package/rust/core/shared/value.rs +0 -18
  273. package/rust/core/utils/validation.rs +0 -35
  274. package/rust/utils/file.rs +0 -35
  275. package/rust/utils/signature.rs +0 -17
  276. package/rust/utils/version.rs +0 -15
  277. package/typescript/scripts/postbuild.ts +0 -8
@@ -1,57 +1,54 @@
1
- use std::collections::HashMap;
2
-
3
- use crate::{
4
- core::{
5
- audio::{ engine::AudioEngine, interpreter::driver::run_audio_program },
6
- parser::statement::Statement,
7
- store::global::GlobalStore,
8
- },
9
- utils::logger::{ LogLevel, Logger },
10
- };
11
-
12
- pub fn render_audio_with_modules(
13
- modules: HashMap<String, Vec<Statement>>,
14
- output_dir: &str,
15
- global_store: &mut GlobalStore
16
- ) -> HashMap<String, AudioEngine> {
17
- let mut result = HashMap::new();
18
-
19
- for (module_name, statements) in modules {
20
- let mut global_max_end_time = 0.0;
21
- let mut audio_engine = AudioEngine::new();
22
-
23
- // Apply the module's variable table if it exists
24
- if let Some(module) = global_store.get_module(&module_name) {
25
- audio_engine.set_variables(module.variable_table.clone());
26
- }
27
-
28
- // Interpret the statements to fill the audio buffer
29
- let (mut audio_engine, module_base_bpm, module_max_end_time) = run_audio_program(
30
- &statements,
31
- audio_engine,
32
- module_name.clone(),
33
- output_dir.to_string()
34
- );
35
-
36
- // Calculate the module's maximum duration
37
- global_max_end_time = module_max_end_time.max(global_max_end_time);
38
- audio_engine.set_duration(global_max_end_time);
39
-
40
- // Check if the buffer contains at least one non-zero sample
41
- if audio_engine.buffer.iter().all(|&s| s == 0) {
42
- let logger = Logger::new();
43
-
44
- logger.log_message(
45
- LogLevel::Warning,
46
- format!("Module '{}' ignored: silent buffer (no non-zero samples)", module_name).as_str()
47
- );
48
-
49
- continue;
50
- }
51
-
52
- // Insert only if the module produces sound
53
- result.insert(module_name, audio_engine);
54
- }
55
-
56
- result
57
- }
1
+ use crate::core::{
2
+ audio::{engine::AudioEngine, interpreter::driver::run_audio_program},
3
+ parser::statement::Statement,
4
+ store::global::GlobalStore,
5
+ };
6
+ use devalang_utils::logger::{LogLevel, Logger};
7
+ use std::collections::HashMap;
8
+
9
+ pub fn render_audio_with_modules(
10
+ modules: HashMap<String, Vec<Statement>>,
11
+ output_dir: &str,
12
+ global_store: &mut GlobalStore,
13
+ ) -> HashMap<String, AudioEngine> {
14
+ let mut result = HashMap::new();
15
+
16
+ for (module_name, statements) in modules {
17
+ let mut global_max_end_time: f32 = 0.0;
18
+ let mut audio_engine = AudioEngine::new(module_name.clone());
19
+
20
+ // Apply global variables to the initial engine
21
+ if let Some(module) = global_store.get_module(&module_name) {
22
+ // interprete statements to fill the audio buffer
23
+ let (module_max_end_time, _cursor_time) = run_audio_program(
24
+ &statements,
25
+ &mut audio_engine,
26
+ module_name.clone(),
27
+ output_dir.to_string(),
28
+ module.variable_table.clone(),
29
+ module.function_table.clone(),
30
+ global_store,
31
+ );
32
+
33
+ // Verify if the buffer is silent (all samples are zero)
34
+ if audio_engine.buffer.iter().all(|&s| s == 0) {
35
+ let logger = Logger::new();
36
+ logger.log_message(
37
+ LogLevel::Warning,
38
+ &format!(
39
+ "Module '{}' ignored: silent buffer (no non-zero samples)",
40
+ module_name
41
+ ),
42
+ );
43
+ }
44
+
45
+ // Determines the maximum end time for the module
46
+ global_max_end_time = global_max_end_time.max(module_max_end_time);
47
+ audio_engine.set_duration(global_max_end_time);
48
+
49
+ result.insert(module_name, audio_engine);
50
+ }
51
+ }
52
+
53
+ result
54
+ }
@@ -0,0 +1,189 @@
1
+ use crate::core::store::variable::VariableTable;
2
+
3
+ // Basic easing functions operating on t in [0,1]
4
+ fn easing_value(func: &str, t: f32) -> Option<f32> {
5
+ let x = t.clamp(0.0, 1.0);
6
+ match func {
7
+ "linear" => Some(x),
8
+ "easeInQuad" => Some(x * x),
9
+ "easeOutQuad" => Some(x * (2.0 - x)),
10
+ "easeInOutQuad" => {
11
+ if x < 0.5 {
12
+ Some(2.0 * x * x)
13
+ } else {
14
+ Some(-1.0 + (4.0 - 2.0 * x) * x)
15
+ }
16
+ }
17
+ // Cubic
18
+ "easeInCubic" => Some(x * x * x),
19
+ "easeOutCubic" => Some(1.0 - (1.0 - x).powi(3)),
20
+ "easeInOutCubic" => {
21
+ if x < 0.5 {
22
+ Some(4.0 * x * x * x)
23
+ } else {
24
+ Some(1.0 - (-2.0 * x + 2.0).powi(3) / 2.0)
25
+ }
26
+ }
27
+ // Quartic
28
+ "easeInQuart" => Some(x.powi(4)),
29
+ "easeOutQuart" => Some(1.0 - (1.0 - x).powi(4)),
30
+ "easeInOutQuart" => {
31
+ if x < 0.5 {
32
+ Some(8.0 * x.powi(4))
33
+ } else {
34
+ Some(1.0 - (-2.0 * x + 2.0).powi(4) / 2.0)
35
+ }
36
+ }
37
+ // Exponential
38
+ "easeInExpo" => Some(if x <= 0.0 {
39
+ 0.0
40
+ } else {
41
+ 2.0_f32.powf(10.0 * x - 10.0)
42
+ }),
43
+ "easeOutExpo" => Some(if x >= 1.0 {
44
+ 1.0
45
+ } else {
46
+ 1.0 - 2.0_f32.powf(-10.0 * x)
47
+ }),
48
+ "easeInOutExpo" => Some(if x <= 0.0 {
49
+ 0.0
50
+ } else if x >= 1.0 {
51
+ 1.0
52
+ } else if x < 0.5 {
53
+ 2.0_f32.powf(20.0 * x - 10.0) / 2.0
54
+ } else {
55
+ (2.0 - 2.0_f32.powf(-20.0 * x + 10.0)) / 2.0
56
+ }),
57
+ // Back (overshoot c ~ 1.70158)
58
+ "easeInBack" => {
59
+ let c = 1.70158;
60
+ Some((c + 1.0) * x * x * x - c * x * x)
61
+ }
62
+ "easeOutBack" => {
63
+ let c = 1.70158;
64
+ let y = 1.0 - x;
65
+ Some(1.0 - ((c + 1.0) * y * y * y - c * y * y))
66
+ }
67
+ "easeInOutBack" => {
68
+ let c1 = 1.70158;
69
+ let c2 = c1 * 1.525;
70
+ let x2 = x * 2.0;
71
+ if x2 < 1.0 {
72
+ Some((x2 * x2 * ((c2 + 1.0) * x2 - c2)) / 2.0)
73
+ } else {
74
+ let x2 = x2 - 2.0;
75
+ Some((x2 * x2 * ((c2 + 1.0) * x2 + c2)) / 2.0 + 1.0)
76
+ }
77
+ }
78
+ // Elastic
79
+ "easeInElastic" => {
80
+ if x == 0.0 {
81
+ Some(0.0)
82
+ } else if x == 1.0 {
83
+ Some(1.0)
84
+ } else {
85
+ let c = 2.0 * std::f32::consts::PI / 3.0;
86
+ Some(-(2.0_f32.powf(10.0 * x - 10.0)) * ((x * 10.0 - 10.75) * c).sin())
87
+ }
88
+ }
89
+ "easeOutElastic" => {
90
+ if x == 0.0 {
91
+ Some(0.0)
92
+ } else if x == 1.0 {
93
+ Some(1.0)
94
+ } else {
95
+ let c = 2.0 * std::f32::consts::PI / 3.0;
96
+ Some(2.0_f32.powf(-10.0 * x) * ((x * 10.0 - 0.75) * c).sin() + 1.0)
97
+ }
98
+ }
99
+ "easeInOutElastic" => {
100
+ if x == 0.0 {
101
+ Some(0.0)
102
+ } else if x == 1.0 {
103
+ Some(1.0)
104
+ } else {
105
+ let c = 2.0 * std::f32::consts::PI / 4.5;
106
+ if x < 0.5 {
107
+ Some(-(2.0_f32.powf(20.0 * x - 10.0)) * ((20.0 * x - 11.125) * c).sin() / 2.0)
108
+ } else {
109
+ Some(
110
+ 2.0_f32.powf(-20.0 * x + 10.0) * ((20.0 * x - 11.125) * c).sin() / 2.0
111
+ + 1.0,
112
+ )
113
+ }
114
+ }
115
+ }
116
+ // Bounce helpers
117
+ "easeInBounce" => Some(1.0 - bounce_out(1.0 - x)),
118
+ "easeOutBounce" => Some(bounce_out(x)),
119
+ "easeInOutBounce" => Some(if x < 0.5 {
120
+ (1.0 - bounce_out(1.0 - 2.0 * x)) / 2.0
121
+ } else {
122
+ (1.0 + bounce_out(2.0 * x - 1.0)) / 2.0
123
+ }),
124
+ _ => None,
125
+ }
126
+ }
127
+
128
+ fn bounce_out(x: f32) -> f32 {
129
+ let n1 = 7.5625;
130
+ let d1 = 2.75;
131
+ if x < 1.0 / d1 {
132
+ n1 * x * x
133
+ } else if x < 2.0 / d1 {
134
+ let x = x - 1.5 / d1;
135
+ n1 * x * x + 0.75
136
+ } else if x < 2.5 / d1 {
137
+ let x = x - 2.25 / d1;
138
+ n1 * x * x + 0.9375
139
+ } else {
140
+ let x = x - 2.625 / d1;
141
+ n1 * x * x + 0.984375
142
+ }
143
+ }
144
+
145
+ // Find and evaluate the first $easing.<fn>(...) occurrence in the string.
146
+ // Accepts a single argument expression producing t in [0,1].
147
+ pub fn find_and_eval_first_easing_call<EvalFn>(
148
+ s: &str,
149
+ eval: EvalFn,
150
+ vars: &VariableTable,
151
+ bpm: f32,
152
+ beat: f32,
153
+ ) -> Option<String>
154
+ where
155
+ EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
156
+ {
157
+ let start = s.find("$easing.")?;
158
+ let open_rel = s[start..].find('(')?;
159
+ let open = start + open_rel;
160
+ let func = &s[start + 9..open];
161
+
162
+ // Find matching close parenthesis
163
+ let mut depth: i32 = 0;
164
+ let mut close_abs: Option<usize> = None;
165
+ for (i, ch) in s[open..].char_indices() {
166
+ match ch {
167
+ '(' => depth += 1,
168
+ ')' => {
169
+ depth -= 1;
170
+ if depth == 0 {
171
+ close_abs = Some(open + i);
172
+ break;
173
+ }
174
+ }
175
+ _ => {}
176
+ }
177
+ }
178
+ let close = close_abs?;
179
+
180
+ let inner = &s[open + 1..close];
181
+ let t = eval(inner, vars, bpm, beat)?;
182
+ let result = easing_value(func, t)?;
183
+
184
+ let mut replaced = String::new();
185
+ replaced.push_str(&s[..start]);
186
+ replaced.push_str(&result.to_string());
187
+ replaced.push_str(&s[close + 1..]);
188
+ Some(replaced)
189
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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;
@@ -0,0 +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
+ }