1use dirtydata_core::types::ConfigSnapshot;
2use dirtydata_host::PluginHost;
3use rand::prelude::*;
4use rand_pcg::Pcg32;
5use std::sync::Arc;
6use std::collections::VecDeque;
7
8use super::base::*;
9
10pub struct OscillatorNode {
15 phase: f32,
16 freq_smooth: Option<SmoothedValue>,
17}
18
19impl OscillatorNode {
20 pub fn new() -> Self {
21 Self { phase: 0.0, freq_smooth: None }
22 }
23}
24
25impl DspNode for OscillatorNode {
26 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
27 let freq_target = config.get("frequency").and_then(|v| v.as_float()).unwrap_or(440.0) as f32;
28 let wave_type = config.get("waveform").and_then(|v| v.as_string());
29
30 let smooth = self.freq_smooth.get_or_insert_with(|| SmoothedValue::new(freq_target, ctx.sample_rate, 10.0));
31 let freq = smooth.next();
32 let phase_inc = freq / ctx.sample_rate;
33
34 let val = match wave_type.map(|s| s.as_str()).unwrap_or("sine") {
35 "sine" => (self.phase * 2.0 * std::f32::consts::PI).sin(),
36 "saw" => (self.phase * 2.0) - 1.0,
37 "square" => if self.phase < 0.5 { 1.0 } else { -1.0 },
38 "triangle" => {
39 let v = self.phase * 4.0;
40 if v < 1.0 { v - 0.0 }
41 else if v < 3.0 { 2.0 - v }
42 else { v - 4.0 }
43 }
44 _ => (self.phase * 2.0 * std::f32::consts::PI).sin(),
45 };
46
47 outputs[0][0] = val;
48 outputs[0][1] = val;
49
50 self.phase = (self.phase + phase_inc) % 1.0;
51 }
52
53 fn update_parameter(&mut self, param: &str, value: f32) {
54 if param == "frequency" {
55 if let Some(s) = &mut self.freq_smooth {
56 s.set_target(value);
57 }
58 }
59 }
60
61 fn extract_state(&self) -> NodeState {
62 NodeState::from_json(serde_json::json!({ "phase": self.phase }))
63 }
64
65 fn inject_state(&mut self, state: &NodeState) {
66 if let Some(val) = state.to_json::<serde_json::Value>() {
67 if let Some(phase) = val.get("phase").and_then(|v| v.as_f64()) {
68 self.phase = phase as f32;
69 }
70 }
71 }
72}
73
74pub struct NoiseNode {
75 rng: Pcg32,
76}
77
78impl NoiseNode {
79 pub fn new(seed: u64) -> Self {
80 Self { rng: Pcg32::seed_from_u64(seed) }
81 }
82}
83
84impl DspNode for NoiseNode {
85 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
86 let val: f32 = self.rng.gen_range(-1.0..1.0);
87 outputs[0][0] = val;
88 outputs[0][1] = val;
89 }
90}
91
92pub struct AssetReaderNode {
93 data: Arc<Vec<f32>>,
94 cursor: usize,
95}
96
97impl AssetReaderNode {
98 pub fn new(data: Arc<Vec<f32>>) -> Self {
99 Self { data, cursor: 0 }
100 }
101}
102
103impl DspNode for AssetReaderNode {
104 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
105 if self.cursor + 1 < self.data.len() {
106 outputs[0][0] = self.data[self.cursor];
107 outputs[0][1] = self.data[self.cursor + 1];
108 self.cursor += 2;
109 } else {
110 outputs[0][0] = 0.0;
111 outputs[0][1] = 0.0;
112 }
113 }
114}
115
116pub struct GainNode {
121 gain_smooth: Option<SmoothedValue>,
122}
123
124impl GainNode {
125 pub fn new() -> Self {
126 Self { gain_smooth: None }
127 }
128}
129
130impl DspNode for GainNode {
131 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
132 let gain_db_target = config.get("gain_db").and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
133
134 let smooth = self.gain_smooth.get_or_insert_with(|| SmoothedValue::new(gain_db_target, ctx.sample_rate, 10.0));
135 let gain_db = smooth.next();
136 let linear = 10.0_f32.powf(gain_db / 20.0);
137
138 if inputs.len() >= 2 {
139 outputs[0][0] = inputs[0] * linear;
140 outputs[0][1] = inputs[1] * linear;
141 }
142 }
143
144 fn update_parameter(&mut self, param: &str, value: f32) {
145 if param == "gain_db" {
146 if let Some(s) = &mut self.gain_smooth {
147 s.set_target(value);
148 }
149 }
150 }
151}
152
153impl BiquadFilterNode {
154 pub fn new() -> Self {
155 Self { z1: [0.0, 0.0], z2: [0.0, 0.0], freq_smooth: None }
156 }
157}
158
159pub struct BiquadFilterNode {
160 z1: [f32; 2],
161 z2: [f32; 2],
162 freq_smooth: Option<SmoothedValue>,
163}
164
165impl DspNode for BiquadFilterNode {
166 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
167 let freq_target = config.get("frequency").and_then(|v| v.as_float()).unwrap_or(1000.0) as f32;
168 let q = config.get("q").and_then(|v| v.as_float()).unwrap_or(0.707) as f32;
169 let filter_type = config.get("type").and_then(|v| v.as_string());
170
171 let smooth = self.freq_smooth.get_or_insert_with(|| SmoothedValue::new(freq_target, ctx.sample_rate, 10.0));
172 let freq = smooth.next();
173
174 let w0 = 2.0 * std::f32::consts::PI * freq / ctx.sample_rate;
176 let alpha = w0.sin() / (2.0 * q);
177 let cos_w0 = w0.cos();
178
179 let (b0, b1, b2, a0, a1, a2) = match filter_type.map(|s| s.as_str()).unwrap_or("lpf") {
180 "hpf" => {
181 let b0 = (1.0 + cos_w0) / 2.0;
182 let b1 = -(1.0 + cos_w0);
183 let b2 = (1.0 + cos_w0) / 2.0;
184 let a0 = 1.0 + alpha;
185 let a1 = -2.0 * cos_w0;
186 let a2 = 1.0 - alpha;
187 (b0, b1, b2, a0, a1, a2)
188 }
189 "bandpass" => {
190 let b0 = alpha;
191 let b1 = 0.0;
192 let b2 = -alpha;
193 let a0 = 1.0 + alpha;
194 let a1 = -2.0 * cos_w0;
195 let a2 = 1.0 - alpha;
196 (b0, b1, b2, a0, a1, a2)
197 }
198 "notch" => {
199 let b0 = 1.0;
200 let b1 = -2.0 * cos_w0;
201 let b2 = 1.0;
202 let a0 = 1.0 + alpha;
203 let a1 = -2.0 * cos_w0;
204 let a2 = 1.0 - alpha;
205 (b0, b1, b2, a0, a1, a2)
206 }
207 "peak" => {
208 let gain_db = config.get("gain_db").and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
209 let a_val = 10.0_f32.powf(gain_db / 40.0);
210 let b0 = 1.0 + alpha * a_val;
211 let b1 = -2.0 * cos_w0;
212 let b2 = 1.0 - alpha * a_val;
213 let a0 = 1.0 + alpha / a_val;
214 let a1 = -2.0 * cos_w0;
215 let a2 = 1.0 - alpha / a_val;
216 (b0, b1, b2, a0, a1, a2)
217 }
218 _ => { let b0 = (1.0 - cos_w0) / 2.0;
220 let b1 = 1.0 - cos_w0;
221 let b2 = (1.0 - cos_w0) / 2.0;
222 let a0 = 1.0 + alpha;
223 let a1 = -2.0 * cos_w0;
224 let a2 = 1.0 - alpha;
225 (b0, b1, b2, a0, a1, a2)
226 }
227 };
228
229 let inv_a0 = 1.0 / a0;
230 let ff0 = b0 * inv_a0;
231 let ff1 = b1 * inv_a0;
232 let ff2 = b2 * inv_a0;
233 let fb1 = a1 * inv_a0;
234 let fb2 = a2 * inv_a0;
235
236 for i in 0..2 {
237 let x = if inputs.len() > i { inputs[i] } else { 0.0 };
238 let y = ff0 * x + self.z1[i];
239 self.z1[i] = ff1 * x - fb1 * y + self.z2[i];
240 self.z2[i] = ff2 * x - fb2 * y;
241 outputs[0][i] = y;
242 }
243 }
244
245 fn update_parameter(&mut self, param: &str, value: f32) {
246 if param == "frequency" {
247 if let Some(s) = &mut self.freq_smooth {
248 s.set_target(value);
249 }
250 }
251 }
252}
253
254pub struct CompressorNode {
255 envelope: f32,
256}
257
258impl CompressorNode {
259 pub fn new() -> Self {
260 Self { envelope: 0.0 }
261 }
262}
263
264impl DspNode for CompressorNode {
265 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
266 let threshold_db = config.get("threshold_db").and_then(|v| v.as_float()).unwrap_or(-20.0) as f32;
267 let ratio = config.get("ratio").and_then(|v| v.as_float()).unwrap_or(4.0) as f32;
268 let attack_ms = config.get("attack_ms").and_then(|v| v.as_float()).unwrap_or(10.0) as f32;
269 let release_ms = config.get("release_ms").and_then(|v| v.as_float()).unwrap_or(100.0) as f32;
270
271 let threshold = 10.0_f32.powf(threshold_db / 20.0);
272 let attack_alpha = 1.0 - (-1.0 / (attack_ms * ctx.sample_rate / 1000.0)).exp();
273 let release_alpha = 1.0 - (-1.0 / (release_ms * ctx.sample_rate / 1000.0)).exp();
274
275 let (l, r) = if inputs.len() >= 2 {
276 (inputs[0], inputs[1])
277 } else if inputs.len() == 1 {
278 (inputs[0], inputs[0])
279 } else {
280 (0.0, 0.0)
281 };
282
283 let peak = l.abs().max(r.abs());
284 let alpha = if peak > self.envelope { attack_alpha } else { release_alpha };
285 self.envelope += alpha * (peak - self.envelope);
286
287 let gain = if self.envelope > threshold {
288 let over_db = 20.0 * (self.envelope / threshold).log10();
289 let reduction_db = over_db * (1.0 - 1.0 / ratio);
290 10.0_f32.powf(-reduction_db / 20.0)
291 } else {
292 1.0
293 };
294
295 outputs[0][0] = l * gain;
296 outputs[0][1] = r * gain;
297 }
298}
299
300pub struct ForeignNode {
301 host: Option<PluginHost>,
302 plugin_name: String,
303 buffer_size: usize,
304 in_buffer: Vec<f32>,
305 out_buffer: Vec<f32>,
306 buffer_idx: usize,
307 has_crashed: bool,
308}
309
310impl ForeignNode {
311 pub fn new(plugin_name: String, buffer_size: usize) -> Self {
312 Self {
313 host: None,
314 plugin_name,
315 buffer_size,
316 in_buffer: vec![0.0; buffer_size],
317 out_buffer: vec![0.0; buffer_size],
318 buffer_idx: 0,
319 has_crashed: false,
320 }
321 }
322
323 fn ensure_host(&mut self) -> bool {
324 if self.has_crashed { return false; }
325 if self.host.is_some() { return true; }
326
327 match PluginHost::new(&self.plugin_name, self.buffer_size) {
328 Ok(h) => {
329 self.host = Some(h);
330 true
331 }
332 Err(_) => {
333 self.has_crashed = true;
334 false
335 }
336 }
337 }
338}
339
340impl DspNode for ForeignNode {
341 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
342 if !self.ensure_host() {
343 outputs[0] = [0.0, 0.0];
345 return;
346 }
347
348 let input_val = if !inputs.is_empty() { inputs[0] } else { 0.0 };
349 self.in_buffer[self.buffer_idx] = input_val;
350
351 outputs[0][0] = self.out_buffer[self.buffer_idx];
354 outputs[0][1] = self.out_buffer[self.buffer_idx];
355
356 self.buffer_idx += 1;
357 if self.buffer_idx >= self.buffer_size {
358 self.buffer_idx = 0;
359 if let Some(host) = self.host.as_mut() {
361 if host.process(&self.in_buffer, &mut self.out_buffer).is_err() {
362 self.has_crashed = true;
363 self.host = None;
364 if let Some(flag) = _ctx.crash_flag {
365 flag.store(true, std::sync::atomic::Ordering::SeqCst);
366 }
367 }
368 }
369 }
370 }
371
372 fn update_parameter(&mut self, param: &str, value: f32) {
373 if let Some(host) = self.host.as_mut() {
374 if let Ok(id) = param.parse::<u32>() {
375 let _ = host.set_parameter(id, value);
376 }
377 }
378 }
379}
380
381pub struct DelayNode {
382 buffer: Vec<[f32; 2]>,
383 write_pos: usize,
384}
385
386impl DelayNode {
387 pub fn new(max_delay_samples: usize) -> Self {
388 Self {
389 buffer: vec![[0.0, 0.0]; max_delay_samples],
390 write_pos: 0,
391 }
392 }
393}
394
395impl DspNode for DelayNode {
396 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
397 let delay_samples = config.get("delay_samples").and_then(|v| v.as_float()).unwrap_or(4410.0) as usize;
398 let feedback = config.get("feedback").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
399
400 let read_pos = (self.write_pos + self.buffer.len() - delay_samples) % self.buffer.len();
401 let delayed = self.buffer[read_pos];
402
403 outputs[0][0] = delayed[0];
404 outputs[0][1] = delayed[1];
405
406 let in_l = if inputs.len() >= 1 { inputs[0] } else { 0.0 };
407 let in_r = if inputs.len() >= 2 { inputs[1] } else { 0.0 };
408
409 self.buffer[self.write_pos] = [
410 in_l + delayed[0] * feedback,
411 in_r + delayed[1] * feedback,
412 ];
413
414 self.write_pos = (self.write_pos + 1) % self.buffer.len();
415 }
416}
417
418pub struct AddNode;
423impl AddNode { pub fn new() -> Self { Self } }
424
425impl DspNode for AddNode {
426 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
427 let mut l = 0.0;
429 let mut r = 0.0;
430 for chunk in inputs.chunks_exact(2) {
431 l += chunk[0];
432 r += chunk[1];
433 }
434 outputs[0][0] = l;
435 outputs[0][1] = r;
436 }
437}
438
439pub struct MultiplyNode;
440impl MultiplyNode { pub fn new() -> Self { Self } }
441
442impl DspNode for MultiplyNode {
443 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
444 if inputs.len() >= 4 {
445 outputs[0][0] = inputs[0] * inputs[2];
446 outputs[0][1] = inputs[1] * inputs[3];
447 } else {
448 outputs[0][0] = 0.0;
449 outputs[0][1] = 0.0;
450 }
451 }
452}
453
454pub struct ClipNode;
455impl ClipNode { pub fn new() -> Self { Self } }
456
457impl DspNode for ClipNode {
458 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
459 let min = config.get("min").and_then(|v| v.as_float()).unwrap_or(-1.0) as f32;
460 let max = config.get("max").and_then(|v| v.as_float()).unwrap_or(1.0) as f32;
461
462 if inputs.len() >= 2 {
463 outputs[0][0] = inputs[0].clamp(min, max);
464 outputs[0][1] = inputs[1].clamp(min, max);
465 }
466 }
467}
468
469pub struct TriggerNode;
474impl TriggerNode { pub fn new() -> Self { Self } }
475
476impl DspNode for TriggerNode {
477 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
478 let trigger_sample = config.get("sample").and_then(|v| v.as_float()).unwrap_or(0.0) as u64;
479 let val = if ctx.global_sample_index == trigger_sample { 1.0 } else { 0.0 };
480 outputs[0][0] = val;
481 outputs[0][1] = val;
482 }
483}
484
485#[derive(Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
486enum EnvState { Idle, Attack, Decay, Sustain, Release, FastRelease }
487
488pub struct EnvelopeNode {
489 state: EnvState,
490 level: f32,
491}
492
493impl EnvelopeNode {
494 pub fn new() -> Self {
495 Self { state: EnvState::Idle, level: 0.0 }
496 }
497
498 pub fn is_idle(&self) -> bool {
499 self.state == EnvState::Idle
500 }
501}
502
503impl DspNode for EnvelopeNode {
504 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
505 let a = config.get("attack").and_then(|v| v.as_float()).unwrap_or(0.1) as f32;
506 let d = config.get("decay").and_then(|v| v.as_float()).unwrap_or(0.1) as f32;
507 let s = config.get("sustain").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
508 let r = config.get("release").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
509
510 let gate = inputs.get(0).cloned().unwrap_or(0.0) > 0.0;
511
512 match self.state {
513 EnvState::Idle => {
514 if gate { self.state = EnvState::Attack; }
515 }
516 EnvState::Attack => {
517 if !gate { self.state = EnvState::Release; }
518 else {
519 self.level += 1.0 / (a * ctx.sample_rate);
520 if self.level >= 1.0 {
521 self.level = 1.0;
522 self.state = EnvState::Decay;
523 }
524 }
525 }
526 EnvState::Decay => {
527 if !gate { self.state = EnvState::Release; }
528 else {
529 self.level -= (1.0 - s) / (d * ctx.sample_rate);
530 if self.level <= s {
531 self.level = s;
532 self.state = EnvState::Sustain;
533 }
534 }
535 }
536 EnvState::Sustain => {
537 if !gate { self.state = EnvState::Release; }
538 }
539 EnvState::Release => {
540 if gate { self.state = EnvState::Attack; }
541 else {
542 let release_rate = 1.0 / (r.max(0.001) * ctx.sample_rate);
545 self.level -= release_rate;
546 if self.level <= 0.0 {
547 self.level = 0.0;
548 self.state = EnvState::Idle;
549 }
550 }
551 }
552 EnvState::FastRelease => {
553 let fade_out_rate = 1.0 / (0.005 * ctx.sample_rate);
555 self.level -= fade_out_rate;
556 if self.level <= 0.0 {
557 self.level = 0.0;
558 self.state = EnvState::Idle;
559 }
560 }
561 }
562
563 outputs[0][0] = self.level;
564 outputs[0][1] = self.level;
565 }
566
567 fn update_parameter(&mut self, param: &str, _value: f32) {
568 if param == "steal" {
569 self.state = EnvState::FastRelease;
570 }
571 }
572
573 fn extract_state(&self) -> NodeState {
574 NodeState::from_json(serde_json::json!({
575 "state": self.state,
576 "level": self.level
577 }))
578 }
579
580 fn inject_state(&mut self, state: &NodeState) {
581 if let Some(data) = state.to_json::<serde_json::Value>() {
582 if let Some(s) = data.get("state").and_then(|v| serde_json::from_value::<EnvState>(v.clone()).ok()) {
583 self.state = s;
584 }
585 if let Some(l) = data.get("level").and_then(|v| v.as_f64()) {
586 self.level = l as f32;
587 }
588 }
589 }
590}
591
592pub struct SequencerNode {
593 last_step_idx: i32,
594}
595
596impl SequencerNode {
597 pub fn new() -> Self {
598 Self { last_step_idx: -1 }
599 }
600}
601
602impl DspNode for SequencerNode {
603 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
604 let bpm = config.get("bpm").and_then(|v| v.as_float()).unwrap_or(120.0) as f32;
605 let steps_data = config.get("steps").and_then(|v| v.as_list());
606
607 let samples_per_step = (60.0 / (bpm * 4.0)) * ctx.sample_rate;
608 let current_step_idx = ((ctx.global_sample_index as f32 / samples_per_step) as i32) % 16;
609
610 outputs[0] = [0.0, 0.0];
611
612 if current_step_idx != self.last_step_idx {
613 if let Some(steps) = steps_data {
615 let step = &steps[current_step_idx as usize];
616 if let Some(note_val) = step.as_float() {
617 let note = note_val as u32;
620 let vel = 100u32;
621 outputs[0][0] = 1.0; outputs[0][1] = ((note << 8) | vel) as f32;
623 } else {
624 outputs[0][0] = 2.0; }
629 }
630 self.last_step_idx = current_step_idx;
631 }
632 }
633}
634
635pub struct AutomationNode;
636impl AutomationNode { pub fn new() -> Self { Self } }
637
638impl DspNode for AutomationNode {
639 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
640 let keyframes = config.get("keyframes").and_then(|v| v.as_list());
641 let current_time = ctx.global_sample_index as f64 / ctx.sample_rate as f64;
642
643 let mut val = 0.0;
644
645 if let Some(keys) = keyframes {
646 let mut prev_t = 0.0;
647 let mut prev_v = 0.0;
648 let mut found = false;
649
650 for key in keys {
651 if let Some(pair) = key.as_list() {
652 if pair.len() >= 2 {
653 let t = pair[0].as_float().unwrap_or(0.0);
654 let v = pair[1].as_float().unwrap_or(0.0) as f32;
655
656 if current_time < t {
657 let dt = t - prev_t;
658 if dt > 0.0 {
659 let frac = ((current_time - prev_t) / dt) as f32;
660 val = prev_v + (v - prev_v) * frac;
661 } else {
662 val = v;
663 }
664 found = true;
665 break;
666 }
667 prev_t = t;
668 prev_v = v;
669 }
670 }
671 }
672 if !found {
673 val = prev_v;
674 }
675 }
676
677 outputs[0][0] = val;
678 outputs[0][1] = val;
679 }
680}
681
682pub struct MidiEvent {
683 pub sample_index: u64,
684 pub message: [u8; 3],
685}
686
687pub struct MidiInNode {
688 event_rx: crossbeam_channel::Receiver<MidiEvent>,
689 gate: f32,
690 pitch_hz: f32,
691 velocity: f32,
692 pending_events: Vec<MidiEvent>,
693}
694
695impl MidiInNode {
696 pub fn new(event_rx: crossbeam_channel::Receiver<MidiEvent>) -> Self {
697 Self {
698 event_rx,
699 gate: 0.0,
700 pitch_hz: 440.0,
701 velocity: 0.0,
702 pending_events: Vec::new(),
703 }
704 }
705}
706
707impl DspNode for MidiInNode {
708 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, ctx: &ProcessContext) {
709 while let Ok(event) = self.event_rx.try_recv() {
711 self.pending_events.push(event);
712 }
713
714 self.pending_events.retain(|event| {
716 if event.sample_index <= ctx.global_sample_index {
717 let status = event.message[0] & 0xF0;
718 match status {
719 0x90 => { let note = event.message[1];
721 let vel = event.message[2];
722 if vel > 0 {
723 self.gate = 1.0;
724 self.pitch_hz = 440.0 * 2.0_f32.powf((note as f32 - 69.0) / 12.0);
725 self.velocity = vel as f32 / 127.0;
726 } else {
727 self.gate = 0.0;
728 }
729 }
730 0x80 => { self.gate = 0.0;
732 }
733 _ => {}
734 }
735 false } else {
737 true }
739 });
740
741 outputs[0][0] = self.gate;
743 outputs[0][1] = self.gate;
744 if outputs.len() > 1 {
746 outputs[1][0] = self.pitch_hz;
747 outputs[1][1] = self.pitch_hz;
748 }
749 if outputs.len() > 2 {
751 outputs[2][0] = self.velocity;
752 outputs[2][1] = self.velocity;
753 }
754 }
755}
756
757
758
759pub struct WavefolderNode {
764 _stages: usize,
765}
766
767impl WavefolderNode {
768 pub fn new() -> Self { Self { _stages: 4 } }
769}
770
771impl DspNode for WavefolderNode {
772 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
773 let gain = config.get("gain").and_then(|v| v.as_float()).unwrap_or(1.0) as f32;
774 let stages = config.get("stages").and_then(|v| match v {
775 dirtydata_core::types::ConfigValue::Int(i) => Some(*i as usize),
776 _ => None,
777 }).unwrap_or(4);
778
779 for i in 0..outputs.len() {
780 let mut l = inputs.get(i * 2).cloned().unwrap_or(0.0) * gain;
781 let mut r = inputs.get(i * 2 + 1).cloned().unwrap_or(0.0) * gain;
782
783 for _ in 0..stages {
784 l = (l * std::f32::consts::PI * 0.5).sin();
785 r = (r * std::f32::consts::PI * 0.5).sin();
786 }
787 outputs[i] = [l, r];
788 }
789 }
790}
791
792pub struct LorenzNode {
793 state: [f32; 3],
794 sigma: f32,
795 rho: f32,
796 beta: f32,
797}
798
799impl LorenzNode {
800 pub fn new() -> Self {
801 Self { state: [0.1, 0.0, 0.0], sigma: 10.0, rho: 28.0, beta: 8.0/3.0 }
802 }
803}
804
805impl DspNode for LorenzNode {
806 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
807 let speed = config.get("speed").and_then(|v| v.as_float()).unwrap_or(1.0) as f32;
808 let dt = speed / ctx.sample_rate;
809
810 let sigma = self.sigma;
811 let rho = self.rho;
812 let beta = self.beta;
813
814 rk4_step_fixed(&mut self.state, dt, 0.0, |state, _t| {
816 [
817 sigma * (state[1] - state[0]),
818 state[0] * (rho - state[2]) - state[1],
819 state[0] * state[1] - beta * state[2],
820 ]
821 });
822
823 for s in &mut self.state {
825 *s = s.clamp(-100.0, 100.0);
826 if !s.is_finite() { *s = 0.1; }
827 }
828
829 outputs[0] = [self.state[0] * 0.05, self.state[1] * 0.05];
831 if outputs.len() > 1 {
832 outputs[1] = [self.state[2] * 0.05, 0.0];
833 }
834 }
835}
836
837pub struct MackeyGlassNode {
838 history: VecDeque<f32>,
839 _tau_samples: usize,
840 beta: f32,
841 gamma: f32,
842 n: f32,
843 current_x: f32,
844}
845
846impl MackeyGlassNode {
847 pub fn new(tau_ms: f32, sample_rate: f32) -> Self {
848 let tau_samples = (tau_ms * 0.001 * sample_rate) as usize;
849 let mut history = VecDeque::with_capacity(tau_samples + 1);
850 for _ in 0..=tau_samples { history.push_back(0.5); }
851 Self { history, _tau_samples: tau_samples, beta: 2.0, gamma: 1.0, n: 10.0, current_x: 0.5 }
852 }
853}
854
855impl DspNode for MackeyGlassNode {
856 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
857 let speed = config.get("speed").and_then(|v| v.as_float()).unwrap_or(1.0) as f32;
858 let dt = speed / ctx.sample_rate;
859
860 let x_tau = *self.history.front().unwrap();
861
862 let f = |x: f32, xt: f32| self.beta * xt / (1.0 + xt.powf(self.n)) - self.gamma * x;
864
865 let k1 = f(self.current_x, x_tau);
866 let k2 = f(self.current_x + k1 * dt * 0.5, x_tau);
867 let k3 = f(self.current_x + k2 * dt * 0.5, x_tau);
868 let k4 = f(self.current_x + k3 * dt, x_tau);
869
870 self.current_x += (dt / 6.0) * (k1 + 2.0 * k2 + 2.0 * k3 + k4);
871 self.history.push_back(self.current_x);
872 self.history.pop_front();
873
874 outputs[0] = [self.current_x, self.current_x];
875 }
876}
877
878pub struct GrayScottNode {
879 u: [Vec<f32>; 2], v: [Vec<f32>; 2],
881 current: usize,
882 size: usize,
883 f: f32,
884 k: f32,
885 du: f32,
886 dv: f32,
887}
888
889impl GrayScottNode {
890 pub fn new(size: usize) -> Self {
891 let u0 = vec![1.0; size];
892 let mut v0 = vec![0.0; size];
893 for i in (size/2 - 5)..(size/2 + 5) { v0[i] = 0.5; }
894 Self {
895 u: [u0.clone(), vec![0.0; size]],
896 v: [v0.clone(), vec![0.0; size]],
897 current: 0,
898 size, f: 0.0545, k: 0.062, du: 0.1, dv: 0.05,
899 }
900 }
901}
902
903impl DspNode for GrayScottNode {
904 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
905 let cur = self.current;
906 let nxt = 1 - cur;
907
908 for i in 0..self.size {
909 let prev = if i == 0 { self.size - 1 } else { i - 1 };
910 let next = if i == self.size - 1 { 0 } else { i + 1 };
911
912 let u_val = self.u[cur][i];
913 let v_val = self.v[cur][i];
914 let lap_u = self.u[cur][prev] + self.u[cur][next] - 2.0 * u_val;
915 let lap_v = self.v[cur][prev] + self.v[cur][next] - 2.0 * v_val;
916 let uv2 = u_val * v_val * v_val;
917
918 self.u[nxt][i] = (u_val + self.du * lap_u - uv2 + self.f * (1.0 - u_val)).clamp(0.0, 1.5);
919 self.v[nxt][i] = (v_val + self.dv * lap_v + uv2 - (self.f + self.k) * v_val).clamp(0.0, 1.5);
920 }
921
922 self.current = nxt;
923 outputs[0] = [self.u[nxt][self.size/2] * 2.0 - 1.0, self.v[nxt][self.size/2] * 2.0 - 1.0];
924 }
925}
926
927pub struct SlewLimiterNode {
928 current: f32,
929}
930
931impl SlewLimiterNode {
932 pub fn new() -> Self { Self { current: 0.0 } }
933}
934
935impl DspNode for SlewLimiterNode {
936 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
937 let rise = config.get("rise").and_then(|v| v.as_float()).unwrap_or(0.1) as f32;
938 let fall = config.get("fall").and_then(|v| v.as_float()).unwrap_or(0.1) as f32;
939
940 for i in 0..outputs.len() {
941 let target = inputs.get(i * 2).cloned().unwrap_or(0.0);
942 let diff = target - self.current;
943 let limit = if diff > 0.0 { rise } else { fall };
944 let step = diff.clamp(-limit / ctx.sample_rate, limit / ctx.sample_rate);
945 self.current += step;
946 outputs[i] = [self.current, self.current];
947 }
948 }
949}
950
951pub struct SampleHoldNode {
952 last_val: [f32; 2],
953 last_trig: f32,
954}
955
956impl SampleHoldNode {
957 pub fn new() -> Self { Self { last_val: [0.0, 0.0], last_trig: 0.0 } }
958}
959
960impl DspNode for SampleHoldNode {
961 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
962 for i in 0..outputs.len() {
963 let sig_l = inputs.get(i * 2).cloned().unwrap_or(0.0);
964 let sig_r = inputs.get(i * 2 + 1).cloned().unwrap_or(0.0);
965 let trig = inputs.get(i * 2 + 2).cloned().unwrap_or(0.0); if trig > 0.5 && self.last_trig <= 0.5 {
968 self.last_val = [sig_l, sig_r];
969 }
970 self.last_trig = trig;
971 outputs[i] = self.last_val;
972 }
973 }
974}
975
976pub struct ClockNode {
977 phase: f32,
978}
979
980impl ClockNode {
981 pub fn new() -> Self { Self { phase: 0.0 } }
982}
983
984impl DspNode for ClockNode {
985 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
986 let bpm = config.get("bpm").and_then(|v| v.as_float()).unwrap_or(120.0) as f32;
987 let division = config.get("division").and_then(|v| v.as_float()).unwrap_or(4.0) as f32; let freq = (bpm / 60.0) * (division / 4.0);
990 let phase_step = freq / ctx.sample_rate;
991
992 for i in 0..outputs.len() {
993 let old_phase = self.phase;
994 self.phase = (self.phase + phase_step).fract();
995
996 let trigger = if self.phase < old_phase { 1.0 } else { 0.0 };
997 outputs[i] = [trigger, trigger];
998 }
999 }
1000}
1001
1002pub struct ProbabilityGateNode {
1003 rng: Pcg32,
1004}
1005
1006impl ProbabilityGateNode {
1007 pub fn new() -> Self { Self { rng: Pcg32::seed_from_u64(42) } }
1008}
1009
1010impl DspNode for ProbabilityGateNode {
1011 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1012 let prob = config.get("probability").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1013
1014 for i in 0..outputs.len() {
1015 let trig = inputs.get(i * 2).cloned().unwrap_or(0.0);
1016 let mut out = 0.0;
1017 if trig > 0.5 {
1018 if self.rng.gen::<f32>() < prob {
1019 out = 1.0;
1020 }
1021 }
1022 outputs[i] = [out, out];
1023 }
1024 }
1025}
1026
1027pub struct ReverbNode {
1028 delays: Vec<VecDeque<f32>>,
1029 feedback_matrix: [[f32; 4]; 4],
1030}
1031
1032impl ReverbNode {
1033 pub fn new(sample_rate: f32) -> Self {
1034 let delay_times = [0.037, 0.043, 0.051, 0.061]; let delays = delay_times.iter().map(|&t| {
1036 let size = (t * sample_rate) as usize;
1037 let mut dq = VecDeque::with_capacity(size);
1038 for _ in 0..size { dq.push_back(0.0); }
1039 dq
1040 }).collect();
1041
1042 let h = 0.5;
1044 let feedback_matrix = [
1045 [h, h, h, h],
1046 [h, -h, h, -h],
1047 [h, h, -h, -h],
1048 [h, -h, -h, h],
1049 ];
1050
1051 Self { delays, feedback_matrix }
1052 }
1053}
1054
1055impl DspNode for ReverbNode {
1056 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1057 let decay = config.get("decay").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1058 let mix = config.get("mix").and_then(|v| v.as_float()).unwrap_or(0.3) as f32;
1059
1060 for i in 0..outputs.len() {
1061 let input_l = inputs.get(i * 2).cloned().unwrap_or(0.0);
1062 let input_r = inputs.get(i * 2 + 1).cloned().unwrap_or(0.0);
1063 let mono_in = (input_l + input_r) * 0.5;
1064
1065 let mut y = [0.0; 4];
1067 for j in 0..4 {
1068 y[j] = *self.delays[j].front().unwrap();
1069 }
1070
1071 let mut fb = [0.0; 4];
1073 for row in 0..4 {
1074 for col in 0..4 {
1075 fb[row] += self.feedback_matrix[row][col] * y[col];
1076 }
1077 }
1078
1079 for j in 0..4 {
1081 self.delays[j].push_back(mono_in + fb[j] * decay);
1082 self.delays[j].pop_front();
1083 }
1084
1085 let wet_l = y[0] + y[1];
1087 let wet_r = y[2] + y[3];
1088
1089 outputs[i] = [
1090 input_l * (1.0 - mix) + wet_l * mix,
1091 input_r * (1.0 - mix) + wet_r * mix
1092 ];
1093 }
1094 }
1095}
1096
1097pub struct Grain {
1098 pos: f32,
1099 duration_samples: f32,
1100 current_sample: f32,
1101 active: bool,
1102}
1103
1104pub struct GranularNode {
1105 buffer: Vec<[f32; 2]>,
1106 write_pos: usize,
1107 grains: Vec<Grain>,
1108 next_grain_samples: f32,
1109}
1110
1111impl GranularNode {
1112 pub fn new(sample_rate: f32) -> Self {
1113 let buf_size = (sample_rate * 2.0) as usize; let mut grains = Vec::new();
1115 for _ in 0..16 {
1116 grains.push(Grain { pos: 0.0, duration_samples: 0.0, current_sample: 0.0, active: false });
1117 }
1118 Self {
1119 buffer: vec![[0.0, 0.0]; buf_size],
1120 write_pos: 0,
1121 grains,
1122 next_grain_samples: 0.0,
1123 }
1124 }
1125}
1126
1127impl DspNode for GranularNode {
1128 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
1129 let pos_norm = config.get("position").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1130 let size_ms = config.get("size").and_then(|v| v.as_float()).unwrap_or(50.0) as f32;
1131 let density = config.get("density").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1132
1133 let size_samples = (size_ms * 0.001 * ctx.sample_rate) as f32;
1134
1135 for i in 0..outputs.len() {
1137 let in_l = inputs.get(i * 2).cloned().unwrap_or(0.0);
1138 let in_r = inputs.get(i * 2 + 1).cloned().unwrap_or(0.0);
1139 self.buffer[self.write_pos] = [in_l, in_r];
1140 self.write_pos = (self.write_pos + 1) % self.buffer.len();
1141
1142 self.next_grain_samples -= 1.0;
1144 if self.next_grain_samples <= 0.0 {
1145 if let Some(grain) = self.grains.iter_mut().find(|g| !g.active) {
1146 grain.active = true;
1147 grain.current_sample = 0.0;
1148 grain.duration_samples = size_samples;
1149 let jitter = (rand::random::<f32>() - 0.5) * 0.05;
1151 grain.pos = (pos_norm + jitter).clamp(0.0, 1.0);
1152 }
1153 self.next_grain_samples = (1.0 - density) * size_samples * 0.5 + 100.0;
1154 }
1155
1156 let mut mixed = [0.0, 0.0];
1158 for grain in self.grains.iter_mut().filter(|g| g.active) {
1159 let norm_idx = grain.current_sample / grain.duration_samples;
1160
1161 let window = 1.0 - (2.0 * norm_idx - 1.0).abs();
1163
1164 let read_base = (grain.pos * (self.buffer.len() as f32 - 1.0)) as usize;
1165 let read_idx = (read_base + grain.current_sample as usize) % self.buffer.len();
1166 let val = self.buffer[read_idx];
1167
1168 mixed[0] += val[0] * window;
1169 mixed[1] += val[1] * window;
1170
1171 grain.current_sample += 1.0;
1172 if grain.current_sample >= grain.duration_samples {
1173 grain.active = false;
1174 }
1175 }
1176
1177 outputs[i] = mixed;
1178 }
1179 }
1180}
1181
1182pub struct WasmNode {
1183 instance: Option<wasmtime::Instance>,
1184 store: Option<wasmtime::Store<()>>,
1185 process_fn: Option<wasmtime::TypedFunc<(f32, f32), i64>>,
1186 failed: bool,
1187}
1188
1189impl WasmNode {
1190 pub fn new() -> Self {
1191 Self { instance: None, store: None, process_fn: None, failed: false }
1192 }
1193
1194 fn init(&mut self, path: &str) -> anyhow::Result<()> {
1195 let engine = wasmtime::Engine::default();
1196 let module = wasmtime::Module::from_file(&engine, path)?;
1197 let mut store = wasmtime::Store::new(&engine, ());
1198 let instance = wasmtime::Instance::new(&mut store, &module, &[])?;
1199
1200 let process_fn = instance.get_typed_func::<(f32, f32), i64>(&mut store, "process")?;
1201
1202 self.instance = Some(instance);
1203 self.store = Some(store);
1204 self.process_fn = Some(process_fn);
1205 Ok(())
1206 }
1207}
1208
1209impl DspNode for WasmNode {
1210 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1211 if self.instance.is_none() && !self.failed {
1212 if let Some(path) = config.get("path").and_then(|v| v.as_string()) {
1213 if let Err(e) = self.init(path) {
1214 eprintln!("Failed to init WasmNode: {}", e);
1215 self.failed = true;
1216 }
1217 }
1218 }
1219
1220 if let (Some(store), Some(f)) = (self.store.as_mut(), self.process_fn.as_mut()) {
1221 for i in 0..outputs.len() {
1222 let in_l = inputs.get(i * 2).cloned().unwrap_or(0.0);
1223 let in_r = inputs.get(i * 2 + 1).cloned().unwrap_or(0.0);
1224
1225 match f.call(&mut *store, (in_l, in_r)) {
1226 Ok(res) => {
1227 let out_l = f32::from_bits((res >> 32) as u32);
1229 let out_r = f32::from_bits(res as u32);
1230 outputs[i] = [out_l, out_r];
1231 }
1232 Err(_) => {
1233 outputs[i] = [in_l, in_r];
1234 }
1235 }
1236 }
1237 } else {
1238 for i in 0..outputs.len() {
1240 outputs[i] = [
1241 inputs.get(i * 2).cloned().unwrap_or(0.0),
1242 inputs.get(i * 2 + 1).cloned().unwrap_or(0.0)
1243 ];
1244 }
1245 }
1246 }
1247}
1248
1249pub struct LogicNode;
1255impl LogicNode { pub fn new() -> Self { Self } }
1256impl DspNode for LogicNode {
1257 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1258 let mode = config.get("mode").and_then(|v| v.as_string()).map(|s| s.as_str()).unwrap_or("AND");
1259 let threshold = config.get("threshold").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1260
1261 let a = inputs.get(0).cloned().unwrap_or(0.0) > threshold;
1262 let b = inputs.get(1).cloned().unwrap_or(0.0) > threshold;
1263
1264 let res = match mode {
1265 "AND" => a && b,
1266 "OR" => a || b,
1267 "XOR" => a ^ b,
1268 "NOT" => !a,
1269 _ => a && b,
1270 };
1271
1272 let val = if res { 1.0 } else { 0.0 };
1273 for out in outputs.iter_mut() {
1274 *out = [val, val];
1275 }
1276 }
1277}
1278
1279use rustfft::{FftPlanner, num_complex::Complex};
1280
1281pub struct SpectralFreezeNode {
1283 size: usize,
1284 buffer: Vec<f32>,
1285 fft_result: Vec<Complex<f32>>,
1286 frozen: bool,
1287 write_pos: usize,
1288 read_pos: usize,
1289}
1290
1291impl SpectralFreezeNode {
1292 pub fn new(size: usize) -> Self {
1293 Self {
1294 size,
1295 buffer: vec![0.0; size],
1296 fft_result: vec![Complex::default(); size],
1297 frozen: false,
1298 write_pos: 0,
1299 read_pos: 0,
1300 }
1301 }
1302}
1303
1304impl DspNode for SpectralFreezeNode {
1305 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1306 let freeze = config.get("freeze").and_then(|v| v.as_bool()).unwrap_or(false);
1307 let input = inputs.get(0).cloned().unwrap_or(0.0);
1308
1309 if freeze && !self.frozen {
1310 let mut planner = FftPlanner::new();
1312 let fft = planner.plan_fft_forward(self.size);
1313 let mut complex_buf: Vec<Complex<f32>> = self.buffer.iter().map(|&x| Complex::new(x, 0.0)).collect();
1314 fft.process(&mut complex_buf);
1315 self.fft_result = complex_buf;
1316 self.frozen = true;
1317
1318 let ifft = planner.plan_fft_inverse(self.size);
1320 let mut inv_buf = self.fft_result.clone();
1321 ifft.process(&mut inv_buf);
1322 for (i, c) in inv_buf.iter().enumerate() {
1323 self.buffer[i] = c.re / self.size as f32;
1324 }
1325 } else if !freeze {
1326 self.frozen = false;
1327 }
1328
1329 if !self.frozen {
1331 self.buffer[self.write_pos] = input;
1332 self.write_pos = (self.write_pos + 1) % self.size;
1333 }
1334
1335 let out_val = if self.frozen {
1337 let v = self.buffer[self.read_pos];
1338 self.read_pos = (self.read_pos + 1) % self.size;
1339 v
1340 } else {
1341 input
1342 };
1343
1344 for out in outputs.iter_mut() {
1345 *out = [out_val, out_val];
1346 }
1347 }
1348}
1349
1350pub struct FFTConvolveNode {
1352 size: usize,
1353 input_buffer: Vec<f32>,
1354 impulse_buffer: Vec<f32>,
1355 result_buffer: Vec<f32>,
1356 pos: usize,
1357}
1358
1359impl FFTConvolveNode {
1360 pub fn new(size: usize) -> Self {
1361 Self {
1362 size,
1363 input_buffer: vec![0.0; size],
1364 impulse_buffer: vec![0.0; size],
1365 result_buffer: vec![0.0; size],
1366 pos: 0,
1367 }
1368 }
1369}
1370
1371impl DspNode for FFTConvolveNode {
1372 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
1373 let input = inputs.get(0).cloned().unwrap_or(0.0);
1374 let impulse = inputs.get(1).cloned().unwrap_or(0.0);
1375
1376 self.input_buffer[self.pos] = input;
1377 self.impulse_buffer[self.pos] = impulse;
1378 self.pos += 1;
1379
1380 if self.pos >= self.size {
1381 let mut planner = FftPlanner::new();
1383 let fft = planner.plan_fft_forward(self.size);
1384
1385 let mut in_complex: Vec<Complex<f32>> = self.input_buffer.iter().map(|&x| Complex::new(x, 0.0)).collect();
1386 let mut imp_complex: Vec<Complex<f32>> = self.impulse_buffer.iter().map(|&x| Complex::new(x, 0.0)).collect();
1387
1388 fft.process(&mut in_complex);
1389 fft.process(&mut imp_complex);
1390
1391 for i in 0..self.size {
1393 in_complex[i] *= imp_complex[i];
1394 }
1395
1396 let ifft = planner.plan_fft_inverse(self.size);
1397 ifft.process(&mut in_complex);
1398
1399 for (i, c) in in_complex.iter().enumerate() {
1400 self.result_buffer[i] = c.re / self.size as f32;
1401 }
1402 self.pos = 0;
1403 }
1404
1405 let out_val = self.result_buffer[self.pos];
1406
1407 for out in outputs.iter_mut() {
1408 *out = [out_val, out_val];
1409 }
1410 }
1411}
1412
1413pub struct OscOutNode {
1415 last_sent_val: f32,
1416 threshold: f32,
1417}
1418
1419impl OscOutNode {
1420 pub fn new() -> Self {
1421 Self {
1422 last_sent_val: 0.0,
1423 threshold: 0.001,
1424 }
1425 }
1426}
1427
1428impl DspNode for OscOutNode {
1429 fn process(&mut self, inputs: &[f32], _outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
1430 let addr = config.get("address").and_then(|v| v.as_string()).map(|s| s.as_str()).unwrap_or("/dirtydata/out");
1431 let val = inputs.get(0).cloned().unwrap_or(0.0);
1432
1433 if (val - self.last_sent_val).abs() > self.threshold {
1435 if let Some(tx) = ctx.osc_tx {
1436 let _ = tx.try_send(OscMessage {
1437 addr: addr.to_string(),
1438 args: vec![rosc::OscType::Float(val)],
1439 });
1440 self.last_sent_val = val;
1441 }
1442 }
1443 }
1444}
1445
1446pub struct FeedbackNode {
1453 latch: [f32; 2],
1454}
1455
1456impl FeedbackNode {
1457 pub fn new() -> Self {
1458 Self { latch: [0.0, 0.0] }
1459 }
1460}
1461
1462impl DspNode for FeedbackNode {
1463 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
1464 outputs[0] = self.latch;
1466
1467 if inputs.len() >= 2 {
1469 self.latch = [inputs[0], inputs[1]];
1470 }
1471 }
1472}
1473
1474pub struct InputProxyNode { value: f32 }
1479impl InputProxyNode { pub fn new() -> Self { Self { value: 0.0 } } }
1480impl DspNode for InputProxyNode {
1481 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
1482 outputs[0] = [self.value, self.value];
1483 }
1484 fn update_parameter(&mut self, _param: &str, value: f32) { self.value = value; }
1485}
1486
1487pub struct OutputProxyNode;
1488impl OutputProxyNode { pub fn new() -> Self { Self } }
1489impl DspNode for OutputProxyNode {
1490 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
1491 let val = inputs.get(0).cloned().unwrap_or(0.0);
1492 outputs[0] = [val, val];
1493 }
1494}
1495
1496pub struct SubGraphNode {
1497 runner: Option<crate::DspRunner>,
1498 last_graph_hash: String,
1499}
1500
1501impl SubGraphNode {
1502 pub fn new() -> Self {
1503 Self { runner: None, last_graph_hash: String::new() }
1504 }
1505}
1506
1507impl DspNode for SubGraphNode {
1508 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
1509 let graph_json = config.get("graph_json").and_then(|v| v.as_string()).map(|s| s.as_str()).unwrap_or("");
1510 let hash = blake3::hash(graph_json.as_bytes()).to_string();
1511
1512 if hash != self.last_graph_hash && !graph_json.is_empty() {
1513 if let Ok(graph) = serde_json::from_str::<dirtydata_core::ir::Graph>(&graph_json) {
1514 self.runner = Some(crate::DspRunner::new(graph, None, ctx.sample_rate));
1515 self.last_graph_hash = hash;
1516 }
1517 }
1518
1519 if let Some(runner) = &mut self.runner {
1520 let mut proxy_ids = Vec::new();
1521 for (id, n) in &runner.get_graph().nodes {
1522 if n.kind == dirtydata_core::types::NodeKind::InputProxy {
1523 proxy_ids.push(*id);
1524 }
1525 }
1526 for (id, node) in runner.nodes_mut() {
1527 if proxy_ids.contains(id) {
1528 node.update_parameter("value", inputs.get(0).cloned().unwrap_or(0.0));
1529 }
1530 }
1531
1532 let sub_out = runner.process_sample(ctx);
1533 outputs[0] = sub_out;
1534 } else {
1535 for o in outputs { *o = [0.0, 0.0]; }
1536 }
1537 }
1538}
1539
1540pub struct ZdfLadderNode {
1545 inner: dirtydata_dsp_zdf::ZdfLadder,
1546}
1547impl ZdfLadderNode {
1548 pub fn new(sample_rate: f32) -> Self {
1549 Self { inner: dirtydata_dsp_zdf::ZdfLadder::new(sample_rate) }
1550 }
1551}
1552impl DspNode for ZdfLadderNode {
1553 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1554 let input = inputs.get(0).copied().unwrap_or(0.0);
1555 let cutoff = config.get("cutoff").and_then(|v| v.as_float()).unwrap_or(1000.0) as f32;
1556 let res = config.get("resonance").and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
1557 let drive = config.get("drive").and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
1558
1559 let out = self.inner.process(input, cutoff, res, drive);
1560 for o in outputs { *o = [out, out]; }
1561 }
1562}
1563
1564pub struct SvfNode {
1565 inner: dirtydata_dsp_svf::Svf,
1566}
1567impl SvfNode {
1568 pub fn new(sample_rate: f32) -> Self {
1569 Self { inner: dirtydata_dsp_svf::Svf::new(sample_rate) }
1570 }
1571}
1572impl DspNode for SvfNode {
1573 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1574 let input = inputs.get(0).copied().unwrap_or(0.0);
1575 let cutoff = config.get("cutoff").and_then(|v| v.as_float()).unwrap_or(1000.0) as f32;
1576 let q = config.get("q").and_then(|v| v.as_float()).unwrap_or(0.707) as f32;
1577 let mode = config.get("mode").and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
1578 let drive = config.get("drive").and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
1579
1580 let svf_out = if drive > 0.01 {
1581 self.inner.process_nonlinear(input, cutoff, q, drive)
1582 } else {
1583 self.inner.process(input, cutoff, q)
1584 };
1585 let out = match mode as i32 {
1586 0 => svf_out.lp,
1587 1 => svf_out.hp,
1588 2 => svf_out.bp,
1589 3 => svf_out.notch,
1590 4 => svf_out.ap,
1591 _ => svf_out.peak,
1592 };
1593 for o in outputs { *o = [out, out]; }
1594 }
1595}
1596
1597pub struct DiodeClipperNode {
1598 inner: dirtydata_dsp_clipper::DiodeClipper,
1599}
1600impl DiodeClipperNode {
1601 pub fn new() -> Self {
1602 Self { inner: dirtydata_dsp_clipper::DiodeClipper::new() }
1603 }
1604}
1605impl DspNode for DiodeClipperNode {
1606 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1607 let input = inputs.get(0).copied().unwrap_or(0.0);
1608 let drive = config.get("drive").and_then(|v| v.as_float()).unwrap_or(1.0) as f32;
1609 let asymmetry = config.get("asymmetry").and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
1610
1611 let out = self.inner.process(input, drive, asymmetry);
1612 for o in outputs { *o = [out, out]; }
1613 }
1614}
1615
1616pub struct BbdDelayNode {
1617 inner: dirtydata_dsp_bbd::BbdDelay,
1618}
1619impl BbdDelayNode {
1620 pub fn new(sample_rate: f32) -> Self {
1621 Self { inner: dirtydata_dsp_bbd::BbdDelay::new(sample_rate, 2.0) }
1622 }
1623}
1624impl DspNode for BbdDelayNode {
1625 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1626 let input = inputs.get(0).copied().unwrap_or(0.0);
1627 let time_ms = config.get("time_ms").and_then(|v| v.as_float()).unwrap_or(300.0) as f32;
1628 let feedback = config.get("feedback").and_then(|v| v.as_float()).unwrap_or(0.3) as f32;
1629 let dirt = config.get("dirt").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1630
1631 let out = self.inner.process(input, time_ms, feedback, dirt);
1632 for o in outputs { *o = [out, out]; }
1633 }
1634}
1635
1636pub struct WdfSimpleRcNode {
1641 inner: dirtydata_dsp_wdf::WdfSimpleRc,
1642}
1643impl WdfSimpleRcNode {
1644 pub fn new(sample_rate: f32) -> Self {
1645 Self { inner: dirtydata_dsp_wdf::WdfSimpleRc::new(1000.0, 1e-6, sample_rate) }
1646 }
1647}
1648impl DspNode for WdfSimpleRcNode {
1649 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
1650 let input = inputs.get(0).copied().unwrap_or(0.0);
1651 let out = self.inner.process(input);
1652 for o in outputs { *o = [out, out]; }
1653 }
1654}
1655
1656pub struct WdfDiodeClipperNode {
1657 inner: dirtydata_dsp_wdf::WdfDiodeClipper,
1658}
1659impl WdfDiodeClipperNode {
1660 pub fn new(sample_rate: f32) -> Self {
1661 Self { inner: dirtydata_dsp_wdf::WdfDiodeClipper::new(4700.0, 10e-9, sample_rate) }
1662 }
1663}
1664impl DspNode for WdfDiodeClipperNode {
1665 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, _ctx: &ProcessContext) {
1666 let input = inputs.get(0).copied().unwrap_or(0.0);
1667 let out = self.inner.process(input);
1668 for o in outputs { *o = [out, out]; }
1669 }
1670}
1671
1672pub struct KarplusStrongNode {
1673 inner: dirtydata_dsp_ks::KarplusStrong,
1674}
1675impl KarplusStrongNode {
1676 pub fn new(sample_rate: f32) -> Self {
1677 Self { inner: dirtydata_dsp_ks::KarplusStrong::new(sample_rate) }
1678 }
1679}
1680impl DspNode for KarplusStrongNode {
1681 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1682 let input = inputs.get(0).copied().unwrap_or(0.0);
1683 let freq = config.get("freq").and_then(|v| v.as_float()).unwrap_or(440.0) as f32;
1684 let damping = config.get("damping").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1685 let dispersion = config.get("dispersion").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1686 let pick_pos = config.get("pick_pos").and_then(|v| v.as_float()).unwrap_or(0.2) as f32;
1687
1688 let out = self.inner.process(input, freq, damping, dispersion, pick_pos);
1689 for o in outputs { *o = [out, out]; }
1690 }
1691}
1692
1693pub struct ModalResonatorNode {
1694 inner: dirtydata_dsp_modal::ModalResonatorBank,
1695 last_material: u32,
1696 last_freq: f32,
1697 last_bright: f32,
1698}
1699impl ModalResonatorNode {
1700 pub fn new(sample_rate: f32) -> Self {
1701 Self {
1702 inner: dirtydata_dsp_modal::ModalResonatorBank::new(sample_rate),
1703 last_material: 999,
1704 last_freq: -1.0,
1705 last_bright: -1.0,
1706 }
1707 }
1708}
1709impl DspNode for ModalResonatorNode {
1710 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1711 let input = inputs.get(0).copied().unwrap_or(0.0);
1712
1713 let material = config.get("material").and_then(|v| v.as_float()).unwrap_or(0.0) as u32;
1714 let freq = config.get("base_freq").and_then(|v| v.as_float()).unwrap_or(440.0) as f32;
1715 let bright = config.get("brightness").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1716
1717 if material != self.last_material || (freq - self.last_freq).abs() > 0.1 || (bright - self.last_bright).abs() > 0.01 {
1718 self.inner.set_material(material, freq, bright);
1719 self.last_material = material;
1720 self.last_freq = freq;
1721 self.last_bright = bright;
1722 }
1723
1724 let out = self.inner.process(input);
1725 for o in outputs { *o = [out, out]; }
1726 }
1727}
1728
1729pub struct SpringReverbNode {
1730 inner: dirtydata_dsp_spring::SpringReverb,
1731}
1732impl SpringReverbNode {
1733 pub fn new(sample_rate: f32) -> Self {
1734 Self { inner: dirtydata_dsp_spring::SpringReverb::new(sample_rate) }
1735 }
1736}
1737impl DspNode for SpringReverbNode {
1738 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1739 let input = inputs.get(0).copied().unwrap_or(0.0);
1740 let decay = config.get("decay").and_then(|v| v.as_float()).unwrap_or(0.8) as f32;
1741 let dispersion = config.get("dispersion").and_then(|v| v.as_float()).unwrap_or(0.6) as f32;
1742
1743 let out = self.inner.process(input, decay, dispersion);
1744 for o in outputs { *o = [out, out]; }
1745 }
1746}
1747
1748pub struct ChuaCircuitNode {
1753 inner: dirtydata_dsp_chaos::ChuaCircuit,
1754}
1755impl ChuaCircuitNode {
1756 pub fn new(sample_rate: f32) -> Self {
1757 Self { inner: dirtydata_dsp_chaos::ChuaCircuit::new(sample_rate) }
1758 }
1759}
1760impl DspNode for ChuaCircuitNode {
1761 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1762 let alpha = config.get("alpha").and_then(|v| v.as_float()).unwrap_or(15.6) as f32;
1763 let beta = config.get("beta").and_then(|v| v.as_float()).unwrap_or(28.0) as f32;
1764 let rate = config.get("rate").and_then(|v| v.as_float()).unwrap_or(1.0) as f32;
1765
1766 let out = self.inner.process(alpha, beta, rate);
1767 for o in outputs { *o = [out, out]; }
1768 }
1769}
1770
1771pub struct ReactionDiffusionNode {
1772 inner: dirtydata_dsp_reaction::ReactionDiffusion,
1773}
1774impl ReactionDiffusionNode {
1775 pub fn new() -> Self {
1776 Self { inner: dirtydata_dsp_reaction::ReactionDiffusion::new(256) }
1777 }
1778}
1779impl DspNode for ReactionDiffusionNode {
1780 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1781 let input = inputs.get(0).copied().unwrap_or(0.0);
1782 let da = config.get("da").and_then(|v| v.as_float()).unwrap_or(1.0) as f32;
1783 let db = config.get("db").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1784 let f = config.get("f").and_then(|v| v.as_float()).unwrap_or(0.055) as f32;
1785 let k = config.get("k").and_then(|v| v.as_float()).unwrap_or(0.062) as f32;
1786
1787 let out = self.inner.process(input, da, db, f, k);
1788 for o in outputs { *o = [out, out]; }
1789 }
1790}
1791
1792pub struct TapeMachineNode {
1793 inner: dirtydata_dsp_tape::TapeMachine,
1794}
1795impl TapeMachineNode {
1796 pub fn new(sample_rate: f32) -> Self {
1797 Self { inner: dirtydata_dsp_tape::TapeMachine::new(sample_rate) }
1798 }
1799}
1800impl DspNode for TapeMachineNode {
1801 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1802 let input = inputs.get(0).copied().unwrap_or(0.0);
1803 let drive = config.get("drive").and_then(|v| v.as_float()).unwrap_or(1.0) as f32;
1804 let wow = config.get("wow").and_then(|v| v.as_float()).unwrap_or(0.1) as f32;
1805 let flutter = config.get("flutter").and_then(|v| v.as_float()).unwrap_or(0.1) as f32;
1806 let bias = config.get("bias").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1807
1808 let out = self.inner.process(input, drive, wow, flutter, bias);
1809 for o in outputs { *o = [out, out]; }
1810 }
1811}
1812
1813pub struct MatrixMixerNode {
1818 inner: dirtydata_dsp_matrix::MatrixMixer,
1819}
1820impl MatrixMixerNode {
1821 pub fn new(num_in: usize, num_out: usize) -> Self {
1822 Self { inner: dirtydata_dsp_matrix::MatrixMixer::new(num_in, num_out) }
1823 }
1824}
1825impl DspNode for MatrixMixerNode {
1826 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1827 let g00 = config.get("g00").and_then(|v| v.as_float()).unwrap_or(1.0) as f32;
1829 let g01 = config.get("g01").and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
1830 let g10 = config.get("g10").and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
1831 let g11 = config.get("g11").and_then(|v| v.as_float()).unwrap_or(1.0) as f32;
1832
1833 self.inner.set_gain(0, 0, g00);
1834 self.inner.set_gain(1, 0, g01);
1835 self.inner.set_gain(0, 1, g10);
1836 self.inner.set_gain(1, 1, g11);
1837
1838 let in_flat: Vec<f32> = inputs.iter().copied().collect();
1839 let mut out_flat = vec![0.0; outputs.len() * 2];
1840 self.inner.process(&in_flat, &mut out_flat);
1841
1842 for (i, o) in outputs.iter_mut().enumerate() {
1843 o[0] = out_flat[i * 2];
1844 o[1] = out_flat[i * 2 + 1];
1845 }
1846 }
1847}
1848
1849pub struct SlewNode {
1854 inner: dirtydata_dsp_cv::Slew,
1855}
1856impl SlewNode {
1857 pub fn new() -> Self { Self { inner: dirtydata_dsp_cv::Slew::new() } }
1858}
1859impl DspNode for SlewNode {
1860 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
1861 let input = inputs.get(0).copied().unwrap_or(0.0);
1862 let rise = config.get("rise").and_then(|v| v.as_float()).unwrap_or(0.1) as f32;
1863 let fall = config.get("fall").and_then(|v| v.as_float()).unwrap_or(0.1) as f32;
1864 let out = self.inner.process(input, rise, fall, ctx.sample_rate);
1865 for o in outputs { *o = [out, out]; }
1866 }
1867}
1868
1869pub struct EuclideanSequencerNode {
1870 inner: dirtydata_dsp_cv::EuclideanSequencer,
1871}
1872impl EuclideanSequencerNode {
1873 pub fn new() -> Self { Self { inner: dirtydata_dsp_cv::EuclideanSequencer::new() } }
1874}
1875impl DspNode for EuclideanSequencerNode {
1876 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1877 let clock = inputs.get(0).copied().unwrap_or(0.0);
1878 self.inner.steps = config.get("steps").and_then(|v| v.as_float()).unwrap_or(16.0) as u32;
1879 self.inner.hits = config.get("hits").and_then(|v| v.as_float()).unwrap_or(4.0) as u32;
1880 let out = self.inner.process(clock);
1881 for o in outputs { *o = [out, out]; }
1882 }
1883}
1884
1885pub struct BitCrushNode {
1890 inner: dirtydata_dsp_destruction::BitCrush,
1891}
1892impl BitCrushNode {
1893 pub fn new() -> Self { Self { inner: dirtydata_dsp_destruction::BitCrush::new() } }
1894}
1895impl DspNode for BitCrushNode {
1896 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, _ctx: &ProcessContext) {
1897 let input = inputs.get(0).copied().unwrap_or(0.0);
1898 let bits = config.get("bits").and_then(|v| v.as_float()).unwrap_or(8.0) as f32;
1899 let srr = config.get("srr").and_then(|v| v.as_float()).unwrap_or(1.0) as f32;
1900 let out = self.inner.process(input, bits, srr);
1901 for o in outputs { *o = [out, out]; }
1902 }
1903}
1904
1905pub struct FunctionGeneratorNode {
1910 inner: dirtydata_dsp_control::FunctionGenerator,
1911}
1912impl FunctionGeneratorNode {
1913 pub fn new() -> Self { Self { inner: dirtydata_dsp_control::FunctionGenerator::new() } }
1914}
1915impl DspNode for FunctionGeneratorNode {
1916 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
1917 let trigger = inputs.get(0).copied().unwrap_or(0.0);
1918 let rise = config.get("rise").and_then(|v| v.as_float()).unwrap_or(0.1) as f32;
1919 let fall = config.get("fall").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
1920 let cycle = config.get("cycle").and_then(|v| v.as_bool()).unwrap_or(false);
1921 let out = self.inner.process(trigger, rise, fall, cycle, ctx.sample_rate);
1922 for o in outputs { *o = [out, out]; }
1923 }
1924}
1925
1926pub struct CircuitSandboxNode {
1931 solver: dirtydata_dsp_circuit::MnaSolver,
1932 probe_voltages: Vec<f32>,
1934}
1935
1936impl CircuitSandboxNode {
1937 pub fn new(sample_rate: f32) -> Self {
1938 let mut solver = dirtydata_dsp_circuit::MnaSolver::new(1.0 / sample_rate as f64);
1939
1940 solver.set_num_nodes(7);
1946
1947 solver.add_element(dirtydata_dsp_circuit::CircuitElement::VoltageSource {
1949 pos: dirtydata_dsp_circuit::NodeId(1), neg: dirtydata_dsp_circuit::NodeId(0), voltage: 0.0,
1950 });
1951
1952 solver.add_element(dirtydata_dsp_circuit::CircuitElement::VoltageSource {
1954 pos: dirtydata_dsp_circuit::NodeId(2), neg: dirtydata_dsp_circuit::NodeId(0), voltage: 0.7,
1955 });
1956
1957 for i in 0..4 {
1959 let n_in = if i == 0 { 1 } else { 3 + i - 1 };
1960 let n_out = 3 + i;
1961
1962 solver.add_element(dirtydata_dsp_circuit::CircuitElement::Diode {
1964 a: dirtydata_dsp_circuit::NodeId(n_in),
1965 k: dirtydata_dsp_circuit::NodeId(n_out),
1966 material: dirtydata_dsp_circuit::Material::Silicon,
1967 is: 1e-12,
1968 });
1969 solver.add_element(dirtydata_dsp_circuit::CircuitElement::Capacitor {
1971 a: dirtydata_dsp_circuit::NodeId(n_out),
1972 b: dirtydata_dsp_circuit::NodeId(0),
1973 value: 1e-8,
1974 state_v: 0.0,
1975 tolerance: 0.1,
1976 material: dirtydata_dsp_circuit::Material::Ceramic,
1977 });
1978 }
1979
1980 Self { solver, probe_voltages: vec![0.0; 256] }
1981 }
1982}
1983
1984impl DspNode for CircuitSandboxNode {
1985 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
1986 let input = inputs.get(0).copied().unwrap_or(0.0) as f64;
1987 let cutoff = config.get("cutoff").and_then(|v| v.as_float()).unwrap_or(0.7) as f64;
1988
1989 if let Some(temp) = config.get("temp_c").and_then(|v| v.as_float()) {
1991 self.solver.context.temperature_c = temp as f64;
1992 }
1993 if let Some(drift) = config.get("drift").and_then(|v| v.as_float()) {
1994 self.solver.context.global_drift = drift as f64;
1995 }
1996 if let Some(vcc) = config.get("vcc").and_then(|v| v.as_float()) {
1997 self.solver.context.vcc = vcc as f64;
1998 }
1999
2000 if let Some(dirtydata_dsp_circuit::CircuitElement::VoltageSource { voltage, .. }) = self.solver.add_element_dummy_handle(0) {
2002 *voltage = input;
2003 }
2004 if let Some(dirtydata_dsp_circuit::CircuitElement::VoltageSource { voltage, .. }) = self.solver.add_element_dummy_handle(1) {
2005 *voltage = cutoff;
2006 }
2007
2008 let state = self.solver.solve();
2009 let out = state.voltages.get(6).copied().unwrap_or(0.0) as f32; if ctx.sample_rate > 0.0 {
2014 self.probe_voltages.rotate_left(1);
2016 if let Some(last) = self.probe_voltages.last_mut() { *last = out; }
2017
2018 if state.iterations > 40 {
2020 }
2022 }
2023
2024 for o in outputs { *o = [out, out]; }
2025 }
2026}
2027
2028pub struct CircuitModuleNode {
2033 solver: dirtydata_dsp_circuit::MnaSolver,
2034 input_v_sources: Vec<usize>,
2036 output_nodes: Vec<usize>,
2038}
2039
2040impl CircuitModuleNode {
2041 pub fn new(sample_rate: f32, definition_json: &str) -> Option<Self> {
2042 let def: dirtydata_core::types::CircuitDefinition = serde_json::from_str(definition_json).ok()?;
2043 let elements: Vec<dirtydata_dsp_circuit::CircuitElement> = serde_json::from_str(&def.elements_json).ok()?;
2044
2045 let mut solver = dirtydata_dsp_circuit::MnaSolver::new(1.0 / sample_rate as f64);
2046
2047 let mut max_node = 0;
2049 for el in &elements {
2050 match el {
2051 dirtydata_dsp_circuit::CircuitElement::Resistor { a, b, .. } => { max_node = max_node.max(a.0).max(b.0); }
2052 dirtydata_dsp_circuit::CircuitElement::Capacitor { a, b, .. } => { max_node = max_node.max(a.0).max(b.0); }
2053 dirtydata_dsp_circuit::CircuitElement::Diode { a, k, .. } => { max_node = max_node.max(a.0).max(k.0); }
2054 dirtydata_dsp_circuit::CircuitElement::VoltageSource { pos, neg, .. } => { max_node = max_node.max(pos.0).max(neg.0); }
2055 }
2056 }
2057 solver.set_num_nodes(max_node + 1);
2058
2059 let mut input_v_sources = Vec::new();
2060 for (_, &node_id) in &def.input_mappings {
2061 let idx = solver.num_elements(); solver.add_element(dirtydata_dsp_circuit::CircuitElement::VoltageSource {
2063 pos: dirtydata_dsp_circuit::NodeId(node_id),
2064 neg: dirtydata_dsp_circuit::NodeId(0), voltage: 0.0,
2066 });
2067 input_v_sources.push(idx);
2068 }
2069
2070 for el in elements { solver.add_element(el); }
2071
2072 let mut output_nodes = Vec::new();
2073 for (_, &node_id) in &def.output_mappings {
2074 output_nodes.push(node_id);
2075 }
2076
2077 Some(Self { solver, input_v_sources, output_nodes })
2078 }
2079}
2080
2081impl DspNode for CircuitModuleNode {
2082 fn process(&mut self, inputs: &[f32], outputs: &mut [[f32; 2]], _config: &ConfigSnapshot, ctx: &ProcessContext) {
2083 for (i, &v_idx) in self.input_v_sources.iter().enumerate() {
2085 if let Some(val) = inputs.get(i) {
2086 if let Some(dirtydata_dsp_circuit::CircuitElement::VoltageSource { voltage, .. }) = self.solver.add_element_dummy_handle(v_idx) {
2087 *voltage = *val as f64;
2088 }
2089 }
2090 }
2091
2092 let state = self.solver.solve();
2094
2095 if let (Some(info), Some(id)) = (ctx.convergence_info.as_ref(), ctx.node_id) {
2096 info.insert(id, state.iterations);
2097 }
2098
2099 if !state.converged {
2100 if let (Some(diag), Some(id)) = (ctx.node_diagnostics.as_ref(), ctx.node_id) {
2101 diag.insert(id, crate::DiagnosticRecord {
2102 message: state.failure_culprit.clone().unwrap_or_default(),
2103 severity: crate::DiagnosticSeverity::Error,
2104 timestamp: ctx.global_sample_index,
2105 });
2106 }
2107 }
2108
2109 for (i, &node_id) in self.output_nodes.iter().enumerate() {
2111 if let Some(out_pair) = outputs.get_mut(i) {
2112 let v = state.voltages.get(node_id).copied().unwrap_or(0.0) as f32;
2113 *out_pair = [v, v];
2114 }
2115 }
2116 }
2117}
2118
2119pub struct VocalTractNode {
2124 inner: dirtydata_dsp_vocal::VocalTract,
2125}
2126impl VocalTractNode {
2127 pub fn new(sample_rate: f32) -> Self {
2128 let _ = sample_rate;
2129 Self { inner: dirtydata_dsp_vocal::VocalTract::new(44) } }
2131}
2132impl DspNode for VocalTractNode {
2133 fn process(&mut self, _inputs: &[f32], outputs: &mut [[f32; 2]], config: &ConfigSnapshot, ctx: &ProcessContext) {
2134 let freq = config.get("pitch").and_then(|v| v.as_float()).unwrap_or(110.0) as f32;
2135 let tongue_x = config.get("tongue_x").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
2136 let tongue_y = config.get("tongue_y").and_then(|v| v.as_float()).unwrap_or(0.5) as f32;
2137 let tension = config.get("tension").and_then(|v| v.as_float()).unwrap_or(0.8) as f32;
2138 let velum = config.get("velum").and_then(|v| v.as_float()).unwrap_or(0.0) as f32;
2139
2140 let vowel = config.get("vowel").and_then(|v| v.as_string());
2142 if let Some(v) = vowel {
2143 if let Some(ch) = v.chars().next() {
2144 self.inner.set_vowel(ch);
2145 }
2146 } else {
2147 self.inner.glottis.set_freq(freq);
2148 self.inner.set_tongue(tongue_x, tongue_y);
2149 self.inner.set_velum(velum);
2150 }
2151
2152 let out = self.inner.process(ctx.sample_rate, tension);
2153 for o in outputs { *o = [out, out]; }
2154 }
2155}
2156
2157
2158