@devaloop/devalang 0.0.1-alpha.13 → 0.0.1-alpha.14
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 +8 -9
- package/Cargo.toml +58 -54
- package/README.md +36 -21
- package/docs/CHANGELOG.md +40 -2
- package/docs/CONTRIBUTING.md +1 -0
- package/docs/ROADMAP.md +2 -2
- package/docs/TODO.md +5 -4
- package/examples/bank.deva +2 -4
- package/examples/function.deva +15 -0
- package/examples/index.deva +25 -11
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +6 -6
- package/project-version.json +3 -3
- package/rust/cli/bank.rs +2 -1
- package/rust/cli/build.rs +69 -30
- package/rust/cli/check.rs +45 -5
- package/rust/cli/driver.rs +40 -28
- package/rust/cli/install.rs +22 -7
- package/rust/cli/login.rs +134 -0
- package/rust/cli/mod.rs +2 -1
- package/rust/cli/play.rs +44 -19
- package/rust/common/api.rs +8 -0
- package/rust/common/cdn.rs +2 -5
- package/rust/common/mod.rs +3 -1
- package/rust/common/sso.rs +8 -0
- package/rust/config/driver.rs +19 -1
- package/rust/config/loader.rs +56 -10
- package/rust/core/audio/engine.rs +152 -42
- package/rust/core/audio/interpreter/arrow_call.rs +34 -15
- package/rust/core/audio/interpreter/call.rs +2 -2
- package/rust/core/audio/interpreter/driver.rs +19 -14
- package/rust/core/audio/interpreter/spawn.rs +2 -2
- package/rust/core/builder/mod.rs +11 -6
- package/rust/core/lexer/handler/indent.rs +16 -2
- package/rust/core/lexer/token.rs +1 -0
- package/rust/core/mod.rs +2 -1
- package/rust/core/parser/driver.rs +46 -5
- package/rust/core/parser/handler/arrow_call.rs +78 -18
- package/rust/core/parser/handler/bank.rs +35 -7
- package/rust/core/parser/handler/dot.rs +43 -22
- package/rust/core/plugin/loader.rs +48 -0
- package/rust/core/plugin/mod.rs +1 -0
- package/rust/core/preprocessor/loader.rs +6 -4
- package/rust/installer/addon.rs +80 -0
- package/rust/installer/bank.rs +24 -14
- package/rust/installer/mod.rs +4 -1
- package/rust/installer/plugin.rs +55 -0
- package/rust/main.rs +32 -9
- package/rust/utils/logger.rs +16 -0
- package/rust/utils/spinner.rs +2 -4
package/rust/config/loader.rs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
use std::{ fs, path::Path };
|
|
2
|
-
use
|
|
3
|
-
use crate::config::driver::{ BankEntry, Config };
|
|
2
|
+
use crate::config::driver::{ BankEntry, BankMetadata, Config };
|
|
4
3
|
|
|
5
4
|
pub fn load_config(path: Option<&Path>) -> Option<Config> {
|
|
6
5
|
let config_path = path.unwrap_or_else(|| Path::new(".devalang"));
|
|
@@ -56,6 +55,59 @@ pub fn remove_bank_from_config(config: &mut Config, dependency: &str) {
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
58
|
+
pub fn add_plugin_to_config(config: &mut Config, real_path: &Path, dependency: &str) {
|
|
59
|
+
if config.plugins.is_none() {
|
|
60
|
+
config.plugins = Some(Vec::new());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let plugins = config.plugins.as_mut().unwrap();
|
|
64
|
+
|
|
65
|
+
let exists = plugins.iter().any(|p| p.path == dependency);
|
|
66
|
+
if exists {
|
|
67
|
+
println!("Plugin '{}' already in config", dependency);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let metadata_path = Path::new(real_path).join("plugin.toml");
|
|
72
|
+
|
|
73
|
+
if !metadata_path.exists() {
|
|
74
|
+
eprintln!("❌ Plugin metadata file '{}' does not exist", metadata_path.display());
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let metadata_content = std::fs
|
|
79
|
+
::read_to_string(&metadata_path)
|
|
80
|
+
.expect("Failed to read plugin metadata file");
|
|
81
|
+
|
|
82
|
+
let metadata: std::collections::HashMap<String, String> = toml
|
|
83
|
+
::from_str(&metadata_content)
|
|
84
|
+
.expect("Failed to parse plugin metadata file");
|
|
85
|
+
|
|
86
|
+
let plugin_entry = crate::config::driver::PluginEntry {
|
|
87
|
+
path: dependency.to_string(),
|
|
88
|
+
version: metadata
|
|
89
|
+
.get("version")
|
|
90
|
+
.cloned()
|
|
91
|
+
.unwrap_or_else(|| "0.0.1".to_string()),
|
|
92
|
+
author: metadata
|
|
93
|
+
.get("author")
|
|
94
|
+
.cloned()
|
|
95
|
+
.unwrap_or_else(|| "unknown".to_string()),
|
|
96
|
+
access: metadata
|
|
97
|
+
.get("access")
|
|
98
|
+
.cloned()
|
|
99
|
+
.unwrap_or_else(|| "public".to_string()),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
plugins.push(plugin_entry);
|
|
103
|
+
|
|
104
|
+
if let Err(e) = config.write(config) {
|
|
105
|
+
eprintln!("❌ Failed to write config: {}", e);
|
|
106
|
+
} else {
|
|
107
|
+
println!("✅ Plugin '{}' added to config", dependency);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
59
111
|
pub fn add_bank_to_config(config: &mut Config, real_path: &Path, dependency: &str) {
|
|
60
112
|
if config.banks.is_none() {
|
|
61
113
|
config.banks = Some(Vec::new());
|
|
@@ -80,24 +132,18 @@ pub fn add_bank_to_config(config: &mut Config, real_path: &Path, dependency: &st
|
|
|
80
132
|
::read_to_string(&metadata_path)
|
|
81
133
|
.expect("Failed to read bank metadata file");
|
|
82
134
|
|
|
83
|
-
let metadata:
|
|
135
|
+
let metadata: BankMetadata = toml
|
|
84
136
|
::from_str(&metadata_content)
|
|
85
137
|
.expect("Failed to parse bank metadata file");
|
|
86
138
|
|
|
87
139
|
let bank_to_insert = BankEntry {
|
|
88
140
|
path: dependency.to_string(),
|
|
89
141
|
version: Some(
|
|
90
|
-
metadata
|
|
142
|
+
metadata.bank
|
|
91
143
|
.get("version")
|
|
92
144
|
.cloned()
|
|
93
145
|
.unwrap_or_else(|| "0.0.1".to_string())
|
|
94
146
|
),
|
|
95
|
-
author: Some(
|
|
96
|
-
metadata
|
|
97
|
-
.get("author")
|
|
98
|
-
.cloned()
|
|
99
|
-
.unwrap_or_else(|| "unknown".to_string())
|
|
100
|
-
),
|
|
101
147
|
};
|
|
102
148
|
|
|
103
149
|
banks.push(bank_to_insert);
|
|
@@ -100,30 +100,141 @@ impl AudioEngine {
|
|
|
100
100
|
freq: f32,
|
|
101
101
|
amp: f32,
|
|
102
102
|
start_time_ms: f32,
|
|
103
|
-
duration_ms: f32
|
|
103
|
+
duration_ms: f32,
|
|
104
|
+
synth_params: HashMap<String, Value>,
|
|
105
|
+
note_params: HashMap<String, Value>
|
|
104
106
|
) {
|
|
107
|
+
let valid_synth_params = vec!["attack", "decay", "sustain", "release"];
|
|
108
|
+
let valid_note_params = vec![
|
|
109
|
+
"duration",
|
|
110
|
+
"velocity",
|
|
111
|
+
"glide",
|
|
112
|
+
"slide",
|
|
113
|
+
"amp",
|
|
114
|
+
"target_freq",
|
|
115
|
+
"target_amp",
|
|
116
|
+
"modulation",
|
|
117
|
+
"expression"
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
// Synth params validation
|
|
121
|
+
for key in synth_params.keys() {
|
|
122
|
+
if !valid_synth_params.contains(&key.as_str()) {
|
|
123
|
+
eprintln!("⚠️ Unknown synth parameter: '{}'", key);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Note params validation
|
|
128
|
+
for key in note_params.keys() {
|
|
129
|
+
if !valid_note_params.contains(&key.as_str()) {
|
|
130
|
+
eprintln!("⚠️ Unknown note parameter: '{}'", key);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Synth parameters
|
|
135
|
+
let attack = self.extract_f32(&synth_params, "attack").unwrap_or(0.0);
|
|
136
|
+
let decay = self.extract_f32(&synth_params, "decay").unwrap_or(0.0);
|
|
137
|
+
let sustain = self.extract_f32(&synth_params, "sustain").unwrap_or(0.0);
|
|
138
|
+
let release = self.extract_f32(&synth_params, "release").unwrap_or(0.0);
|
|
139
|
+
let attack_s = if attack > 10.0 { attack / 1000.0 } else { attack };
|
|
140
|
+
let decay_s = if decay > 10.0 { decay / 1000.0 } else { decay };
|
|
141
|
+
let release_s = if release > 10.0 { release / 1000.0 } else { release };
|
|
142
|
+
let sustain_level = if sustain > 1.0 { (sustain / 100.0).clamp(0.0, 1.0) } else { sustain.clamp(0.0, 1.0) };
|
|
143
|
+
|
|
144
|
+
// Note parameters
|
|
145
|
+
let duration_ms = self.extract_f32(¬e_params, "duration").unwrap_or(duration_ms);
|
|
146
|
+
let velocity = self.extract_f32(¬e_params, "velocity").unwrap_or(1.0);
|
|
147
|
+
let glide = self.extract_boolean(¬e_params, "glide").unwrap_or(false);
|
|
148
|
+
let slide = self.extract_boolean(¬e_params, "slide").unwrap_or(false);
|
|
149
|
+
|
|
150
|
+
let _amplitude = (i16::MAX as f32) * amp.clamp(0.0, 1.0) * velocity.clamp(0.0, 1.0);
|
|
151
|
+
|
|
152
|
+
// Logic for glide and slide
|
|
153
|
+
let freq_start = freq;
|
|
154
|
+
let mut freq_end = freq;
|
|
155
|
+
let amp_start = amp * velocity.clamp(0.0, 1.0);
|
|
156
|
+
let mut amp_end = amp_start;
|
|
157
|
+
|
|
158
|
+
if glide {
|
|
159
|
+
if let Some(Value::Number(target_freq)) = note_params.get("target_freq") {
|
|
160
|
+
freq_end = *target_freq;
|
|
161
|
+
} else {
|
|
162
|
+
freq_end = freq * 1.5; // Par défaut, glide vers une quinte
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if slide {
|
|
167
|
+
if let Some(Value::Number(target_amp)) = note_params.get("target_amp") {
|
|
168
|
+
amp_end = *target_amp * velocity.clamp(0.0, 1.0);
|
|
169
|
+
} else {
|
|
170
|
+
amp_end = amp_start * 0.5; // Par défaut, slide vers la moitié
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
105
174
|
let sample_rate = SAMPLE_RATE as f32;
|
|
106
175
|
let channels = CHANNELS as usize;
|
|
107
176
|
|
|
108
177
|
let total_samples = ((duration_ms / 1000.0) * sample_rate) as usize;
|
|
109
178
|
let start_sample = ((start_time_ms / 1000.0) * sample_rate) as usize;
|
|
110
|
-
let amplitude = (i16::MAX as f32) * amp.clamp(0.0, 1.0);
|
|
111
179
|
|
|
112
180
|
let mut samples = Vec::with_capacity(total_samples);
|
|
113
|
-
|
|
181
|
+
let fade_len = (sample_rate * 0.01) as usize; // 10 ms fade
|
|
182
|
+
|
|
183
|
+
let attack_samples = (attack_s * sample_rate) as usize;
|
|
184
|
+
let decay_samples = (decay_s * sample_rate) as usize;
|
|
185
|
+
let release_samples = (release_s * sample_rate) as usize;
|
|
186
|
+
let sustain_samples = if total_samples > attack_samples + decay_samples + release_samples {
|
|
187
|
+
total_samples - attack_samples - decay_samples - release_samples
|
|
188
|
+
} else {
|
|
189
|
+
0
|
|
190
|
+
};
|
|
114
191
|
|
|
115
192
|
for i in 0..total_samples {
|
|
116
193
|
let t = ((start_sample + i) as f32) / sample_rate;
|
|
117
|
-
|
|
194
|
+
|
|
195
|
+
// Glide
|
|
196
|
+
let current_freq = if glide {
|
|
197
|
+
freq_start + ((freq_end - freq_start) * (i as f32)) / (total_samples as f32)
|
|
198
|
+
} else {
|
|
199
|
+
freq
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Slide
|
|
203
|
+
let current_amp = if slide {
|
|
204
|
+
amp_start + ((amp_end - amp_start) * (i as f32)) / (total_samples as f32)
|
|
205
|
+
} else {
|
|
206
|
+
amp_start
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
let phase = 2.0 * std::f32::consts::PI * current_freq * t;
|
|
118
210
|
|
|
119
211
|
let mut value = match waveform.as_str() {
|
|
120
212
|
"sine" => phase.sin(),
|
|
121
|
-
"square" => if phase.sin() >= 0.0 { 1.0 } else { -1.0 }
|
|
122
|
-
"saw" => 2.0 * (
|
|
123
|
-
"triangle" => (2.0 * (2.0 * (
|
|
213
|
+
"square" => if phase.sin() >= 0.0 { 1.0 } else { -1.0 },
|
|
214
|
+
"saw" => 2.0 * (current_freq * t - (current_freq * t + 0.5).floor()),
|
|
215
|
+
"triangle" => (2.0 * (2.0 * (current_freq * t).fract() - 1.0)).abs() * 2.0 - 1.0,
|
|
124
216
|
_ => 0.0,
|
|
125
217
|
};
|
|
126
218
|
|
|
219
|
+
// ADSR envelope
|
|
220
|
+
let envelope = if i < attack_samples {
|
|
221
|
+
(i as f32) / (attack_samples as f32)
|
|
222
|
+
} else if i < attack_samples + decay_samples {
|
|
223
|
+
1.0 -
|
|
224
|
+
(1.0 - sustain_level) * (((i - attack_samples) as f32) / (decay_samples as f32))
|
|
225
|
+
} else if i < attack_samples + decay_samples + sustain_samples {
|
|
226
|
+
sustain_level
|
|
227
|
+
} else {
|
|
228
|
+
if release_samples > 0 {
|
|
229
|
+
sustain_level *
|
|
230
|
+
(1.0 -
|
|
231
|
+
((i - attack_samples - decay_samples - sustain_samples) as f32) /
|
|
232
|
+
(release_samples as f32))
|
|
233
|
+
} else {
|
|
234
|
+
0.0
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
127
238
|
// Fade in/out
|
|
128
239
|
if i < fade_len {
|
|
129
240
|
value *= (i as f32) / (fade_len as f32);
|
|
@@ -131,7 +242,9 @@ impl AudioEngine {
|
|
|
131
242
|
value *= ((total_samples - i) as f32) / (fade_len as f32);
|
|
132
243
|
}
|
|
133
244
|
|
|
134
|
-
|
|
245
|
+
value *= envelope;
|
|
246
|
+
// Application de l'amplitude dynamique (slide + velocity)
|
|
247
|
+
samples.push((value * (i16::MAX as f32) * current_amp) as i16);
|
|
135
248
|
}
|
|
136
249
|
|
|
137
250
|
// Convert to stereo
|
|
@@ -148,8 +261,9 @@ impl AudioEngine {
|
|
|
148
261
|
}
|
|
149
262
|
|
|
150
263
|
for (i, sample) in stereo_samples.iter().enumerate() {
|
|
151
|
-
|
|
152
|
-
|
|
264
|
+
// Debug: note si on rencontre des samples non nuls
|
|
265
|
+
// (pour traquer les buffers silencieux)
|
|
266
|
+
// if i == 0 { eprintln!("[debug] first stereo sample: {}", sample); }
|
|
153
267
|
self.buffer[offset + i] = self.buffer[offset + i].saturating_add(*sample);
|
|
154
268
|
}
|
|
155
269
|
}
|
|
@@ -168,8 +282,8 @@ impl AudioEngine {
|
|
|
168
282
|
}
|
|
169
283
|
|
|
170
284
|
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
171
|
-
|
|
172
|
-
|
|
285
|
+
let module_root = Path::new(&self.module_name);
|
|
286
|
+
let resolved_path: String;
|
|
173
287
|
|
|
174
288
|
// Get the variable path from the variable table
|
|
175
289
|
let mut var_path = filepath.to_string();
|
|
@@ -179,41 +293,14 @@ impl AudioEngine {
|
|
|
179
293
|
var_path = sample_path.clone();
|
|
180
294
|
}
|
|
181
295
|
|
|
182
|
-
//
|
|
183
|
-
if var_path.
|
|
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
|
-
}
|
|
198
|
-
|
|
199
|
-
resolved_path = root
|
|
200
|
-
.join(".deva")
|
|
201
|
-
.join("bank")
|
|
202
|
-
.join(bank_name)
|
|
203
|
-
.join(format!("{}.wav", entity_name))
|
|
204
|
-
.to_string_lossy()
|
|
205
|
-
.to_string();
|
|
206
|
-
} else {
|
|
207
|
-
eprintln!("❌ Invalid namespace format: {}", var_path);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
} else if var_path.starts_with("devalang://") {
|
|
296
|
+
// Handle devalang:// protocol
|
|
297
|
+
if var_path.starts_with("devalang://") {
|
|
211
298
|
let path_after_protocol = var_path.replace("devalang://", "");
|
|
212
299
|
let parts: Vec<&str> = path_after_protocol.split('/').collect();
|
|
213
300
|
|
|
214
301
|
if parts.len() < 3 {
|
|
215
302
|
eprintln!(
|
|
216
|
-
"❌ Invalid devalang:// path format. Expected devalang://<type>/<bank>/<entity>"
|
|
303
|
+
"❌ Invalid devalang:// path format. Expected devalang://<type>/<author>.<bank>/<entity>"
|
|
217
304
|
);
|
|
218
305
|
return;
|
|
219
306
|
}
|
|
@@ -405,4 +492,27 @@ impl AudioEngine {
|
|
|
405
492
|
}
|
|
406
493
|
}
|
|
407
494
|
}
|
|
495
|
+
|
|
496
|
+
fn extract_f32(&self, map: &HashMap<String, Value>, key: &str) -> Option<f32> {
|
|
497
|
+
match map.get(key) {
|
|
498
|
+
Some(Value::Number(n)) => Some(*n),
|
|
499
|
+
Some(Value::String(s)) => s.parse::<f32>().ok(),
|
|
500
|
+
Some(Value::Boolean(b)) => Some(if *b { 1.0 } else { 0.0 }),
|
|
501
|
+
_ => None,
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
fn extract_boolean(&self, map: &HashMap<String, Value>, key: &str) -> Option<bool> {
|
|
506
|
+
match map.get(key) {
|
|
507
|
+
Some(Value::Boolean(b)) => Some(*b),
|
|
508
|
+
Some(Value::Number(n)) => Some(*n != 0.0),
|
|
509
|
+
Some(Value::Identifier(s)) => {
|
|
510
|
+
if s == "true" { Some(true) } else if s == "false" { Some(false) } else { None }
|
|
511
|
+
}
|
|
512
|
+
Some(Value::String(s)) => {
|
|
513
|
+
if s == "true" { Some(true) } else if s == "false" { Some(false) } else { None }
|
|
514
|
+
}
|
|
515
|
+
_ => None,
|
|
516
|
+
}
|
|
517
|
+
}
|
|
408
518
|
}
|
|
@@ -23,11 +23,16 @@ pub fn interprete_call_arrow_statement(
|
|
|
23
23
|
.unwrap_or(0.0);
|
|
24
24
|
|
|
25
25
|
if let StatementKind::ArrowCall { target, method, args } = &stmt.kind {
|
|
26
|
-
let Some(Value::
|
|
26
|
+
let Some(Value::Statement(synth_stmt)) = variable_table.get(target) else {
|
|
27
27
|
println!("❌ Synth '{}' not found in variable table", target);
|
|
28
28
|
return (*max_end_time, cursor_copy);
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
let Value::Map(synth_map) = &synth_stmt.value else {
|
|
32
|
+
println!("❌ Invalid synth statement for '{}', expected a map.", target);
|
|
33
|
+
return (*max_end_time, cursor_copy);
|
|
34
|
+
};
|
|
35
|
+
|
|
31
36
|
let Some(Value::String(entity)) = synth_map.get("entity") else {
|
|
32
37
|
println!("❌ Missing 'entity' key in synth '{}'.", target);
|
|
33
38
|
return (*max_end_time, cursor_copy);
|
|
@@ -53,37 +58,51 @@ pub fn interprete_call_arrow_statement(
|
|
|
53
58
|
return (*max_end_time, cursor_copy);
|
|
54
59
|
};
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
// Synth parameters
|
|
62
|
+
let synth_params = params.clone();
|
|
63
|
+
let amp = extract_f32(&synth_params, "amp", base_bpm).unwrap_or(1.0);
|
|
58
64
|
|
|
59
65
|
if method == "note" {
|
|
60
|
-
let
|
|
66
|
+
let filtered_args: Vec<_> = args
|
|
67
|
+
.iter()
|
|
68
|
+
.filter(|arg| !matches!(arg, Value::Unknown))
|
|
69
|
+
.collect();
|
|
70
|
+
|
|
71
|
+
let Some(Value::Identifier(note_name)) = filtered_args.get(0).map(|v| (*v).clone()) else {
|
|
61
72
|
println!("❌ Invalid or missing argument for 'note' method on '{}'.", target);
|
|
62
73
|
return (*max_end_time, cursor_copy);
|
|
63
74
|
};
|
|
64
75
|
|
|
65
|
-
let mut
|
|
66
|
-
if let Some(
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
let mut note_params = HashMap::new();
|
|
77
|
+
if let Some(arg1) = filtered_args.get(1) {
|
|
78
|
+
match (*arg1).clone() {
|
|
79
|
+
Value::Map(map) => {
|
|
80
|
+
for (key, value) in map {
|
|
81
|
+
note_params.insert(key, value);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
_ => {}
|
|
69
85
|
}
|
|
70
86
|
}
|
|
71
87
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
)
|
|
88
|
+
// Note parameters and calculations
|
|
89
|
+
let amp_note = extract_f32(¬e_params, "amp", base_bpm).unwrap_or(amp);
|
|
90
|
+
let duration_ms = extract_f32(¬e_params, "duration", base_bpm)
|
|
91
|
+
.unwrap_or(base_duration * 1000.0);
|
|
92
|
+
|
|
75
93
|
let duration_secs = duration_ms / 1000.0;
|
|
76
|
-
|
|
77
|
-
let final_freq = note_to_freq(note_name);
|
|
94
|
+
let final_freq = note_to_freq(¬e_name);
|
|
78
95
|
let start_time = cursor_copy;
|
|
79
96
|
let end_time = start_time + duration_secs;
|
|
80
97
|
|
|
81
98
|
audio_engine.insert_note(
|
|
82
99
|
waveform.clone(),
|
|
83
100
|
final_freq,
|
|
84
|
-
|
|
101
|
+
amp_note,
|
|
85
102
|
start_time * 1000.0,
|
|
86
|
-
duration_ms
|
|
103
|
+
duration_ms,
|
|
104
|
+
synth_params,
|
|
105
|
+
note_params
|
|
87
106
|
);
|
|
88
107
|
|
|
89
108
|
*max_end_time = (*max_end_time).max(end_time);
|
|
@@ -18,7 +18,7 @@ pub fn interprete_call_statement(
|
|
|
18
18
|
) -> (f32, f32) {
|
|
19
19
|
match &stmt.kind {
|
|
20
20
|
StatementKind::Call { name, args } => {
|
|
21
|
-
//
|
|
21
|
+
// Classic function call case
|
|
22
22
|
if let Some(func) = functions.functions.get(name) {
|
|
23
23
|
if func.parameters.len() != args.len() {
|
|
24
24
|
eprintln!(
|
|
@@ -48,7 +48,7 @@ pub fn interprete_call_statement(
|
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
//
|
|
51
|
+
// Group case
|
|
52
52
|
if let Some(group_stmt) = find_group(name, variable_table, global_store) {
|
|
53
53
|
if let Value::Map(map) = &group_stmt.value {
|
|
54
54
|
if let Some(Value::Block(body)) = map.get("body") {
|
|
@@ -52,8 +52,8 @@ pub fn run_audio_program(
|
|
|
52
52
|
pub fn execute_audio_block(
|
|
53
53
|
audio_engine: &mut AudioEngine,
|
|
54
54
|
global_store: &GlobalStore,
|
|
55
|
-
variable_table: VariableTable,
|
|
56
|
-
functions_table: FunctionTable,
|
|
55
|
+
mut variable_table: VariableTable,
|
|
56
|
+
mut functions_table: FunctionTable,
|
|
57
57
|
statements: Vec<Statement>,
|
|
58
58
|
mut base_bpm: f32,
|
|
59
59
|
mut base_duration: f32,
|
|
@@ -68,20 +68,21 @@ pub fn execute_audio_block(
|
|
|
68
68
|
for stmt in others {
|
|
69
69
|
match &stmt.kind {
|
|
70
70
|
StatementKind::Load { .. } => {
|
|
71
|
-
if
|
|
72
|
-
|
|
73
|
-
&stmt,
|
|
74
|
-
&mut variable_table.clone()
|
|
75
|
-
)
|
|
76
|
-
{
|
|
77
|
-
// Extend the variable_table if necessary
|
|
71
|
+
if let Some(new_table) = interprete_load_statement(&stmt, &mut variable_table) {
|
|
72
|
+
variable_table = new_table;
|
|
78
73
|
}
|
|
79
74
|
}
|
|
80
75
|
StatementKind::Let { .. } => {
|
|
81
|
-
interprete_let_statement(&stmt, &mut variable_table
|
|
76
|
+
if let Some(new_table) = interprete_let_statement(&stmt, &mut variable_table) {
|
|
77
|
+
variable_table = new_table;
|
|
78
|
+
}
|
|
82
79
|
}
|
|
83
80
|
StatementKind::Function { .. } => {
|
|
84
|
-
|
|
81
|
+
if let Some(new_functions) =
|
|
82
|
+
interprete_function_statement(&stmt, &mut functions_table)
|
|
83
|
+
{
|
|
84
|
+
functions_table = new_functions;
|
|
85
|
+
}
|
|
85
86
|
}
|
|
86
87
|
StatementKind::Tempo => {
|
|
87
88
|
if let Some((new_bpm, new_duration)) = interprete_tempo_statement(&stmt) {
|
|
@@ -129,7 +130,7 @@ pub fn execute_audio_block(
|
|
|
129
130
|
max_end_time = new_max;
|
|
130
131
|
}
|
|
131
132
|
StatementKind::Call { .. } => {
|
|
132
|
-
let (new_max,
|
|
133
|
+
let (new_max, _) = interprete_call_statement(
|
|
133
134
|
&stmt,
|
|
134
135
|
audio_engine,
|
|
135
136
|
&variable_table,
|
|
@@ -140,7 +141,7 @@ pub fn execute_audio_block(
|
|
|
140
141
|
max_end_time,
|
|
141
142
|
cursor_time,
|
|
142
143
|
);
|
|
143
|
-
cursor_time =
|
|
144
|
+
cursor_time = new_max;
|
|
144
145
|
max_end_time = new_max;
|
|
145
146
|
}
|
|
146
147
|
StatementKind::ArrowCall { .. } => {
|
|
@@ -154,8 +155,12 @@ pub fn execute_audio_block(
|
|
|
154
155
|
Some(&mut cursor_time),
|
|
155
156
|
true
|
|
156
157
|
);
|
|
158
|
+
|
|
157
159
|
cursor_time = new_cursor;
|
|
158
|
-
|
|
160
|
+
|
|
161
|
+
if new_max > max_end_time {
|
|
162
|
+
max_end_time = new_max;
|
|
163
|
+
}
|
|
159
164
|
}
|
|
160
165
|
_ => {}
|
|
161
166
|
}
|
|
@@ -20,7 +20,7 @@ pub fn interprete_spawn_statement(
|
|
|
20
20
|
StatementKind::Spawn { name, args } => {
|
|
21
21
|
let mut local_engine = AudioEngine::new(audio_engine.module_name.clone());
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// Function case
|
|
24
24
|
if let Some(func) = functions.functions.get(name) {
|
|
25
25
|
if func.parameters.len() != args.len() {
|
|
26
26
|
eprintln!(
|
|
@@ -53,7 +53,7 @@ pub fn interprete_spawn_statement(
|
|
|
53
53
|
return (spawn_max.max(max_end_time), cursor_time);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
//
|
|
56
|
+
// Group case
|
|
57
57
|
if let Some(group_stmt) = find_group(name, variable_table, global_store) {
|
|
58
58
|
if let Value::Map(map) = &group_stmt.value {
|
|
59
59
|
if let Some(Value::Block(body)) = map.get("body") {
|
package/rust/core/builder/mod.rs
CHANGED
|
@@ -3,7 +3,6 @@ use crate::core::parser::statement::Statement;
|
|
|
3
3
|
use crate::core::store::global::GlobalStore;
|
|
4
4
|
use std::{ collections::HashMap, fs::create_dir_all };
|
|
5
5
|
use std::io::Write;
|
|
6
|
-
|
|
7
6
|
use crate::utils::logger::Logger;
|
|
8
7
|
|
|
9
8
|
pub struct Builder {}
|
|
@@ -13,7 +12,12 @@ impl Builder {
|
|
|
13
12
|
Builder {}
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
pub fn build_ast(
|
|
15
|
+
pub fn build_ast(
|
|
16
|
+
&self,
|
|
17
|
+
modules: &HashMap<String, Vec<Statement>>,
|
|
18
|
+
out_dir: &str,
|
|
19
|
+
compress: bool
|
|
20
|
+
) {
|
|
17
21
|
for (name, statements) in modules {
|
|
18
22
|
let formatted_name = name.split("/").last().unwrap_or(name);
|
|
19
23
|
let formatted_name = formatted_name.replace(".deva", "");
|
|
@@ -22,10 +26,11 @@ impl Builder {
|
|
|
22
26
|
|
|
23
27
|
let file_path = format!("{}/ast/{}.json", out_dir, formatted_name);
|
|
24
28
|
let mut file = std::fs::File::create(file_path).expect("Failed to create AST file");
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.expect("Failed to serialize AST")
|
|
29
|
+
let content = if compress {
|
|
30
|
+
serde_json::to_string(&statements).expect("Failed to serialize AST")
|
|
31
|
+
} else {
|
|
32
|
+
serde_json::to_string_pretty(&statements).expect("Failed to serialize AST")
|
|
33
|
+
};
|
|
29
34
|
|
|
30
35
|
file.write_all(content.as_bytes()).expect("Failed to write AST to file");
|
|
31
36
|
}
|
|
@@ -16,10 +16,24 @@ pub fn handle_indent_lexer(
|
|
|
16
16
|
*current_indent += 1;
|
|
17
17
|
chars.next();
|
|
18
18
|
col += 1;
|
|
19
|
+
tokens.push(Token {
|
|
20
|
+
kind: TokenKind::Whitespace,
|
|
21
|
+
lexeme: " ".to_string(),
|
|
22
|
+
line: *line,
|
|
23
|
+
column: col,
|
|
24
|
+
indent: *current_indent,
|
|
25
|
+
});
|
|
19
26
|
} else if c == '\t' {
|
|
20
27
|
*current_indent += 4;
|
|
21
28
|
chars.next();
|
|
22
29
|
col += 4;
|
|
30
|
+
tokens.push(Token {
|
|
31
|
+
kind: TokenKind::Whitespace,
|
|
32
|
+
lexeme: "\t".to_string(),
|
|
33
|
+
line: *line,
|
|
34
|
+
column: col,
|
|
35
|
+
indent: *current_indent,
|
|
36
|
+
});
|
|
23
37
|
} else {
|
|
24
38
|
break;
|
|
25
39
|
}
|
|
@@ -32,7 +46,7 @@ pub fn handle_indent_lexer(
|
|
|
32
46
|
indent_stack.push(*current_indent);
|
|
33
47
|
tokens.push(Token {
|
|
34
48
|
kind: TokenKind::Indent,
|
|
35
|
-
lexeme: String::
|
|
49
|
+
lexeme: String::from("<INDENT>"),
|
|
36
50
|
line: *line,
|
|
37
51
|
column: *column,
|
|
38
52
|
indent: *current_indent,
|
|
@@ -42,7 +56,7 @@ pub fn handle_indent_lexer(
|
|
|
42
56
|
indent_stack.pop();
|
|
43
57
|
tokens.push(Token {
|
|
44
58
|
kind: TokenKind::Dedent,
|
|
45
|
-
lexeme: String::
|
|
59
|
+
lexeme: String::from("<DEDENT>"),
|
|
46
60
|
line: *line,
|
|
47
61
|
column: *column,
|
|
48
62
|
indent: *current_indent,
|
package/rust/core/lexer/token.rs
CHANGED
package/rust/core/mod.rs
CHANGED