1use 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#[cfg(feature = "cli")]
8use crate::engine::plugin::{loader::load_plugin, runner::WasmPluginRunner};
9
10#[derive(Debug, Clone)]
12pub struct FilterDef {
13 pub filter_type: String, pub cutoff: f32, pub resonance: f32, }
17
18#[derive(Debug, Clone)]
20pub struct SynthParams {
21 pub waveform: String,
22 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>, pub plugin_author: Option<String>,
31 pub plugin_name: Option<String>,
32 pub plugin_export: Option<String>,
33}
34
35impl Default for SynthParams {
36 fn default() -> Self {
37 Self {
38 waveform: "sine".to_string(),
39 attack: 0.01,
40 decay: 0.1,
41 sustain: 0.7,
42 release: 0.2,
43 synth_type: None,
44 filters: Vec::new(),
45 options: HashMap::new(),
46 plugin_author: None,
47 plugin_name: None,
48 plugin_export: None,
49 }
50 }
51}
52
53pub fn generate_note(
55 midi_note: u8,
56 duration_ms: f32,
57 velocity: f32,
58 params: &SynthParams,
59 sample_rate: u32,
60) -> Result<Vec<f32>> {
61 generate_note_with_options(
62 midi_note,
63 duration_ms,
64 velocity,
65 params,
66 sample_rate,
67 0.0,
68 0.0,
69 )
70}
71
72pub fn generate_note_with_options(
76 midi_note: u8,
77 duration_ms: f32,
78 velocity: f32,
79 params: &SynthParams,
80 sample_rate: u32,
81 pan: f32, detune: f32, ) -> Result<Vec<f32>> {
84 #[cfg(feature = "cli")]
88 {
89 if let Some(ref author) = params.plugin_author {
90 if let Some(ref name) = params.plugin_name {
91 return generate_note_with_plugin(
93 midi_note,
94 duration_ms,
95 velocity,
96 params,
97 sample_rate,
98 pan,
99 detune,
100 author,
101 name,
102 params.plugin_export.as_deref(),
103 );
104 }
105 }
106 }
107
108 let base_frequency = midi_to_frequency(midi_note);
111
112 let frequency = if detune.abs() > 0.01 {
114 base_frequency * 2.0_f32.powf(detune / 1200.0)
115 } else {
116 base_frequency
117 };
118
119 let velocity = velocity.clamp(0.0, 1.0);
120
121 let mut modified_params = params.clone();
123
124 let synth_type: Option<Box<dyn SynthType>> = if let Some(ref type_name) = params.synth_type {
126 get_synth_type(type_name)
127 } else {
128 None
129 };
130
131 if let Some(ref stype) = synth_type {
132 stype.modify_params(&mut modified_params);
133 }
134
135 let attack_samples = time_to_samples(modified_params.attack, sample_rate);
137 let decay_samples = time_to_samples(modified_params.decay, sample_rate);
138 let release_samples = time_to_samples(modified_params.release, sample_rate);
139
140 let duration_seconds = duration_ms / 1000.0;
142 let total_samples = time_to_samples(duration_seconds, sample_rate);
143 let envelope_samples = attack_samples + decay_samples + release_samples;
144 let sustain_samples = total_samples.saturating_sub(envelope_samples);
145
146 let mut samples = Vec::with_capacity(total_samples * 2); let pan = pan.clamp(-1.0, 1.0);
150 let pan_angle = (pan + 1.0) * 0.25 * std::f32::consts::PI; let left_gain = pan_angle.cos();
152 let right_gain = pan_angle.sin();
153
154 for i in 0..total_samples {
155 let time = i as f32 / sample_rate as f32;
156
157 let osc_sample = oscillator_sample(&modified_params.waveform, frequency, time);
159
160 let envelope = adsr_envelope(
162 i,
163 attack_samples,
164 decay_samples,
165 sustain_samples,
166 release_samples,
167 modified_params.sustain,
168 );
169
170 let amplitude = osc_sample * envelope * velocity * 0.3; samples.push(amplitude * left_gain);
175 samples.push(amplitude * right_gain);
176 }
177
178 for filter in &modified_params.filters {
180 apply_filter(&mut samples, filter, sample_rate)?;
181 }
182
183 if let Some(stype) = synth_type {
185 stype.post_process(&mut samples, sample_rate, &modified_params.options)?;
186 }
187
188 Ok(samples)
189}
190
191fn apply_filter(samples: &mut [f32], filter: &FilterDef, sample_rate: u32) -> Result<()> {
193 match filter.filter_type.to_lowercase().as_str() {
194 "lowpass" => apply_lowpass(samples, filter.cutoff, sample_rate),
195 "highpass" => apply_highpass(samples, filter.cutoff, sample_rate),
196 "bandpass" => apply_bandpass(samples, filter.cutoff, sample_rate),
197 _ => Ok(()),
198 }
199}
200
201fn apply_lowpass(samples: &mut [f32], cutoff: f32, sample_rate: u32) -> Result<()> {
203 let dt = 1.0 / sample_rate as f32;
204 let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff);
205 let alpha = dt / (rc + dt);
206
207 let mut prev = 0.0f32;
208 for i in (0..samples.len()).step_by(2) {
209 let filtered = prev + alpha * (samples[i] - prev);
211 prev = filtered;
212 samples[i] = filtered;
213
214 if i + 1 < samples.len() {
216 samples[i + 1] = filtered;
217 }
218 }
219
220 Ok(())
221}
222
223fn apply_highpass(samples: &mut [f32], cutoff: f32, sample_rate: u32) -> Result<()> {
225 let dt = 1.0 / sample_rate as f32;
226 let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff);
227 let alpha = rc / (rc + dt);
228
229 let mut prev_input = 0.0f32;
230 let mut prev_output = 0.0f32;
231
232 for i in (0..samples.len()).step_by(2) {
233 let current = samples[i];
234 let filtered = alpha * (prev_output + current - prev_input);
235
236 prev_input = current;
237 prev_output = filtered;
238
239 samples[i] = filtered;
240 if i + 1 < samples.len() {
241 samples[i + 1] = filtered;
242 }
243 }
244
245 Ok(())
246}
247
248fn apply_bandpass(samples: &mut [f32], center: f32, sample_rate: u32) -> Result<()> {
250 let bandwidth = center * 0.5; apply_highpass(samples, center - bandwidth, sample_rate)?;
254 apply_lowpass(samples, center + bandwidth, sample_rate)?;
255
256 Ok(())
257}
258
259pub fn generate_chord(
261 midi_notes: &[u8],
262 duration_ms: f32,
263 velocity: f32,
264 params: &SynthParams,
265 sample_rate: u32,
266) -> Result<Vec<f32>> {
267 generate_chord_with_options(
268 midi_notes,
269 duration_ms,
270 velocity,
271 params,
272 sample_rate,
273 0.0,
274 0.0,
275 0.0,
276 )
277}
278
279pub fn generate_chord_with_options(
281 midi_notes: &[u8],
282 duration_ms: f32,
283 velocity: f32,
284 params: &SynthParams,
285 sample_rate: u32,
286 pan: f32, detune: f32, spread: f32, ) -> Result<Vec<f32>> {
290 if midi_notes.is_empty() {
291 return Ok(Vec::new());
292 }
293
294 let num_notes = midi_notes.len();
295 let spread = spread.clamp(0.0, 1.0);
296
297 let mut result: Option<Vec<f32>> = None;
299
300 for (i, &midi_note) in midi_notes.iter().enumerate() {
301 let note_pan = if num_notes > 1 && spread > 0.0 {
303 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)
307 } else {
308 pan
309 };
310
311 let note_samples = generate_note_with_options(
313 midi_note,
314 duration_ms,
315 velocity,
316 params,
317 sample_rate,
318 note_pan,
319 detune,
320 )?;
321
322 match result {
324 None => {
325 result = Some(note_samples);
326 }
327 Some(ref mut buffer) => {
328 for (j, sample) in note_samples.iter().enumerate() {
330 if j < buffer.len() {
331 buffer[j] = (buffer[j] + sample) / 2.0;
332 }
333 }
334 }
335 }
336 }
337
338 Ok(result.unwrap_or_default())
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 #[test]
346 fn test_generate_note() {
347 let params = SynthParams::default();
348 let samples = generate_note(60, 500.0, 0.8, ¶ms, 44100).unwrap();
349
350 assert!(samples.len() > 0);
352 assert_eq!(samples.len() % 2, 0); let has_audio = samples.iter().any(|&s| s.abs() > 0.001);
356 assert!(has_audio);
357 }
358
359 #[test]
360 fn test_generate_chord() {
361 let params = SynthParams::default();
362 let samples = generate_chord(&[60, 64, 67], 500.0, 0.8, ¶ms, 44100).unwrap();
363
364 assert!(samples.len() > 0);
366
367 let has_audio = samples.iter().any(|&s| s.abs() > 0.001);
369 assert!(has_audio);
370 }
371}
372
373#[cfg(feature = "cli")]
375fn generate_note_with_plugin(
376 midi_note: u8,
377 duration_ms: f32,
378 velocity: f32,
379 params: &SynthParams,
380 sample_rate: u32,
381 pan: f32,
382 detune: f32,
383 plugin_author: &str,
384 plugin_name: &str,
385 plugin_export: Option<&str>,
386) -> Result<Vec<f32>> {
387 use once_cell::sync::Lazy;
388 use std::sync::Mutex;
389
390 println!(
391 "🎸 [PLUGIN_GEN] Generating note with plugin: {}.{} (export: {:?})",
392 plugin_author, plugin_name, plugin_export
393 );
394
395 static PLUGIN_RUNNER: Lazy<Mutex<WasmPluginRunner>> =
397 Lazy::new(|| Mutex::new(WasmPluginRunner::new()));
398
399 static PLUGIN_CACHE: Lazy<Mutex<HashMap<String, Vec<u8>>>> =
401 Lazy::new(|| Mutex::new(HashMap::new()));
402
403 let plugin_key = format!("{}.{}", plugin_author, plugin_name);
405 let mut cache = PLUGIN_CACHE.lock().unwrap();
406
407 let wasm_bytes = if let Some(bytes) = cache.get(&plugin_key) {
408 println!(" Using cached plugin ({} bytes)", bytes.len());
409 bytes.clone()
410 } else {
411 println!(" Loading plugin from disk...");
412 let (info, bytes) = load_plugin(plugin_author, plugin_name)
414 .map_err(|e| anyhow::anyhow!("Failed to load plugin: {}", e))?;
415
416 eprintln!(
417 "✅ Loaded plugin: {}.{} (v{})",
418 info.author,
419 info.name,
420 info.version.as_deref().unwrap_or("unknown")
421 );
422
423 cache.insert(plugin_key.clone(), bytes.clone());
424 bytes
425 };
426 drop(cache);
427
428 let base_frequency = midi_to_frequency(midi_note);
430 let frequency = if detune.abs() > 0.01 {
431 base_frequency * 2.0_f32.powf(detune / 1200.0)
432 } else {
433 base_frequency
434 };
435
436 let duration_seconds = duration_ms / 1000.0;
437 let total_samples = (duration_seconds * sample_rate as f32) as usize;
438 let mut buffer = vec![0.0f32; total_samples * 2]; let mut plugin_options = params.options.clone();
442
443 if !plugin_options.contains_key("waveform") {
445 let waveform_value = match params.waveform.as_str() {
447 "sine" => 0.0,
448 "saw" => 1.0,
449 "square" => 2.0,
450 "triangle" => 3.0,
451 _ => 0.0, };
453 plugin_options.insert("waveform".to_string(), waveform_value);
454 }
455
456 let runner = PLUGIN_RUNNER.lock().unwrap();
458 let synth_id = format!("{}_{}", plugin_key, plugin_export.unwrap_or("default"));
459
460 println!(
461 " 📋 Buffer before plugin: first 10 samples: {:?}",
462 &buffer[0..10.min(buffer.len())]
463 );
464
465 runner
466 .render_note_in_place(
467 &wasm_bytes,
468 &mut buffer,
469 Some(&synth_id),
470 plugin_export,
471 frequency,
472 velocity,
473 duration_ms as i32,
474 sample_rate as i32,
475 2, Some(&plugin_options),
477 )
478 .map_err(|e| anyhow::anyhow!("Plugin render error: {}", e))?;
479
480 println!(
481 " 📋 Buffer after plugin: first 10 samples: {:?}",
482 &buffer[0..10.min(buffer.len())]
483 );
484 println!(
485 " 📋 Buffer stats: len={}, max={:.4}, rms={:.4}",
486 buffer.len(),
487 buffer.iter().map(|s| s.abs()).fold(0.0f32, f32::max),
488 (buffer.iter().map(|s| s * s).sum::<f32>() / buffer.len() as f32).sqrt()
489 );
490
491 if pan.abs() > 0.01 {
493 let pan = pan.clamp(-1.0, 1.0);
494 let pan_angle = (pan + 1.0) * 0.25 * std::f32::consts::PI;
495 let left_gain = pan_angle.cos();
496 let right_gain = pan_angle.sin();
497
498 for i in (0..buffer.len()).step_by(2) {
499 if i + 1 < buffer.len() {
500 buffer[i] *= left_gain;
501 buffer[i + 1] *= right_gain;
502 }
503 }
504 }
505
506 for filter in ¶ms.filters {
508 apply_filter(&mut buffer, filter, sample_rate)?;
509 }
510
511 Ok(buffer)
512}