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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,7 +1,7 @@
1
- pub mod engine;
2
- pub mod interpreter;
3
- pub mod loader;
4
- pub mod player;
5
- pub mod renderer;
6
- pub mod evaluator;
7
- pub mod special;
1
+ pub mod engine;
2
+ pub mod evaluator;
3
+ pub mod interpreter;
4
+ pub mod loader;
5
+ pub mod player;
6
+ pub mod renderer;
7
+ pub mod special;
@@ -1,64 +1,64 @@
1
- use rodio::{ Decoder, OutputStream, OutputStreamHandle, Sink, Source };
2
- use std::{ fs::File, io::BufReader };
3
-
4
- pub struct AudioPlayer {
5
- _stream: OutputStream,
6
- handle: OutputStreamHandle,
7
- sink: Sink,
8
- last_path: Option<String>,
9
- }
10
-
11
- impl AudioPlayer {
12
- pub fn new() -> Self {
13
- let (stream, handle) = OutputStream::try_default().unwrap();
14
- let sink = Sink::try_new(&handle).unwrap();
15
-
16
- Self {
17
- _stream: stream,
18
- handle,
19
- sink,
20
- last_path: None,
21
- }
22
- }
23
-
24
- fn load_source(&self, path: &str) -> Option<impl Source<Item = f32> + Send + 'static> {
25
- if let Ok(file) = File::open(path) {
26
- let reader = BufReader::new(file);
27
- match Decoder::new(reader) {
28
- Ok(decoder) => Some(decoder.convert_samples()),
29
- Err(e) => {
30
- eprintln!("❌ Failed to decode audio file '{}': {}", path, e);
31
- None
32
- }
33
- }
34
- } else {
35
- eprintln!("❌ Could not open audio file: {}", path);
36
- None
37
- }
38
- }
39
-
40
- pub fn play_file_once(&mut self, path: &str) {
41
- self.sink.stop();
42
- self.sink = Sink::try_new(&self.handle).unwrap();
43
- self.sink.set_volume(1.0);
44
-
45
- if let Some(source) = self.load_source(path) {
46
- self.sink.append(source);
47
- self.last_path = Some(path.to_string());
48
- } else {
49
- eprintln!("⚠️ Skipping playback: failed to load '{}'", path);
50
- }
51
- }
52
-
53
- pub fn replay_last(&mut self) {
54
- if let Some(path) = self.last_path.clone() {
55
- self.play_file_once(&path);
56
- } else {
57
- eprintln!("⚠️ No previous audio to replay.");
58
- }
59
- }
60
-
61
- pub fn wait_until_end(&self) {
62
- self.sink.sleep_until_end();
63
- }
64
- }
1
+ use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink, Source};
2
+ use std::{fs::File, io::BufReader};
3
+
4
+ pub struct AudioPlayer {
5
+ _stream: OutputStream,
6
+ handle: OutputStreamHandle,
7
+ sink: Sink,
8
+ last_path: Option<String>,
9
+ }
10
+
11
+ impl AudioPlayer {
12
+ pub fn new() -> Self {
13
+ let (stream, handle) = OutputStream::try_default().unwrap();
14
+ let sink = Sink::try_new(&handle).unwrap();
15
+
16
+ Self {
17
+ _stream: stream,
18
+ handle,
19
+ sink,
20
+ last_path: None,
21
+ }
22
+ }
23
+
24
+ fn load_source(&self, path: &str) -> Option<impl Source<Item = f32> + Send + 'static> {
25
+ if let Ok(file) = File::open(path) {
26
+ let reader = BufReader::new(file);
27
+ match Decoder::new(reader) {
28
+ Ok(decoder) => Some(decoder.convert_samples()),
29
+ Err(e) => {
30
+ eprintln!("❌ Failed to decode audio file '{}': {}", path, e);
31
+ None
32
+ }
33
+ }
34
+ } else {
35
+ eprintln!("❌ Could not open audio file: {}", path);
36
+ None
37
+ }
38
+ }
39
+
40
+ pub fn play_file_once(&mut self, path: &str) {
41
+ self.sink.stop();
42
+ self.sink = Sink::try_new(&self.handle).unwrap();
43
+ self.sink.set_volume(1.0);
44
+
45
+ if let Some(source) = self.load_source(path) {
46
+ self.sink.append(source);
47
+ self.last_path = Some(path.to_string());
48
+ } else {
49
+ eprintln!("⚠️ Skipping playback: failed to load '{}'", path);
50
+ }
51
+ }
52
+
53
+ pub fn replay_last(&mut self) {
54
+ if let Some(path) = self.last_path.clone() {
55
+ self.play_file_once(&path);
56
+ } else {
57
+ eprintln!("⚠️ No previous audio to replay.");
58
+ }
59
+ }
60
+
61
+ pub fn wait_until_end(&self) {
62
+ self.sink.sleep_until_end();
63
+ }
64
+ }
@@ -1,53 +1,56 @@
1
- use std::collections::HashMap;
2
- use crate::{
3
- core::{
4
- audio::{ engine::AudioEngine, interpreter::driver::run_audio_program },
5
- parser::statement::Statement,
6
- store::global::GlobalStore,
7
- },
8
- utils::logger::{ LogLevel, Logger },
9
- };
10
-
11
- pub fn render_audio_with_modules(
12
- modules: HashMap<String, Vec<Statement>>,
13
- output_dir: &str,
14
- global_store: &mut GlobalStore
15
- ) -> HashMap<String, AudioEngine> {
16
- let mut result = HashMap::new();
17
-
18
- for (module_name, statements) in modules {
19
- let mut global_max_end_time: f32 = 0.0;
20
- let mut audio_engine = AudioEngine::new(module_name.clone());
21
-
22
- // Apply global variables to the initial engine
23
- if let Some(module) = global_store.get_module(&module_name) {
24
- // interprete statements to fill the audio buffer
25
- let (module_max_end_time, _cursor_time) = run_audio_program(
26
- &statements,
27
- &mut audio_engine,
28
- module_name.clone(),
29
- output_dir.to_string(),
30
- module.variable_table.clone(),
31
- module.function_table.clone(),
32
- global_store
33
- );
34
-
35
- // Verify if the buffer is silent (all samples are zero)
36
- if audio_engine.buffer.iter().all(|&s| s == 0) {
37
- let logger = Logger::new();
38
- logger.log_message(
39
- LogLevel::Warning,
40
- &format!("Module '{}' ignored: silent buffer (no non-zero samples)", module_name)
41
- );
42
- }
43
-
44
- // Determines the maximum end time for the module
45
- global_max_end_time = global_max_end_time.max(module_max_end_time);
46
- audio_engine.set_duration(global_max_end_time);
47
-
48
- result.insert(module_name, audio_engine);
49
- }
50
- }
51
-
52
- result
53
- }
1
+ use crate::{
2
+ core::{
3
+ audio::{engine::AudioEngine, interpreter::driver::run_audio_program},
4
+ parser::statement::Statement,
5
+ store::global::GlobalStore,
6
+ },
7
+ utils::logger::{LogLevel, Logger},
8
+ };
9
+ use std::collections::HashMap;
10
+
11
+ pub fn render_audio_with_modules(
12
+ modules: HashMap<String, Vec<Statement>>,
13
+ output_dir: &str,
14
+ global_store: &mut GlobalStore,
15
+ ) -> HashMap<String, AudioEngine> {
16
+ let mut result = HashMap::new();
17
+
18
+ for (module_name, statements) in modules {
19
+ let mut global_max_end_time: f32 = 0.0;
20
+ let mut audio_engine = AudioEngine::new(module_name.clone());
21
+
22
+ // Apply global variables to the initial engine
23
+ if let Some(module) = global_store.get_module(&module_name) {
24
+ // interprete statements to fill the audio buffer
25
+ let (module_max_end_time, _cursor_time) = run_audio_program(
26
+ &statements,
27
+ &mut audio_engine,
28
+ module_name.clone(),
29
+ output_dir.to_string(),
30
+ module.variable_table.clone(),
31
+ module.function_table.clone(),
32
+ global_store,
33
+ );
34
+
35
+ // Verify if the buffer is silent (all samples are zero)
36
+ if audio_engine.buffer.iter().all(|&s| s == 0) {
37
+ let logger = Logger::new();
38
+ logger.log_message(
39
+ LogLevel::Warning,
40
+ &format!(
41
+ "Module '{}' ignored: silent buffer (no non-zero samples)",
42
+ module_name
43
+ ),
44
+ );
45
+ }
46
+
47
+ // Determines the maximum end time for the module
48
+ global_max_end_time = global_max_end_time.max(module_max_end_time);
49
+ audio_engine.set_duration(global_max_end_time);
50
+
51
+ result.insert(module_name, audio_engine);
52
+ }
53
+ }
54
+
55
+ result
56
+ }
@@ -1,120 +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 { Some(2.0 * x * x) } else { Some(-1.0 + (4.0 - 2.0 * x) * x) }
12
- }
13
- // Cubic
14
- "easeInCubic" => Some(x * x * x),
15
- "easeOutCubic" => Some(1.0 - (1.0 - x).powi(3)),
16
- "easeInOutCubic" => {
17
- if x < 0.5 { Some(4.0 * x * x * x) } else { Some(1.0 - (-2.0 * x + 2.0).powi(3) / 2.0) }
18
- }
19
- // Quartic
20
- "easeInQuart" => Some(x.powi(4)),
21
- "easeOutQuart" => Some(1.0 - (1.0 - x).powi(4)),
22
- "easeInOutQuart" => {
23
- if x < 0.5 { Some(8.0 * x.powi(4)) } else { Some(1.0 - (-2.0 * x + 2.0).powi(4) / 2.0) }
24
- }
25
- // Exponential
26
- "easeInExpo" => Some(if x <= 0.0 { 0.0 } else { 2.0_f32.powf(10.0 * x - 10.0) }),
27
- "easeOutExpo" => Some(if x >= 1.0 { 1.0 } else { 1.0 - 2.0_f32.powf(-10.0 * x) }),
28
- "easeInOutExpo" => Some(if x <= 0.0 { 0.0 } else if x >= 1.0 { 1.0 } else if x < 0.5 { 2.0_f32.powf(20.0 * x - 10.0) / 2.0 } else { (2.0 - 2.0_f32.powf(-20.0 * x + 10.0)) / 2.0 }),
29
- // Back (overshoot c ~ 1.70158)
30
- "easeInBack" => { let c = 1.70158; Some((c + 1.0) * x * x * x - c * x * x) }
31
- "easeOutBack" => { let c = 1.70158; let y = 1.0 - x; Some(1.0 - ((c + 1.0) * y * y * y - c * y * y)) }
32
- "easeInOutBack" => {
33
- let c1 = 1.70158; let c2 = c1 * 1.525; let x2 = x * 2.0;
34
- if x2 < 1.0 { Some((x2 * x2 * ((c2 + 1.0) * x2 - c2)) / 2.0) } else {
35
- let x2 = x2 - 2.0; Some((x2 * x2 * ((c2 + 1.0) * x2 + c2)) / 2.0 + 1.0)
36
- }
37
- }
38
- // Elastic
39
- "easeInElastic" => {
40
- if x == 0.0 { Some(0.0) } else if x == 1.0 { Some(1.0) } else {
41
- let c = 2.0 * std::f32::consts::PI / 3.0;
42
- Some(- (2.0_f32.powf(10.0 * x - 10.0)) * ((x * 10.0 - 10.75) * c).sin())
43
- }
44
- }
45
- "easeOutElastic" => {
46
- if x == 0.0 { Some(0.0) } else if x == 1.0 { Some(1.0) } else {
47
- let c = 2.0 * std::f32::consts::PI / 3.0;
48
- Some(2.0_f32.powf(-10.0 * x) * ((x * 10.0 - 0.75) * c).sin() + 1.0)
49
- }
50
- }
51
- "easeInOutElastic" => {
52
- if x == 0.0 { Some(0.0) } else if x == 1.0 { Some(1.0) } else {
53
- let c = 2.0 * std::f32::consts::PI / 4.5;
54
- if x < 0.5 {
55
- Some(-(2.0_f32.powf(20.0 * x - 10.0)) * ((20.0 * x - 11.125) * c).sin() / 2.0)
56
- } else {
57
- Some(2.0_f32.powf(-20.0 * x + 10.0) * ((20.0 * x - 11.125) * c).sin() / 2.0 + 1.0)
58
- }
59
- }
60
- }
61
- // Bounce helpers
62
- "easeInBounce" => Some(1.0 - bounce_out(1.0 - x)),
63
- "easeOutBounce" => Some(bounce_out(x)),
64
- "easeInOutBounce" => Some(if x < 0.5 { (1.0 - bounce_out(1.0 - 2.0 * x)) / 2.0 } else { (1.0 + bounce_out(2.0 * x - 1.0)) / 2.0 }),
65
- _ => None,
66
- }
67
- }
68
-
69
- fn bounce_out(x: f32) -> f32 {
70
- let n1 = 7.5625; let d1 = 2.75;
71
- if x < 1.0 / d1 {
72
- n1 * x * x
73
- } else if x < 2.0 / d1 {
74
- let x = x - 1.5 / d1; n1 * x * x + 0.75
75
- } else if x < 2.5 / d1 {
76
- let x = x - 2.25 / d1; n1 * x * x + 0.9375
77
- } else {
78
- let x = x - 2.625 / d1; n1 * x * x + 0.984375
79
- }
80
- }
81
-
82
- // Find and evaluate the first $easing.<fn>(...) occurrence in the string.
83
- // Accepts a single argument expression producing t in [0,1].
84
- pub fn find_and_eval_first_easing_call<EvalFn>(
85
- s: &str,
86
- eval: EvalFn,
87
- vars: &VariableTable,
88
- bpm: f32,
89
- beat: f32,
90
- ) -> Option<String>
91
- where
92
- EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
93
- {
94
- let start = s.find("$easing.")?;
95
- let open_rel = s[start..].find('(')?;
96
- let open = start + open_rel;
97
- let func = &s[start + 9..open];
98
-
99
- // Find matching close parenthesis
100
- let mut depth: i32 = 0;
101
- let mut close_abs: Option<usize> = None;
102
- for (i, ch) in s[open..].char_indices() {
103
- match ch {
104
- '(' => depth += 1,
105
- ')' => { depth -= 1; if depth == 0 { close_abs = Some(open + i); break; } }
106
- _ => {}
107
- }
108
- }
109
- let close = close_abs?;
110
-
111
- let inner = &s[open + 1..close];
112
- let t = eval(inner, vars, bpm, beat)?;
113
- let result = easing_value(func, t)?;
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
+ // 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
+ }
@@ -1,41 +1,43 @@
1
- use crate::core::store::variable::VariableTable;
2
- use std::sync::OnceLock;
3
- use std::time::{ SystemTime, UNIX_EPOCH };
4
-
5
- static SESSION_SEED: OnceLock<f32> = OnceLock::new();
6
-
7
- pub fn get_session_seed() -> f32 {
8
- *SESSION_SEED.get_or_init(|| {
9
- let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
10
- // Build a stable 0..1 seed from nanos
11
- let nanos = now.subsec_nanos();
12
- ((nanos as f32) / 1_000_000_000.0).clamp(0.0, 1.0)
13
- })
14
- }
15
-
16
- // Resolve special environment variables like $env.bpm, $env.beat, $env.position
17
- // For now, $env.position is treated as an alias of beat.
18
- pub fn resolve_env_atom(atom: &str, bpm: f32, beat: f32) -> Option<f32> {
19
- match atom {
20
- "$env.bpm" => Some(bpm),
21
- "$env.beat" => Some(beat),
22
- "$env.position" => Some(beat),
23
- // Optional seed for deterministic randomness
24
- "$env.seed" => Some(get_session_seed()),
25
- _ => None,
26
- }
27
- }
28
-
29
- // Utility: resolve an identifier or numeric literal to f32 using the variable table
30
- pub fn resolve_atom_or_var(atom: &str, vars: &VariableTable, bpm: f32, beat: f32) -> Option<f32> {
31
- if let Some(v) = resolve_env_atom(atom, bpm, beat) {
32
- return Some(v);
33
- }
34
- if let Ok(n) = atom.parse::<f32>() {
35
- return Some(n);
36
- }
37
- if let Some(crate::core::shared::value::Value::Number(n)) = vars.get(atom) {
38
- return Some(*n);
39
- }
40
- None
41
- }
1
+ use crate::core::store::variable::VariableTable;
2
+ use std::sync::OnceLock;
3
+ use std::time::{SystemTime, UNIX_EPOCH};
4
+
5
+ static SESSION_SEED: OnceLock<f32> = OnceLock::new();
6
+
7
+ pub fn get_session_seed() -> f32 {
8
+ *SESSION_SEED.get_or_init(|| {
9
+ let now = SystemTime::now()
10
+ .duration_since(UNIX_EPOCH)
11
+ .unwrap_or_default();
12
+ // Build a stable 0..1 seed from nanos
13
+ let nanos = now.subsec_nanos();
14
+ ((nanos as f32) / 1_000_000_000.0).clamp(0.0, 1.0)
15
+ })
16
+ }
17
+
18
+ // Resolve special environment variables like $env.bpm, $env.beat, $env.position
19
+ // For now, $env.position is treated as an alias of beat.
20
+ pub fn resolve_env_atom(atom: &str, bpm: f32, beat: f32) -> Option<f32> {
21
+ match atom {
22
+ "$env.bpm" => Some(bpm),
23
+ "$env.beat" => Some(beat),
24
+ "$env.position" => Some(beat),
25
+ // Optional seed for deterministic randomness
26
+ "$env.seed" => Some(get_session_seed()),
27
+ _ => None,
28
+ }
29
+ }
30
+
31
+ // Utility: resolve an identifier or numeric literal to f32 using the variable table
32
+ pub fn resolve_atom_or_var(atom: &str, vars: &VariableTable, bpm: f32, beat: f32) -> Option<f32> {
33
+ if let Some(v) = resolve_env_atom(atom, bpm, beat) {
34
+ return Some(v);
35
+ }
36
+ if let Ok(n) = atom.parse::<f32>() {
37
+ return Some(n);
38
+ }
39
+ if let Some(crate::core::shared::value::Value::Number(n)) = vars.get(atom) {
40
+ return Some(*n);
41
+ }
42
+ None
43
+ }