devalang_core/core/audio/engine/
synth.rs1use devalang_types::Value;
2use std::collections::HashMap;
3
4const SAMPLE_RATE: u32 = 44100;
6const CHANNELS: u16 = 2;
7
8#[derive(Debug, Clone, PartialEq)]
15pub struct AudioEngine {
16 pub volume: f32,
18 pub buffer: Vec<i16>,
20 pub module_name: String,
22 pub note_count: usize,
24}
25
26impl AudioEngine {
27 pub fn new(module_name: String) -> Self {
28 AudioEngine {
29 volume: 1.0,
30 buffer: vec![],
31 module_name,
32 note_count: 0,
33 }
34 }
35
36 pub fn get_buffer(&self) -> &[i16] {
37 &self.buffer
38 }
39
40 pub fn get_normalized_buffer(&self) -> Vec<f32> {
41 self.buffer.iter().map(|&s| (s as f32) / 32768.0).collect()
42 }
43
44 pub fn mix(&mut self, other: &AudioEngine) {
45 let max_len = self.buffer.len().max(other.buffer.len());
46 self.buffer.resize(max_len, 0);
47
48 for (i, &sample) in other.buffer.iter().enumerate() {
49 self.buffer[i] = self.buffer[i].saturating_add(sample);
50 }
51 }
52
53 pub fn merge_with(&mut self, other: AudioEngine) {
54 if other.buffer.iter().all(|&s| s == 0) {
55 eprintln!("⚠️ Skipping merge: other buffer is silent");
56 return;
57 }
58
59 if self.buffer.iter().all(|&s| s == 0) {
60 self.buffer = other.buffer;
61 return;
62 }
63
64 self.mix(&other);
65 }
66
67 pub fn set_duration(&mut self, duration_secs: f32) {
68 let total_samples = (duration_secs * (SAMPLE_RATE as f32) * (CHANNELS as f32)) as usize;
69
70 if self.buffer.len() < total_samples {
71 self.buffer.resize(total_samples, 0);
72 }
73 }
74
75 pub fn generate_wav_file(&mut self, output_dir: &String) -> Result<(), String> {
76 if self.buffer.len() % (CHANNELS as usize) != 0 {
77 self.buffer.push(0);
78 println!("Completed buffer to respect stereo format.");
79 }
80
81 let spec = hound::WavSpec {
82 channels: CHANNELS,
83 sample_rate: SAMPLE_RATE,
84 bits_per_sample: 16,
85 sample_format: hound::SampleFormat::Int,
86 };
87
88 let mut writer = hound::WavWriter::create(output_dir, spec)
89 .map_err(|e| format!("Error creating WAV file: {}", e))?;
90
91 for sample in &self.buffer {
92 writer
93 .write_sample(*sample)
94 .map_err(|e| format!("Error writing sample: {:?}", e))?;
95 }
96
97 writer
98 .finalize()
99 .map_err(|e| format!("Error finalizing WAV: {:?}", e))?;
100
101 Ok(())
102 }
103
104 pub fn insert_note(
106 &mut self,
107 waveform: String,
108 freq: f32,
109 amp: f32,
110 start_time_ms: f32,
111 duration_ms: f32,
112 synth_params: HashMap<String, Value>,
113 note_params: HashMap<String, Value>,
114 automation: Option<HashMap<String, Value>>,
115 ) {
116 let attack = self.extract_f32(&synth_params, "attack").unwrap_or(0.0);
118 let decay = self.extract_f32(&synth_params, "decay").unwrap_or(0.0);
119 let sustain = self.extract_f32(&synth_params, "sustain").unwrap_or(1.0);
120 let release = self.extract_f32(&synth_params, "release").unwrap_or(0.0);
121 let attack_s = if attack > 10.0 {
122 attack / 1000.0
123 } else {
124 attack
125 };
126 let decay_s = if decay > 10.0 { decay / 1000.0 } else { decay };
127 let release_s = if release > 10.0 {
128 release / 1000.0
129 } else {
130 release
131 };
132 let sustain_level = if sustain > 1.0 {
133 (sustain / 100.0).clamp(0.0, 1.0)
134 } else {
135 sustain.clamp(0.0, 1.0)
136 };
137
138 let duration_ms = self
139 .extract_f32(¬e_params, "duration")
140 .unwrap_or(duration_ms);
141 let velocity = self.extract_f32(¬e_params, "velocity").unwrap_or(1.0);
142 let glide = self.extract_boolean(¬e_params, "glide").unwrap_or(false);
143 let slide = self.extract_boolean(¬e_params, "slide").unwrap_or(false);
144
145 let _amplitude = (i16::MAX as f32) * amp.clamp(0.0, 1.0) * velocity.clamp(0.0, 1.0);
146
147 let freq_start = freq;
148 let mut freq_end = freq;
149 let amp_start = amp * velocity.clamp(0.0, 1.0);
150 let mut amp_end = amp_start;
151
152 if glide {
153 if let Some(Value::Number(target_freq)) = note_params.get("target_freq") {
154 freq_end = *target_freq;
155 } else {
156 freq_end = freq * 1.5;
157 }
158 }
159
160 if slide {
161 if let Some(Value::Number(target_amp)) = note_params.get("target_amp") {
162 amp_end = *target_amp * velocity.clamp(0.0, 1.0);
163 } else {
164 amp_end = amp_start * 0.5;
165 }
166 }
167
168 let sample_rate = SAMPLE_RATE as f32;
169 let channels = CHANNELS as usize;
170
171 let total_samples = ((duration_ms / 1000.0) * sample_rate) as usize;
172 let start_sample = ((start_time_ms / 1000.0) * sample_rate) as usize;
173
174 let (volume_env, pan_env, pitch_env) =
175 crate::core::audio::engine::helpers::env_maps_from_automation(&automation);
176
177 let mut stereo_samples: Vec<i16> = Vec::with_capacity(total_samples * 2);
178 let fade_len = (sample_rate * 0.01) as usize; let attack_samples = (attack_s * sample_rate) as usize;
181 let decay_samples = (decay_s * sample_rate) as usize;
182 let release_samples = (release_s * sample_rate) as usize;
183 let sustain_samples = if total_samples > attack_samples + decay_samples + release_samples {
184 total_samples - attack_samples - decay_samples - release_samples
185 } else {
186 0
187 };
188
189 for i in 0..total_samples {
190 let t = ((start_sample + i) as f32) / sample_rate;
191
192 let current_freq = if glide {
194 freq_start + ((freq_end - freq_start) * (i as f32)) / (total_samples as f32)
195 } else {
196 freq
197 };
198
199 let pitch_semi = crate::core::audio::engine::helpers::eval_env_map(
201 &pitch_env,
202 (i as f32) / (total_samples as f32),
203 0.0,
204 );
205 let current_freq = current_freq * (2.0_f32).powf(pitch_semi / 12.0);
206
207 let current_amp = if slide {
209 amp_start + ((amp_end - amp_start) * (i as f32)) / (total_samples as f32)
210 } else {
211 amp_start
212 };
213
214 let mut value =
215 crate::core::audio::engine::helpers::oscillator_sample(&waveform, current_freq, t);
216
217 let envelope = crate::core::audio::engine::helpers::adsr_envelope_value(
219 i,
220 attack_samples,
221 decay_samples,
222 sustain_samples,
223 release_samples,
224 sustain_level,
225 );
226
227 if i < fade_len {
229 value *= (i as f32) / (fade_len as f32);
230 } else if i >= total_samples - fade_len {
231 value *= ((total_samples - i) as f32) / (fade_len as f32);
232 }
233
234 value *= envelope;
235 let mut sample_val = value * (i16::MAX as f32) * current_amp;
236
237 let vol_mul = crate::core::audio::engine::helpers::eval_env_map(
238 &volume_env,
239 (i as f32) / (total_samples as f32),
240 1.0,
241 )
242 .clamp(0.0, 10.0);
243 sample_val *= vol_mul;
244
245 let pan_val = crate::core::audio::engine::helpers::eval_env_map(
246 &pan_env,
247 (i as f32) / (total_samples as f32),
248 0.0,
249 )
250 .clamp(-1.0, 1.0);
251 let (left_gain, right_gain) = crate::core::audio::engine::helpers::pan_gains(pan_val);
252
253 let left = (sample_val * left_gain)
254 .round()
255 .clamp(i16::MIN as f32, i16::MAX as f32) as i16;
256 let right = (sample_val * right_gain)
257 .round()
258 .clamp(i16::MIN as f32, i16::MAX as f32) as i16;
259
260 stereo_samples.push(left);
261 stereo_samples.push(right);
262 }
263
264 self.note_count = self.note_count.saturating_add(1);
266
267 crate::core::audio::engine::helpers::mix_stereo_samples_into_buffer(
268 self,
269 start_sample,
270 channels,
271 &stereo_samples,
272 );
273 }
274
275 fn extract_f32(&self, map: &HashMap<String, Value>, key: &str) -> Option<f32> {
277 match map.get(key) {
278 Some(Value::Number(n)) => Some(*n),
279 Some(Value::String(s)) => s.parse::<f32>().ok(),
280 Some(Value::Boolean(b)) => Some(if *b { 1.0 } else { 0.0 }),
281 _ => None,
282 }
283 }
284
285 fn extract_boolean(&self, map: &HashMap<String, Value>, key: &str) -> Option<bool> {
286 match map.get(key) {
287 Some(Value::Boolean(b)) => Some(*b),
288 Some(Value::Number(n)) => Some(*n != 0.0),
289 Some(Value::Identifier(s)) => {
290 if s == "true" {
291 Some(true)
292 } else if s == "false" {
293 Some(false)
294 } else {
295 None
296 }
297 }
298 Some(Value::String(s)) => {
299 if s == "true" {
300 Some(true)
301 } else if s == "false" {
302 Some(false)
303 } else {
304 None
305 }
306 }
307 _ => None,
308 }
309 }
310}