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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/Cargo.toml +84 -81
  2. package/README.md +3 -2
  3. package/docs/CHANGELOG.md +41 -0
  4. package/docs/ROADMAP.md +3 -3
  5. package/examples/chain.deva +19 -0
  6. package/examples/plugin.deva +10 -10
  7. package/examples/routing.deva +23 -0
  8. package/out-tsc/bin/project-version.json +6 -0
  9. package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +8 -8
  10. package/out-tsc/scripts/version/copy-to-binary.d.ts +1 -0
  11. package/out-tsc/scripts/version/copy-to-binary.js +79 -0
  12. package/package.json +23 -10
  13. package/project-version.json +3 -3
  14. package/rust/bindings/Cargo.toml +9 -0
  15. package/rust/bindings/src/lib.rs +86 -0
  16. package/rust/cli/addon/commands.rs +35 -0
  17. package/rust/cli/addon/download.rs +234 -0
  18. package/rust/cli/addon/install.rs +33 -0
  19. package/rust/cli/addon/list.rs +224 -0
  20. package/rust/cli/addon/metadata.rs +124 -0
  21. package/rust/cli/addon/mod.rs +8 -0
  22. package/rust/cli/addon/remove.rs +271 -0
  23. package/rust/cli/addon/update.rs +305 -0
  24. package/rust/cli/{install/addon.rs → addon/utils.rs} +109 -118
  25. package/rust/cli/build/commands.rs +153 -153
  26. package/rust/cli/build/process.rs +165 -165
  27. package/rust/cli/check/mod.rs +208 -208
  28. package/rust/cli/discover/commands.rs +275 -253
  29. package/rust/cli/discover/config.rs +109 -111
  30. package/rust/cli/discover/fs.rs +19 -19
  31. package/rust/cli/discover/install.rs +214 -103
  32. package/rust/cli/discover/metadata.rs +48 -48
  33. package/rust/cli/discover/mod.rs +5 -5
  34. package/rust/cli/me/commands.rs +52 -0
  35. package/rust/cli/me/mod.rs +1 -0
  36. package/rust/cli/mod.rs +12 -12
  37. package/rust/cli/parser.rs +30 -69
  38. package/rust/cli/play/commands.rs +375 -375
  39. package/rust/cli/play/process.rs +159 -159
  40. package/rust/core/audio/engine/driver.rs +19 -2
  41. package/rust/core/audio/engine/export.rs +169 -169
  42. package/rust/core/audio/engine/mod.rs +56 -56
  43. package/rust/core/audio/engine/notes/dsp.rs +88 -85
  44. package/rust/core/audio/engine/notes/mod.rs +53 -44
  45. package/rust/core/audio/engine/notes/params.rs +294 -294
  46. package/rust/core/audio/engine/sample/insert.rs +148 -47
  47. package/rust/core/audio/engine/sample/mod.rs +40 -40
  48. package/rust/core/audio/engine/sample/padding.rs +170 -170
  49. package/rust/core/audio/evaluator/condition.rs +61 -61
  50. package/rust/core/audio/evaluator/numeric.rs +152 -152
  51. package/rust/core/audio/evaluator/rhs.rs +16 -16
  52. package/rust/core/audio/evaluator/string_expr.rs +94 -94
  53. package/rust/core/audio/interpreter/driver.rs +574 -574
  54. package/rust/core/audio/interpreter/mod.rs +2 -2
  55. package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +9 -5
  56. package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +398 -384
  57. package/rust/core/audio/interpreter/statements/arrow_call/methods/effects.rs +323 -0
  58. package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +1 -0
  59. package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +66 -11
  60. package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -3
  61. package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -192
  62. package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -24
  63. package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -116
  64. package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -97
  65. package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -100
  66. package/rust/core/audio/interpreter/statements/automate.rs +16 -16
  67. package/rust/core/audio/interpreter/statements/call.rs +31 -1
  68. package/rust/core/audio/interpreter/statements/condition.rs +72 -72
  69. package/rust/core/audio/interpreter/statements/function.rs +24 -24
  70. package/rust/core/audio/interpreter/statements/let_.rs +36 -36
  71. package/rust/core/audio/interpreter/statements/load.rs +17 -17
  72. package/rust/core/audio/interpreter/statements/loop_.rs +115 -115
  73. package/rust/core/audio/interpreter/statements/spawn.rs +51 -2
  74. package/rust/core/audio/interpreter/statements/trigger.rs +242 -239
  75. package/rust/core/audio/loader/trigger.rs +98 -98
  76. package/rust/core/audio/player.rs +70 -70
  77. package/rust/core/audio/special/mod.rs +9 -9
  78. package/rust/core/builder/mod.rs +129 -129
  79. package/rust/core/debugger/lexer.rs +27 -27
  80. package/rust/core/debugger/logs.rs +52 -52
  81. package/rust/core/debugger/preprocessor.rs +27 -27
  82. package/rust/core/debugger/store.rs +38 -38
  83. package/rust/core/lexer/driver.rs +59 -59
  84. package/rust/core/lexer/handler/arrow.rs +82 -82
  85. package/rust/core/lexer/handler/at.rs +21 -21
  86. package/rust/core/lexer/handler/brace.rs +41 -41
  87. package/rust/core/lexer/handler/colon.rs +21 -21
  88. package/rust/core/lexer/handler/comment.rs +30 -30
  89. package/rust/core/lexer/handler/dot.rs +21 -21
  90. package/rust/core/lexer/handler/driver.rs +337 -337
  91. package/rust/core/lexer/handler/identifier.rs +47 -47
  92. package/rust/core/lexer/handler/indent.rs +66 -66
  93. package/rust/core/lexer/handler/mod.rs +15 -15
  94. package/rust/core/lexer/handler/newline.rs +23 -23
  95. package/rust/core/lexer/handler/number.rs +31 -31
  96. package/rust/core/lexer/handler/operator.rs +46 -46
  97. package/rust/core/lexer/handler/parenthesis.rs +41 -41
  98. package/rust/core/lexer/handler/slash.rs +21 -21
  99. package/rust/core/lexer/handler/string.rs +63 -63
  100. package/rust/core/lexer/mod.rs +3 -3
  101. package/rust/core/mod.rs +9 -9
  102. package/rust/core/parser/driver/block.rs +111 -111
  103. package/rust/core/parser/driver/cursor.rs +82 -82
  104. package/rust/core/parser/driver/driver_impl.rs +21 -1
  105. package/rust/core/parser/driver/mod.rs +6 -6
  106. package/rust/core/parser/driver/parse_array.rs +120 -120
  107. package/rust/core/parser/driver/parse_map.rs +247 -223
  108. package/rust/core/parser/driver/parser.rs +160 -160
  109. package/rust/core/parser/handler/arrow_call.rs +65 -14
  110. package/rust/core/parser/handler/identifier/synth.rs +171 -135
  111. package/rust/core/parser/handler/mod.rs +9 -9
  112. package/rust/core/parser/handler/pattern.rs +24 -1
  113. package/rust/core/plugin/loader.rs +137 -137
  114. package/rust/core/plugin/mod.rs +2 -2
  115. package/rust/core/plugin/runner/non_wasm.rs +481 -297
  116. package/rust/core/plugin/runner/wasm32.rs +1 -0
  117. package/rust/core/preprocessor/loader/inject.rs +313 -278
  118. package/rust/core/preprocessor/loader/loader_helpers.rs +110 -110
  119. package/rust/core/preprocessor/loader/mod.rs +235 -235
  120. package/rust/core/preprocessor/module.rs +55 -55
  121. package/rust/core/preprocessor/processor/handlers.rs +107 -107
  122. package/rust/core/preprocessor/resolver/bank.rs +49 -49
  123. package/rust/core/preprocessor/resolver/call.rs +124 -124
  124. package/rust/core/preprocessor/resolver/condition.rs +95 -95
  125. package/rust/core/preprocessor/resolver/driver.rs +324 -324
  126. package/rust/core/preprocessor/resolver/function.rs +69 -69
  127. package/rust/core/preprocessor/resolver/group.rs +122 -122
  128. package/rust/core/preprocessor/resolver/let_.rs +32 -32
  129. package/rust/core/preprocessor/resolver/loop_.rs +318 -318
  130. package/rust/core/preprocessor/resolver/mod.rs +16 -16
  131. package/rust/core/preprocessor/resolver/pattern.rs +95 -83
  132. package/rust/core/preprocessor/resolver/spawn.rs +99 -99
  133. package/rust/core/preprocessor/resolver/synth.rs +54 -54
  134. package/rust/core/preprocessor/resolver/tempo.rs +48 -48
  135. package/rust/core/preprocessor/resolver/trigger.rs +116 -116
  136. package/rust/core/preprocessor/resolver/value.rs +176 -176
  137. package/rust/core/store/global.rs +57 -57
  138. package/rust/lib.rs +323 -323
  139. package/rust/macros/Cargo.toml +14 -0
  140. package/rust/macros/src/lib.rs +52 -0
  141. package/rust/main.rs +311 -142
  142. package/rust/types/Cargo.toml +1 -1
  143. package/rust/types/src/addons.rs +3 -1
  144. package/rust/types/src/config.rs +1 -3
  145. package/rust/utils/Cargo.toml +5 -2
  146. package/rust/utils/src/file.rs +397 -14
  147. package/rust/utils/src/path.rs +31 -2
  148. package/rust/utils/src/version.rs +38 -7
  149. package/rust/web/auth.rs +5 -0
  150. package/rust/web/forge.rs +5 -0
  151. package/rust/web/mod.rs +5 -3
  152. package/typescript/scripts/version/copy-to-binary.ts +82 -0
  153. package/rust/cli/bank/api.rs +0 -122
  154. package/rust/cli/bank/commands.rs +0 -306
  155. package/rust/cli/bank/mod.rs +0 -29
  156. package/rust/cli/install/bank.rs +0 -72
  157. package/rust/cli/install/commands.rs +0 -35
  158. package/rust/cli/install/mod.rs +0 -4
  159. package/rust/cli/install/plugin.rs +0 -80
@@ -109,6 +109,18 @@ pub fn interprete_call_statement(
109
109
  let total_bar = 4.0 * base_duration;
110
110
  let step_duration = total_bar / step_count; // seconds per step
111
111
 
112
+ // extract optional swing/humanize from pattern_stmt.value
113
+ let mut swing: f32 = 0.0;
114
+ let mut humanize: f32 = 0.0;
115
+ if let Value::Map(m) = &pattern_stmt.value {
116
+ if let Some(Value::Number(s)) = m.get("swing") {
117
+ swing = *s;
118
+ }
119
+ if let Some(Value::Number(h)) = m.get("humanize") {
120
+ humanize = *h;
121
+ }
122
+ }
123
+
112
124
  let mut updated_max = max_end_time;
113
125
 
114
126
  for (i, ch) in pattern_str.chars().enumerate() {
@@ -117,7 +129,25 @@ pub fn interprete_call_statement(
117
129
  }
118
130
 
119
131
  // Schedule a trigger at cursor_time + offset
120
- let event_time = cursor_time + (i as f32) * step_duration;
132
+ let mut event_time = cursor_time + (i as f32) * step_duration;
133
+ if swing.abs() > 0.0001 {
134
+ if i % 2 == 1 {
135
+ event_time += swing * step_duration;
136
+ } else {
137
+ event_time -= swing * step_duration;
138
+ }
139
+ }
140
+
141
+ if humanize.abs() > 0.0001 {
142
+ let jitter_range = humanize * step_duration;
143
+ let seed = (audio_engine.module_name.len() + i) as u64
144
+ + (event_time.to_bits() as u64);
145
+ let mut x = seed.wrapping_mul(0x9E3779B97F4A7C15).rotate_left(13);
146
+ x ^= x >> 7;
147
+ let r = (x as i64 % 1000) as f32 / 1000.0;
148
+ let jitter = (r * 2.0 - 1.0) * jitter_range / 2.0;
149
+ event_time += jitter;
150
+ }
121
151
 
122
152
  // Resolve trigger value similarly to interprete_trigger_statement
123
153
  let mut trigger_val = Value::String(target_entity.clone());
@@ -1,72 +1,72 @@
1
- use devalang_types::Value;
2
-
3
- use crate::core::{
4
- audio::{
5
- engine::AudioEngine, evaluator::evaluate_condition_string,
6
- interpreter::driver::execute_audio_block,
7
- },
8
- parser::statement::Statement,
9
- store::global::GlobalStore,
10
- };
11
- use devalang_types::store::{FunctionTable, VariableTable};
12
-
13
- pub fn interprete_condition_statement(
14
- stmt: &Statement,
15
- audio_engine: &mut AudioEngine,
16
- global_store: &GlobalStore,
17
- variable_table: &VariableTable,
18
- functions_table: &FunctionTable,
19
- base_bpm: f32,
20
- base_duration: f32,
21
- max_end_time: f32,
22
- cursor_time: f32,
23
- ) -> (f32, f32) {
24
- let cur_time = cursor_time;
25
- let max_time = max_end_time;
26
-
27
- let mut current = stmt.value.clone();
28
-
29
- loop {
30
- let Value::Map(map) = current else {
31
- break;
32
- };
33
-
34
- let should_execute = match map.get("condition") {
35
- Some(Value::Boolean(b)) => *b,
36
- Some(Value::String(expr)) => evaluate_condition_string(expr, &variable_table.clone()),
37
- Some(_) => false,
38
- None => true,
39
- };
40
-
41
- if should_execute {
42
- if let Some(Value::Block(block)) = map.get("body") {
43
- let (new_max, cursor_time) = execute_audio_block(
44
- audio_engine,
45
- global_store,
46
- variable_table.clone(),
47
- functions_table.clone(),
48
- block,
49
- base_bpm,
50
- base_duration,
51
- max_time,
52
- cur_time,
53
- );
54
- return (new_max, cursor_time);
55
- } else {
56
- break;
57
- }
58
- }
59
-
60
- // Advance to the next condition
61
- match map.get("next") {
62
- Some(Value::Map(next_map)) => {
63
- current = Value::Map(next_map.clone());
64
- }
65
- _ => {
66
- break;
67
- }
68
- }
69
- }
70
-
71
- (max_end_time, cursor_time)
72
- }
1
+ use devalang_types::Value;
2
+
3
+ use crate::core::{
4
+ audio::{
5
+ engine::AudioEngine, evaluator::evaluate_condition_string,
6
+ interpreter::driver::execute_audio_block,
7
+ },
8
+ parser::statement::Statement,
9
+ store::global::GlobalStore,
10
+ };
11
+ use devalang_types::store::{FunctionTable, VariableTable};
12
+
13
+ pub fn interprete_condition_statement(
14
+ stmt: &Statement,
15
+ audio_engine: &mut AudioEngine,
16
+ global_store: &GlobalStore,
17
+ variable_table: &VariableTable,
18
+ functions_table: &FunctionTable,
19
+ base_bpm: f32,
20
+ base_duration: f32,
21
+ max_end_time: f32,
22
+ cursor_time: f32,
23
+ ) -> (f32, f32) {
24
+ let cur_time = cursor_time;
25
+ let max_time = max_end_time;
26
+
27
+ let mut current = stmt.value.clone();
28
+
29
+ loop {
30
+ let Value::Map(map) = current else {
31
+ break;
32
+ };
33
+
34
+ let should_execute = match map.get("condition") {
35
+ Some(Value::Boolean(b)) => *b,
36
+ Some(Value::String(expr)) => evaluate_condition_string(expr, &variable_table.clone()),
37
+ Some(_) => false,
38
+ None => true,
39
+ };
40
+
41
+ if should_execute {
42
+ if let Some(Value::Block(block)) = map.get("body") {
43
+ let (new_max, cursor_time) = execute_audio_block(
44
+ audio_engine,
45
+ global_store,
46
+ variable_table.clone(),
47
+ functions_table.clone(),
48
+ block,
49
+ base_bpm,
50
+ base_duration,
51
+ max_time,
52
+ cur_time,
53
+ );
54
+ return (new_max, cursor_time);
55
+ } else {
56
+ break;
57
+ }
58
+ }
59
+
60
+ // Advance to the next condition
61
+ match map.get("next") {
62
+ Some(Value::Map(next_map)) => {
63
+ current = Value::Map(next_map.clone());
64
+ }
65
+ _ => {
66
+ break;
67
+ }
68
+ }
69
+ }
70
+
71
+ (max_end_time, cursor_time)
72
+ }
@@ -1,24 +1,24 @@
1
- use crate::core::parser::statement::{Statement, StatementKind};
2
- use devalang_types::store::{FunctionDef, FunctionTable};
3
-
4
- pub fn interprete_function_statement(
5
- stmt: &Statement,
6
- functions_table: &mut FunctionTable,
7
- ) -> Option<FunctionTable> {
8
- if let StatementKind::Function {
9
- name,
10
- parameters,
11
- body,
12
- } = &stmt.kind
13
- {
14
- functions_table.add_function(FunctionDef {
15
- name: name.clone(),
16
- parameters: parameters.clone(),
17
- body: body.clone(),
18
- });
19
-
20
- return Some(functions_table.clone());
21
- }
22
-
23
- None
24
- }
1
+ use crate::core::parser::statement::{Statement, StatementKind};
2
+ use devalang_types::store::{FunctionDef, FunctionTable};
3
+
4
+ pub fn interprete_function_statement(
5
+ stmt: &Statement,
6
+ functions_table: &mut FunctionTable,
7
+ ) -> Option<FunctionTable> {
8
+ if let StatementKind::Function {
9
+ name,
10
+ parameters,
11
+ body,
12
+ } = &stmt.kind
13
+ {
14
+ functions_table.add_function(FunctionDef {
15
+ name: name.clone(),
16
+ parameters: parameters.clone(),
17
+ body: body.clone(),
18
+ });
19
+
20
+ return Some(functions_table.clone());
21
+ }
22
+
23
+ None
24
+ }
@@ -1,36 +1,36 @@
1
- use crate::core::parser::statement::{Statement, StatementKind};
2
- use devalang_types::Value;
3
- use devalang_types::store::VariableTable;
4
-
5
- pub fn interprete_let_statement(
6
- stmt: &Statement,
7
- variable_table: &mut VariableTable,
8
- ) -> Option<VariableTable> {
9
- if let StatementKind::Let { name } = &stmt.kind {
10
- // If RHS is a string and looks like an expression, evaluate it
11
- let evaluated = match &stmt.value {
12
- Value::String(s) if s.contains("$env") || s.contains("$math") => {
13
- // We don't have direct env here; use defaults or infer from table
14
- let bpm = if let Some(Value::Number(n)) = variable_table.get("bpm") {
15
- *n
16
- } else {
17
- 120.0
18
- };
19
- // Try to infer beat from time-based variables if any, else 0.0
20
- let beat = if let Some(Value::Number(n)) = variable_table.get("beat") {
21
- *n
22
- } else {
23
- 0.0
24
- };
25
- crate::core::audio::evaluator::evaluate_rhs_into_value(s, variable_table, bpm, beat)
26
- }
27
- other => other.clone(),
28
- };
29
-
30
- variable_table.set(name.to_string(), evaluated);
31
-
32
- return Some(variable_table.clone());
33
- }
34
-
35
- None
36
- }
1
+ use crate::core::parser::statement::{Statement, StatementKind};
2
+ use devalang_types::Value;
3
+ use devalang_types::store::VariableTable;
4
+
5
+ pub fn interprete_let_statement(
6
+ stmt: &Statement,
7
+ variable_table: &mut VariableTable,
8
+ ) -> Option<VariableTable> {
9
+ if let StatementKind::Let { name } = &stmt.kind {
10
+ // If RHS is a string and looks like an expression, evaluate it
11
+ let evaluated = match &stmt.value {
12
+ Value::String(s) if s.contains("$env") || s.contains("$math") => {
13
+ // We don't have direct env here; use defaults or infer from table
14
+ let bpm = if let Some(Value::Number(n)) = variable_table.get("bpm") {
15
+ *n
16
+ } else {
17
+ 120.0
18
+ };
19
+ // Try to infer beat from time-based variables if any, else 0.0
20
+ let beat = if let Some(Value::Number(n)) = variable_table.get("beat") {
21
+ *n
22
+ } else {
23
+ 0.0
24
+ };
25
+ crate::core::audio::evaluator::evaluate_rhs_into_value(s, variable_table, bpm, beat)
26
+ }
27
+ other => other.clone(),
28
+ };
29
+
30
+ variable_table.set(name.to_string(), evaluated);
31
+
32
+ return Some(variable_table.clone());
33
+ }
34
+
35
+ None
36
+ }
@@ -1,17 +1,17 @@
1
- use devalang_types::Value;
2
-
3
- use crate::core::parser::statement::{Statement, StatementKind};
4
- use devalang_types::store::VariableTable;
5
-
6
- pub fn interprete_load_statement(
7
- stmt: &Statement,
8
- variable_table: &mut VariableTable,
9
- ) -> Option<VariableTable> {
10
- if let StatementKind::Load { source, alias } = &stmt.kind {
11
- variable_table.set(alias.to_string(), Value::Sample(source.clone()));
12
-
13
- return Some(variable_table.clone());
14
- }
15
-
16
- None
17
- }
1
+ use devalang_types::Value;
2
+
3
+ use crate::core::parser::statement::{Statement, StatementKind};
4
+ use devalang_types::store::VariableTable;
5
+
6
+ pub fn interprete_load_statement(
7
+ stmt: &Statement,
8
+ variable_table: &mut VariableTable,
9
+ ) -> Option<VariableTable> {
10
+ if let StatementKind::Load { source, alias } = &stmt.kind {
11
+ variable_table.set(alias.to_string(), Value::Sample(source.clone()));
12
+
13
+ return Some(variable_table.clone());
14
+ }
15
+
16
+ None
17
+ }
@@ -1,115 +1,115 @@
1
- use devalang_types::Value;
2
-
3
- use crate::core::{
4
- audio::{engine::AudioEngine, interpreter::driver::execute_audio_block},
5
- parser::statement::Statement,
6
- store::global::GlobalStore,
7
- };
8
- use devalang_types::store::{FunctionTable, VariableTable};
9
-
10
- pub fn interprete_loop_statement(
11
- stmt: &Statement,
12
- audio_engine: &mut AudioEngine,
13
- global_store: &GlobalStore,
14
- variable_table: &VariableTable,
15
- functions_table: &FunctionTable,
16
- base_bpm: f32,
17
- base_duration: f32,
18
- max_end_time: f32,
19
- cursor_time: f32,
20
- ) -> (f32, f32) {
21
- if let Value::Map(loop_value) = &stmt.value {
22
- // Foreach form: { foreach: Identifier(name), array: Array([...]), body: Block }
23
- if let (
24
- Some(Value::Identifier(var_name)),
25
- Some(Value::Array(items)),
26
- Some(Value::Block(loop_body)),
27
- ) = (
28
- loop_value.get("foreach"),
29
- loop_value.get("array"),
30
- loop_value.get("body"),
31
- ) {
32
- let engine = audio_engine;
33
- let mut cur_time = cursor_time;
34
- let mut max_time = max_end_time;
35
-
36
- for item in items {
37
- let mut scoped_vars = variable_table.clone();
38
- scoped_vars.set(var_name.clone(), item.clone());
39
-
40
- let (block_end_time, new_cursor) = execute_audio_block(
41
- engine,
42
- global_store,
43
- scoped_vars,
44
- functions_table.clone(),
45
- loop_body,
46
- base_bpm,
47
- base_duration,
48
- max_time,
49
- cur_time,
50
- );
51
-
52
- cur_time = new_cursor.max(block_end_time);
53
- max_time = max_time.max(cur_time);
54
- }
55
-
56
- return (max_time, cur_time);
57
- }
58
-
59
- let loop_count = match loop_value.get("iterator") {
60
- Some(Value::Number(n)) => *n as usize,
61
- Some(Value::Identifier(ident)) => {
62
- if let Some(Value::Number(n)) = variable_table.get(ident) {
63
- *n as usize
64
- } else {
65
- eprintln!("❌ Loop iterator must be a number, found: {:?}", ident);
66
- return (max_end_time, cursor_time);
67
- }
68
- }
69
- _ => {
70
- eprintln!(
71
- "❌ Loop iterator must be a number, found: {:?}",
72
- loop_value.get("iterator")
73
- );
74
- return (max_end_time, cursor_time);
75
- }
76
- };
77
-
78
- let loop_body = match loop_value.get("body") {
79
- Some(Value::Block(body)) => body.clone(),
80
- _ => {
81
- eprintln!(
82
- "❌ Loop body must be a block, found: {:?}",
83
- loop_value.get("body")
84
- );
85
- return (max_end_time, cursor_time);
86
- }
87
- };
88
-
89
- let engine = audio_engine;
90
- let mut cur_time = cursor_time;
91
- let mut max_time = max_end_time;
92
-
93
- for _ in 0..loop_count {
94
- let (block_end_time, new_cursor) = execute_audio_block(
95
- engine,
96
- global_store,
97
- variable_table.clone(),
98
- functions_table.clone(),
99
- &loop_body,
100
- base_bpm,
101
- base_duration,
102
- max_time,
103
- cur_time,
104
- );
105
-
106
- cur_time = new_cursor.max(block_end_time);
107
- max_time = max_time.max(cur_time);
108
- }
109
-
110
- return (max_time, cur_time);
111
- }
112
-
113
- eprintln!("❌ Loop statement value is not a map");
114
- (max_end_time, cursor_time)
115
- }
1
+ use devalang_types::Value;
2
+
3
+ use crate::core::{
4
+ audio::{engine::AudioEngine, interpreter::driver::execute_audio_block},
5
+ parser::statement::Statement,
6
+ store::global::GlobalStore,
7
+ };
8
+ use devalang_types::store::{FunctionTable, VariableTable};
9
+
10
+ pub fn interprete_loop_statement(
11
+ stmt: &Statement,
12
+ audio_engine: &mut AudioEngine,
13
+ global_store: &GlobalStore,
14
+ variable_table: &VariableTable,
15
+ functions_table: &FunctionTable,
16
+ base_bpm: f32,
17
+ base_duration: f32,
18
+ max_end_time: f32,
19
+ cursor_time: f32,
20
+ ) -> (f32, f32) {
21
+ if let Value::Map(loop_value) = &stmt.value {
22
+ // Foreach form: { foreach: Identifier(name), array: Array([...]), body: Block }
23
+ if let (
24
+ Some(Value::Identifier(var_name)),
25
+ Some(Value::Array(items)),
26
+ Some(Value::Block(loop_body)),
27
+ ) = (
28
+ loop_value.get("foreach"),
29
+ loop_value.get("array"),
30
+ loop_value.get("body"),
31
+ ) {
32
+ let engine = audio_engine;
33
+ let mut cur_time = cursor_time;
34
+ let mut max_time = max_end_time;
35
+
36
+ for item in items {
37
+ let mut scoped_vars = variable_table.clone();
38
+ scoped_vars.set(var_name.clone(), item.clone());
39
+
40
+ let (block_end_time, new_cursor) = execute_audio_block(
41
+ engine,
42
+ global_store,
43
+ scoped_vars,
44
+ functions_table.clone(),
45
+ loop_body,
46
+ base_bpm,
47
+ base_duration,
48
+ max_time,
49
+ cur_time,
50
+ );
51
+
52
+ cur_time = new_cursor.max(block_end_time);
53
+ max_time = max_time.max(cur_time);
54
+ }
55
+
56
+ return (max_time, cur_time);
57
+ }
58
+
59
+ let loop_count = match loop_value.get("iterator") {
60
+ Some(Value::Number(n)) => *n as usize,
61
+ Some(Value::Identifier(ident)) => {
62
+ if let Some(Value::Number(n)) = variable_table.get(ident) {
63
+ *n as usize
64
+ } else {
65
+ eprintln!("❌ Loop iterator must be a number, found: {:?}", ident);
66
+ return (max_end_time, cursor_time);
67
+ }
68
+ }
69
+ _ => {
70
+ eprintln!(
71
+ "❌ Loop iterator must be a number, found: {:?}",
72
+ loop_value.get("iterator")
73
+ );
74
+ return (max_end_time, cursor_time);
75
+ }
76
+ };
77
+
78
+ let loop_body = match loop_value.get("body") {
79
+ Some(Value::Block(body)) => body.clone(),
80
+ _ => {
81
+ eprintln!(
82
+ "❌ Loop body must be a block, found: {:?}",
83
+ loop_value.get("body")
84
+ );
85
+ return (max_end_time, cursor_time);
86
+ }
87
+ };
88
+
89
+ let engine = audio_engine;
90
+ let mut cur_time = cursor_time;
91
+ let mut max_time = max_end_time;
92
+
93
+ for _ in 0..loop_count {
94
+ let (block_end_time, new_cursor) = execute_audio_block(
95
+ engine,
96
+ global_store,
97
+ variable_table.clone(),
98
+ functions_table.clone(),
99
+ &loop_body,
100
+ base_bpm,
101
+ base_duration,
102
+ max_time,
103
+ cur_time,
104
+ );
105
+
106
+ cur_time = new_cursor.max(block_end_time);
107
+ max_time = max_time.max(cur_time);
108
+ }
109
+
110
+ return (max_time, cur_time);
111
+ }
112
+
113
+ eprintln!("❌ Loop statement value is not a map");
114
+ (max_end_time, cursor_time)
115
+ }
@@ -71,7 +71,22 @@ pub fn interprete_spawn_statement(
71
71
 
72
72
  // Pattern case: allow spawning a pattern similar to call
73
73
  if let Some(pattern_stmt) = find_pattern(name, variable_table, global_store) {
74
- if let Value::String(pat) = &pattern_stmt.value {
74
+ // Support Value::String(pattern) or Value::Map({ pattern: "..", swing: .., humanize: .. })
75
+ let mut pat_opt: Option<String> = None;
76
+ let mut opts_map: Option<std::collections::HashMap<String, Value>> = None;
77
+
78
+ match &pattern_stmt.value {
79
+ Value::String(s) => pat_opt = Some(s.clone()),
80
+ Value::Map(m) => {
81
+ if let Some(Value::String(s)) = m.get("pattern") {
82
+ pat_opt = Some(s.clone());
83
+ }
84
+ opts_map = Some(m.clone());
85
+ }
86
+ _ => {}
87
+ }
88
+
89
+ if let Some(pat) = pat_opt {
75
90
  let mut target_entity = name.clone();
76
91
  if let StatementKind::Pattern { name: _n, target } = &pattern_stmt.kind {
77
92
  if let Some(t) = target {
@@ -97,6 +112,18 @@ pub fn interprete_spawn_statement(
97
112
  let total_bar = 4.0 * base_duration;
98
113
  let step_duration = total_bar / step_count;
99
114
 
115
+ // extract optional swing/humanize from pattern_stmt.value
116
+ let mut swing: f32 = 0.0;
117
+ let mut humanize: f32 = 0.0;
118
+ if let Value::Map(m) = &pattern_stmt.value {
119
+ if let Some(Value::Number(s)) = m.get("swing") {
120
+ swing = *s;
121
+ }
122
+ if let Some(Value::Number(h)) = m.get("humanize") {
123
+ humanize = *h;
124
+ }
125
+ }
126
+
100
127
  let mut updated_max = max_end_time;
101
128
 
102
129
  for (i, ch) in pattern_str.chars().enumerate() {
@@ -104,7 +131,29 @@ pub fn interprete_spawn_statement(
104
131
  continue;
105
132
  }
106
133
 
107
- let event_time = cursor_time + (i as f32) * step_duration;
134
+ // Apply swing: shift every other step by +/- swing*step_duration
135
+ let mut event_time = cursor_time + (i as f32) * step_duration;
136
+ if swing.abs() > 0.0001 {
137
+ // swing applies to off-beats: shift odd steps forward, even steps back
138
+ if i % 2 == 1 {
139
+ event_time += swing * step_duration;
140
+ } else {
141
+ event_time -= swing * step_duration;
142
+ }
143
+ }
144
+
145
+ // Apply humanize: small random jitter in [-humanize*step_duration/2, +...]
146
+ if humanize.abs() > 0.0001 {
147
+ let jitter_range = humanize * step_duration;
148
+ // lightweight RNG using a simple hash to avoid adding rand dependency
149
+ let seed = (audio_engine.module_name.len() + i) as u64
150
+ + (event_time.to_bits() as u64);
151
+ let mut x = seed.wrapping_mul(0x9E3779B97F4A7C15).rotate_left(13);
152
+ x ^= x >> 7;
153
+ let r = (x as i64 % 1000) as f32 / 1000.0; // [0,1)
154
+ let jitter = (r * 2.0 - 1.0) * jitter_range / 2.0;
155
+ event_time += jitter;
156
+ }
108
157
 
109
158
  let mut trigger_val = Value::String(target_entity.clone());
110
159
  if let Some(val) = variable_table.variables.get(&target_entity) {