devalang_core/core/audio/
engine.rs

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            // allow per-note automation map
120            "automate"
121        ];
122
123        // Synth params validation
124        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        // Note params validation
131        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        // Synth parameters
138        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        // Note parameters
152        let duration_ms = self.extract_f32(&note_params, "duration").unwrap_or(duration_ms);
153        let velocity = self.extract_f32(&note_params, "velocity").unwrap_or(1.0);
154        let glide = self.extract_boolean(&note_params, "glide").unwrap_or(false);
155        let slide = self.extract_boolean(&note_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        // Logic for glide and slide
160        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; // By default, glide to a perfect fifth
170            }
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; // By default, slide to half the amplitude
178            }
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        // Precompute automation envelopes
187        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; // 10 ms fade
191
192        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            // Glide
205            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            // Pitch automation (in semitones), applied as frequency multiplier
212            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            // Slide
216            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            // ADSR envelope
225            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            // Fade in/out
235            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            // Apply dynamic amplitude (slide + velocity)
243            let mut sample_val = value * (i16::MAX as f32) * current_amp;
244
245            // Volume automation multiplier
246            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            // Pan automation [-1..1]; consistency with pad_samples method
251            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        // Get the variable path from the variable table
287        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        // Handle devalang:// protocol
295        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            // Else, resolve as a relative path
319            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        // Verify if the file exists
326        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        // Calculate buffer offset and size
356        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        // Apply effects and mix
363        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        // Default values
380        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; // delay time in seconds
388        let delay_feedback = 0.35; // default feedback
389
390        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            // PITCH FIRST
430            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            // GAIN
439            adjusted *= gain;
440
441            // FADE IN/OUT
442            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            // DRIVE (soft)
450            if drive > 0.0 {
451                let normalized = adjusted / (i16::MAX as f32);
452                let pre_gain = (10f32).powf(drive / 20.0); // dB mapping
453                let driven = (normalized * pre_gain).tanh();
454                adjusted = driven * (i16::MAX as f32);
455            }
456
457            // DELAY
458            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            // REVERB
467            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            // CLAMP
475            let adjusted_sample = adjusted.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
476
477            // PAN
478            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    // ===== Helper methods to keep long functions modular and readable =====
494
495    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    // Evaluate envelope map at progress [0,1]
522    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            // accept keys like "0" or "0%"
536            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            // Debug: track if we hit non-zero samples (to trace silent buffers)
632            // if i == 0 { eprintln!("[debug] first stereo sample: {}", sample); }
633            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}