@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.
- package/Cargo.toml +84 -81
- package/README.md +3 -2
- package/docs/CHANGELOG.md +41 -0
- package/docs/ROADMAP.md +3 -3
- package/examples/chain.deva +19 -0
- package/examples/plugin.deva +10 -10
- package/examples/routing.deva +23 -0
- package/out-tsc/bin/project-version.json +6 -0
- package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +8 -8
- package/out-tsc/scripts/version/copy-to-binary.d.ts +1 -0
- package/out-tsc/scripts/version/copy-to-binary.js +79 -0
- package/package.json +23 -10
- package/project-version.json +3 -3
- package/rust/bindings/Cargo.toml +9 -0
- package/rust/bindings/src/lib.rs +86 -0
- package/rust/cli/addon/commands.rs +35 -0
- package/rust/cli/addon/download.rs +234 -0
- package/rust/cli/addon/install.rs +33 -0
- package/rust/cli/addon/list.rs +224 -0
- package/rust/cli/addon/metadata.rs +124 -0
- package/rust/cli/addon/mod.rs +8 -0
- package/rust/cli/addon/remove.rs +271 -0
- package/rust/cli/addon/update.rs +305 -0
- package/rust/cli/{install/addon.rs → addon/utils.rs} +109 -118
- package/rust/cli/build/commands.rs +153 -153
- package/rust/cli/build/process.rs +165 -165
- package/rust/cli/check/mod.rs +208 -208
- package/rust/cli/discover/commands.rs +275 -253
- package/rust/cli/discover/config.rs +109 -111
- package/rust/cli/discover/fs.rs +19 -19
- package/rust/cli/discover/install.rs +214 -103
- package/rust/cli/discover/metadata.rs +48 -48
- package/rust/cli/discover/mod.rs +5 -5
- package/rust/cli/me/commands.rs +52 -0
- package/rust/cli/me/mod.rs +1 -0
- package/rust/cli/mod.rs +12 -12
- package/rust/cli/parser.rs +30 -69
- package/rust/cli/play/commands.rs +375 -375
- package/rust/cli/play/process.rs +159 -159
- package/rust/core/audio/engine/driver.rs +19 -2
- package/rust/core/audio/engine/export.rs +169 -169
- package/rust/core/audio/engine/mod.rs +56 -56
- package/rust/core/audio/engine/notes/dsp.rs +88 -85
- package/rust/core/audio/engine/notes/mod.rs +53 -44
- package/rust/core/audio/engine/notes/params.rs +294 -294
- package/rust/core/audio/engine/sample/insert.rs +148 -47
- package/rust/core/audio/engine/sample/mod.rs +40 -40
- package/rust/core/audio/engine/sample/padding.rs +170 -170
- package/rust/core/audio/evaluator/condition.rs +61 -61
- package/rust/core/audio/evaluator/numeric.rs +152 -152
- package/rust/core/audio/evaluator/rhs.rs +16 -16
- package/rust/core/audio/evaluator/string_expr.rs +94 -94
- package/rust/core/audio/interpreter/driver.rs +574 -574
- package/rust/core/audio/interpreter/mod.rs +2 -2
- package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +9 -5
- package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +398 -384
- package/rust/core/audio/interpreter/statements/arrow_call/methods/effects.rs +323 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +1 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +66 -11
- package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -3
- package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -192
- package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -24
- package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -116
- package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -97
- package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -100
- package/rust/core/audio/interpreter/statements/automate.rs +16 -16
- package/rust/core/audio/interpreter/statements/call.rs +31 -1
- package/rust/core/audio/interpreter/statements/condition.rs +72 -72
- package/rust/core/audio/interpreter/statements/function.rs +24 -24
- package/rust/core/audio/interpreter/statements/let_.rs +36 -36
- package/rust/core/audio/interpreter/statements/load.rs +17 -17
- package/rust/core/audio/interpreter/statements/loop_.rs +115 -115
- package/rust/core/audio/interpreter/statements/spawn.rs +51 -2
- package/rust/core/audio/interpreter/statements/trigger.rs +242 -239
- package/rust/core/audio/loader/trigger.rs +98 -98
- package/rust/core/audio/player.rs +70 -70
- package/rust/core/audio/special/mod.rs +9 -9
- package/rust/core/builder/mod.rs +129 -129
- package/rust/core/debugger/lexer.rs +27 -27
- package/rust/core/debugger/logs.rs +52 -52
- package/rust/core/debugger/preprocessor.rs +27 -27
- package/rust/core/debugger/store.rs +38 -38
- package/rust/core/lexer/driver.rs +59 -59
- package/rust/core/lexer/handler/arrow.rs +82 -82
- package/rust/core/lexer/handler/at.rs +21 -21
- package/rust/core/lexer/handler/brace.rs +41 -41
- package/rust/core/lexer/handler/colon.rs +21 -21
- package/rust/core/lexer/handler/comment.rs +30 -30
- package/rust/core/lexer/handler/dot.rs +21 -21
- package/rust/core/lexer/handler/driver.rs +337 -337
- package/rust/core/lexer/handler/identifier.rs +47 -47
- package/rust/core/lexer/handler/indent.rs +66 -66
- package/rust/core/lexer/handler/mod.rs +15 -15
- package/rust/core/lexer/handler/newline.rs +23 -23
- package/rust/core/lexer/handler/number.rs +31 -31
- package/rust/core/lexer/handler/operator.rs +46 -46
- package/rust/core/lexer/handler/parenthesis.rs +41 -41
- package/rust/core/lexer/handler/slash.rs +21 -21
- package/rust/core/lexer/handler/string.rs +63 -63
- package/rust/core/lexer/mod.rs +3 -3
- package/rust/core/mod.rs +9 -9
- package/rust/core/parser/driver/block.rs +111 -111
- package/rust/core/parser/driver/cursor.rs +82 -82
- package/rust/core/parser/driver/driver_impl.rs +21 -1
- package/rust/core/parser/driver/mod.rs +6 -6
- package/rust/core/parser/driver/parse_array.rs +120 -120
- package/rust/core/parser/driver/parse_map.rs +247 -223
- package/rust/core/parser/driver/parser.rs +160 -160
- package/rust/core/parser/handler/arrow_call.rs +65 -14
- package/rust/core/parser/handler/identifier/synth.rs +171 -135
- package/rust/core/parser/handler/mod.rs +9 -9
- package/rust/core/parser/handler/pattern.rs +24 -1
- package/rust/core/plugin/loader.rs +137 -137
- package/rust/core/plugin/mod.rs +2 -2
- package/rust/core/plugin/runner/non_wasm.rs +481 -297
- package/rust/core/plugin/runner/wasm32.rs +1 -0
- package/rust/core/preprocessor/loader/inject.rs +313 -278
- package/rust/core/preprocessor/loader/loader_helpers.rs +110 -110
- package/rust/core/preprocessor/loader/mod.rs +235 -235
- package/rust/core/preprocessor/module.rs +55 -55
- package/rust/core/preprocessor/processor/handlers.rs +107 -107
- package/rust/core/preprocessor/resolver/bank.rs +49 -49
- package/rust/core/preprocessor/resolver/call.rs +124 -124
- package/rust/core/preprocessor/resolver/condition.rs +95 -95
- package/rust/core/preprocessor/resolver/driver.rs +324 -324
- package/rust/core/preprocessor/resolver/function.rs +69 -69
- package/rust/core/preprocessor/resolver/group.rs +122 -122
- package/rust/core/preprocessor/resolver/let_.rs +32 -32
- package/rust/core/preprocessor/resolver/loop_.rs +318 -318
- package/rust/core/preprocessor/resolver/mod.rs +16 -16
- package/rust/core/preprocessor/resolver/pattern.rs +95 -83
- package/rust/core/preprocessor/resolver/spawn.rs +99 -99
- package/rust/core/preprocessor/resolver/synth.rs +54 -54
- package/rust/core/preprocessor/resolver/tempo.rs +48 -48
- package/rust/core/preprocessor/resolver/trigger.rs +116 -116
- package/rust/core/preprocessor/resolver/value.rs +176 -176
- package/rust/core/store/global.rs +57 -57
- package/rust/lib.rs +323 -323
- package/rust/macros/Cargo.toml +14 -0
- package/rust/macros/src/lib.rs +52 -0
- package/rust/main.rs +311 -142
- package/rust/types/Cargo.toml +1 -1
- package/rust/types/src/addons.rs +3 -1
- package/rust/types/src/config.rs +1 -3
- package/rust/utils/Cargo.toml +5 -2
- package/rust/utils/src/file.rs +397 -14
- package/rust/utils/src/path.rs +31 -2
- package/rust/utils/src/version.rs +38 -7
- package/rust/web/auth.rs +5 -0
- package/rust/web/forge.rs +5 -0
- package/rust/web/mod.rs +5 -3
- package/typescript/scripts/version/copy-to-binary.ts +82 -0
- package/rust/cli/bank/api.rs +0 -122
- package/rust/cli/bank/commands.rs +0 -306
- package/rust/cli/bank/mod.rs +0 -29
- package/rust/cli/install/bank.rs +0 -72
- package/rust/cli/install/commands.rs +0 -35
- package/rust/cli/install/mod.rs +0 -4
- 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
|
+
}
|
|
@@ -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((
|
|
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
|
¶ms_num,
|
|
133
152
|
Some(¶ms_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 =
|
|
182
|
-
compute_arp_step(
|
|
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;
|