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