devalang_wasm/engine/audio/interpreter/driver/
renderer_graph.rs1use super::AudioInterpreter;
3use crate::engine::audio::interpreter::audio_graph::Connection;
4use std::collections::HashMap;
5
6type NodeBuffers = HashMap<String, Vec<f32>>;
8
9pub fn render_audio_graph(
11 interpreter: &AudioInterpreter,
12 total_samples: usize,
13) -> anyhow::Result<Vec<f32>> {
14 let total_duration = total_samples as f32 / interpreter.sample_rate as f32;
15
16 let mut node_buffers: NodeBuffers = HashMap::new();
18 for node_name in interpreter.audio_graph.node_names() {
19 node_buffers.insert(node_name, vec![0.0f32; total_samples * 2]);
20 }
21
22 render_events_into_nodes(interpreter, &mut node_buffers, total_duration)?;
24
25 apply_node_effects(interpreter, &mut node_buffers)?;
27
28 apply_routing_and_ducking(interpreter, &mut node_buffers)?;
30
31 let master_buffer = mix_to_master(interpreter, &node_buffers)?;
33
34 Ok(master_buffer)
35}
36
37fn get_event_target_node(
40 event: &crate::engine::audio::events::AudioEvent,
41 _interpreter: &AudioInterpreter,
42) -> String {
43 use crate::engine::audio::events::AudioEvent;
44
45 match event {
46 AudioEvent::Note { synth_id, .. } | AudioEvent::Chord { synth_id, .. } => {
47 if synth_id.contains("mySynth")
49 || synth_id.contains("lead")
50 || synth_id.contains("Lead")
51 {
52 "myLeadNode".to_string()
53 } else {
54 "$master".to_string()
55 }
56 }
57 AudioEvent::Sample { uri, .. } => {
58 if uri.contains("kick") || uri.contains("Kick") || uri.contains("drum") {
60 "myKickNode".to_string()
61 } else {
62 "$master".to_string()
63 }
64 }
65 }
66}
67
68fn render_events_into_nodes(
70 interpreter: &AudioInterpreter,
71 node_buffers: &mut NodeBuffers,
72 total_duration: f32,
73) -> anyhow::Result<()> {
74 use crate::engine::audio::events::AudioEvent;
75 use crate::engine::audio::generator::{SynthParams, generate_note_with_options};
76
77 let total_samples = (total_duration * interpreter.sample_rate as f32).ceil() as usize;
78
79 for event in &interpreter.events.events {
80 let target_node = get_event_target_node(event, interpreter);
82
83 let target_buffer = node_buffers.get_mut(&target_node);
85 if target_buffer.is_none() {
86 continue;
87 }
88 let target_buffer = target_buffer.unwrap();
89
90 match event {
91 AudioEvent::Note {
92 midi,
93 start_time,
94 duration,
95 synth_def,
96 pan,
97 detune,
98 gain,
99 velocity,
100 attack,
101 release,
102 ..
103 } => {
104 let mut params = SynthParams {
105 waveform: synth_def.waveform.clone(),
106 attack: synth_def.attack,
107 decay: synth_def.decay,
108 sustain: synth_def.sustain,
109 release: synth_def.release,
110 synth_type: synth_def.synth_type.clone(),
111 filters: synth_def.filters.clone(),
112 options: synth_def.options.clone(),
113 lfo: synth_def.lfo.clone(),
114 plugin_author: synth_def.plugin_author.clone(),
115 plugin_name: synth_def.plugin_name.clone(),
116 plugin_export: synth_def.plugin_export.clone(),
117 };
118
119 if let Some(a) = attack {
120 params.attack = a / 1000.0;
121 }
122 if let Some(r) = release {
123 params.release = r / 1000.0;
124 }
125
126 let samples = generate_note_with_options(
127 *midi,
128 *duration * 1000.0, velocity * gain, ¶ms,
131 interpreter.sample_rate,
132 *pan,
133 *detune,
134 )?;
135
136 let start_sample = (*start_time * interpreter.sample_rate as f32).ceil() as usize;
137 let start_idx = start_sample * 2; let end_idx = (start_idx + samples.len()).min(total_samples * 2);
139 let write_len = end_idx - start_idx;
140
141 if start_idx < total_samples * 2 && write_len > 0 {
142 target_buffer[start_idx..end_idx]
143 .iter_mut()
144 .zip(samples[0..write_len].iter())
145 .for_each(|(dst, src)| *dst += src);
146 }
147 }
148 AudioEvent::Sample {
149 uri: _uri,
150 start_time: _start_time,
151 velocity: _velocity,
152 ..
153 } => {
154 #[cfg(feature = "cli")]
156 {
157 use crate::engine::audio::samples;
158
159 if let Some(sample_data) = samples::get_sample(_uri) {
160 let start_sample =
161 (*_start_time * interpreter.sample_rate as f32).ceil() as usize;
162 let start_idx = start_sample * 2; let end_idx =
164 (start_idx + sample_data.samples.len()).min(total_samples * 2);
165 let write_len = end_idx - start_idx;
166
167 if start_idx < total_samples * 2 && write_len > 0 {
168 let velocity_scale = _velocity;
170 target_buffer[start_idx..end_idx]
171 .iter_mut()
172 .zip(sample_data.samples[0..write_len].iter())
173 .for_each(|(dst, src)| *dst += src * velocity_scale);
174 }
175 }
176 }
177 }
178 _ => {}
179 }
180 }
181
182 Ok(())
183}
184
185fn apply_node_effects(
187 interpreter: &AudioInterpreter,
188 node_buffers: &mut NodeBuffers,
189) -> anyhow::Result<()> {
190 use crate::engine::audio::effects::chain::build_effect_chain;
191
192 for (node_name, node_config) in &interpreter.audio_graph.nodes {
193 if let Some(effects_value) = &node_config.effects {
194 let effects_array = match effects_value {
196 crate::language::syntax::ast::Value::Array(arr) => arr.clone(),
197 _ => vec![effects_value.clone()],
198 };
199
200 let mut effect_chain = build_effect_chain(&effects_array, false);
201
202 if let Some(buffer) = node_buffers.get_mut(node_name) {
203 effect_chain.process(buffer, interpreter.sample_rate);
205 }
206 }
207 }
208
209 Ok(())
210}
211
212fn apply_routing_and_ducking(
214 interpreter: &AudioInterpreter,
215 node_buffers: &mut NodeBuffers,
216) -> anyhow::Result<()> {
217 for connection in interpreter.audio_graph.connections.iter() {
219 match connection {
220 Connection::Duck {
221 source,
222 destination,
223 effect_params: _,
224 } => {
225 apply_duck(source, destination, node_buffers, interpreter.sample_rate)?;
226 }
227 Connection::Sidechain {
228 source,
229 destination,
230 effect_params: _,
231 } => {
232 apply_sidechain(source, destination, node_buffers, interpreter.sample_rate)?;
233 }
234 _ => {}
235 }
236 }
237
238 for connection in interpreter.audio_graph.connections.iter() {
240 match connection {
241 Connection::Route {
242 source,
243 destination,
244 gain,
245 } => {
246 if let (Some(src_buf), Some(dst_buf)) = (
248 node_buffers.get(source).cloned(),
249 node_buffers.get_mut(destination),
250 ) {
251 for j in 0..src_buf.len() {
252 dst_buf[j] += src_buf[j] * gain;
253 }
254 }
255 }
256 _ => {}
257 }
258 }
259
260 Ok(())
261}
262
263fn apply_duck(
265 source_name: &str,
266 destination_name: &str,
267 node_buffers: &mut NodeBuffers,
268 sample_rate: u32,
269) -> anyhow::Result<()> {
270 let dest_envelope = if let Some(dest_buf) = node_buffers.get(destination_name) {
272 compute_envelope(dest_buf, sample_rate)
273 } else {
274 return Ok(());
275 };
276
277 let src_opt = node_buffers.get_mut(source_name);
279 if src_opt.is_none() {
280 return Ok(());
281 }
282
283 let src_buf = src_opt.unwrap();
284
285 let frame_rate = 100; let samples_per_frame = (sample_rate / frame_rate) as usize * 2; for frame_idx in (0..src_buf.len()).step_by(2) {
290 let current_envelope_idx = frame_idx / samples_per_frame;
292
293 if current_envelope_idx < dest_envelope.len() {
294 let dest_level = dest_envelope[current_envelope_idx];
295
296 let threshold = 0.005; let sensitivity = if dest_level > threshold {
299 ((dest_level - threshold) / (0.2 - threshold)).min(1.0)
300 } else {
301 0.0
302 };
303
304 let max_duck_reduction = 0.95; let compression_gain = 1.0 - (sensitivity * max_duck_reduction);
306
307 src_buf[frame_idx] *= compression_gain;
308 if frame_idx + 1 < src_buf.len() {
309 src_buf[frame_idx + 1] *= compression_gain;
310 }
311 }
312 }
313
314 Ok(())
315}
316
317fn apply_sidechain(
319 source_name: &str,
320 destination_name: &str,
321 node_buffers: &mut NodeBuffers,
322 sample_rate: u32,
323) -> anyhow::Result<()> {
324 let dest_envelope = if let Some(dest_buf) = node_buffers.get(destination_name) {
326 compute_envelope(dest_buf, sample_rate)
327 } else {
328 return Ok(());
329 };
330
331 if let Some(src_buf) = node_buffers.get_mut(source_name) {
333 let frame_rate = 100;
335 let samples_per_frame = (sample_rate / frame_rate) as usize * 2;
336
337 for frame_idx in (0..src_buf.len()).step_by(2) {
338 let current_envelope_idx = frame_idx / samples_per_frame;
339
340 if current_envelope_idx < dest_envelope.len() {
341 let dest_level = dest_envelope[current_envelope_idx];
342
343 let normalized_linear = (dest_level * 10.0).min(1.0);
345 let gate_open = 1.0 - (normalized_linear * 0.5); src_buf[frame_idx] *= gate_open;
348 if frame_idx + 1 < src_buf.len() {
349 src_buf[frame_idx + 1] *= gate_open;
350 }
351 }
352 }
353 }
354
355 Ok(())
356}
357
358fn compute_envelope(buffer: &[f32], sample_rate: u32) -> Vec<f32> {
360 let frame_rate = 100; let samples_per_frame = sample_rate / frame_rate;
362 let mut envelope = Vec::new();
363
364 for chunk in buffer.chunks(samples_per_frame as usize * 2) {
365 let rms: f32 = (chunk.iter().map(|s| s * s).sum::<f32>() / chunk.len() as f32).sqrt();
366 envelope.push(rms.min(1.0).max(0.0));
367 }
368
369 envelope
370}
371
372fn mix_to_master(
374 _interpreter: &AudioInterpreter,
375 node_buffers: &NodeBuffers,
376) -> anyhow::Result<Vec<f32>> {
377 let master_buf = node_buffers
378 .get("$master")
379 .ok_or_else(|| anyhow::anyhow!("Master node not found"))?
380 .clone();
381
382 let mut result = master_buf;
383
384 for (node_name, buffer) in node_buffers {
386 if node_name != "$master" {
387 for i in 0..buffer.len() {
388 result[i] += buffer[i];
389 }
390 }
391 }
392
393 Ok(result)
394}