devalang_wasm/engine/audio/interpreter/driver/
renderer.rs

1#![allow(unused_macros)]
2use super::AudioInterpreter;
3use crate::engine::audio::effects::chain::{EffectChain, build_effect_chain};
4use crate::engine::audio::effects::normalize_effects;
5use crate::engine::audio::effects::processors::{
6    DelayProcessor, DriveProcessor, EffectProcessor, ReverbProcessor,
7};
8use crate::engine::audio::generator::{
9    SynthParams, generate_chord_with_options, generate_note_with_options,
10};
11use anyhow::Result;
12
13// Conditional logging macros for CLI feature
14#[cfg(feature = "cli")]
15macro_rules! log_info {
16    ($logger:expr, $($arg:tt)*) => {
17        $logger.info(format!($($arg)*))
18    };
19}
20
21#[cfg(not(feature = "cli"))]
22macro_rules! log_info {
23    ($_logger:expr, $($arg:tt)*) => {
24        let _ = ($($arg)*);
25    };
26}
27
28#[cfg(feature = "cli")]
29macro_rules! log_warn {
30    ($logger:expr, $($arg:tt)*) => {
31        $logger.warn(format!($($arg)*))
32    };
33}
34
35#[cfg(not(feature = "cli"))]
36macro_rules! log_warn {
37    ($_logger:expr, $($arg:tt)*) => {
38        let _ = ($($arg)*);
39    };
40}
41
42#[cfg(feature = "cli")]
43macro_rules! log_error {
44    ($logger:expr, $($arg:tt)*) => {
45        $logger.error(format!($($arg)*))
46    };
47}
48
49#[cfg(not(feature = "cli"))]
50macro_rules! log_error {
51    ($_logger:expr, $($arg:tt)*) => {
52        let _ = ($($arg)*);
53    };
54}
55
56pub fn render_audio(interpreter: &AudioInterpreter) -> Result<Vec<f32>> {
57    let total_duration = interpreter.events.total_duration();
58    if total_duration <= 0.0 {
59        return Ok(Vec::new());
60    }
61
62    let total_samples = (total_duration * interpreter.sample_rate as f32).ceil() as usize;
63
64    #[cfg(feature = "cli")]
65    let logger = crate::tools::logger::Logger::new();
66    #[cfg(not(feature = "cli"))]
67    let _logger = ();
68
69    // Check if we should use audio graph rendering (when routing is configured)
70    if !interpreter.audio_graph.node_names().is_empty()
71        && interpreter.audio_graph.node_names().len() > 1
72    {
73        log_info!(
74            logger,
75            "Using audio graph rendering with {} nodes",
76            interpreter.audio_graph.node_names().len()
77        );
78        return super::renderer_graph::render_audio_graph(interpreter, total_samples)
79            .map_err(|e| anyhow::anyhow!("Audio graph rendering failed: {}", e));
80    }
81
82    // Default: simple buffer rendering (no routing)
83    let mut buffer = vec![0.0f32; total_samples * 2]; // stereo
84
85    log_info!(
86        logger,
87        "Starting audio rendering: {} events, {} synths, duration {:.2}s",
88        interpreter.events.events.len(),
89        interpreter.events.synths.len(),
90        total_duration
91    );
92
93    // No-op pre-scan: logs are stored separately in interpreter.events.logs and are ignored by renderer.
94
95    // Render each event (copied logic from driver)
96    let mut note_count = 0;
97    let mut sample_count = 0;
98    for event in &interpreter.events.events {
99        match event {
100            crate::engine::audio::events::AudioEvent::Note {
101                midi,
102                start_time,
103                duration,
104                velocity,
105                synth_id,
106                synth_def,
107                pan,
108                detune,
109                gain,
110                attack,
111                release,
112                delay_time,
113                delay_feedback,
114                delay_mix,
115                reverb_amount,
116                drive_amount,
117                drive_color,
118                use_per_note_automation,
119                ..
120            } => {
121                note_count += 1;
122                // Log note rendering only if needed (debug mode)
123
124                let mut params = SynthParams {
125                    waveform: synth_def.waveform.clone(),
126                    attack: synth_def.attack,
127                    decay: synth_def.decay,
128                    sustain: synth_def.sustain,
129                    release: synth_def.release,
130                    synth_type: synth_def.synth_type.clone(),
131                    filters: synth_def.filters.clone(),
132                    options: synth_def.options.clone(),
133                    lfo: synth_def.lfo.clone(),
134                    plugin_author: synth_def.plugin_author.clone(),
135                    plugin_name: synth_def.plugin_name.clone(),
136                    plugin_export: synth_def.plugin_export.clone(),
137                };
138
139                if let Some(a) = attack {
140                    params.attack = a / 1000.0;
141                }
142                if let Some(r) = release {
143                    params.release = r / 1000.0;
144                }
145
146                // Handle decay("auto") mode - calculate decay based on note duration
147                if let Some(&decay_mode) = params.options.get("decay_mode") {
148                    if decay_mode > 0.5 {
149                        // Check if decay_mode is set to "auto" (1.0)
150                        // Calculate automatic decay: use most of duration minus attack and a small tail
151                        let attack_ms = params.attack * 1000.0;
152                        let tail_ms = 50.0; // Small tail at end
153                        let available_ms = (duration * 1000.0).max(attack_ms + tail_ms);
154                        let auto_decay = available_ms - attack_ms - tail_ms;
155                        params.decay = (auto_decay / 1000.0).max(0.01); // Convert to seconds, minimum 0.01s
156                    }
157                }
158
159                let mut samples = if *use_per_note_automation {
160                    // Generate per-note automation with segments
161                    if let Some(automation_ctx) =
162                        interpreter.note_automation_templates.get(synth_id.as_str())
163                    {
164                        use crate::engine::audio::automation::evaluate_template_at;
165
166                        let mut all_samples = Vec::new();
167                        let num_segments = 8; // Generate 8 segments per note for smooth automation
168                        let segment_duration = duration / num_segments as f32;
169
170                        for segment_idx in 0..num_segments {
171                            // Calculate progress for this segment (0.0 to 1.0)
172                            let segment_progress = (segment_idx as f32 + 0.5) / num_segments as f32;
173
174                            // Evaluate templates for this progress point
175                            let segment_pan = automation_ctx
176                                .templates
177                                .iter()
178                                .find(|t| t.param_name == "pan")
179                                .map(|t| evaluate_template_at(t, segment_progress))
180                                .unwrap_or(*pan);
181
182                            let segment_detune = automation_ctx
183                                .templates
184                                .iter()
185                                .find(|t| t.param_name == "pitch" || t.param_name == "detune")
186                                .map(|t| evaluate_template_at(t, segment_progress))
187                                .unwrap_or(*detune);
188
189                            let segment_gain = automation_ctx
190                                .templates
191                                .iter()
192                                .find(|t| t.param_name == "volume" || t.param_name == "gain")
193                                .map(|t| evaluate_template_at(t, segment_progress))
194                                .unwrap_or(*gain);
195
196                            // Clone and modify params for this segment
197                            let mut segment_params = params.clone();
198
199                            // Apply cutoff automation
200                            for filter in &mut segment_params.filters {
201                                let segment_cutoff = automation_ctx
202                                    .templates
203                                    .iter()
204                                    .find(|t| t.param_name == "cutoff")
205                                    .map(|t| evaluate_template_at(t, segment_progress))
206                                    .unwrap_or(filter.cutoff);
207                                filter.cutoff = segment_cutoff;
208
209                                let segment_resonance = automation_ctx
210                                    .templates
211                                    .iter()
212                                    .find(|t| t.param_name == "resonance")
213                                    .map(|t| evaluate_template_at(t, segment_progress))
214                                    .unwrap_or(filter.resonance);
215                                filter.resonance = segment_resonance;
216                            }
217
218                            // Generate this segment with updated params
219                            let segment_samples = generate_note_with_options(
220                                *midi,
221                                segment_duration * 1000.0,
222                                (velocity * segment_gain).clamp(0.0, 1.0),
223                                &segment_params,
224                                interpreter.sample_rate,
225                                segment_pan,
226                                segment_detune,
227                            )?;
228
229                            all_samples.extend(segment_samples);
230                        }
231
232                        all_samples
233                    } else {
234                        // No templates found, generate normally
235                        generate_note_with_options(
236                            *midi,
237                            duration * 1000.0,
238                            velocity * gain,
239                            &params,
240                            interpreter.sample_rate,
241                            *pan,
242                            *detune,
243                        )?
244                    }
245                } else {
246                    // Generate note normally (global mode or no automation)
247                    generate_note_with_options(
248                        *midi,
249                        duration * 1000.0,
250                        velocity * gain,
251                        &params,
252                        interpreter.sample_rate,
253                        *pan,
254                        *detune,
255                    )?
256                };
257
258                // If this event has an effects map/array, build an effect chain and apply it.
259                // Also avoid double-applying drive/reverb/delay when those keys appear in the effects map.
260                let mut skip_drive = false;
261                let mut skip_reverb = false;
262                let mut skip_delay = false;
263                let mut effect_chain: Option<EffectChain> = None;
264
265                // Try to extract per-event effects (if present) and build a chain
266                if let crate::engine::audio::events::AudioEvent::Note { effects, .. } = event {
267                    if let Some(eff_val) = effects {
268                        match eff_val {
269                            crate::language::syntax::ast::Value::Array(arr) => {
270                                let chain = build_effect_chain(arr, true);
271                                if !chain.is_empty() {
272                                    effect_chain = Some(chain);
273                                }
274                            }
275                            crate::language::syntax::ast::Value::Map(_) => {
276                                let normalized = normalize_effects(&Some(eff_val.clone()));
277                                if !normalized.is_empty() {
278                                    let mut chain = EffectChain::new(true);
279                                    for (k, v) in normalized.into_iter() {
280                                        chain.add_effect(
281                                            &k,
282                                            Some(crate::language::syntax::ast::Value::Map(v)),
283                                        );
284                                        // mark skips for known audio processors
285                                        match k.as_str() {
286                                            "drive" => skip_drive = true,
287                                            "reverb" => skip_reverb = true,
288                                            "delay" => skip_delay = true,
289                                            _ => {}
290                                        }
291                                    }
292                                    effect_chain = Some(chain);
293                                }
294                            }
295                            _ => {}
296                        }
297                    }
298                }
299
300                if let Some(chain) = effect_chain.as_mut() {
301                    chain.process(&mut samples, interpreter.sample_rate);
302                }
303
304                // Apply legacy per-field processors only when not overridden by the effects map
305                if let Some(amount) = drive_amount {
306                    if !skip_drive {
307                        let color = drive_color.unwrap_or(0.5);
308                        let mix = 0.7;
309                        // tone default to 0.5, color passed from drive_color
310                        let mut processor = DriveProcessor::new(*amount, 0.5, color, mix);
311                        processor.process(&mut samples, interpreter.sample_rate);
312                    }
313                }
314                if let Some(amount) = reverb_amount {
315                    if !skip_reverb {
316                        let room_size = *amount;
317                        let damping = 0.5;
318                        let decay = 0.5;
319                        let mix = *amount * 0.5;
320                        let mut processor = ReverbProcessor::new(room_size, damping, decay, mix);
321                        processor.process(&mut samples, interpreter.sample_rate);
322                    }
323                }
324                if let Some(time) = delay_time {
325                    if !skip_delay {
326                        let feedback = delay_feedback.unwrap_or(0.3);
327                        let mix = delay_mix.unwrap_or(0.5);
328                        let mut processor = DelayProcessor::new(*time, feedback, mix);
329                        processor.process(&mut samples, interpreter.sample_rate);
330                    }
331                }
332
333                let start_sample = (*start_time * interpreter.sample_rate as f32) as usize * 2;
334                for (i, &sample) in samples.iter().enumerate() {
335                    let buf_idx = start_sample + i;
336                    if buf_idx < buffer.len() {
337                        buffer[buf_idx] += sample;
338                    }
339                }
340            }
341
342            crate::engine::audio::events::AudioEvent::Chord {
343                midis,
344                start_time,
345                duration,
346                velocity,
347                synth_id: _,
348                synth_def,
349                pan,
350                detune,
351                spread,
352                gain,
353                attack,
354                release,
355                delay_time,
356                delay_feedback,
357                delay_mix,
358                reverb_amount,
359                drive_amount,
360                drive_color,
361                effects,
362                use_per_note_automation: _,
363            } => {
364                let mut params = SynthParams {
365                    waveform: synth_def.waveform.clone(),
366                    attack: synth_def.attack,
367                    decay: synth_def.decay,
368                    sustain: synth_def.sustain,
369                    release: synth_def.release,
370                    synth_type: synth_def.synth_type.clone(),
371                    filters: synth_def.filters.clone(),
372                    options: synth_def.options.clone(),
373                    lfo: synth_def.lfo.clone(),
374                    plugin_author: synth_def.plugin_author.clone(),
375                    plugin_name: synth_def.plugin_name.clone(),
376                    plugin_export: synth_def.plugin_export.clone(),
377                };
378                if let Some(a) = attack {
379                    params.attack = a / 1000.0;
380                }
381                if let Some(r) = release {
382                    params.release = r / 1000.0;
383                }
384
385                let mut samples = generate_chord_with_options(
386                    midis,
387                    duration * 1000.0,
388                    velocity * gain,
389                    &params,
390                    interpreter.sample_rate,
391                    *pan,
392                    *detune,
393                    *spread,
394                )?;
395
396                // Build and apply effect chain if per-event effects are present
397                let mut skip_drive = false;
398                let mut skip_reverb = false;
399                let mut skip_delay = false;
400                let mut effect_chain: Option<EffectChain> = None;
401                if let Some(eff_val) = effects {
402                    match eff_val {
403                        crate::language::syntax::ast::Value::Array(arr) => {
404                            let chain = build_effect_chain(arr, true);
405                            if !chain.is_empty() {
406                                effect_chain = Some(chain);
407                            }
408                        }
409                        crate::language::syntax::ast::Value::Map(_) => {
410                            let normalized = normalize_effects(&Some(eff_val.clone()));
411                            if !normalized.is_empty() {
412                                let mut chain = EffectChain::new(true);
413                                for (k, v) in normalized.into_iter() {
414                                    chain.add_effect(
415                                        &k,
416                                        Some(crate::language::syntax::ast::Value::Map(v)),
417                                    );
418                                    match k.as_str() {
419                                        "drive" => skip_drive = true,
420                                        "reverb" => skip_reverb = true,
421                                        "delay" => skip_delay = true,
422                                        _ => {}
423                                    }
424                                }
425                                effect_chain = Some(chain);
426                            }
427                        }
428                        _ => {}
429                    }
430                }
431
432                if let Some(chain) = effect_chain.as_mut() {
433                    chain.process(&mut samples, interpreter.sample_rate);
434                }
435
436                if let Some(amount) = drive_amount {
437                    if !skip_drive {
438                        let color = drive_color.unwrap_or(0.5);
439                        let mix = 0.7;
440                        let mut processor = DriveProcessor::new(*amount, 0.5, color, mix);
441                        processor.process(&mut samples, interpreter.sample_rate);
442                    }
443                }
444                if let Some(amount) = reverb_amount {
445                    if !skip_reverb {
446                        let room_size = *amount;
447                        let damping = 0.5;
448                        let decay = 0.5;
449                        let mix = *amount * 0.5;
450                        let mut processor = ReverbProcessor::new(room_size, damping, decay, mix);
451                        processor.process(&mut samples, interpreter.sample_rate);
452                    }
453                }
454                if let Some(time) = delay_time {
455                    if !skip_delay {
456                        let feedback = delay_feedback.unwrap_or(0.3);
457                        let mix = delay_mix.unwrap_or(0.5);
458                        let mut processor = DelayProcessor::new(*time, feedback, mix);
459                        processor.process(&mut samples, interpreter.sample_rate);
460                    }
461                }
462
463                let start_sample = (*start_time * interpreter.sample_rate as f32) as usize * 2;
464                for (i, &sample) in samples.iter().enumerate() {
465                    let buf_idx = start_sample + i;
466                    if buf_idx < buffer.len() {
467                        buffer[buf_idx] += sample;
468                    }
469                }
470            }
471
472            crate::engine::audio::events::AudioEvent::Sample {
473                uri,
474                start_time,
475                velocity,
476                effects: _effects,
477                source: _,
478            } => {
479                sample_count += 1;
480                // Log sample rendering only if needed (debug mode)
481
482                // WASM path: samples are provided by the web registry as i16 PCM
483                #[cfg(feature = "wasm")]
484                {
485                    use crate::web::registry::samples::get_sample;
486                    if let Some(pcm_data) = get_sample(uri) {
487                        let start_sample_idx =
488                            (*start_time * interpreter.sample_rate as f32) as usize;
489                        for (i, &pcm_value) in pcm_data.iter().enumerate() {
490                            let sample = (pcm_value as f32 / 32768.0) * velocity;
491                            let stereo_pos = (start_sample_idx + i) * 2;
492                            let buf_idx_l = stereo_pos;
493                            let buf_idx_r = stereo_pos + 1;
494                            if buf_idx_l < buffer.len() {
495                                buffer[buf_idx_l] += sample;
496                            }
497                            if buf_idx_r < buffer.len() {
498                                buffer[buf_idx_r] += sample;
499                            }
500                        }
501                    } else {
502                        log_warn!(logger, "Sample not found in registry: {}", uri);
503                    }
504                }
505
506                // CLI/native path: use SampleData (mono f32) and resample/scale into stereo buffer
507                #[cfg(feature = "cli")]
508                {
509                    use crate::engine::audio::samples;
510                    if let Some(sample_data) = samples::get_sample(uri) {
511                        let start_sample_idx =
512                            (*start_time * interpreter.sample_rate as f32) as usize;
513                        // velocity is in 0.0..1.0 range for sample events
514                        let velocity_scale = velocity;
515                        let resample_ratio =
516                            interpreter.sample_rate as f32 / sample_data.sample_rate as f32;
517
518                        // Make a mutable copy so we can run effects on it (effects expect f32 slices)
519                        let mut proc_samples = sample_data.samples.clone();
520
521                        // Build and apply effect chain for sample events (trigger context)
522                        let mut sample_chain: Option<EffectChain> = None;
523                        if let Some(eff_val) = _effects {
524                            match eff_val {
525                                crate::language::syntax::ast::Value::Array(arr) => {
526                                    let chain = build_effect_chain(arr, false);
527                                    if !chain.is_empty() {
528                                        sample_chain = Some(chain);
529                                    }
530                                }
531                                crate::language::syntax::ast::Value::Map(_) => {
532                                    let normalized = normalize_effects(&Some(eff_val.clone()));
533                                    if !normalized.is_empty() {
534                                        let mut chain = EffectChain::new(false);
535                                        for (k, v) in normalized.into_iter() {
536                                            chain.add_effect(
537                                                &k,
538                                                Some(crate::language::syntax::ast::Value::Map(v)),
539                                            );
540                                        }
541                                        sample_chain = Some(chain);
542                                    }
543                                }
544                                _ => {}
545                            }
546                        }
547
548                        if let Some(chain) = sample_chain.as_mut() {
549                            chain.process(&mut proc_samples, interpreter.sample_rate);
550                        }
551
552                        for (i, &sample) in proc_samples.iter().enumerate() {
553                            let output_idx =
554                                start_sample_idx + (i as f32 * resample_ratio) as usize;
555                            let stereo_pos = output_idx * 2;
556                            let buf_idx_l = stereo_pos;
557                            let buf_idx_r = stereo_pos + 1;
558                            let scaled_sample = sample * velocity_scale;
559                            if buf_idx_l < buffer.len() {
560                                buffer[buf_idx_l] += scaled_sample;
561                            }
562                            if buf_idx_r < buffer.len() {
563                                buffer[buf_idx_r] += scaled_sample;
564                            }
565                        }
566                    } else {
567                        log_error!(logger, "Bank sample not found: {}", uri);
568                    }
569                }
570            }
571            crate::engine::audio::events::AudioEvent::Stop { target, time: _ } => {
572                // Stop events: these are control events that signal to stop playback
573                // They don't contribute to audio rendering directly
574                // Target is optional: None = stop all, Some = stop specific entity
575                // For now, we just log it. The actual stopping would be handled by the playback engine
576                if let Some(entity) = target {
577                    log_info!(logger, "Stop event for entity: {}", entity);
578                } else {
579                    log_info!(logger, "Stop all event");
580                }
581            }
582        }
583    }
584
585    log_info!(
586        logger,
587        "Rendered {} notes + {} samples",
588        note_count,
589        sample_count
590    );
591    let max_amplitude = buffer.iter().map(|&s| s.abs()).fold(0.0f32, f32::max);
592    log_info!(
593        logger,
594        "Max amplitude before normalization: {:.4}",
595        max_amplitude
596    );
597
598    if max_amplitude > 1.0 {
599        for sample in buffer.iter_mut() {
600            *sample /= max_amplitude;
601        }
602    }
603
604    Ok(buffer)
605}
606
607pub fn render_audio_wrapper(interpreter: &mut AudioInterpreter) -> Result<Vec<f32>> {
608    render_audio(interpreter)
609}