@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
@@ -0,0 +1,323 @@
1
+ use crate::core::audio::engine::AudioEngine;
2
+ use devalang_types::Value;
3
+ use devalang_utils::logger::{LogLevel, Logger};
4
+ use std::collections::HashMap;
5
+
6
+ fn parse_value_to_f32(v: &Value) -> Option<f32> {
7
+ match v {
8
+ Value::Number(n) => Some(*n as f32),
9
+ Value::String(s) => s.parse::<f32>().ok(),
10
+ Value::Identifier(s) => s.parse::<f32>().ok(),
11
+ Value::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
12
+ _ => None,
13
+ }
14
+ }
15
+
16
+ // Simple nearest-neighbour resampler that produces an output with the same frame count
17
+ // while applying a time-scaling factor per frame. channels = interleaved channel count.
18
+ fn resample_segment_nearest(
19
+ src: &[i16],
20
+ channels: usize,
21
+ start_rate: f32,
22
+ end_rate: f32,
23
+ ) -> Vec<i16> {
24
+ if src.is_empty() || channels == 0 {
25
+ return Vec::new();
26
+ }
27
+
28
+ let frames = src.len() / channels;
29
+ if frames == 0 {
30
+ return Vec::new();
31
+ }
32
+
33
+ // Copy source into frames x channels matrix access via frame*channels + ch
34
+ let mut out = vec![0i16; frames * channels];
35
+
36
+ for f in 0..frames {
37
+ let t = if frames > 1 {
38
+ f as f32 / (frames - 1) as f32
39
+ } else {
40
+ 0.0
41
+ };
42
+ let rate = start_rate + t * (end_rate - start_rate);
43
+ let inv_rate = if rate == 0.0 { 1.0 } else { 1.0 / rate };
44
+
45
+ // determine source frame position (nearest)
46
+ let src_frame_pos = (f as f32 * inv_rate).clamp(0.0, (frames - 1) as f32);
47
+ let src_idx = src_frame_pos.round() as usize;
48
+
49
+ for ch in 0..channels {
50
+ let s_idx = src_idx.saturating_mul(channels).saturating_add(ch);
51
+ let o_idx = f.saturating_mul(channels).saturating_add(ch);
52
+ let sample = if s_idx < src.len() { src[s_idx] } else { 0 };
53
+ out[o_idx] = sample;
54
+ }
55
+ }
56
+
57
+ out
58
+ }
59
+
60
+ // Basic effect application for chainable effects. This is intentionally minimal:
61
+ // - Accepts a target synth name and args parsed from the arrow call.
62
+ // - Applies effects by mutating the AudioEngine or scheduling transforms.
63
+ // Current effects implemented: echo, reverb, slide (as parameter modifiers).
64
+
65
+ pub fn apply_effect_chain(
66
+ method: &str,
67
+ args: &Vec<Value>,
68
+ target: &str,
69
+ audio_engine: &mut AudioEngine,
70
+ variable_table: &devalang_types::VariableTable,
71
+ ) {
72
+ match method {
73
+ "echo" => apply_echo(args, target, audio_engine, variable_table),
74
+ "reverb" => apply_reverb(args, target, audio_engine, variable_table),
75
+ "slide" => apply_slide(args, target, audio_engine, variable_table),
76
+ "arp" => apply_arp_effect(args, target, audio_engine, variable_table),
77
+ _ => {
78
+ let logger = Logger::new();
79
+ logger.log_message(
80
+ LogLevel::Error,
81
+ &format!("Unknown chainable effect '{}' on '{}'.", method, target),
82
+ );
83
+ }
84
+ }
85
+ }
86
+
87
+ fn parse_map_arg(args: &Vec<Value>) -> Option<HashMap<String, Value>> {
88
+ // Typical usage: method({ key: value }) -> args[0] is a Map
89
+ if let Some(Value::Map(m)) = args.first() {
90
+ return Some(m.clone());
91
+ }
92
+ None
93
+ }
94
+
95
+ fn apply_echo(
96
+ args: &Vec<Value>,
97
+ _target: &str,
98
+ engine: &mut AudioEngine,
99
+ _variable_table: &devalang_types::VariableTable,
100
+ ) {
101
+ let map = parse_map_arg(args);
102
+ let mut delay_ms = 250.0_f32;
103
+ let mut feedback = 0.5_f32;
104
+
105
+ if let Some(m) = map {
106
+ if let Some(Value::Number(n)) = m.get("delay") {
107
+ delay_ms = *n as f32;
108
+ }
109
+ if let Some(Value::Number(n)) = m.get("feedback") {
110
+ feedback = *n as f32;
111
+ }
112
+ }
113
+
114
+ // Very small and cheap echo: we will add a delayed, attenuated copy of the buffer
115
+ let sample_rate = engine.sample_rate as f32;
116
+ let channels = engine.channels as usize;
117
+ let delay_samples = ((delay_ms / 1000.0) * sample_rate) as usize * channels;
118
+
119
+ if delay_samples == 0 || engine.buffer.is_empty() {
120
+ return;
121
+ }
122
+
123
+ // Mix a single echo pass
124
+ let mut out = engine.buffer.clone();
125
+ for i in delay_samples..engine.buffer.len() {
126
+ let src = engine.buffer[i - delay_samples] as f32;
127
+ let added = (src * feedback)
128
+ .round()
129
+ .clamp(i16::MIN as f32, i16::MAX as f32) as i16;
130
+ out[i] = out[i].saturating_add(added);
131
+ }
132
+
133
+ engine.buffer = out;
134
+ }
135
+
136
+ fn apply_reverb(
137
+ args: &Vec<Value>,
138
+ _target: &str,
139
+ engine: &mut AudioEngine,
140
+ _variable_table: &devalang_types::VariableTable,
141
+ ) {
142
+ let map = parse_map_arg(args);
143
+ let mut room_size = 0.5_f32;
144
+ if let Some(m) = map {
145
+ if let Some(Value::Number(n)) = m.get("room_size") {
146
+ room_size = *n as f32;
147
+ }
148
+ }
149
+
150
+ // Cheap reverb: multiple short comb filters (very approximate)
151
+ if engine.buffer.is_empty() {
152
+ return;
153
+ }
154
+
155
+ let sample_rate = engine.sample_rate as f32;
156
+ let channels = engine.channels as usize;
157
+ let reverb_delay_samples = ((0.03 * room_size) * sample_rate) as usize * channels;
158
+ if reverb_delay_samples == 0 {
159
+ return;
160
+ }
161
+
162
+ let mut out = engine.buffer.clone();
163
+ for i in reverb_delay_samples..engine.buffer.len() {
164
+ let src = engine.buffer[i - reverb_delay_samples] as f32;
165
+ let added = (src * room_size * 0.5)
166
+ .round()
167
+ .clamp(i16::MIN as f32, i16::MAX as f32) as i16;
168
+ out[i] = out[i].saturating_add(added);
169
+ }
170
+
171
+ engine.buffer = out;
172
+ }
173
+
174
+ fn apply_slide(
175
+ args: &Vec<Value>,
176
+ _target: &str,
177
+ _engine: &mut AudioEngine,
178
+ _variable_table: &devalang_types::VariableTable,
179
+ ) {
180
+ // Slide: apply a linear pitch glide across the most recently recorded note ranges
181
+ let map = parse_map_arg(args);
182
+ let mut from_semitones = 0.0_f32;
183
+ let mut to_semitones = 0.0_f32;
184
+
185
+ if let Some(m) = map {
186
+ if let Some(v) = m.get("from") {
187
+ if let Some(f) = parse_value_to_f32(v) {
188
+ from_semitones = f;
189
+ }
190
+ }
191
+ if let Some(v) = m.get("to") {
192
+ if let Some(t) = parse_value_to_f32(v) {
193
+ to_semitones = t;
194
+ }
195
+ }
196
+ }
197
+
198
+ // Compute rate multipliers from semitone offsets
199
+ let start_rate = 2f32.powf(from_semitones / 12.0);
200
+ let end_rate = 2f32.powf(to_semitones / 12.0);
201
+
202
+ let channels = _engine.channels as usize;
203
+ if channels == 0 {
204
+ return;
205
+ }
206
+
207
+ // For each recorded last note range for the target, apply a per-frame resample glide
208
+ if let Some(ranges) = _engine.last_notes.get(_target) {
209
+ for (start_sample, total_samples) in ranges.iter() {
210
+ if *total_samples == 0 {
211
+ continue;
212
+ }
213
+ // clamp range
214
+ let end = (*start_sample)
215
+ .saturating_add(*total_samples)
216
+ .min(_engine.buffer.len());
217
+ if *start_sample >= end {
218
+ continue;
219
+ }
220
+
221
+ // work on a copy of the segment
222
+ let seg = _engine.buffer[*start_sample..end].to_vec();
223
+ let processed = resample_segment_nearest(&seg, channels, start_rate, end_rate);
224
+
225
+ // Mix processed back into buffer (replace to preserve duration)
226
+ for i in 0..processed.len().min(seg.len()) {
227
+ _engine.buffer[*start_sample + i] = processed[i];
228
+ }
229
+ }
230
+ } else {
231
+ let logger = Logger::new();
232
+ logger.log_message(
233
+ LogLevel::Warning,
234
+ "Slide requested but no recent notes found for target",
235
+ );
236
+ }
237
+ }
238
+
239
+ fn apply_arp_effect(
240
+ args: &Vec<Value>,
241
+ _target: &str,
242
+ _engine: &mut AudioEngine,
243
+ _variable_table: &devalang_types::VariableTable,
244
+ ) {
245
+ // Arp effect: split the last note into N slices and re-pitch each slice across a spread
246
+ let map = parse_map_arg(args);
247
+ let mut steps: usize = 4;
248
+ let mut spread_semitones: f32 = 0.0;
249
+
250
+ if let Some(m) = map {
251
+ if let Some(v) = m.get("steps") {
252
+ if let Some(s) = parse_value_to_f32(v) {
253
+ steps = (s as usize).max(1);
254
+ }
255
+ }
256
+ if let Some(v) = m.get("spread") {
257
+ if let Some(s) = parse_value_to_f32(v) {
258
+ spread_semitones = s;
259
+ }
260
+ }
261
+ }
262
+
263
+ let channels = _engine.channels as usize;
264
+ if channels == 0 {
265
+ return;
266
+ }
267
+
268
+ if let Some(ranges) = _engine.last_notes.get(_target) {
269
+ for (start_sample, total_samples) in ranges.iter() {
270
+ if *total_samples == 0 {
271
+ continue;
272
+ }
273
+ let end_sample = (*start_sample)
274
+ .saturating_add(*total_samples)
275
+ .min(_engine.buffer.len());
276
+ if *start_sample >= end_sample {
277
+ continue;
278
+ }
279
+
280
+ let seg = _engine.buffer[*start_sample..end_sample].to_vec();
281
+ let frames = seg.len() / channels;
282
+ if frames == 0 {
283
+ continue;
284
+ }
285
+
286
+ // For each step, compute semitone and pitch multiplier and place slice at computed offset
287
+ for step in 0..steps {
288
+ let t = if steps > 1 {
289
+ step as f32 / (steps - 1) as f32
290
+ } else {
291
+ 0.0
292
+ };
293
+ let semis = t * spread_semitones;
294
+ let rate = 2f32.powf(semis / 12.0);
295
+
296
+ // Resample the entire segment to the same duration using nearest approach with rate
297
+ let processed = resample_segment_nearest(&seg, channels, rate, rate);
298
+
299
+ // place the processed slice starting at fractional positions across original segment
300
+ let offset_frames =
301
+ ((t * frames as f32).round() as usize).min(frames.saturating_sub(1));
302
+ let offset_samples = offset_frames.saturating_mul(channels);
303
+
304
+ // mix into engine buffer
305
+ for i in 0..processed.len() {
306
+ let dst_idx = *start_sample + offset_samples + i;
307
+ if dst_idx >= _engine.buffer.len() {
308
+ break;
309
+ }
310
+ // simple additive mix and clamp
311
+ let sum = (_engine.buffer[dst_idx] as i32) + (processed[i] as i32);
312
+ _engine.buffer[dst_idx] = sum.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
313
+ }
314
+ }
315
+ }
316
+ } else {
317
+ let logger = Logger::new();
318
+ logger.log_message(
319
+ LogLevel::Warning,
320
+ "Arp effect requested but no recent notes found for target",
321
+ );
322
+ }
323
+ }
@@ -1,2 +1,3 @@
1
1
  pub mod chord;
2
+ pub mod effects;
2
3
  pub mod note;
@@ -86,11 +86,11 @@ pub fn interprete_note_method(
86
86
  let alias = waveform_str.split('.').next().unwrap_or("");
87
87
  if let Some(Value::String(uri)) = variable_table.get(alias) {
88
88
  if let Some(id) = uri.strip_prefix("devalang://plugin/") {
89
- let mut parts = id.split('.');
89
+ let mut parts = id.split('/');
90
90
  let author = parts.next().unwrap_or("");
91
91
  let name = parts.next().unwrap_or("");
92
92
  let key = format!("{}:{}", author, name);
93
- if let Some((_info, wasm_bytes)) = global_store.plugins.get(&key) {
93
+ if let Some((info, wasm_bytes)) = global_store.plugins.get(&key) {
94
94
  // Prepare buffer (stereo f32)
95
95
  let sample_rate = 44100.0_f32;
96
96
  let total_samples = ((duration_ms / 1000.0) * sample_rate) as usize;
@@ -120,6 +120,25 @@ pub fn interprete_note_method(
120
120
  _ => {}
121
121
  }
122
122
  }
123
+
124
+ // collect exported names to pass to the runner (preference list)
125
+ let exported_names_vec: Vec<String> =
126
+ info.exports.iter().map(|e| e.name.clone()).collect();
127
+
128
+ // Debug log: exported names and synth param keys
129
+ {
130
+ let logger = devalang_utils::logger::Logger::new();
131
+ logger.log_message(
132
+ devalang_utils::logger::LogLevel::Debug,
133
+ &format!(
134
+ "Calling plugin runner for '{}' with {} exported names and {} synth params",
135
+ key,
136
+ exported_names_vec.len(),
137
+ synth_params.len()
138
+ )
139
+ );
140
+ }
141
+
123
142
  let _ = runner.render_note_with_params_in_place(
124
143
  wasm_bytes,
125
144
  &mut fbuf,
@@ -131,7 +150,9 @@ pub fn interprete_note_method(
131
150
  2,
132
151
  &params_num,
133
152
  Some(&params_str),
153
+ Some(&exported_names_vec),
134
154
  );
155
+
135
156
  for (i, sample) in fbuf.iter().enumerate().take(total_samples * channels) {
136
157
  let s = (sample.clamp(-1.0, 1.0) * (i16::MAX as f32)) as i16;
137
158
  let idx = start_index + i;
@@ -178,8 +199,12 @@ pub fn interprete_note_method(
178
199
  "arp" => {
179
200
  // compute a step (ms) from synth params (rate/step). compute_arp_step
180
201
  // will interpret `rate` as number of notes across the provided duration
181
- let step_ms = crate::core::audio::interpreter::statements::arrow_call::types::arp::
182
- compute_arp_step(duration_ms, 1, &synth_params);
202
+ let step_ms =
203
+ crate::core::audio::interpreter::statements::arrow_call::types::arp::compute_arp_step(
204
+ duration_ms,
205
+ 1,
206
+ &synth_params
207
+ );
183
208
  let steps = if step_ms > 0.0 {
184
209
  ((duration_ms / step_ms).ceil() as usize).max(1)
185
210
  } else {
@@ -198,13 +223,14 @@ pub fn interprete_note_method(
198
223
  amp_note,
199
224
  &synth_params,
200
225
  &final_note_params,
201
- &automation,
226
+ &automation
202
227
  );
203
228
 
204
229
  // sub-note duration: default to step_ms so arp steps are audible and sequenced
205
230
  let sub_duration_ms = if step_ms > 0.0 { step_ms } else { duration_ms };
206
231
 
207
- audio_engine.insert_note(
232
+ let _ranges = audio_engine.insert_note(
233
+ Some(target.to_string()),
208
234
  waveform_str.to_string(),
209
235
  freq_step,
210
236
  amp_out,
@@ -214,6 +240,20 @@ pub fn interprete_note_method(
214
240
  params_out.clone(),
215
241
  automation.clone(),
216
242
  );
243
+ // Apply per-note effects if present in synth_params or note params
244
+ if let Some(ev) = params_out.get("effects") {
245
+ // for now expect effects as array of maps or identifiers
246
+ match ev {
247
+ Value::Array(arr) => {
248
+ for eff in arr.iter() {
249
+ if let Value::Map(_m) = eff {
250
+ // each map may have single key -> value
251
+ }
252
+ }
253
+ }
254
+ _ => {}
255
+ }
256
+ }
217
257
  }
218
258
 
219
259
  // mark handled to avoid the unconditional insert below
@@ -230,7 +270,7 @@ pub fn interprete_note_method(
230
270
  amp_note,
231
271
  &synth_params,
232
272
  &final_note_params,
233
- &automation,
273
+ &automation
234
274
  );
235
275
  final_amp = amp_out;
236
276
  final_note_params = params_out;
@@ -246,7 +286,7 @@ pub fn interprete_note_method(
246
286
  amp_note,
247
287
  &synth_params,
248
288
  &final_note_params,
249
- &automation,
289
+ &automation
250
290
  );
251
291
  final_amp = amp_out;
252
292
  final_note_params = params_out;
@@ -256,16 +296,31 @@ pub fn interprete_note_method(
256
296
  }
257
297
 
258
298
  if !handled {
259
- audio_engine.insert_note(
299
+ let ranges = audio_engine.insert_note(
300
+ Some(target.to_string()),
260
301
  waveform_str.to_string(),
261
302
  final_freq,
262
303
  final_amp,
263
304
  start_ms,
264
305
  duration_ms,
265
306
  synth_params.clone(),
266
- final_note_params,
267
- automation,
307
+ final_note_params.clone(),
308
+ automation.clone(),
268
309
  );
310
+ // apply per-note effects specified in final_note_params
311
+ if let Some(Value::Map(eff_map)) = final_note_params.get("effects") {
312
+ // delegate to effects module per range
313
+ for (_start, _len) in ranges.iter() {
314
+ // for simplicity apply using engine buffer ranges via effects module
315
+ crate::core::audio::interpreter::statements::arrow_call::methods::effects::apply_effect_chain(
316
+ "echo",
317
+ &vec![Value::Map(eff_map.clone())],
318
+ target,
319
+ audio_engine,
320
+ variable_table
321
+ );
322
+ }
323
+ }
269
324
  }
270
325
  }
271
326
 
@@ -1,3 +1,3 @@
1
- pub mod interprete;
2
- pub mod methods;
3
- pub mod types;
1
+ pub mod interprete;
2
+ pub mod methods;
3
+ pub mod types;