@devaloop/devalang 0.0.1-alpha.10 → 0.0.1-alpha.12
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/.devalang +6 -1
- package/Cargo.toml +6 -2
- package/README.md +59 -142
- package/docs/CHANGELOG.md +60 -1
- package/docs/ROADMAP.md +1 -1
- package/docs/TODO.md +1 -1
- package/examples/bank.deva +9 -0
- package/examples/duration.deva +9 -0
- package/examples/index.deva +6 -6
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +2 -1
- package/project-version.json +3 -3
- package/rust/cli/bank.rs +455 -0
- package/rust/cli/build.rs +1 -1
- package/rust/cli/check.rs +1 -1
- package/rust/cli/driver.rs +280 -0
- package/rust/cli/install.rs +17 -0
- package/rust/cli/mod.rs +5 -200
- package/rust/cli/play.rs +1 -1
- package/rust/cli/update.rs +4 -0
- package/rust/common/cdn.rs +11 -0
- package/rust/common/mod.rs +1 -0
- package/rust/config/driver.rs +76 -0
- package/rust/config/loader.rs +98 -1
- package/rust/config/mod.rs +1 -15
- package/rust/core/audio/engine.rs +151 -10
- package/rust/core/audio/interpreter/arrow_call.rs +17 -4
- package/rust/core/audio/interpreter/trigger.rs +56 -2
- package/rust/core/audio/loader/trigger.rs +12 -0
- package/rust/core/lexer/handler/driver.rs +12 -1
- package/rust/core/lexer/handler/mod.rs +1 -0
- package/rust/core/lexer/handler/slash.rs +21 -0
- package/rust/core/lexer/token.rs +1 -0
- package/rust/core/parser/driver.rs +36 -2
- package/rust/core/parser/handler/arrow_call.rs +29 -4
- package/rust/core/parser/handler/dot.rs +102 -37
- package/rust/core/preprocessor/loader.rs +93 -14
- package/rust/core/preprocessor/resolver/driver.rs +5 -0
- package/rust/core/shared/bank.rs +21 -0
- package/rust/core/shared/duration.rs +1 -0
- package/rust/core/shared/mod.rs +2 -1
- package/rust/core/shared/value.rs +1 -0
- package/rust/installer/bank.rs +55 -0
- package/rust/installer/mod.rs +2 -0
- package/rust/installer/utils.rs +56 -0
- package/rust/main.rs +62 -5
- package/docs/COMMANDS.md +0 -85
- package/docs/CONFIG.md +0 -30
- package/docs/SYNTAX.md +0 -230
package/rust/config/loader.rs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
use std::{ fs, path::Path };
|
|
2
|
-
use
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
use crate::config::driver::{ BankEntry, Config };
|
|
3
4
|
|
|
4
5
|
pub fn load_config(path: Option<&Path>) -> Option<Config> {
|
|
5
6
|
let config_path = path.unwrap_or_else(|| Path::new(".devalang"));
|
|
@@ -11,3 +12,99 @@ pub fn load_config(path: Option<&Path>) -> Option<Config> {
|
|
|
11
12
|
None
|
|
12
13
|
}
|
|
13
14
|
}
|
|
15
|
+
|
|
16
|
+
pub fn update_bank_version_in_config(config: &mut Config, dependency: &str, new_version: &str) {
|
|
17
|
+
// Si le vecteur banks n'existe pas, on ne fait rien
|
|
18
|
+
if config.banks.is_none() {
|
|
19
|
+
println!("No banks configured.");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let banks = config.banks.as_mut().unwrap();
|
|
24
|
+
|
|
25
|
+
if let Some(bank) = banks.iter_mut().find(|b| b.path.contains(dependency)) {
|
|
26
|
+
bank.version = Some(new_version.to_string());
|
|
27
|
+
|
|
28
|
+
if let Err(e) = config.write(config) {
|
|
29
|
+
eprintln!("❌ Failed to write config: {}", e);
|
|
30
|
+
} else {
|
|
31
|
+
println!("✅ Bank '{}' updated to version '{}'", dependency, new_version);
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
println!("Bank '{}' not found in config", dependency);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub fn remove_bank_from_config(config: &mut Config, dependency: &str) {
|
|
39
|
+
if config.banks.is_none() {
|
|
40
|
+
println!("No banks configured.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let banks = config.banks.as_mut().unwrap();
|
|
45
|
+
|
|
46
|
+
if let Some(index) = banks.iter().position(|b| b.path.contains(dependency)) {
|
|
47
|
+
banks.remove(index);
|
|
48
|
+
|
|
49
|
+
if let Err(e) = config.write(config) {
|
|
50
|
+
eprintln!("❌ Failed to write config: {}", e);
|
|
51
|
+
} else {
|
|
52
|
+
println!("✅ Bank '{}' removed from config", dependency);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
println!("Bank '{}' not found in config", dependency);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pub fn add_bank_to_config(config: &mut Config, real_path: &Path, dependency: &str) {
|
|
60
|
+
if config.banks.is_none() {
|
|
61
|
+
config.banks = Some(Vec::new());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let banks = config.banks.as_mut().unwrap();
|
|
65
|
+
|
|
66
|
+
let exists = banks.iter().any(|b| b.path == dependency);
|
|
67
|
+
if exists {
|
|
68
|
+
println!("Bank '{}' already in config", dependency);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let metadata_path = Path::new(real_path).join("bank.toml");
|
|
73
|
+
|
|
74
|
+
if !metadata_path.exists() {
|
|
75
|
+
eprintln!("❌ Bank metadata file '{}' does not exist", metadata_path.display());
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let metadata_content = fs
|
|
80
|
+
::read_to_string(&metadata_path)
|
|
81
|
+
.expect("Failed to read bank metadata file");
|
|
82
|
+
|
|
83
|
+
let metadata: HashMap<String, String> = toml
|
|
84
|
+
::from_str(&metadata_content)
|
|
85
|
+
.expect("Failed to parse bank metadata file");
|
|
86
|
+
|
|
87
|
+
let bank_to_insert = BankEntry {
|
|
88
|
+
path: dependency.to_string(),
|
|
89
|
+
version: Some(
|
|
90
|
+
metadata
|
|
91
|
+
.get("version")
|
|
92
|
+
.cloned()
|
|
93
|
+
.unwrap_or_else(|| "0.0.1".to_string())
|
|
94
|
+
),
|
|
95
|
+
author: Some(
|
|
96
|
+
metadata
|
|
97
|
+
.get("author")
|
|
98
|
+
.cloned()
|
|
99
|
+
.unwrap_or_else(|| "unknown".to_string())
|
|
100
|
+
),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
banks.push(bank_to_insert);
|
|
104
|
+
|
|
105
|
+
if let Err(e) = config.write(config) {
|
|
106
|
+
eprintln!("❌ Failed to write config: {}", e);
|
|
107
|
+
} else {
|
|
108
|
+
println!("✅ Bank '{}' added to config", dependency);
|
|
109
|
+
}
|
|
110
|
+
}
|
package/rust/config/mod.rs
CHANGED
|
@@ -1,16 +1,2 @@
|
|
|
1
1
|
pub mod loader;
|
|
2
|
-
|
|
3
|
-
use serde::Deserialize;
|
|
4
|
-
|
|
5
|
-
#[derive(Debug, Deserialize, Clone)]
|
|
6
|
-
pub struct Config {
|
|
7
|
-
pub defaults: ConfigDefaults,
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
#[derive(Debug, Deserialize, Clone)]
|
|
11
|
-
pub struct ConfigDefaults {
|
|
12
|
-
pub entry: Option<String>,
|
|
13
|
-
pub output: Option<String>,
|
|
14
|
-
pub watch: Option<bool>,
|
|
15
|
-
pub repeat: Option<bool>,
|
|
16
|
-
}
|
|
2
|
+
pub mod driver;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
use std::{ collections::HashMap, fs::File, io::BufReader };
|
|
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
4
|
|
|
5
5
|
use crate::core::{
|
|
6
|
+
shared::value::Value,
|
|
6
7
|
store::variable::VariableTable,
|
|
7
8
|
utils::path::{ normalize_path, resolve_relative_path },
|
|
8
9
|
};
|
|
@@ -167,11 +168,56 @@ impl AudioEngine {
|
|
|
167
168
|
filepath: &str,
|
|
168
169
|
time_secs: f32,
|
|
169
170
|
dur_sec: f32,
|
|
170
|
-
effects: Option<HashMap<String,
|
|
171
|
+
effects: Option<HashMap<String, Value>>
|
|
171
172
|
) {
|
|
172
|
-
|
|
173
|
+
if filepath.is_empty() {
|
|
174
|
+
eprintln!("❌ Empty file path provided for audio sample.");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let mut resolved_path = String::new();
|
|
179
|
+
|
|
180
|
+
if filepath.starts_with("devalang://") {
|
|
181
|
+
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
182
|
+
let parts = filepath.split("devalang://").collect::<Vec<&str>>();
|
|
183
|
+
let object_parts = parts.get(1).unwrap_or(&"").split("/").collect::<Vec<&str>>();
|
|
184
|
+
let object_type = object_parts.get(0).unwrap_or(&"").to_lowercase();
|
|
185
|
+
let object_dir = object_parts.get(1).unwrap_or(&"").to_string();
|
|
186
|
+
let object_name = object_parts.get(2).unwrap_or(&"").to_string();
|
|
187
|
+
|
|
188
|
+
if object_type.contains("bank") {
|
|
189
|
+
resolved_path = root
|
|
190
|
+
.join(".deva")
|
|
191
|
+
.join("bank")
|
|
192
|
+
.join(object_dir)
|
|
193
|
+
.join(format!("{}.wav", object_name))
|
|
194
|
+
.to_str()
|
|
195
|
+
.unwrap_or("")
|
|
196
|
+
.to_string();
|
|
197
|
+
} else {
|
|
198
|
+
eprintln!("❌ Unsupported devalang:// object type: {}", object_type);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
let module_path = &self.module_name;
|
|
203
|
+
let root = Path::new(module_path).parent();
|
|
204
|
+
|
|
205
|
+
if let Some(root_path) = root {
|
|
206
|
+
resolved_path = root_path.join(filepath).to_str().unwrap_or("").to_string();
|
|
207
|
+
} else {
|
|
208
|
+
eprintln!("❌ Could not resolve root path for module: {}", module_path);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if !Path::new(&resolved_path).exists() {
|
|
214
|
+
eprintln!("❌ Audio file not found at: {}", resolved_path);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
173
217
|
|
|
174
|
-
let file = BufReader::new(
|
|
218
|
+
let file = BufReader::new(
|
|
219
|
+
File::open(&resolved_path).expect(&format!("Failed to open audio file {}", filepath))
|
|
220
|
+
);
|
|
175
221
|
let decoder = Decoder::new(file).expect("Failed to decode audio file");
|
|
176
222
|
|
|
177
223
|
// Mono or stereo reading possible here, we will duplicate in L/R
|
|
@@ -183,7 +229,7 @@ impl AudioEngine {
|
|
|
183
229
|
return;
|
|
184
230
|
}
|
|
185
231
|
|
|
186
|
-
//
|
|
232
|
+
// Pad the buffer to ensure it can accommodate the new samples
|
|
187
233
|
let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
|
|
188
234
|
let required_len = offset + samples.len() * (CHANNELS as usize);
|
|
189
235
|
let padded_required_len = if required_len % 2 == 1 {
|
|
@@ -193,21 +239,116 @@ impl AudioEngine {
|
|
|
193
239
|
};
|
|
194
240
|
|
|
195
241
|
self.buffer.resize(padded_required_len, 0);
|
|
196
|
-
|
|
242
|
+
|
|
243
|
+
// Apply effects
|
|
244
|
+
if let Some(effects_map) = effects {
|
|
245
|
+
self.pad_samples(&samples, time_secs, Some(effects_map));
|
|
246
|
+
} else {
|
|
247
|
+
self.pad_samples(&samples, time_secs, None);
|
|
248
|
+
}
|
|
197
249
|
}
|
|
198
250
|
|
|
199
|
-
fn pad_samples(
|
|
251
|
+
fn pad_samples(
|
|
252
|
+
&mut self,
|
|
253
|
+
samples: &[i16],
|
|
254
|
+
time_secs: f32,
|
|
255
|
+
effects_map: Option<HashMap<String, Value>>
|
|
256
|
+
) {
|
|
200
257
|
let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
|
|
258
|
+
let total_samples = samples.len();
|
|
259
|
+
|
|
260
|
+
// Default values
|
|
261
|
+
let mut gain = 1.0;
|
|
262
|
+
let mut pan = 0.0;
|
|
263
|
+
let mut fade_in = 0.0;
|
|
264
|
+
let mut fade_out = 0.0;
|
|
265
|
+
let mut pitch = 1.0;
|
|
266
|
+
let mut drive = 0.0;
|
|
267
|
+
let mut reverb = 0.0;
|
|
268
|
+
|
|
269
|
+
if let Some(map) = &effects_map {
|
|
270
|
+
for (key, val) in map {
|
|
271
|
+
match (key.as_str(), val) {
|
|
272
|
+
("gain", Value::Number(v)) => {
|
|
273
|
+
gain = *v;
|
|
274
|
+
}
|
|
275
|
+
("pan", Value::Number(v)) => {
|
|
276
|
+
pan = *v;
|
|
277
|
+
}
|
|
278
|
+
("fadeIn", Value::Number(v)) => {
|
|
279
|
+
fade_in = *v;
|
|
280
|
+
}
|
|
281
|
+
("fadeOut", Value::Number(v)) => {
|
|
282
|
+
fade_out = *v;
|
|
283
|
+
}
|
|
284
|
+
("pitch", Value::Number(v)) => {
|
|
285
|
+
pitch = *v;
|
|
286
|
+
}
|
|
287
|
+
("drive", Value::Number(v)) => {
|
|
288
|
+
// Drive effect can be implemented here if needed
|
|
289
|
+
drive = *v;
|
|
290
|
+
}
|
|
291
|
+
("reverb", Value::Number(v)) => {
|
|
292
|
+
reverb = *v;
|
|
293
|
+
}
|
|
294
|
+
_ => eprintln!("⚠️ Unknown or invalid effect '{}'", key),
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let fade_in_samples = (fade_in * (SAMPLE_RATE as f32)) as usize;
|
|
300
|
+
let fade_out_samples = (fade_out * (SAMPLE_RATE as f32)) as usize;
|
|
201
301
|
|
|
202
302
|
for (i, &sample) in samples.iter().enumerate() {
|
|
203
|
-
|
|
303
|
+
// Gain
|
|
304
|
+
let mut adjusted = (sample as f32) * gain;
|
|
305
|
+
|
|
306
|
+
// Fade in
|
|
307
|
+
if fade_in_samples > 0 && i < fade_in_samples {
|
|
308
|
+
adjusted *= (i as f32) / (fade_in_samples as f32);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Fade out
|
|
312
|
+
if fade_out_samples > 0 && i >= total_samples.saturating_sub(fade_out_samples) {
|
|
313
|
+
adjusted *= ((total_samples - i) as f32) / (fade_out_samples as f32);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Pitch adjustment
|
|
317
|
+
if pitch != 1.0 {
|
|
318
|
+
let pitch_adjusted_index = ((i as f32) / pitch) as usize;
|
|
319
|
+
if pitch_adjusted_index < total_samples {
|
|
320
|
+
adjusted = (samples[pitch_adjusted_index] as f32) * gain;
|
|
321
|
+
} else {
|
|
322
|
+
adjusted = 0.0; // Out of bounds, set to zero
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Drive effect
|
|
327
|
+
if drive > 0.0 {
|
|
328
|
+
adjusted = adjusted.tanh() * (1.0 + drive);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Reverb effect
|
|
332
|
+
if reverb > 0.0 {
|
|
333
|
+
let reverb_delay = (reverb * (SAMPLE_RATE as f32)) as usize;
|
|
334
|
+
if i >= reverb_delay {
|
|
335
|
+
adjusted += self.buffer[offset + i - reverb_delay] as f32 * 0.5; // Simple feedback
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Clamp
|
|
340
|
+
let adjusted_sample = adjusted.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
|
|
341
|
+
|
|
342
|
+
// Pan (L/R split)
|
|
343
|
+
let left = ((adjusted_sample as f32) * (1.0 - pan.clamp(0.0, 1.0))) as i16;
|
|
344
|
+
let right = ((adjusted_sample as f32) * (1.0 + pan.clamp(-1.0, 0.0)).abs()) as i16;
|
|
204
345
|
|
|
205
346
|
let left_pos = offset + i * 2;
|
|
206
347
|
let right_pos = left_pos + 1;
|
|
207
348
|
|
|
208
349
|
if right_pos < self.buffer.len() {
|
|
209
|
-
self.buffer[left_pos] = self.buffer[left_pos].saturating_add(
|
|
210
|
-
self.buffer[right_pos] = self.buffer[right_pos].saturating_add(
|
|
350
|
+
self.buffer[left_pos] = self.buffer[left_pos].saturating_add(left);
|
|
351
|
+
self.buffer[right_pos] = self.buffer[right_pos].saturating_add(right);
|
|
211
352
|
}
|
|
212
353
|
}
|
|
213
354
|
}
|
|
@@ -53,8 +53,8 @@ pub fn interprete_call_arrow_statement(
|
|
|
53
53
|
return (*max_end_time, cursor_copy);
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
let freq = extract_f32(params, "freq").unwrap_or(440.0);
|
|
57
|
-
let amp = extract_f32(params, "amp").unwrap_or(1.0);
|
|
56
|
+
let freq = extract_f32(params, "freq", base_bpm).unwrap_or(440.0);
|
|
57
|
+
let amp = extract_f32(params, "amp", base_bpm).unwrap_or(1.0);
|
|
58
58
|
|
|
59
59
|
if method == "note" {
|
|
60
60
|
let Some(Value::Identifier(note_name)) = args.get(0) else {
|
|
@@ -69,7 +69,9 @@ pub fn interprete_call_arrow_statement(
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
let duration_ms = extract_f32(&final_note_params, "duration").unwrap_or(
|
|
72
|
+
let duration_ms = extract_f32(&final_note_params, "duration", base_bpm).unwrap_or(
|
|
73
|
+
base_duration
|
|
74
|
+
);
|
|
73
75
|
let duration_secs = duration_ms / 1000.0;
|
|
74
76
|
|
|
75
77
|
let final_freq = note_to_freq(note_name);
|
|
@@ -101,10 +103,21 @@ pub fn interprete_call_arrow_statement(
|
|
|
101
103
|
(*max_end_time, cursor_copy)
|
|
102
104
|
}
|
|
103
105
|
|
|
104
|
-
fn extract_f32(map: &HashMap<String, Value>, key: &str) -> Option<f32> {
|
|
106
|
+
fn extract_f32(map: &HashMap<String, Value>, key: &str, base_bpm: f32) -> Option<f32> {
|
|
105
107
|
map.get(key).and_then(|v| {
|
|
106
108
|
match v {
|
|
107
109
|
Value::Number(n) => Some(*n),
|
|
110
|
+
Value::Beat(beat_str) => {
|
|
111
|
+
let parts: Vec<&str> = beat_str.split('/').collect();
|
|
112
|
+
if parts.len() == 2 {
|
|
113
|
+
let numerator = parts[0].parse::<f32>().ok()?;
|
|
114
|
+
let denominator = parts[1].parse::<f32>().ok()?;
|
|
115
|
+
|
|
116
|
+
Some((numerator / denominator) * ((60.0 / base_bpm) * 1000.0))
|
|
117
|
+
} else {
|
|
118
|
+
None
|
|
119
|
+
}
|
|
120
|
+
}
|
|
108
121
|
_ => None,
|
|
109
122
|
}
|
|
110
123
|
})
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
|
|
1
3
|
use crate::core::{
|
|
2
4
|
audio::{ engine::AudioEngine, loader::trigger::load_trigger },
|
|
3
5
|
parser::statement::{ Statement, StatementKind },
|
|
@@ -14,7 +16,7 @@ pub fn interprete_trigger_statement(
|
|
|
14
16
|
max_end_time: f32
|
|
15
17
|
) -> Option<(f32, f32, AudioEngine)> {
|
|
16
18
|
if let StatementKind::Trigger { entity, duration } = &stmt.kind {
|
|
17
|
-
if let Some(trigger_val) =
|
|
19
|
+
if let Some(trigger_val) = resolve_namespaced_variable(entity, variable_table) {
|
|
18
20
|
let duration_secs = match duration {
|
|
19
21
|
Duration::Number(n) => *n,
|
|
20
22
|
|
|
@@ -41,6 +43,20 @@ pub fn interprete_trigger_statement(
|
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
Duration::Beat(beat_str) => {
|
|
47
|
+
// Assuming beat_str is in the format "numerator/denominator"
|
|
48
|
+
let parts: Vec<&str> = beat_str.split('/').collect();
|
|
49
|
+
|
|
50
|
+
if parts.len() != 2 {
|
|
51
|
+
eprintln!("❌ Invalid beat duration format: {}", beat_str);
|
|
52
|
+
return None;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let numerator: f32 = parts[0].parse().unwrap_or(1.0);
|
|
56
|
+
let denominator: f32 = parts[1].parse().unwrap_or(1.0);
|
|
57
|
+
numerator / denominator
|
|
58
|
+
}
|
|
59
|
+
|
|
44
60
|
Duration::Auto => 1.0,
|
|
45
61
|
};
|
|
46
62
|
|
|
@@ -53,8 +69,13 @@ pub fn interprete_trigger_statement(
|
|
|
53
69
|
variable_table.clone()
|
|
54
70
|
);
|
|
55
71
|
|
|
72
|
+
if let Some(effects) = extract_effects(stmt.value.clone()) {
|
|
73
|
+
audio_engine.insert_sample(&src, cursor_time, duration_final, Some(effects));
|
|
74
|
+
} else {
|
|
75
|
+
audio_engine.insert_sample(&src, cursor_time, duration_final, None);
|
|
76
|
+
}
|
|
77
|
+
|
|
56
78
|
let mut updated_engine = audio_engine.clone();
|
|
57
|
-
updated_engine.insert_sample(&src, cursor_time, duration_final, None);
|
|
58
79
|
|
|
59
80
|
let new_cursor_time = cursor_time + duration_final;
|
|
60
81
|
let new_max_end_time = new_cursor_time.max(max_end_time);
|
|
@@ -67,3 +88,36 @@ pub fn interprete_trigger_statement(
|
|
|
67
88
|
|
|
68
89
|
None
|
|
69
90
|
}
|
|
91
|
+
|
|
92
|
+
fn resolve_namespaced_variable<'a>(path: &str, variables: &'a VariableTable) -> Option<&'a Value> {
|
|
93
|
+
let mut current: Option<&Value> = None;
|
|
94
|
+
|
|
95
|
+
for (i, part) in path.split('.').enumerate() {
|
|
96
|
+
if i == 0 {
|
|
97
|
+
current = variables.get(part);
|
|
98
|
+
} else {
|
|
99
|
+
current = match current {
|
|
100
|
+
Some(Value::Map(map)) => map.get(part),
|
|
101
|
+
_ => {
|
|
102
|
+
return None;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
current
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn extract_effects(value: Value) -> Option<HashMap<String, Value>> {
|
|
112
|
+
if let Value::Map(map) = value.clone() {
|
|
113
|
+
let mut effects = HashMap::new();
|
|
114
|
+
|
|
115
|
+
for (key, val) in map {
|
|
116
|
+
effects.insert(key.clone(), val);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
Some(effects)
|
|
120
|
+
} else {
|
|
121
|
+
None
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -43,6 +43,18 @@ pub fn load_trigger(
|
|
|
43
43
|
duration_as_secs = base_duration;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
Duration::Beat(beat_str) => {
|
|
47
|
+
let parts: Vec<&str> = beat_str.split('/').collect();
|
|
48
|
+
|
|
49
|
+
if parts.len() == 2 {
|
|
50
|
+
let numerator: f32 = parts[0].parse().unwrap_or(1.0);
|
|
51
|
+
let denominator: f32 = parts[1].parse().unwrap_or(1.0);
|
|
52
|
+
duration_as_secs = numerator / denominator * base_duration;
|
|
53
|
+
} else {
|
|
54
|
+
eprintln!("❌ Invalid beat duration format: {}", beat_str);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
46
58
|
_ => {
|
|
47
59
|
eprintln!("❌ Invalid duration type. Expected an identifier.");
|
|
48
60
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
use crate::core::lexer::{
|
|
2
2
|
handler::{
|
|
3
|
-
arrow::handle_arrow_lexer, at::handle_at_lexer, brace::{ handle_lbrace_lexer, handle_rbrace_lexer }, colon::handle_colon_lexer, comment::handle_comment_lexer, dot::handle_dot_lexer, identifier::handle_identifier_lexer, indent::handle_indent_lexer, newline::handle_newline_lexer, number::handle_number_lexer, operator::handle_operator_lexer, string::handle_string_lexer
|
|
3
|
+
arrow::handle_arrow_lexer, at::handle_at_lexer, brace::{ handle_lbrace_lexer, handle_rbrace_lexer }, colon::handle_colon_lexer, comment::handle_comment_lexer, dot::handle_dot_lexer, identifier::handle_identifier_lexer, indent::handle_indent_lexer, newline::handle_newline_lexer, number::handle_number_lexer, operator::handle_operator_lexer, slash::handle_slash_lexer, string::handle_string_lexer
|
|
4
4
|
},
|
|
5
5
|
token::{ Token, TokenKind },
|
|
6
6
|
};
|
|
@@ -101,6 +101,17 @@ pub fn handle_content_lexing(content: String) -> Result<Vec<Token>, String> {
|
|
|
101
101
|
&mut column
|
|
102
102
|
);
|
|
103
103
|
}
|
|
104
|
+
'/' => {
|
|
105
|
+
handle_slash_lexer(
|
|
106
|
+
ch,
|
|
107
|
+
&mut chars,
|
|
108
|
+
&mut current_indent,
|
|
109
|
+
&mut indent_stack,
|
|
110
|
+
&mut tokens,
|
|
111
|
+
&mut line,
|
|
112
|
+
&mut column
|
|
113
|
+
);
|
|
114
|
+
}
|
|
104
115
|
'-' => {
|
|
105
116
|
handle_arrow_lexer(
|
|
106
117
|
ch,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
use crate::core::lexer::token::{ Token, TokenKind };
|
|
2
|
+
|
|
3
|
+
pub fn handle_slash_lexer(
|
|
4
|
+
char: char,
|
|
5
|
+
chars: &mut std::iter::Peekable<std::str::Chars>,
|
|
6
|
+
current_indent: &mut usize,
|
|
7
|
+
indent_stack: &mut Vec<usize>,
|
|
8
|
+
tokens: &mut Vec<Token>,
|
|
9
|
+
line: &mut usize,
|
|
10
|
+
column: &mut usize
|
|
11
|
+
) {
|
|
12
|
+
let mut slash = char.to_string();
|
|
13
|
+
|
|
14
|
+
tokens.push(Token {
|
|
15
|
+
kind: TokenKind::Slash,
|
|
16
|
+
lexeme: slash,
|
|
17
|
+
line: *line,
|
|
18
|
+
column: *column,
|
|
19
|
+
indent: *current_indent,
|
|
20
|
+
});
|
|
21
|
+
}
|
package/rust/core/lexer/token.rs
CHANGED
|
@@ -64,6 +64,10 @@ impl Parser {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
pub fn peek_nth_kind(&self, n: usize) -> Option<TokenKind> {
|
|
68
|
+
self.peek_nth(n).map(|t| t.kind.clone())
|
|
69
|
+
}
|
|
70
|
+
|
|
67
71
|
pub fn advance_if(&mut self, kind: TokenKind) -> bool {
|
|
68
72
|
if self.match_token(kind) { true } else { false }
|
|
69
73
|
}
|
|
@@ -172,6 +176,10 @@ impl Parser {
|
|
|
172
176
|
self.peek().map_or(false, |t| t.kind == kind)
|
|
173
177
|
}
|
|
174
178
|
|
|
179
|
+
pub fn peek_kind(&self) -> Option<TokenKind> {
|
|
180
|
+
self.peek().map(|t| t.kind.clone())
|
|
181
|
+
}
|
|
182
|
+
|
|
175
183
|
pub fn parse_map_value(&mut self) -> Option<Value> {
|
|
176
184
|
if !self.match_token(TokenKind::LBrace) {
|
|
177
185
|
return None;
|
|
@@ -198,9 +206,35 @@ impl Parser {
|
|
|
198
206
|
Value::String(token.lexeme.clone())
|
|
199
207
|
}
|
|
200
208
|
TokenKind::Number => {
|
|
201
|
-
|
|
202
|
-
|
|
209
|
+
let mut number_str = token.lexeme.clone();
|
|
210
|
+
self.advance(); // consume the first number
|
|
211
|
+
|
|
212
|
+
if let Some(dot_token) = self.peek_clone() {
|
|
213
|
+
if dot_token.kind == TokenKind::Dot {
|
|
214
|
+
self.advance(); // consume the dot
|
|
215
|
+
|
|
216
|
+
if let Some(decimal_token) = self.peek_clone() {
|
|
217
|
+
if decimal_token.kind == TokenKind::Number {
|
|
218
|
+
self.advance(); // consume the number after the dot
|
|
219
|
+
number_str.push('.');
|
|
220
|
+
number_str.push_str(&decimal_token.lexeme);
|
|
221
|
+
} else {
|
|
222
|
+
println!(
|
|
223
|
+
"Expected number after dot, got {:?}",
|
|
224
|
+
decimal_token
|
|
225
|
+
);
|
|
226
|
+
return Some(Value::Null);
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
println!("Expected number after dot, but reached EOF");
|
|
230
|
+
return Some(Value::Null);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
Value::Number(number_str.parse::<f32>().unwrap_or(0.0))
|
|
203
236
|
}
|
|
237
|
+
|
|
204
238
|
TokenKind::Identifier => {
|
|
205
239
|
self.advance();
|
|
206
240
|
Value::Identifier(token.lexeme.clone())
|
|
@@ -89,14 +89,39 @@ pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) ->
|
|
|
89
89
|
TokenKind::Identifier =>
|
|
90
90
|
Value::Identifier(value_token.lexeme.clone()),
|
|
91
91
|
TokenKind::String => Value::String(value_token.lexeme.clone()),
|
|
92
|
-
TokenKind::Number =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
TokenKind::Number => {
|
|
93
|
+
if let Some(TokenKind::Slash) = parser.peek_kind() {
|
|
94
|
+
parser.advance(); // consume slash
|
|
95
|
+
if let Some(denominator_token) = parser.peek_clone() {
|
|
96
|
+
if denominator_token.kind == TokenKind::Number {
|
|
97
|
+
parser.advance(); // consume denominator
|
|
98
|
+
let denominator =
|
|
99
|
+
denominator_token.lexeme.clone();
|
|
100
|
+
Value::Beat(
|
|
101
|
+
format!(
|
|
102
|
+
"{}/{}",
|
|
103
|
+
value_token.lexeme,
|
|
104
|
+
denominator
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
} else {
|
|
108
|
+
Value::Unknown
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
Value::Unknown
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
// Regular number without slash
|
|
115
|
+
Value::Number(
|
|
116
|
+
value_token.lexeme.parse::<f32>().unwrap_or(0.0)
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
96
120
|
TokenKind::Boolean =>
|
|
97
121
|
Value::Boolean(
|
|
98
122
|
value_token.lexeme.parse::<bool>().unwrap_or(false)
|
|
99
123
|
),
|
|
124
|
+
|
|
100
125
|
_ => Value::Unknown,
|
|
101
126
|
};
|
|
102
127
|
map.insert(key, value);
|