@devaloop/devalang 0.0.1-alpha.12 → 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 +8 -3
- package/README.md +36 -34
- package/docs/CHANGELOG.md +65 -1
- package/docs/CONTRIBUTING.md +1 -0
- package/docs/ROADMAP.md +2 -2
- package/docs/TODO.md +6 -5
- package/examples/bank.deva +2 -4
- package/examples/function.deva +15 -0
- package/examples/index.deva +25 -14
- 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 +76 -14
- package/rust/cli/check.rs +71 -8
- 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 +45 -20
- 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 +254 -91
- package/rust/core/audio/interpreter/arrow_call.rs +34 -15
- 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 +90 -128
- 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/builder/mod.rs +11 -6
- 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/indent.rs +16 -2
- package/rust/core/lexer/handler/mod.rs +1 -0
- package/rust/core/lexer/handler/parenthesis.rs +41 -0
- package/rust/core/lexer/token.rs +4 -0
- package/rust/core/mod.rs +2 -1
- package/rust/core/parser/driver.rs +47 -4
- 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 +81 -123
- 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/plugin/loader.rs +48 -0
- package/rust/core/plugin/mod.rs +1 -0
- package/rust/core/preprocessor/loader.rs +50 -32
- 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/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/lib.rs +10 -7
- package/rust/main.rs +32 -9
- package/rust/utils/logger.rs +16 -0
- package/rust/utils/mod.rs +45 -1
- 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);
|
|
@@ -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);
|
|
@@ -109,30 +100,141 @@ impl AudioEngine {
|
|
|
109
100
|
freq: f32,
|
|
110
101
|
amp: f32,
|
|
111
102
|
start_time_ms: f32,
|
|
112
|
-
duration_ms: f32
|
|
103
|
+
duration_ms: f32,
|
|
104
|
+
synth_params: HashMap<String, Value>,
|
|
105
|
+
note_params: HashMap<String, Value>
|
|
113
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
|
+
|
|
114
174
|
let sample_rate = SAMPLE_RATE as f32;
|
|
115
175
|
let channels = CHANNELS as usize;
|
|
116
176
|
|
|
117
177
|
let total_samples = ((duration_ms / 1000.0) * sample_rate) as usize;
|
|
118
178
|
let start_sample = ((start_time_ms / 1000.0) * sample_rate) as usize;
|
|
119
|
-
let amplitude = (i16::MAX as f32) * amp.clamp(0.0, 1.0);
|
|
120
179
|
|
|
121
180
|
let mut samples = Vec::with_capacity(total_samples);
|
|
122
|
-
|
|
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
|
+
};
|
|
123
191
|
|
|
124
192
|
for i in 0..total_samples {
|
|
125
193
|
let t = ((start_sample + i) as f32) / sample_rate;
|
|
126
|
-
|
|
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;
|
|
127
210
|
|
|
128
211
|
let mut value = match waveform.as_str() {
|
|
129
212
|
"sine" => phase.sin(),
|
|
130
|
-
"square" => if phase.sin() >= 0.0 { 1.0 } else { -1.0 }
|
|
131
|
-
"saw" => 2.0 * (
|
|
132
|
-
"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,
|
|
133
216
|
_ => 0.0,
|
|
134
217
|
};
|
|
135
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
|
+
|
|
136
238
|
// Fade in/out
|
|
137
239
|
if i < fade_len {
|
|
138
240
|
value *= (i as f32) / (fade_len as f32);
|
|
@@ -140,7 +242,9 @@ impl AudioEngine {
|
|
|
140
242
|
value *= ((total_samples - i) as f32) / (fade_len as f32);
|
|
141
243
|
}
|
|
142
244
|
|
|
143
|
-
|
|
245
|
+
value *= envelope;
|
|
246
|
+
// Application de l'amplitude dynamique (slide + velocity)
|
|
247
|
+
samples.push((value * (i16::MAX as f32) * current_amp) as i16);
|
|
144
248
|
}
|
|
145
249
|
|
|
146
250
|
// Convert to stereo
|
|
@@ -157,8 +261,9 @@ impl AudioEngine {
|
|
|
157
261
|
}
|
|
158
262
|
|
|
159
263
|
for (i, sample) in stereo_samples.iter().enumerate() {
|
|
160
|
-
|
|
161
|
-
|
|
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); }
|
|
162
267
|
self.buffer[offset + i] = self.buffer[offset + i].saturating_add(*sample);
|
|
163
268
|
}
|
|
164
269
|
}
|
|
@@ -168,79 +273,95 @@ impl AudioEngine {
|
|
|
168
273
|
filepath: &str,
|
|
169
274
|
time_secs: f32,
|
|
170
275
|
dur_sec: f32,
|
|
171
|
-
effects: Option<HashMap<String, Value
|
|
276
|
+
effects: Option<HashMap<String, Value>>,
|
|
277
|
+
variable_table: &VariableTable
|
|
172
278
|
) {
|
|
173
279
|
if filepath.is_empty() {
|
|
174
280
|
eprintln!("❌ Empty file path provided for audio sample.");
|
|
175
281
|
return;
|
|
176
282
|
}
|
|
177
283
|
|
|
178
|
-
let
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
284
|
+
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
285
|
+
let module_root = Path::new(&self.module_name);
|
|
286
|
+
let resolved_path: String;
|
|
287
|
+
|
|
288
|
+
// Get the variable path from the variable table
|
|
289
|
+
let mut var_path = filepath.to_string();
|
|
290
|
+
if let Some(Value::String(variable_path)) = variable_table.variables.get(filepath) {
|
|
291
|
+
var_path = variable_path.clone();
|
|
292
|
+
} else if let Some(Value::Sample(sample_path)) = variable_table.variables.get(filepath) {
|
|
293
|
+
var_path = sample_path.clone();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Handle devalang:// protocol
|
|
297
|
+
if var_path.starts_with("devalang://") {
|
|
298
|
+
let path_after_protocol = var_path.replace("devalang://", "");
|
|
299
|
+
let parts: Vec<&str> = path_after_protocol.split('/').collect();
|
|
300
|
+
|
|
301
|
+
if parts.len() < 3 {
|
|
302
|
+
eprintln!(
|
|
303
|
+
"❌ Invalid devalang:// path format. Expected devalang://<type>/<author>.<bank>/<entity>"
|
|
304
|
+
);
|
|
199
305
|
return;
|
|
200
306
|
}
|
|
307
|
+
|
|
308
|
+
let obj_type = parts[0];
|
|
309
|
+
let bank_name = parts[1];
|
|
310
|
+
let entity_name = parts[2];
|
|
311
|
+
|
|
312
|
+
resolved_path = root
|
|
313
|
+
.join(".deva")
|
|
314
|
+
.join(obj_type)
|
|
315
|
+
.join(bank_name)
|
|
316
|
+
.join(format!("{}.wav", entity_name))
|
|
317
|
+
.to_string_lossy()
|
|
318
|
+
.to_string();
|
|
201
319
|
} else {
|
|
202
|
-
|
|
203
|
-
let
|
|
320
|
+
// Else, resolve as a relative path
|
|
321
|
+
let entry_dir = module_root.parent().unwrap_or(root);
|
|
322
|
+
let absolute_path = root.join(entry_dir).join(&var_path);
|
|
204
323
|
|
|
205
|
-
|
|
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
|
-
}
|
|
324
|
+
resolved_path = normalize_path(absolute_path.to_string_lossy().to_string());
|
|
211
325
|
}
|
|
212
326
|
|
|
327
|
+
// Verify if the file exists
|
|
213
328
|
if !Path::new(&resolved_path).exists() {
|
|
214
329
|
eprintln!("❌ Audio file not found at: {}", resolved_path);
|
|
215
330
|
return;
|
|
216
331
|
}
|
|
217
332
|
|
|
218
|
-
let file =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
333
|
+
let file = match File::open(&resolved_path) {
|
|
334
|
+
Ok(f) => BufReader::new(f),
|
|
335
|
+
Err(e) => {
|
|
336
|
+
eprintln!("❌ Failed to open audio file {}: {}", resolved_path, e);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
let decoder = match Decoder::new(file) {
|
|
342
|
+
Ok(d) => d,
|
|
343
|
+
Err(e) => {
|
|
344
|
+
eprintln!("❌ Failed to decode audio file {}: {}", resolved_path, e);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
222
348
|
|
|
223
|
-
// Mono or stereo reading possible here, we will duplicate in L/R
|
|
224
349
|
let max_mono_samples = (dur_sec * (SAMPLE_RATE as f32)) as usize;
|
|
225
350
|
let samples: Vec<i16> = decoder.convert_samples().take(max_mono_samples).collect();
|
|
226
351
|
|
|
227
352
|
if samples.is_empty() {
|
|
228
|
-
eprintln!("No samples
|
|
353
|
+
eprintln!("❌ No samples read from {}", resolved_path);
|
|
229
354
|
return;
|
|
230
355
|
}
|
|
231
356
|
|
|
232
|
-
//
|
|
357
|
+
// Calculate buffer offset and size
|
|
233
358
|
let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
|
|
234
359
|
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);
|
|
360
|
+
if self.buffer.len() < required_len {
|
|
361
|
+
self.buffer.resize(required_len, 0);
|
|
362
|
+
}
|
|
242
363
|
|
|
243
|
-
// Apply effects
|
|
364
|
+
// Apply effects and mix
|
|
244
365
|
if let Some(effects_map) = effects {
|
|
245
366
|
self.pad_samples(&samples, time_secs, Some(effects_map));
|
|
246
367
|
} else {
|
|
@@ -263,8 +384,10 @@ impl AudioEngine {
|
|
|
263
384
|
let mut fade_in = 0.0;
|
|
264
385
|
let mut fade_out = 0.0;
|
|
265
386
|
let mut pitch = 1.0;
|
|
266
|
-
let mut drive = 0.0;
|
|
387
|
+
let mut drive = 0.0;
|
|
267
388
|
let mut reverb = 0.0;
|
|
389
|
+
let mut delay = 0.0; // delay time in seconds
|
|
390
|
+
let delay_feedback = 0.35; // default feedback
|
|
268
391
|
|
|
269
392
|
if let Some(map) = &effects_map {
|
|
270
393
|
for (key, val) in map {
|
|
@@ -285,12 +408,14 @@ impl AudioEngine {
|
|
|
285
408
|
pitch = *v;
|
|
286
409
|
}
|
|
287
410
|
("drive", Value::Number(v)) => {
|
|
288
|
-
// Drive effect can be implemented here if needed
|
|
289
411
|
drive = *v;
|
|
290
412
|
}
|
|
291
413
|
("reverb", Value::Number(v)) => {
|
|
292
414
|
reverb = *v;
|
|
293
415
|
}
|
|
416
|
+
("delay", Value::Number(v)) => {
|
|
417
|
+
delay = *v;
|
|
418
|
+
}
|
|
294
419
|
_ => eprintln!("⚠️ Unknown or invalid effect '{}'", key),
|
|
295
420
|
}
|
|
296
421
|
}
|
|
@@ -299,49 +424,64 @@ impl AudioEngine {
|
|
|
299
424
|
let fade_in_samples = (fade_in * (SAMPLE_RATE as f32)) as usize;
|
|
300
425
|
let fade_out_samples = (fade_out * (SAMPLE_RATE as f32)) as usize;
|
|
301
426
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
427
|
+
let delay_samples = if delay > 0.0 { (delay * (SAMPLE_RATE as f32)) as usize } else { 0 };
|
|
428
|
+
let mut delay_buffer: Vec<f32> = vec![0.0; total_samples + delay_samples];
|
|
429
|
+
|
|
430
|
+
for i in 0..total_samples {
|
|
431
|
+
// PITCH FIRST
|
|
432
|
+
let pitch_index = if pitch != 1.0 { ((i as f32) / pitch) as usize } else { i };
|
|
433
|
+
|
|
434
|
+
let mut adjusted = if pitch_index < total_samples {
|
|
435
|
+
samples[pitch_index] as f32
|
|
436
|
+
} else {
|
|
437
|
+
0.0
|
|
438
|
+
};
|
|
305
439
|
|
|
306
|
-
//
|
|
440
|
+
// GAIN
|
|
441
|
+
adjusted *= gain;
|
|
442
|
+
|
|
443
|
+
// FADE IN/OUT
|
|
307
444
|
if fade_in_samples > 0 && i < fade_in_samples {
|
|
308
445
|
adjusted *= (i as f32) / (fade_in_samples as f32);
|
|
309
446
|
}
|
|
310
|
-
|
|
311
|
-
// Fade out
|
|
312
447
|
if fade_out_samples > 0 && i >= total_samples.saturating_sub(fade_out_samples) {
|
|
313
448
|
adjusted *= ((total_samples - i) as f32) / (fade_out_samples as f32);
|
|
314
449
|
}
|
|
315
450
|
|
|
316
|
-
//
|
|
317
|
-
if
|
|
318
|
-
let
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
adjusted = 0.0; // Out of bounds, set to zero
|
|
323
|
-
}
|
|
451
|
+
// DRIVE (soft)
|
|
452
|
+
if drive > 0.0 {
|
|
453
|
+
let normalized = adjusted / (i16::MAX as f32);
|
|
454
|
+
let pre_gain = (10f32).powf(drive / 20.0); // dB mapping
|
|
455
|
+
let driven = (normalized * pre_gain).tanh();
|
|
456
|
+
adjusted = driven * (i16::MAX as f32);
|
|
324
457
|
}
|
|
325
458
|
|
|
326
|
-
//
|
|
327
|
-
if
|
|
328
|
-
|
|
459
|
+
// DELAY
|
|
460
|
+
if delay_samples > 0 && i >= delay_samples {
|
|
461
|
+
let echo = delay_buffer[i - delay_samples] * delay_feedback;
|
|
462
|
+
adjusted += echo;
|
|
463
|
+
}
|
|
464
|
+
if delay_samples > 0 {
|
|
465
|
+
delay_buffer[i] = adjusted;
|
|
329
466
|
}
|
|
330
467
|
|
|
331
|
-
//
|
|
468
|
+
// REVERB
|
|
332
469
|
if reverb > 0.0 {
|
|
333
|
-
let reverb_delay = (
|
|
470
|
+
let reverb_delay = (0.03 * (SAMPLE_RATE as f32)) as usize;
|
|
334
471
|
if i >= reverb_delay {
|
|
335
|
-
adjusted += self.buffer[offset + i - reverb_delay] as f32 *
|
|
472
|
+
adjusted += (self.buffer[offset + i - reverb_delay] as f32) * reverb;
|
|
336
473
|
}
|
|
337
474
|
}
|
|
338
475
|
|
|
339
|
-
//
|
|
476
|
+
// CLAMP
|
|
340
477
|
let adjusted_sample = adjusted.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
|
|
341
478
|
|
|
342
|
-
//
|
|
343
|
-
let
|
|
344
|
-
let
|
|
479
|
+
// PAN
|
|
480
|
+
let left_gain = 1.0 - pan.max(0.0); // Pan > 0 => reduce left
|
|
481
|
+
let right_gain = 1.0 + pan.min(0.0); // Pan < 0 => reduce right
|
|
482
|
+
|
|
483
|
+
let left = ((adjusted_sample as f32) * left_gain) as i16;
|
|
484
|
+
let right = ((adjusted_sample as f32) * right_gain) as i16;
|
|
345
485
|
|
|
346
486
|
let left_pos = offset + i * 2;
|
|
347
487
|
let right_pos = left_pos + 1;
|
|
@@ -352,4 +492,27 @@ impl AudioEngine {
|
|
|
352
492
|
}
|
|
353
493
|
}
|
|
354
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
|
+
}
|
|
355
518
|
}
|