devalang_wasm/engine/audio/
generator.rs1use super::synth::types::{SynthType, get_synth_type};
2use super::synth::{adsr_envelope, midi_to_frequency, oscillator_sample, time_to_samples};
3use anyhow::Result;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone)]
9pub struct FilterDef {
10 pub filter_type: String, pub cutoff: f32, pub resonance: f32, }
14
15#[derive(Debug, Clone)]
17pub struct SynthParams {
18 pub waveform: String,
19 pub attack: f32, pub decay: f32, pub sustain: f32, pub release: f32, pub synth_type: Option<String>, pub filters: Vec<FilterDef>, pub options: HashMap<String, f32>, }
27
28impl Default for SynthParams {
29 fn default() -> Self {
30 Self {
31 waveform: "sine".to_string(),
32 attack: 0.01,
33 decay: 0.1,
34 sustain: 0.7,
35 release: 0.2,
36 synth_type: None,
37 filters: Vec::new(),
38 options: HashMap::new(),
39 }
40 }
41}
42
43pub fn generate_note(
45 midi_note: u8,
46 duration_ms: f32,
47 velocity: f32,
48 params: &SynthParams,
49 sample_rate: u32,
50) -> Result<Vec<f32>> {
51 generate_note_with_options(
52 midi_note,
53 duration_ms,
54 velocity,
55 params,
56 sample_rate,
57 0.0,
58 0.0,
59 )
60}
61
62pub fn generate_note_with_options(
64 midi_note: u8,
65 duration_ms: f32,
66 velocity: f32,
67 params: &SynthParams,
68 sample_rate: u32,
69 pan: f32, detune: f32, ) -> Result<Vec<f32>> {
72 let base_frequency = midi_to_frequency(midi_note);
73
74 let frequency = if detune.abs() > 0.01 {
76 base_frequency * 2.0_f32.powf(detune / 1200.0)
77 } else {
78 base_frequency
79 };
80
81 let velocity = velocity.clamp(0.0, 1.0);
82
83 let mut modified_params = params.clone();
85
86 let synth_type: Option<Box<dyn SynthType>> = if let Some(ref type_name) = params.synth_type {
88 get_synth_type(type_name)
89 } else {
90 None
91 };
92
93 if let Some(ref stype) = synth_type {
94 stype.modify_params(&mut modified_params);
95 }
96
97 let attack_samples = time_to_samples(modified_params.attack, sample_rate);
99 let decay_samples = time_to_samples(modified_params.decay, sample_rate);
100 let release_samples = time_to_samples(modified_params.release, sample_rate);
101
102 let duration_seconds = duration_ms / 1000.0;
104 let total_samples = time_to_samples(duration_seconds, sample_rate);
105 let envelope_samples = attack_samples + decay_samples + release_samples;
106 let sustain_samples = total_samples.saturating_sub(envelope_samples);
107
108 let mut samples = Vec::with_capacity(total_samples * 2); let pan = pan.clamp(-1.0, 1.0);
112 let pan_angle = (pan + 1.0) * 0.25 * std::f32::consts::PI; let left_gain = pan_angle.cos();
114 let right_gain = pan_angle.sin();
115
116 for i in 0..total_samples {
117 let time = i as f32 / sample_rate as f32;
118
119 let osc_sample = oscillator_sample(&modified_params.waveform, frequency, time);
121
122 let envelope = adsr_envelope(
124 i,
125 attack_samples,
126 decay_samples,
127 sustain_samples,
128 release_samples,
129 modified_params.sustain,
130 );
131
132 let amplitude = osc_sample * envelope * velocity * 0.3; samples.push(amplitude * left_gain);
137 samples.push(amplitude * right_gain);
138 }
139
140 for filter in &modified_params.filters {
142 apply_filter(&mut samples, filter, sample_rate)?;
143 }
144
145 if let Some(stype) = synth_type {
147 stype.post_process(&mut samples, sample_rate, &modified_params.options)?;
148 }
149
150 Ok(samples)
151}
152
153fn apply_filter(samples: &mut [f32], filter: &FilterDef, sample_rate: u32) -> Result<()> {
155 match filter.filter_type.to_lowercase().as_str() {
156 "lowpass" => apply_lowpass(samples, filter.cutoff, sample_rate),
157 "highpass" => apply_highpass(samples, filter.cutoff, sample_rate),
158 "bandpass" => apply_bandpass(samples, filter.cutoff, sample_rate),
159 _ => Ok(()),
160 }
161}
162
163fn apply_lowpass(samples: &mut [f32], cutoff: f32, sample_rate: u32) -> Result<()> {
165 let dt = 1.0 / sample_rate as f32;
166 let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff);
167 let alpha = dt / (rc + dt);
168
169 let mut prev = 0.0f32;
170 for i in (0..samples.len()).step_by(2) {
171 let filtered = prev + alpha * (samples[i] - prev);
173 prev = filtered;
174 samples[i] = filtered;
175
176 if i + 1 < samples.len() {
178 samples[i + 1] = filtered;
179 }
180 }
181
182 Ok(())
183}
184
185fn apply_highpass(samples: &mut [f32], cutoff: f32, sample_rate: u32) -> Result<()> {
187 let dt = 1.0 / sample_rate as f32;
188 let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff);
189 let alpha = rc / (rc + dt);
190
191 let mut prev_input = 0.0f32;
192 let mut prev_output = 0.0f32;
193
194 for i in (0..samples.len()).step_by(2) {
195 let current = samples[i];
196 let filtered = alpha * (prev_output + current - prev_input);
197
198 prev_input = current;
199 prev_output = filtered;
200
201 samples[i] = filtered;
202 if i + 1 < samples.len() {
203 samples[i + 1] = filtered;
204 }
205 }
206
207 Ok(())
208}
209
210fn apply_bandpass(samples: &mut [f32], center: f32, sample_rate: u32) -> Result<()> {
212 let bandwidth = center * 0.5; apply_highpass(samples, center - bandwidth, sample_rate)?;
216 apply_lowpass(samples, center + bandwidth, sample_rate)?;
217
218 Ok(())
219}
220
221pub fn generate_chord(
223 midi_notes: &[u8],
224 duration_ms: f32,
225 velocity: f32,
226 params: &SynthParams,
227 sample_rate: u32,
228) -> Result<Vec<f32>> {
229 generate_chord_with_options(
230 midi_notes,
231 duration_ms,
232 velocity,
233 params,
234 sample_rate,
235 0.0,
236 0.0,
237 0.0,
238 )
239}
240
241pub fn generate_chord_with_options(
243 midi_notes: &[u8],
244 duration_ms: f32,
245 velocity: f32,
246 params: &SynthParams,
247 sample_rate: u32,
248 pan: f32, detune: f32, spread: f32, ) -> Result<Vec<f32>> {
252 if midi_notes.is_empty() {
253 return Ok(Vec::new());
254 }
255
256 let num_notes = midi_notes.len();
257 let spread = spread.clamp(0.0, 1.0);
258
259 let mut result: Option<Vec<f32>> = None;
261
262 for (i, &midi_note) in midi_notes.iter().enumerate() {
263 let note_pan = if num_notes > 1 && spread > 0.0 {
265 let position = i as f32 / (num_notes - 1) as f32; let spread_amount = (position - 0.5) * 2.0 * spread; (pan + spread_amount).clamp(-1.0, 1.0)
269 } else {
270 pan
271 };
272
273 let note_samples = generate_note_with_options(
275 midi_note,
276 duration_ms,
277 velocity,
278 params,
279 sample_rate,
280 note_pan,
281 detune,
282 )?;
283
284 match result {
286 None => {
287 result = Some(note_samples);
288 }
289 Some(ref mut buffer) => {
290 for (j, sample) in note_samples.iter().enumerate() {
292 if j < buffer.len() {
293 buffer[j] = (buffer[j] + sample) / 2.0;
294 }
295 }
296 }
297 }
298 }
299
300 Ok(result.unwrap_or_default())
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn test_generate_note() {
309 let params = SynthParams::default();
310 let samples = generate_note(60, 500.0, 0.8, ¶ms, 44100).unwrap();
311
312 assert!(samples.len() > 0);
314 assert_eq!(samples.len() % 2, 0); let has_audio = samples.iter().any(|&s| s.abs() > 0.001);
318 assert!(has_audio);
319 }
320
321 #[test]
322 fn test_generate_chord() {
323 let params = SynthParams::default();
324 let samples = generate_chord(&[60, 64, 67], 500.0, 0.8, ¶ms, 44100).unwrap();
325
326 assert!(samples.len() > 0);
328
329 let has_audio = samples.iter().any(|&s| s.abs() > 0.001);
331 assert!(has_audio);
332 }
333}