1use std::{ collections::HashMap, fs::File, io::BufReader, path::Path };
2use hound::{ SampleFormat, WavSpec, WavWriter };
3use rodio::{ Decoder, Source };
4use crate::core::{
5 shared::value::Value,
6 store::variable::VariableTable,
7 utils::path::normalize_path,
8};
9
10const SAMPLE_RATE: u32 = 44100;
11const CHANNELS: u16 = 2;
12
13#[derive(Debug, Clone, PartialEq)]
14pub struct AudioEngine {
15 pub volume: f32,
16 pub buffer: Vec<i16>,
17 pub module_name: String,
18}
19
20impl AudioEngine {
21 pub fn new(module_name: String) -> Self {
22 AudioEngine {
23 volume: 1.0,
24 buffer: vec![],
25 module_name,
26 }
27 }
28
29 pub fn get_buffer(&self) -> &[i16] {
30 &self.buffer
31 }
32
33 pub fn get_normalized_buffer(&self) -> Vec<f32> {
34 self.buffer
35 .iter()
36 .map(|&s| (s as f32) / 32768.0)
37 .collect()
38 }
39
40 pub fn mix(&mut self, other: &AudioEngine) {
41 let max_len = self.buffer.len().max(other.buffer.len());
42 self.buffer.resize(max_len, 0);
43
44 for (i, &sample) in other.buffer.iter().enumerate() {
45 self.buffer[i] = self.buffer[i].saturating_add(sample);
46 }
47 }
48
49 pub fn merge_with(&mut self, other: AudioEngine) {
50 if other.buffer.iter().all(|&s| s == 0) {
51 eprintln!("⚠️ Skipping merge: other buffer is silent");
52 return;
53 }
54
55 if self.buffer.iter().all(|&s| s == 0) {
56 self.buffer = other.buffer;
57 return;
58 }
59
60 self.mix(&other);
61 }
62
63 pub fn set_duration(&mut self, duration_secs: f32) {
64 let total_samples = (duration_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
65
66 if self.buffer.len() < total_samples {
67 self.buffer.resize(total_samples, 0);
68 }
69 }
70
71 pub fn generate_wav_file(&mut self, output_dir: &String) -> Result<(), String> {
72 if self.buffer.len() % (CHANNELS as usize) != 0 {
73 self.buffer.push(0);
74 println!("Completed buffer to respect stereo format.");
75 }
76
77 let spec = WavSpec {
78 channels: CHANNELS,
79 sample_rate: SAMPLE_RATE,
80 bits_per_sample: 16,
81 sample_format: SampleFormat::Int,
82 };
83
84 let mut writer = WavWriter::create(output_dir, spec).map_err(|e|
85 format!("Error creating WAV file: {}", e)
86 )?;
87
88 for sample in &self.buffer {
89 writer.write_sample(*sample).map_err(|e| format!("Error writing sample: {:?}", e))?;
90 }
91
92 writer.finalize().map_err(|e| format!("Error finalizing WAV: {:?}", e))?;
93
94 Ok(())
95 }
96
97 pub fn insert_note(
98 &mut self,
99 waveform: String,
100 freq: f32,
101 amp: f32,
102 start_time_ms: f32,
103 duration_ms: f32,
104 synth_params: HashMap<String, Value>,
105 note_params: HashMap<String, Value>,
106 automation: Option<HashMap<String, Value>>
107 ) {
108 let valid_synth_params = vec!["attack", "decay", "sustain", "release"];
109 let valid_note_params = vec![
110 "duration",
111 "velocity",
112 "glide",
113 "slide",
114 "amp",
115 "target_freq",
116 "target_amp",
117 "modulation",
118 "expression",
119 "automate"
121 ];
122
123 for key in synth_params.keys() {
125 if !valid_synth_params.contains(&key.as_str()) {
126 eprintln!("⚠️ Unknown synth parameter: '{}'", key);
127 }
128 }
129
130 for key in note_params.keys() {
132 if !valid_note_params.contains(&key.as_str()) {
133 eprintln!("⚠️ Unknown note parameter: '{}'", key);
134 }
135 }
136
137 let attack = self.extract_f32(&synth_params, "attack").unwrap_or(0.0);
139 let decay = self.extract_f32(&synth_params, "decay").unwrap_or(0.0);
140 let sustain = self.extract_f32(&synth_params, "sustain").unwrap_or(1.0);
141 let release = self.extract_f32(&synth_params, "release").unwrap_or(0.0);
142 let attack_s = if attack > 10.0 { attack / 1000.0 } else { attack };
143 let decay_s = if decay > 10.0 { decay / 1000.0 } else { decay };
144 let release_s = if release > 10.0 { release / 1000.0 } else { release };
145 let sustain_level = if sustain > 1.0 {
146 (sustain / 100.0).clamp(0.0, 1.0)
147 } else {
148 sustain.clamp(0.0, 1.0)
149 };
150
151 let duration_ms = self.extract_f32(¬e_params, "duration").unwrap_or(duration_ms);
153 let velocity = self.extract_f32(¬e_params, "velocity").unwrap_or(1.0);
154 let glide = self.extract_boolean(¬e_params, "glide").unwrap_or(false);
155 let slide = self.extract_boolean(¬e_params, "slide").unwrap_or(false);
156
157 let _amplitude = (i16::MAX as f32) * amp.clamp(0.0, 1.0) * velocity.clamp(0.0, 1.0);
158
159 let freq_start = freq;
161 let mut freq_end = freq;
162 let amp_start = amp * velocity.clamp(0.0, 1.0);
163 let mut amp_end = amp_start;
164
165 if glide {
166 if let Some(Value::Number(target_freq)) = note_params.get("target_freq") {
167 freq_end = *target_freq;
168 } else {
169 freq_end = freq * 1.5; }
171 }
172
173 if slide {
174 if let Some(Value::Number(target_amp)) = note_params.get("target_amp") {
175 amp_end = *target_amp * velocity.clamp(0.0, 1.0);
176 } else {
177 amp_end = amp_start * 0.5; }
179 }
180 let sample_rate = SAMPLE_RATE as f32;
181 let channels = CHANNELS as usize;
182
183 let total_samples = ((duration_ms / 1000.0) * sample_rate) as usize;
184 let start_sample = ((start_time_ms / 1000.0) * sample_rate) as usize;
185
186 let (volume_env, pan_env, pitch_env) = Self::env_maps_from_automation(&automation);
188
189 let mut stereo_samples: Vec<i16> = Vec::with_capacity(total_samples * 2);
190 let fade_len = (sample_rate * 0.01) as usize; let attack_samples = (attack_s * sample_rate) as usize;
193 let decay_samples = (decay_s * sample_rate) as usize;
194 let release_samples = (release_s * sample_rate) as usize;
195 let sustain_samples = if total_samples > attack_samples + decay_samples + release_samples {
196 total_samples - attack_samples - decay_samples - release_samples
197 } else {
198 0
199 };
200
201 for i in 0..total_samples {
202 let t = ((start_sample + i) as f32) / sample_rate;
203
204 let current_freq = if glide {
206 freq_start + ((freq_end - freq_start) * (i as f32)) / (total_samples as f32)
207 } else {
208 freq
209 };
210
211 let pitch_semi = Self::eval_env_map(&pitch_env, (i as f32) / (total_samples as f32), 0.0);
213 let current_freq = current_freq * (2.0_f32).powf(pitch_semi / 12.0);
214
215 let current_amp = if slide {
217 amp_start + ((amp_end - amp_start) * (i as f32)) / (total_samples as f32)
218 } else {
219 amp_start
220 };
221
222 let mut value = Self::oscillator_sample(&waveform, current_freq, t);
223
224 let envelope = Self::adsr_envelope_value(
226 i,
227 attack_samples,
228 decay_samples,
229 sustain_samples,
230 release_samples,
231 sustain_level,
232 );
233
234 if i < fade_len {
236 value *= (i as f32) / (fade_len as f32);
237 } else if i >= total_samples - fade_len {
238 value *= ((total_samples - i) as f32) / (fade_len as f32);
239 }
240
241 value *= envelope;
242 let mut sample_val = value * (i16::MAX as f32) * current_amp;
244
245 let vol_mul = Self::eval_env_map(&volume_env, (i as f32) / (total_samples as f32), 1.0)
247 .clamp(0.0, 10.0);
248 sample_val *= vol_mul;
249
250 let pan_val = Self::eval_env_map(&pan_env, (i as f32) / (total_samples as f32), 0.0)
252 .clamp(-1.0, 1.0);
253 let (left_gain, right_gain) = Self::pan_gains(pan_val);
254
255 let left = (sample_val * left_gain)
256 .round()
257 .clamp(i16::MIN as f32, i16::MAX as f32) as i16;
258 let right = (sample_val * right_gain)
259 .round()
260 .clamp(i16::MIN as f32, i16::MAX as f32) as i16;
261
262 stereo_samples.push(left);
263 stereo_samples.push(right);
264 }
265
266 self.mix_stereo_samples_into_buffer(start_sample, channels, &stereo_samples);
267 }
268
269 pub fn insert_sample(
270 &mut self,
271 filepath: &str,
272 time_secs: f32,
273 dur_sec: f32,
274 effects: Option<HashMap<String, Value>>,
275 variable_table: &VariableTable
276 ) {
277 if filepath.is_empty() {
278 eprintln!("❌ Empty file path provided for audio sample.");
279 return;
280 }
281
282 let root = Path::new(env!("CARGO_MANIFEST_DIR"));
283 let module_root = Path::new(&self.module_name);
284 let resolved_path: String;
285
286 let mut var_path = filepath.to_string();
288 if let Some(Value::String(variable_path)) = variable_table.variables.get(filepath) {
289 var_path = variable_path.clone();
290 } else if let Some(Value::Sample(sample_path)) = variable_table.variables.get(filepath) {
291 var_path = sample_path.clone();
292 }
293
294 if var_path.starts_with("devalang://") {
296 let path_after_protocol = var_path.replace("devalang://", "");
297 let parts: Vec<&str> = path_after_protocol.split('/').collect();
298
299 if parts.len() < 3 {
300 eprintln!(
301 "❌ Invalid devalang:// path format. Expected devalang://<type>/<author>.<bank>/<entity>"
302 );
303 return;
304 }
305
306 let obj_type = parts[0];
307 let bank_name = parts[1];
308 let entity_name = parts[2];
309
310 resolved_path = root
311 .join(".deva")
312 .join(obj_type)
313 .join(bank_name)
314 .join(format!("{}.wav", entity_name))
315 .to_string_lossy()
316 .to_string();
317 } else {
318 let entry_dir = module_root.parent().unwrap_or(root);
320 let absolute_path = root.join(entry_dir).join(&var_path);
321
322 resolved_path = normalize_path(absolute_path.to_string_lossy().to_string());
323 }
324
325 if !Path::new(&resolved_path).exists() {
327 eprintln!("❌ Audio file not found at: {}", resolved_path);
328 return;
329 }
330
331 let file = match File::open(&resolved_path) {
332 Ok(f) => BufReader::new(f),
333 Err(e) => {
334 eprintln!("❌ Failed to open audio file {}: {}", resolved_path, e);
335 return;
336 }
337 };
338
339 let decoder = match Decoder::new(file) {
340 Ok(d) => d,
341 Err(e) => {
342 eprintln!("❌ Failed to decode audio file {}: {}", resolved_path, e);
343 return;
344 }
345 };
346
347 let max_mono_samples = (dur_sec * (SAMPLE_RATE as f32)) as usize;
348 let samples: Vec<i16> = decoder.convert_samples().take(max_mono_samples).collect();
349
350 if samples.is_empty() {
351 eprintln!("❌ No samples read from {}", resolved_path);
352 return;
353 }
354
355 let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
357 let required_len = offset + samples.len() * (CHANNELS as usize);
358 if self.buffer.len() < required_len {
359 self.buffer.resize(required_len, 0);
360 }
361
362 if let Some(effects_map) = effects {
364 self.pad_samples(&samples, time_secs, Some(effects_map));
365 } else {
366 self.pad_samples(&samples, time_secs, None);
367 }
368 }
369
370 fn pad_samples(
371 &mut self,
372 samples: &[i16],
373 time_secs: f32,
374 effects_map: Option<HashMap<String, Value>>
375 ) {
376 let offset = (time_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
377 let total_samples = samples.len();
378
379 let mut gain = 1.0;
381 let mut pan = 0.0;
382 let mut fade_in = 0.0;
383 let mut fade_out = 0.0;
384 let mut pitch = 1.0;
385 let mut drive = 0.0;
386 let mut reverb = 0.0;
387 let mut delay = 0.0; let delay_feedback = 0.35; if let Some(map) = &effects_map {
391 for (key, val) in map {
392 match (key.as_str(), val) {
393 ("gain", Value::Number(v)) => {
394 gain = *v;
395 }
396 ("pan", Value::Number(v)) => {
397 pan = *v;
398 }
399 ("fadeIn", Value::Number(v)) => {
400 fade_in = *v;
401 }
402 ("fadeOut", Value::Number(v)) => {
403 fade_out = *v;
404 }
405 ("pitch", Value::Number(v)) => {
406 pitch = *v;
407 }
408 ("drive", Value::Number(v)) => {
409 drive = *v;
410 }
411 ("reverb", Value::Number(v)) => {
412 reverb = *v;
413 }
414 ("delay", Value::Number(v)) => {
415 delay = *v;
416 }
417 _ => eprintln!("⚠️ Unknown or invalid effect '{}'", key),
418 }
419 }
420 }
421
422 let fade_in_samples = (fade_in * (SAMPLE_RATE as f32)) as usize;
423 let fade_out_samples = (fade_out * (SAMPLE_RATE as f32)) as usize;
424
425 let delay_samples = if delay > 0.0 { (delay * (SAMPLE_RATE as f32)) as usize } else { 0 };
426 let mut delay_buffer: Vec<f32> = vec![0.0; total_samples + delay_samples];
427
428 for i in 0..total_samples {
429 let pitch_index = if pitch != 1.0 { ((i as f32) / pitch) as usize } else { i };
431
432 let mut adjusted = if pitch_index < total_samples {
433 samples[pitch_index] as f32
434 } else {
435 0.0
436 };
437
438 adjusted *= gain;
440
441 if fade_in_samples > 0 && i < fade_in_samples {
443 adjusted *= (i as f32) / (fade_in_samples as f32);
444 }
445 if fade_out_samples > 0 && i >= total_samples.saturating_sub(fade_out_samples) {
446 adjusted *= ((total_samples - i) as f32) / (fade_out_samples as f32);
447 }
448
449 if drive > 0.0 {
451 let normalized = adjusted / (i16::MAX as f32);
452 let pre_gain = (10f32).powf(drive / 20.0); let driven = (normalized * pre_gain).tanh();
454 adjusted = driven * (i16::MAX as f32);
455 }
456
457 if delay_samples > 0 && i >= delay_samples {
459 let echo = delay_buffer[i - delay_samples] * delay_feedback;
460 adjusted += echo;
461 }
462 if delay_samples > 0 {
463 delay_buffer[i] = adjusted;
464 }
465
466 if reverb > 0.0 {
468 let reverb_delay = (0.03 * (SAMPLE_RATE as f32)) as usize;
469 if i >= reverb_delay {
470 adjusted += (self.buffer[offset + i - reverb_delay] as f32) * reverb;
471 }
472 }
473
474 let adjusted_sample = adjusted.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
476
477 let (left_gain, right_gain) = Self::pan_gains(pan);
479
480 let left = ((adjusted_sample as f32) * left_gain) as i16;
481 let right = ((adjusted_sample as f32) * right_gain) as i16;
482
483 let left_pos = offset + i * 2;
484 let right_pos = left_pos + 1;
485
486 if right_pos < self.buffer.len() {
487 self.buffer[left_pos] = self.buffer[left_pos].saturating_add(left);
488 self.buffer[right_pos] = self.buffer[right_pos].saturating_add(right);
489 }
490 }
491 }
492
493 fn env_maps_from_automation(
496 automation: &Option<HashMap<String, Value>>
497 ) -> (
498 Option<HashMap<String, Value>>,
499 Option<HashMap<String, Value>>,
500 Option<HashMap<String, Value>>,
501 ) {
502 if let Some(auto) = automation {
503 let vol = match auto.get("volume") {
504 Some(Value::Map(m)) => Some(m.clone()),
505 _ => None,
506 };
507 let pan = match auto.get("pan") {
508 Some(Value::Map(m)) => Some(m.clone()),
509 _ => None,
510 };
511 let pit = match auto.get("pitch") {
512 Some(Value::Map(m)) => Some(m.clone()),
513 _ => None,
514 };
515 (vol, pan, pit)
516 } else {
517 (None, None, None)
518 }
519 }
520
521 fn eval_env_map(
523 env_opt: &Option<HashMap<String, Value>>,
524 progress: f32,
525 default_val: f32,
526 ) -> f32 {
527 let env = match env_opt {
528 Some(m) => m,
529 None => {
530 return default_val;
531 }
532 };
533 let mut points: Vec<(f32, f32)> = Vec::with_capacity(env.len());
534 for (k, v) in env.iter() {
535 let key = if k.ends_with('%') { &k[..k.len() - 1] } else { &k[..] };
537 if let Ok(mut p) = key.parse::<f32>() {
538 p = (p / 100.0).clamp(0.0, 1.0);
539 let val = match v {
540 Value::Number(n) => *n,
541 Value::String(s) => s.parse::<f32>().unwrap_or(default_val),
542 Value::Identifier(s) => s.parse::<f32>().unwrap_or(default_val),
543 _ => default_val,
544 };
545 points.push((p, val));
546 }
547 }
548 if points.is_empty() {
549 return default_val;
550 }
551 points.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
552 let t = progress.clamp(0.0, 1.0);
553 if t <= points[0].0 {
554 return points[0].1;
555 }
556 if t >= points[points.len() - 1].0 {
557 return points[points.len() - 1].1;
558 }
559 for w in points.windows(2) {
560 let (p0, v0) = w[0];
561 let (p1, v1) = w[1];
562 if t >= p0 && t <= p1 {
563 let ratio = if (p1 - p0).abs() < std::f32::EPSILON {
564 0.0
565 } else {
566 (t - p0) / (p1 - p0)
567 };
568 return v0 + (v1 - v0) * ratio;
569 }
570 }
571 default_val
572 }
573
574 fn oscillator_sample(waveform: &str, current_freq: f32, t: f32) -> f32 {
575 let phase = 2.0 * std::f32::consts::PI * current_freq * t;
576 match waveform {
577 "sine" => phase.sin(),
578 "square" => {
579 if phase.sin() >= 0.0 { 1.0 } else { -1.0 }
580 }
581 "saw" => 2.0 * (current_freq * t - (current_freq * t + 0.5).floor()),
582 "triangle" => (2.0 * (2.0 * (current_freq * t).fract() - 1.0)).abs() * 2.0 - 1.0,
583 _ => 0.0,
584 }
585 }
586
587 fn adsr_envelope_value(
588 i: usize,
589 attack_samples: usize,
590 decay_samples: usize,
591 sustain_samples: usize,
592 release_samples: usize,
593 sustain_level: f32,
594 ) -> f32 {
595 if i < attack_samples {
596 (i as f32) / (attack_samples as f32)
597 } else if i < attack_samples + decay_samples {
598 1.0 - (1.0 - sustain_level) * (((i - attack_samples) as f32) / (decay_samples as f32))
599 } else if i < attack_samples + decay_samples + sustain_samples {
600 sustain_level
601 } else if release_samples > 0 {
602 sustain_level
603 * (1.0
604 - ((i - attack_samples - decay_samples - sustain_samples) as f32)
605 / (release_samples as f32))
606 } else {
607 0.0
608 }
609 }
610
611 fn pan_gains(pan_val: f32) -> (f32, f32) {
612 let left_gain = 1.0 - pan_val.max(0.0);
613 let right_gain = 1.0 + pan_val.min(0.0);
614 (left_gain, right_gain)
615 }
616
617 fn mix_stereo_samples_into_buffer(
618 &mut self,
619 start_sample: usize,
620 channels: usize,
621 stereo_samples: &[i16],
622 ) {
623 let offset = start_sample * channels;
624 let required_len = offset + stereo_samples.len();
625
626 if self.buffer.len() < required_len {
627 self.buffer.resize(required_len, 0);
628 }
629
630 for (i, sample) in stereo_samples.iter().enumerate() {
631 self.buffer[offset + i] = self.buffer[offset + i].saturating_add(*sample);
634 }
635 }
636
637 fn extract_f32(&self, map: &HashMap<String, Value>, key: &str) -> Option<f32> {
638 match map.get(key) {
639 Some(Value::Number(n)) => Some(*n),
640 Some(Value::String(s)) => s.parse::<f32>().ok(),
641 Some(Value::Boolean(b)) => Some(if *b { 1.0 } else { 0.0 }),
642 _ => None,
643 }
644 }
645
646 fn extract_boolean(&self, map: &HashMap<String, Value>, key: &str) -> Option<bool> {
647 match map.get(key) {
648 Some(Value::Boolean(b)) => Some(*b),
649 Some(Value::Number(n)) => Some(*n != 0.0),
650 Some(Value::Identifier(s)) => {
651 if s == "true" { Some(true) } else if s == "false" { Some(false) } else { None }
652 }
653 Some(Value::String(s)) => {
654 if s == "true" { Some(true) } else if s == "false" { Some(false) } else { None }
655 }
656 _ => None,
657 }
658 }
659}