1use super::lfo::{LfoParams, apply_lfo_modulation, generate_lfo_value};
2use super::synth::types::{SynthType, get_synth_type};
3use super::synth::{adsr_envelope, midi_to_frequency, oscillator_sample, time_to_samples};
4use anyhow::Result;
6use std::collections::HashMap;
7
8#[cfg(feature = "cli")]
9use crate::engine::plugin::{loader::load_plugin, runner::WasmPluginRunner};
10
11#[derive(Debug, Clone)]
13pub struct FilterDef {
14 pub filter_type: String, pub cutoff: f32, pub resonance: f32, }
18
19#[derive(Debug, Clone)]
21pub struct SynthParams {
22 pub waveform: String,
23 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 lfo: Option<LfoParams>, pub plugin_author: Option<String>,
33 pub plugin_name: Option<String>,
34 pub plugin_export: Option<String>,
35}
36
37impl Default for SynthParams {
38 fn default() -> Self {
39 Self {
40 waveform: "sine".to_string(),
41 attack: 0.01,
42 decay: 0.1,
43 sustain: 0.7,
44 release: 0.2,
45 synth_type: None,
46 filters: Vec::new(),
47 options: HashMap::new(),
48 lfo: None,
49 plugin_author: None,
50 plugin_name: None,
51 plugin_export: None,
52 }
53 }
54}
55
56pub fn generate_note(
58 midi_note: u8,
59 duration_ms: f32,
60 velocity: f32,
61 params: &SynthParams,
62 sample_rate: u32,
63) -> Result<Vec<f32>> {
64 generate_note_with_options(
65 midi_note,
66 duration_ms,
67 velocity,
68 params,
69 sample_rate,
70 0.0,
71 0.0,
72 )
73}
74
75pub fn generate_note_with_options(
79 midi_note: u8,
80 duration_ms: f32,
81 velocity: f32,
82 params: &SynthParams,
83 sample_rate: u32,
84 pan: f32, detune: f32, ) -> Result<Vec<f32>> {
87 #[cfg(feature = "cli")]
91 {
92 if let Some(ref author) = params.plugin_author {
93 if let Some(ref name) = params.plugin_name {
94 return generate_note_with_plugin(
96 midi_note,
97 duration_ms,
98 velocity,
99 params,
100 sample_rate,
101 pan,
102 detune,
103 author,
104 name,
105 params.plugin_export.as_deref(),
106 );
107 }
108 }
109 }
110
111 let base_frequency = midi_to_frequency(midi_note);
114
115 let frequency = if detune.abs() > 0.01 {
117 base_frequency * 2.0_f32.powf(detune / 1200.0)
118 } else {
119 base_frequency
120 };
121
122 let velocity = velocity.clamp(0.0, 1.0);
123
124 let mut modified_params = params.clone();
126
127 let synth_type: Option<Box<dyn SynthType>> = if let Some(ref type_name) = params.synth_type {
129 get_synth_type(type_name)
130 } else {
131 None
132 };
133
134 if let Some(ref stype) = synth_type {
135 stype.modify_params(&mut modified_params);
136 }
137
138 let attack_samples = time_to_samples(modified_params.attack, sample_rate);
140 let decay_samples = time_to_samples(modified_params.decay, sample_rate);
141 let release_samples = time_to_samples(modified_params.release, sample_rate);
142
143 let duration_seconds = duration_ms / 1000.0;
145 let total_samples = time_to_samples(duration_seconds, sample_rate);
146 let envelope_samples = attack_samples + decay_samples + release_samples;
147 let sustain_samples = total_samples.saturating_sub(envelope_samples);
148
149 let mut samples = Vec::with_capacity(total_samples * 2); let pan = pan.clamp(-1.0, 1.0);
153 let pan_angle = (pan + 1.0) * 0.25 * std::f32::consts::PI; let left_gain = pan_angle.cos();
155 let right_gain = pan_angle.sin();
156
157 let bpm = 120.0; for i in 0..total_samples {
161 let time = i as f32 / sample_rate as f32;
162
163 let mut osc_frequency = frequency;
165
166 if let Some(ref lfo) = modified_params.lfo {
168 use crate::engine::audio::lfo::LfoTarget;
169 if lfo.target == LfoTarget::Pitch {
170 let lfo_cents = generate_lfo_value(lfo, time, bpm) * 100.0; osc_frequency = frequency * 2.0_f32.powf(lfo_cents / 1200.0);
172 }
173 }
174
175 let osc_sample = oscillator_sample(&modified_params.waveform, osc_frequency, time);
176
177 let envelope = adsr_envelope(
179 i,
180 attack_samples,
181 decay_samples,
182 sustain_samples,
183 release_samples,
184 modified_params.sustain,
185 );
186
187 let mut amplitude = osc_sample * envelope * velocity * 0.3; if let Some(ref lfo) = modified_params.lfo {
192 use crate::engine::audio::lfo::LfoTarget;
193 if lfo.target == LfoTarget::Volume {
194 let lfo_value = generate_lfo_value(lfo, time, bpm);
195 amplitude *= 1.0 + lfo_value; }
197 }
198
199 samples.push(amplitude * left_gain);
201 samples.push(amplitude * right_gain);
202 }
203
204 for filter in &modified_params.filters {
206 let mut modulated_filter = filter.clone();
207
208 if let Some(ref lfo) = modified_params.lfo {
210 use crate::engine::audio::lfo::LfoTarget;
211 if lfo.target == LfoTarget::FilterCutoff {
212 let cutoff_range = filter.cutoff * 0.5; modulated_filter.cutoff = apply_lfo_modulation(
215 lfo,
216 0.0, bpm,
218 filter.cutoff,
219 cutoff_range,
220 );
221 }
222 }
223
224 apply_filter(&mut samples, &modulated_filter, sample_rate)?;
225 }
226
227 if let Some(stype) = synth_type {
229 stype.post_process(&mut samples, sample_rate, &modified_params.options)?;
230 }
231
232 Ok(samples)
233}
234
235fn apply_filter(samples: &mut [f32], filter: &FilterDef, sample_rate: u32) -> Result<()> {
237 match filter.filter_type.to_lowercase().as_str() {
238 "lowpass" => apply_lowpass(samples, filter.cutoff, sample_rate),
239 "highpass" => apply_highpass(samples, filter.cutoff, sample_rate),
240 "bandpass" => apply_bandpass(samples, filter.cutoff, sample_rate),
241 _ => Ok(()),
242 }
243}
244
245fn apply_lowpass(samples: &mut [f32], cutoff: f32, sample_rate: u32) -> Result<()> {
247 let dt = 1.0 / sample_rate as f32;
248 let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff);
249 let alpha = dt / (rc + dt);
250
251 let mut prev = 0.0f32;
252 for i in (0..samples.len()).step_by(2) {
253 let filtered = prev + alpha * (samples[i] - prev);
255 prev = filtered;
256 samples[i] = filtered;
257
258 if i + 1 < samples.len() {
260 samples[i + 1] = filtered;
261 }
262 }
263
264 Ok(())
265}
266
267fn apply_highpass(samples: &mut [f32], cutoff: f32, sample_rate: u32) -> Result<()> {
269 let dt = 1.0 / sample_rate as f32;
270 let rc = 1.0 / (2.0 * std::f32::consts::PI * cutoff);
271 let alpha = rc / (rc + dt);
272
273 let mut prev_input = 0.0f32;
274 let mut prev_output = 0.0f32;
275
276 for i in (0..samples.len()).step_by(2) {
277 let current = samples[i];
278 let filtered = alpha * (prev_output + current - prev_input);
279
280 prev_input = current;
281 prev_output = filtered;
282
283 samples[i] = filtered;
284 if i + 1 < samples.len() {
285 samples[i + 1] = filtered;
286 }
287 }
288
289 Ok(())
290}
291
292fn apply_bandpass(samples: &mut [f32], center: f32, sample_rate: u32) -> Result<()> {
294 let bandwidth = center * 0.5; apply_highpass(samples, center - bandwidth, sample_rate)?;
298 apply_lowpass(samples, center + bandwidth, sample_rate)?;
299
300 Ok(())
301}
302
303pub fn generate_chord(
305 midi_notes: &[u8],
306 duration_ms: f32,
307 velocity: f32,
308 params: &SynthParams,
309 sample_rate: u32,
310) -> Result<Vec<f32>> {
311 generate_chord_with_options(
312 midi_notes,
313 duration_ms,
314 velocity,
315 params,
316 sample_rate,
317 0.0,
318 0.0,
319 0.0,
320 )
321}
322
323pub fn generate_chord_with_options(
325 midi_notes: &[u8],
326 duration_ms: f32,
327 velocity: f32,
328 params: &SynthParams,
329 sample_rate: u32,
330 pan: f32, detune: f32, spread: f32, ) -> Result<Vec<f32>> {
334 if midi_notes.is_empty() {
335 return Ok(Vec::new());
336 }
337
338 let num_notes = midi_notes.len();
339 let spread = spread.clamp(0.0, 1.0);
340
341 let mut result: Option<Vec<f32>> = None;
343
344 for (i, &midi_note) in midi_notes.iter().enumerate() {
345 let note_pan = if num_notes > 1 && spread > 0.0 {
347 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)
351 } else {
352 pan
353 };
354
355 let note_samples = generate_note_with_options(
357 midi_note,
358 duration_ms,
359 velocity,
360 params,
361 sample_rate,
362 note_pan,
363 detune,
364 )?;
365
366 match result {
368 None => {
369 result = Some(note_samples);
370 }
371 Some(ref mut buffer) => {
372 for (j, sample) in note_samples.iter().enumerate() {
374 if j < buffer.len() {
375 buffer[j] = (buffer[j] + sample) / 2.0;
376 }
377 }
378 }
379 }
380 }
381
382 Ok(result.unwrap_or_default())
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388
389 #[test]
390 fn test_generate_note() {
391 let params = SynthParams::default();
392 let samples = generate_note(60, 500.0, 0.8, ¶ms, 44100).unwrap();
393
394 assert!(samples.len() > 0);
396 assert_eq!(samples.len() % 2, 0); let has_audio = samples.iter().any(|&s| s.abs() > 0.001);
400 assert!(has_audio);
401 }
402
403 #[test]
404 fn test_generate_chord() {
405 let params = SynthParams::default();
406 let samples = generate_chord(&[60, 64, 67], 500.0, 0.8, ¶ms, 44100).unwrap();
407
408 assert!(samples.len() > 0);
410
411 let has_audio = samples.iter().any(|&s| s.abs() > 0.001);
413 assert!(has_audio);
414 }
415}
416
417#[cfg(feature = "cli")]
419fn generate_note_with_plugin(
420 midi_note: u8,
421 duration_ms: f32,
422 velocity: f32,
423 params: &SynthParams,
424 sample_rate: u32,
425 pan: f32,
426 detune: f32,
427 plugin_author: &str,
428 plugin_name: &str,
429 plugin_export: Option<&str>,
430) -> Result<Vec<f32>> {
431 use once_cell::sync::Lazy;
432 use std::sync::Mutex;
433
434 static PLUGIN_RUNNER: Lazy<Mutex<WasmPluginRunner>> =
436 Lazy::new(|| Mutex::new(WasmPluginRunner::new()));
437
438 static PLUGIN_CACHE: Lazy<Mutex<HashMap<String, Vec<u8>>>> =
440 Lazy::new(|| Mutex::new(HashMap::new()));
441
442 let plugin_key = format!("{}.{}", plugin_author, plugin_name);
444 let mut cache = PLUGIN_CACHE.lock().unwrap();
445
446 let wasm_bytes = if let Some(bytes) = cache.get(&plugin_key) {
447 bytes.clone()
449 } else {
450 let (_info, bytes) = load_plugin(plugin_author, plugin_name)
452 .map_err(|e| anyhow::anyhow!("Failed to load plugin: {}", e))?;
453
454 cache.insert(plugin_key.clone(), bytes.clone());
455 bytes
456 };
457 drop(cache);
458
459 let base_frequency = midi_to_frequency(midi_note);
461 let frequency = if detune.abs() > 0.01 {
462 base_frequency * 2.0_f32.powf(detune / 1200.0)
463 } else {
464 base_frequency
465 };
466
467 let duration_seconds = duration_ms / 1000.0;
468 let total_samples = (duration_seconds * sample_rate as f32) as usize;
469 let mut buffer = vec![0.0f32; total_samples * 2]; let mut plugin_options = params.options.clone();
473
474 let audio_params = ["pan", "gain", "volume", "detune", "spread", "decay_mode"];
476 for param in &audio_params {
477 plugin_options.remove(*param);
478 }
479
480 if !plugin_options.contains_key("waveform") && params.waveform != "plugin" {
483 let waveform_value = match params.waveform.as_str() {
485 "sine" => 0.0,
486 "saw" => 1.0,
487 "square" => 2.0,
488 "triangle" => 3.0,
489 _ => 0.0, };
491 plugin_options.insert("waveform".to_string(), waveform_value);
492 }
493
494 let runner = PLUGIN_RUNNER.lock().unwrap();
496 let synth_id = format!("{}_{}", plugin_key, plugin_export.unwrap_or("default"));
497
498 runner
499 .render_note_in_place(
500 &wasm_bytes,
501 &mut buffer,
502 Some(&synth_id), plugin_export,
504 frequency,
505 velocity,
506 duration_ms as i32,
507 sample_rate as i32,
508 2, Some(&plugin_options),
510 )
511 .map_err(|e| anyhow::anyhow!("Plugin render error: {}", e))?;
512
513 if pan.abs() > 0.01 {
515 let pan = pan.clamp(-1.0, 1.0);
516 let pan_angle = (pan + 1.0) * 0.25 * std::f32::consts::PI;
517 let left_gain = pan_angle.cos();
518 let right_gain = pan_angle.sin();
519
520 for i in (0..buffer.len()).step_by(2) {
521 if i + 1 < buffer.len() {
522 buffer[i] *= left_gain;
523 buffer[i + 1] *= right_gain;
524 }
525 }
526 }
527
528 for filter in ¶ms.filters {
530 apply_filter(&mut buffer, filter, sample_rate)?;
531 }
532
533 Ok(buffer)
534}