@devaloop/devalang 0.0.1-alpha.12 → 0.0.1-alpha.13
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 +54 -53
- package/README.md +1 -14
- package/docs/CHANGELOG.md +26 -0
- package/docs/TODO.md +1 -1
- package/examples/index.deva +10 -13
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +1 -1
- package/project-version.json +3 -3
- package/rust/cli/build.rs +25 -2
- package/rust/cli/check.rs +26 -3
- package/rust/cli/play.rs +1 -1
- package/rust/core/audio/engine.rs +126 -73
- package/rust/core/audio/interpreter/call.rs +72 -47
- package/rust/core/audio/interpreter/condition.rs +14 -12
- package/rust/core/audio/interpreter/driver.rs +84 -127
- package/rust/core/audio/interpreter/function.rs +21 -0
- package/rust/core/audio/interpreter/load.rs +1 -1
- package/rust/core/audio/interpreter/loop_.rs +24 -18
- package/rust/core/audio/interpreter/mod.rs +2 -1
- package/rust/core/audio/interpreter/sleep.rs +0 -6
- package/rust/core/audio/interpreter/spawn.rs +78 -60
- package/rust/core/audio/interpreter/trigger.rs +157 -70
- package/rust/core/audio/loader/trigger.rs +37 -4
- package/rust/core/audio/player.rs +20 -10
- package/rust/core/audio/renderer.rs +24 -25
- package/rust/core/debugger/mod.rs +2 -0
- package/rust/core/debugger/module.rs +47 -0
- package/rust/core/debugger/store.rs +25 -11
- package/rust/core/error/mod.rs +6 -0
- package/rust/core/lexer/handler/driver.rs +23 -1
- package/rust/core/lexer/handler/identifier.rs +1 -0
- package/rust/core/lexer/handler/mod.rs +1 -0
- package/rust/core/lexer/handler/parenthesis.rs +41 -0
- package/rust/core/lexer/token.rs +3 -0
- package/rust/core/parser/driver.rs +3 -1
- package/rust/core/parser/handler/dot.rs +64 -127
- package/rust/core/parser/handler/identifier/call.rs +69 -22
- package/rust/core/parser/handler/identifier/function.rs +92 -0
- package/rust/core/parser/handler/identifier/let_.rs +13 -19
- package/rust/core/parser/handler/identifier/mod.rs +1 -0
- package/rust/core/parser/handler/identifier/spawn.rs +74 -27
- package/rust/core/parser/statement.rs +16 -4
- package/rust/core/preprocessor/loader.rs +45 -29
- package/rust/core/preprocessor/module.rs +3 -1
- package/rust/core/preprocessor/processor.rs +26 -1
- package/rust/core/preprocessor/resolver/call.rs +61 -84
- package/rust/core/preprocessor/resolver/condition.rs +11 -6
- package/rust/core/preprocessor/resolver/driver.rs +52 -6
- package/rust/core/preprocessor/resolver/function.rs +78 -0
- package/rust/core/preprocessor/resolver/group.rs +43 -13
- package/rust/core/preprocessor/resolver/let_.rs +7 -10
- package/rust/core/preprocessor/resolver/mod.rs +2 -1
- package/rust/core/preprocessor/resolver/spawn.rs +64 -30
- package/rust/core/preprocessor/resolver/trigger.rs +7 -3
- package/rust/core/preprocessor/resolver/value.rs +10 -1
- package/rust/core/shared/value.rs +4 -1
- package/rust/core/store/function.rs +34 -0
- package/rust/core/store/global.rs +9 -10
- package/rust/core/store/mod.rs +2 -1
- package/rust/core/store/variable.rs +6 -0
- package/rust/lib.rs +10 -7
- package/rust/utils/mod.rs +45 -1
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
use std::{ collections::HashMap, fs::File, io::BufReader, path::Path };
|
|
2
2
|
use hound::{ SampleFormat, WavSpec, WavWriter };
|
|
3
3
|
use rodio::{ Decoder, Source };
|
|
4
|
-
|
|
5
4
|
use crate::core::{
|
|
6
5
|
shared::value::Value,
|
|
7
6
|
store::variable::VariableTable,
|
|
8
|
-
utils::path::
|
|
7
|
+
utils::path::normalize_path,
|
|
9
8
|
};
|
|
10
9
|
|
|
11
10
|
const SAMPLE_RATE: u32 = 44100;
|
|
@@ -14,7 +13,6 @@ const CHANNELS: u16 = 2;
|
|
|
14
13
|
#[derive(Debug, Clone, PartialEq)]
|
|
15
14
|
pub struct AudioEngine {
|
|
16
15
|
pub volume: f32,
|
|
17
|
-
pub variables: VariableTable,
|
|
18
16
|
pub buffer: Vec<i16>,
|
|
19
17
|
pub module_name: String,
|
|
20
18
|
}
|
|
@@ -24,7 +22,6 @@ impl AudioEngine {
|
|
|
24
22
|
AudioEngine {
|
|
25
23
|
volume: 1.0,
|
|
26
24
|
buffer: vec![],
|
|
27
|
-
variables: VariableTable::new(),
|
|
28
25
|
module_name,
|
|
29
26
|
}
|
|
30
27
|
}
|
|
@@ -57,12 +54,10 @@ impl AudioEngine {
|
|
|
57
54
|
|
|
58
55
|
if self.buffer.iter().all(|&s| s == 0) {
|
|
59
56
|
self.buffer = other.buffer;
|
|
60
|
-
self.variables.variables.extend(other.variables.variables);
|
|
61
57
|
return;
|
|
62
58
|
}
|
|
63
59
|
|
|
64
60
|
self.mix(&other);
|
|
65
|
-
self.variables.variables.extend(other.variables.variables);
|
|
66
61
|
}
|
|
67
62
|
|
|
68
63
|
pub fn set_duration(&mut self, duration_secs: f32) {
|
|
@@ -73,10 +68,6 @@ impl AudioEngine {
|
|
|
73
68
|
}
|
|
74
69
|
}
|
|
75
70
|
|
|
76
|
-
pub fn set_variables(&mut self, variables: VariableTable) {
|
|
77
|
-
self.variables = variables;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
71
|
pub fn generate_wav_file(&mut self, output_dir: &String) -> Result<(), String> {
|
|
81
72
|
if self.buffer.len() % (CHANNELS as usize) != 0 {
|
|
82
73
|
self.buffer.push(0);
|
|
@@ -168,79 +159,122 @@ impl AudioEngine {
|
|
|
168
159
|
filepath: &str,
|
|
169
160
|
time_secs: f32,
|
|
170
161
|
dur_sec: f32,
|
|
171
|
-
effects: Option<HashMap<String, Value
|
|
162
|
+
effects: Option<HashMap<String, Value>>,
|
|
163
|
+
variable_table: &VariableTable
|
|
172
164
|
) {
|
|
173
165
|
if filepath.is_empty() {
|
|
174
166
|
eprintln!("❌ Empty file path provided for audio sample.");
|
|
175
167
|
return;
|
|
176
168
|
}
|
|
177
169
|
|
|
170
|
+
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
171
|
+
let module_root = Path::new(&self.module_name);
|
|
178
172
|
let mut resolved_path = String::new();
|
|
179
173
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
174
|
+
// Get the variable path from the variable table
|
|
175
|
+
let mut var_path = filepath.to_string();
|
|
176
|
+
if let Some(Value::String(variable_path)) = variable_table.variables.get(filepath) {
|
|
177
|
+
var_path = variable_path.clone();
|
|
178
|
+
} else if let Some(Value::Sample(sample_path)) = variable_table.variables.get(filepath) {
|
|
179
|
+
var_path = sample_path.clone();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// If it's a namespace
|
|
183
|
+
if var_path.contains(".") {
|
|
184
|
+
let parts: Vec<&str> = var_path.trim_start_matches('.').split('.').collect();
|
|
185
|
+
if parts.len() == 2 {
|
|
186
|
+
let bank_name = parts[0];
|
|
187
|
+
let entity_name = parts[1];
|
|
188
|
+
|
|
189
|
+
// Verifies if the bank is declared
|
|
190
|
+
if !variable_table.variables.contains_key(bank_name) {
|
|
191
|
+
eprintln!(
|
|
192
|
+
"❌ Bank '{}' not declared. Please declare it first using : 'bank {}'",
|
|
193
|
+
bank_name,
|
|
194
|
+
bank_name
|
|
195
|
+
);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
187
198
|
|
|
188
|
-
if object_type.contains("bank") {
|
|
189
199
|
resolved_path = root
|
|
190
200
|
.join(".deva")
|
|
191
201
|
.join("bank")
|
|
192
|
-
.join(
|
|
193
|
-
.join(format!("{}.wav",
|
|
194
|
-
.
|
|
195
|
-
.unwrap_or("")
|
|
202
|
+
.join(bank_name)
|
|
203
|
+
.join(format!("{}.wav", entity_name))
|
|
204
|
+
.to_string_lossy()
|
|
196
205
|
.to_string();
|
|
197
206
|
} else {
|
|
198
|
-
eprintln!("❌
|
|
207
|
+
eprintln!("❌ Invalid namespace format: {}", var_path);
|
|
199
208
|
return;
|
|
200
209
|
}
|
|
201
|
-
} else {
|
|
202
|
-
let
|
|
203
|
-
let
|
|
204
|
-
|
|
205
|
-
if
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
210
|
+
} else if var_path.starts_with("devalang://") {
|
|
211
|
+
let path_after_protocol = var_path.replace("devalang://", "");
|
|
212
|
+
let parts: Vec<&str> = path_after_protocol.split('/').collect();
|
|
213
|
+
|
|
214
|
+
if parts.len() < 3 {
|
|
215
|
+
eprintln!(
|
|
216
|
+
"❌ Invalid devalang:// path format. Expected devalang://<type>/<bank>/<entity>"
|
|
217
|
+
);
|
|
209
218
|
return;
|
|
210
219
|
}
|
|
220
|
+
|
|
221
|
+
let obj_type = parts[0];
|
|
222
|
+
let bank_name = parts[1];
|
|
223
|
+
let entity_name = parts[2];
|
|
224
|
+
|
|
225
|
+
resolved_path = root
|
|
226
|
+
.join(".deva")
|
|
227
|
+
.join(obj_type)
|
|
228
|
+
.join(bank_name)
|
|
229
|
+
.join(format!("{}.wav", entity_name))
|
|
230
|
+
.to_string_lossy()
|
|
231
|
+
.to_string();
|
|
232
|
+
} else {
|
|
233
|
+
// Else, resolve as a relative path
|
|
234
|
+
let entry_dir = module_root.parent().unwrap_or(root);
|
|
235
|
+
let absolute_path = root.join(entry_dir).join(&var_path);
|
|
236
|
+
|
|
237
|
+
resolved_path = normalize_path(absolute_path.to_string_lossy().to_string());
|
|
211
238
|
}
|
|
212
239
|
|
|
240
|
+
// Verify if the file exists
|
|
213
241
|
if !Path::new(&resolved_path).exists() {
|
|
214
242
|
eprintln!("❌ Audio file not found at: {}", resolved_path);
|
|
215
243
|
return;
|
|
216
244
|
}
|
|
217
245
|
|
|
218
|
-
let file =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
246
|
+
let file = match File::open(&resolved_path) {
|
|
247
|
+
Ok(f) => BufReader::new(f),
|
|
248
|
+
Err(e) => {
|
|
249
|
+
eprintln!("❌ Failed to open audio file {}: {}", resolved_path, e);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
let decoder = match Decoder::new(file) {
|
|
255
|
+
Ok(d) => d,
|
|
256
|
+
Err(e) => {
|
|
257
|
+
eprintln!("❌ Failed to decode audio file {}: {}", resolved_path, e);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
222
261
|
|
|
223
|
-
// Mono or stereo reading possible here, we will duplicate in L/R
|
|
224
262
|
let max_mono_samples = (dur_sec * (SAMPLE_RATE as f32)) as usize;
|
|
225
263
|
let samples: Vec<i16> = decoder.convert_samples().take(max_mono_samples).collect();
|
|
226
264
|
|
|
227
265
|
if samples.is_empty() {
|
|
228
|
-
eprintln!("No samples
|
|
266
|
+
eprintln!("❌ No samples read from {}", resolved_path);
|
|
229
267
|
return;
|
|
230
268
|
}
|
|
231
269
|
|
|
232
|
-
//
|
|
270
|
+
// Calculate buffer offset and size
|
|
233
271
|
let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
|
|
234
272
|
let required_len = offset + samples.len() * (CHANNELS as usize);
|
|
235
|
-
|
|
236
|
-
required_len
|
|
237
|
-
}
|
|
238
|
-
required_len
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
self.buffer.resize(padded_required_len, 0);
|
|
273
|
+
if self.buffer.len() < required_len {
|
|
274
|
+
self.buffer.resize(required_len, 0);
|
|
275
|
+
}
|
|
242
276
|
|
|
243
|
-
// Apply effects
|
|
277
|
+
// Apply effects and mix
|
|
244
278
|
if let Some(effects_map) = effects {
|
|
245
279
|
self.pad_samples(&samples, time_secs, Some(effects_map));
|
|
246
280
|
} else {
|
|
@@ -263,8 +297,10 @@ impl AudioEngine {
|
|
|
263
297
|
let mut fade_in = 0.0;
|
|
264
298
|
let mut fade_out = 0.0;
|
|
265
299
|
let mut pitch = 1.0;
|
|
266
|
-
let mut drive = 0.0;
|
|
300
|
+
let mut drive = 0.0;
|
|
267
301
|
let mut reverb = 0.0;
|
|
302
|
+
let mut delay = 0.0; // delay time in seconds
|
|
303
|
+
let delay_feedback = 0.35; // default feedback
|
|
268
304
|
|
|
269
305
|
if let Some(map) = &effects_map {
|
|
270
306
|
for (key, val) in map {
|
|
@@ -285,12 +321,14 @@ impl AudioEngine {
|
|
|
285
321
|
pitch = *v;
|
|
286
322
|
}
|
|
287
323
|
("drive", Value::Number(v)) => {
|
|
288
|
-
// Drive effect can be implemented here if needed
|
|
289
324
|
drive = *v;
|
|
290
325
|
}
|
|
291
326
|
("reverb", Value::Number(v)) => {
|
|
292
327
|
reverb = *v;
|
|
293
328
|
}
|
|
329
|
+
("delay", Value::Number(v)) => {
|
|
330
|
+
delay = *v;
|
|
331
|
+
}
|
|
294
332
|
_ => eprintln!("⚠️ Unknown or invalid effect '{}'", key),
|
|
295
333
|
}
|
|
296
334
|
}
|
|
@@ -299,49 +337,64 @@ impl AudioEngine {
|
|
|
299
337
|
let fade_in_samples = (fade_in * (SAMPLE_RATE as f32)) as usize;
|
|
300
338
|
let fade_out_samples = (fade_out * (SAMPLE_RATE as f32)) as usize;
|
|
301
339
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
let mut adjusted = (sample as f32) * gain;
|
|
340
|
+
let delay_samples = if delay > 0.0 { (delay * (SAMPLE_RATE as f32)) as usize } else { 0 };
|
|
341
|
+
let mut delay_buffer: Vec<f32> = vec![0.0; total_samples + delay_samples];
|
|
305
342
|
|
|
306
|
-
|
|
343
|
+
for i in 0..total_samples {
|
|
344
|
+
// PITCH FIRST
|
|
345
|
+
let pitch_index = if pitch != 1.0 { ((i as f32) / pitch) as usize } else { i };
|
|
346
|
+
|
|
347
|
+
let mut adjusted = if pitch_index < total_samples {
|
|
348
|
+
samples[pitch_index] as f32
|
|
349
|
+
} else {
|
|
350
|
+
0.0
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// GAIN
|
|
354
|
+
adjusted *= gain;
|
|
355
|
+
|
|
356
|
+
// FADE IN/OUT
|
|
307
357
|
if fade_in_samples > 0 && i < fade_in_samples {
|
|
308
358
|
adjusted *= (i as f32) / (fade_in_samples as f32);
|
|
309
359
|
}
|
|
310
|
-
|
|
311
|
-
// Fade out
|
|
312
360
|
if fade_out_samples > 0 && i >= total_samples.saturating_sub(fade_out_samples) {
|
|
313
361
|
adjusted *= ((total_samples - i) as f32) / (fade_out_samples as f32);
|
|
314
362
|
}
|
|
315
363
|
|
|
316
|
-
//
|
|
317
|
-
if
|
|
318
|
-
let
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
adjusted = 0.0; // Out of bounds, set to zero
|
|
323
|
-
}
|
|
364
|
+
// DRIVE (soft)
|
|
365
|
+
if drive > 0.0 {
|
|
366
|
+
let normalized = adjusted / (i16::MAX as f32);
|
|
367
|
+
let pre_gain = (10f32).powf(drive / 20.0); // dB mapping
|
|
368
|
+
let driven = (normalized * pre_gain).tanh();
|
|
369
|
+
adjusted = driven * (i16::MAX as f32);
|
|
324
370
|
}
|
|
325
371
|
|
|
326
|
-
//
|
|
327
|
-
if
|
|
328
|
-
|
|
372
|
+
// DELAY
|
|
373
|
+
if delay_samples > 0 && i >= delay_samples {
|
|
374
|
+
let echo = delay_buffer[i - delay_samples] * delay_feedback;
|
|
375
|
+
adjusted += echo;
|
|
376
|
+
}
|
|
377
|
+
if delay_samples > 0 {
|
|
378
|
+
delay_buffer[i] = adjusted;
|
|
329
379
|
}
|
|
330
380
|
|
|
331
|
-
//
|
|
381
|
+
// REVERB
|
|
332
382
|
if reverb > 0.0 {
|
|
333
|
-
let reverb_delay = (
|
|
383
|
+
let reverb_delay = (0.03 * (SAMPLE_RATE as f32)) as usize;
|
|
334
384
|
if i >= reverb_delay {
|
|
335
|
-
adjusted += self.buffer[offset + i - reverb_delay] as f32 *
|
|
385
|
+
adjusted += (self.buffer[offset + i - reverb_delay] as f32) * reverb;
|
|
336
386
|
}
|
|
337
387
|
}
|
|
338
388
|
|
|
339
|
-
//
|
|
389
|
+
// CLAMP
|
|
340
390
|
let adjusted_sample = adjusted.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
|
|
341
391
|
|
|
342
|
-
//
|
|
343
|
-
let
|
|
344
|
-
let
|
|
392
|
+
// PAN
|
|
393
|
+
let left_gain = 1.0 - pan.max(0.0); // Pan > 0 => reduce left
|
|
394
|
+
let right_gain = 1.0 + pan.min(0.0); // Pan < 0 => reduce right
|
|
395
|
+
|
|
396
|
+
let left = ((adjusted_sample as f32) * left_gain) as i16;
|
|
397
|
+
let right = ((adjusted_sample as f32) * right_gain) as i16;
|
|
345
398
|
|
|
346
399
|
let left_pos = offset + i * 2;
|
|
347
400
|
let right_pos = left_pos + 1;
|
|
@@ -1,70 +1,95 @@
|
|
|
1
1
|
use crate::core::{
|
|
2
|
-
audio::{
|
|
3
|
-
parser::statement::{
|
|
4
|
-
shared::
|
|
5
|
-
store::variable::VariableTable,
|
|
2
|
+
audio::{engine::AudioEngine, interpreter::driver::execute_audio_block},
|
|
3
|
+
parser::statement::{Statement, StatementKind},
|
|
4
|
+
shared::value::Value,
|
|
5
|
+
store::{function::FunctionTable, global::GlobalStore, variable::VariableTable},
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
pub fn interprete_call_statement(
|
|
9
9
|
stmt: &Statement,
|
|
10
|
-
audio_engine: AudioEngine,
|
|
11
|
-
variable_table: VariableTable,
|
|
10
|
+
audio_engine: &mut AudioEngine,
|
|
11
|
+
variable_table: &VariableTable,
|
|
12
|
+
functions: &FunctionTable,
|
|
13
|
+
global_store: &GlobalStore,
|
|
12
14
|
base_bpm: f32,
|
|
13
15
|
base_duration: f32,
|
|
14
16
|
max_end_time: f32,
|
|
15
17
|
cursor_time: f32,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
{
|
|
28
|
-
if let Some(Value::Block(block)) = group_stmt.value.get("body") {
|
|
29
|
-
let (eng, _, end_time) = execute_audio_block(
|
|
30
|
-
audio_engine,
|
|
31
|
-
variable_table,
|
|
32
|
-
block.clone(),
|
|
33
|
-
base_bpm,
|
|
34
|
-
base_duration,
|
|
35
|
-
max_end_time,
|
|
36
|
-
cursor_time
|
|
18
|
+
) -> (f32, f32) {
|
|
19
|
+
match &stmt.kind {
|
|
20
|
+
StatementKind::Call { name, args } => {
|
|
21
|
+
// ✅ 1. Cas : fonction classique
|
|
22
|
+
if let Some(func) = functions.functions.get(name) {
|
|
23
|
+
if func.parameters.len() != args.len() {
|
|
24
|
+
eprintln!(
|
|
25
|
+
"❌ Function '{}' expects {} args, got {}",
|
|
26
|
+
name,
|
|
27
|
+
func.parameters.len(),
|
|
28
|
+
args.len()
|
|
37
29
|
);
|
|
38
|
-
return (
|
|
39
|
-
}
|
|
40
|
-
|
|
30
|
+
return (max_end_time, cursor_time);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let mut local_vars = VariableTable::with_parent(variable_table.clone());
|
|
34
|
+
for (param, arg) in func.parameters.iter().zip(args) {
|
|
35
|
+
local_vars.set(param.clone(), arg.clone());
|
|
41
36
|
}
|
|
42
|
-
} else {
|
|
43
|
-
eprintln!("❌ Group '{}' not found in statements", identifier);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
if let Some(Value::Block(block)) = map.get("body") {
|
|
49
|
-
let (eng, _, end_time) = execute_audio_block(
|
|
38
|
+
return execute_audio_block(
|
|
50
39
|
audio_engine,
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
global_store,
|
|
41
|
+
local_vars,
|
|
42
|
+
functions.clone(),
|
|
43
|
+
func.body.clone(),
|
|
53
44
|
base_bpm,
|
|
54
45
|
base_duration,
|
|
55
46
|
max_end_time,
|
|
56
|
-
cursor_time
|
|
47
|
+
cursor_time,
|
|
57
48
|
);
|
|
58
|
-
return (eng, max_end_time.max(end_time), end_time, cursor_time);
|
|
59
|
-
} else {
|
|
60
|
-
eprintln!("❌ Call map has no 'body' block");
|
|
61
49
|
}
|
|
62
|
-
}
|
|
63
50
|
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
// ✅ 2. Cas : group dans le scope local OU global
|
|
52
|
+
if let Some(group_stmt) = find_group(name, variable_table, global_store) {
|
|
53
|
+
if let Value::Map(map) = &group_stmt.value {
|
|
54
|
+
if let Some(Value::Block(body)) = map.get("body") {
|
|
55
|
+
return execute_audio_block(
|
|
56
|
+
audio_engine,
|
|
57
|
+
global_store,
|
|
58
|
+
variable_table.clone(),
|
|
59
|
+
functions.clone(),
|
|
60
|
+
body.clone(),
|
|
61
|
+
base_bpm,
|
|
62
|
+
base_duration,
|
|
63
|
+
max_end_time,
|
|
64
|
+
cursor_time,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
eprintln!("❌ Function or group '{}' not found", name);
|
|
66
71
|
}
|
|
72
|
+
|
|
73
|
+
_ => eprintln!("❌ interprete_call_statement expected Call, got {:?}", stmt.kind),
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
(
|
|
76
|
+
(max_end_time, cursor_time)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fn find_group<'a>(
|
|
80
|
+
name: &str,
|
|
81
|
+
variable_table: &'a VariableTable,
|
|
82
|
+
global_store: &'a GlobalStore,
|
|
83
|
+
) -> Option<&'a Statement> {
|
|
84
|
+
if let Some(Value::Statement(stmt_box)) = variable_table.get(name) {
|
|
85
|
+
if let StatementKind::Group = stmt_box.kind {
|
|
86
|
+
return Some(stmt_box);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if let Some(Value::Statement(stmt_box)) = global_store.variables.variables.get(name) {
|
|
90
|
+
if let StatementKind::Group = stmt_box.kind {
|
|
91
|
+
return Some(stmt_box);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
None
|
|
70
95
|
}
|
|
@@ -6,20 +6,20 @@ use crate::core::{
|
|
|
6
6
|
},
|
|
7
7
|
parser::statement::Statement,
|
|
8
8
|
shared::value::Value,
|
|
9
|
-
store::variable::VariableTable,
|
|
9
|
+
store::{ function::FunctionTable, global::GlobalStore, variable::VariableTable },
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
pub fn interprete_condition_statement(
|
|
13
13
|
stmt: &Statement,
|
|
14
|
-
audio_engine: AudioEngine,
|
|
15
|
-
|
|
14
|
+
audio_engine: &mut AudioEngine,
|
|
15
|
+
global_store: &GlobalStore,
|
|
16
|
+
variable_table: &VariableTable,
|
|
17
|
+
functions_table: &FunctionTable,
|
|
16
18
|
base_bpm: f32,
|
|
17
19
|
base_duration: f32,
|
|
18
20
|
max_end_time: f32,
|
|
19
21
|
cursor_time: f32
|
|
20
|
-
) -> (
|
|
21
|
-
let mut engine = audio_engine.clone();
|
|
22
|
-
let mut vars = variable_table.clone();
|
|
22
|
+
) -> (f32, f32) {
|
|
23
23
|
let mut cur_time = cursor_time;
|
|
24
24
|
let mut max_time = max_end_time;
|
|
25
25
|
|
|
@@ -32,23 +32,25 @@ pub fn interprete_condition_statement(
|
|
|
32
32
|
|
|
33
33
|
let should_execute = match map.get("condition") {
|
|
34
34
|
Some(Value::Boolean(b)) => *b,
|
|
35
|
-
Some(Value::String(expr)) => evaluate_condition_string(expr, &
|
|
35
|
+
Some(Value::String(expr)) => evaluate_condition_string(expr, &variable_table.clone()),
|
|
36
36
|
Some(_) => false,
|
|
37
37
|
None => true,
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
if should_execute {
|
|
41
41
|
if let Some(Value::Block(block)) = map.get("body") {
|
|
42
|
-
let (
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
let (new_max, cursor_time) = execute_audio_block(
|
|
43
|
+
audio_engine,
|
|
44
|
+
global_store,
|
|
45
|
+
variable_table.clone(),
|
|
46
|
+
functions_table.clone(),
|
|
45
47
|
block.clone(),
|
|
46
48
|
base_bpm,
|
|
47
49
|
base_duration,
|
|
48
50
|
max_time,
|
|
49
51
|
cur_time
|
|
50
52
|
);
|
|
51
|
-
return (
|
|
53
|
+
return (new_max, cursor_time);
|
|
52
54
|
} else {
|
|
53
55
|
break;
|
|
54
56
|
}
|
|
@@ -65,5 +67,5 @@ pub fn interprete_condition_statement(
|
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
(
|
|
70
|
+
(max_end_time, cursor_time)
|
|
69
71
|
}
|