1use alloc::vec;
10use alloc::vec::Vec;
11
12use crate::introspection::{ControlType, ModuleIntrospection, ParamCurve, ParamInfo, ValueFormat};
13
14use crate::analog::{AnalogVco, Saturator, Wavefolder};
15use crate::modules::{
16 Adsr, Arpeggiator, Attenuverter, BernoulliGate, Bitcrusher, ChordMemory, Chorus, Clock,
17 Comparator, Compressor, Crossfader, Crosstalk, DelayLine, DiodeLadderFilter, Distortion,
18 Ducker, EnvelopeFollower, Euclidean, Flanger, FormantOsc, Granular, GroundLoop, KarplusStrong,
19 Lfo, Limiter, LogicAnd, LogicNot, LogicOr, LogicXor, Max, MidSideDecode, MidSideEncode, Min,
20 Mixer, Multiple, NoiseGate, NoiseGenerator, Offset, Oversample, ParametricEq, Phaser,
21 PitchShifter, PrecisionAdder, Quantizer, Rectifier, Reverb, RingModulator, SampleAndHold,
22 SamplePlayer, Scale, ScaleQuantizer, SlewLimiter, StepSequencer, StereoOutput, Supersaw, Svf,
23 Tremolo, UnitDelay, VcSwitch, Vca, Vco, Vibrato, Vocoder, Wavetable,
24};
25
26impl ModuleIntrospection for Vco {}
32impl ModuleIntrospection for Lfo {}
33impl ModuleIntrospection for AnalogVco {}
34
35impl ModuleIntrospection for Svf {}
37impl ModuleIntrospection for DiodeLadderFilter {}
38
39impl ModuleIntrospection for Adsr {}
41impl ModuleIntrospection for Vca {}
42
43impl ModuleIntrospection for Mixer {}
45impl ModuleIntrospection for UnitDelay {}
46impl ModuleIntrospection for Attenuverter {}
47impl ModuleIntrospection for Multiple {}
48impl ModuleIntrospection for SlewLimiter {}
49impl ModuleIntrospection for SampleAndHold {}
50impl ModuleIntrospection for PrecisionAdder {}
51impl ModuleIntrospection for VcSwitch {}
52impl ModuleIntrospection for Min {}
53impl ModuleIntrospection for Max {}
54impl ModuleIntrospection for Crossfader {}
55impl ModuleIntrospection for MidSideEncode {}
56impl ModuleIntrospection for MidSideDecode {}
57
58impl ModuleIntrospection for RingModulator {}
60impl ModuleIntrospection for Rectifier {}
61impl ModuleIntrospection for Crosstalk {}
62
63impl ModuleIntrospection for LogicAnd {}
65impl ModuleIntrospection for LogicOr {}
66impl ModuleIntrospection for LogicXor {}
67impl ModuleIntrospection for LogicNot {}
68impl ModuleIntrospection for Comparator {}
69impl ModuleIntrospection for BernoulliGate {}
70
71impl ModuleIntrospection for Clock {}
73impl ModuleIntrospection for StereoOutput {}
74impl ModuleIntrospection for Arpeggiator {}
75
76impl ModuleIntrospection for ChordMemory {}
78impl ModuleIntrospection for ParametricEq {}
79impl ModuleIntrospection for Wavetable {}
80impl ModuleIntrospection for FormantOsc {}
81impl ModuleIntrospection for PitchShifter {}
82impl ModuleIntrospection for Reverb {}
83impl ModuleIntrospection for Vocoder {}
84impl ModuleIntrospection for Granular {}
85
86impl ModuleIntrospection for Bitcrusher {}
91impl ModuleIntrospection for Chorus {}
92impl ModuleIntrospection for Compressor {}
93impl ModuleIntrospection for DelayLine {}
94impl ModuleIntrospection for EnvelopeFollower {}
95impl ModuleIntrospection for Euclidean {}
96impl ModuleIntrospection for Flanger {}
97impl ModuleIntrospection for KarplusStrong {}
98impl ModuleIntrospection for Limiter {}
99impl ModuleIntrospection for NoiseGate {}
100impl ModuleIntrospection for Phaser {}
101impl ModuleIntrospection for Supersaw {}
102impl ModuleIntrospection for Tremolo {}
103impl ModuleIntrospection for Vibrato {}
104impl ModuleIntrospection for ScaleQuantizer {}
108
109fn oversample_to_index(factor: usize) -> f64 {
112 match factor {
113 4 => 2.0,
114 2 => 1.0,
115 _ => 0.0,
116 }
117}
118
119fn oversample_from_index(value: f64) -> Oversample {
120 match libm::round(value) as i64 {
123 2 => Oversample::X4,
124 1 => Oversample::X2,
125 _ => Oversample::Off,
126 }
127}
128
129impl ModuleIntrospection for Distortion {
130 fn param_infos(&self) -> Vec<ParamInfo> {
131 vec![ParamInfo::select("oversample", "Oversampling", 3)
132 .with_value(oversample_to_index(self.oversample_factor()))]
133 }
134
135 fn set_param_by_id(&mut self, id: &str, value: f64) -> bool {
136 match id {
137 "oversample" => {
138 self.set_oversample(oversample_from_index(value));
139 true
140 }
141 _ => false,
142 }
143 }
144}
145
146impl ModuleIntrospection for Offset {
151 fn param_infos(&self) -> Vec<ParamInfo> {
152 vec![ParamInfo::new("offset", "Offset")
153 .with_range(-10.0, 10.0)
154 .with_default(0.0)
155 .with_value(self.offset)
156 .with_curve(ParamCurve::Linear)
157 .with_control(ControlType::Knob)
158 .with_unit("V")
159 .with_format(ValueFormat::Decimal { places: 2 })]
160 }
161
162 fn set_param_by_id(&mut self, id: &str, value: f64) -> bool {
163 match id {
164 "offset" => {
165 self.set_offset(value);
166 true
167 }
168 _ => false,
169 }
170 }
171}
172
173impl ModuleIntrospection for NoiseGenerator {
174 fn param_infos(&self) -> Vec<ParamInfo> {
175 vec![ParamInfo::percent("correlation", "Stereo Correlation")
176 .with_default(0.3)
177 .with_value(self.correlation)]
178 }
179
180 fn set_param_by_id(&mut self, id: &str, value: f64) -> bool {
181 match id {
182 "correlation" => {
183 self.correlation = value.clamp(0.0, 1.0);
184 true
185 }
186 _ => false,
187 }
188 }
189}
190
191impl ModuleIntrospection for StepSequencer {
192 fn param_infos(&self) -> Vec<ParamInfo> {
193 let mut params = Vec::with_capacity(16);
194
195 for i in 0..8 {
196 if let Some((voltage, _gate)) = self.get_step(i) {
197 params.push(
198 ParamInfo::new(
199 alloc::format!("step_{}_cv", i),
200 alloc::format!("Step {} CV", i + 1),
201 )
202 .with_range(-5.0, 5.0)
203 .with_default(0.0)
204 .with_value(voltage)
205 .with_curve(ParamCurve::Linear)
206 .with_control(ControlType::Slider)
207 .with_unit("V")
208 .with_format(ValueFormat::NoteName),
209 );
210 }
211 }
212
213 for i in 0..8 {
214 if let Some((_voltage, gate)) = self.get_step(i) {
215 params.push(
216 ParamInfo::toggle(
217 alloc::format!("step_{}_gate", i),
218 alloc::format!("Step {} Gate", i + 1),
219 )
220 .with_default(1.0)
225 .with_value(if gate { 1.0 } else { 0.0 }),
226 );
227 }
228 }
229
230 params
231 }
232
233 fn set_param_by_id(&mut self, id: &str, value: f64) -> bool {
234 if let Some(rest) = id.strip_prefix("step_") {
235 if let Some((num_str, param_type)) = rest.split_once('_') {
236 if let Ok(step_idx) = num_str.parse::<usize>() {
237 if step_idx < 8 {
238 if let Some((current_cv, current_gate)) = self.get_step(step_idx) {
239 match param_type {
240 "cv" => {
241 self.set_step(step_idx, value, current_gate);
242 return true;
243 }
244 "gate" => {
245 self.set_step(step_idx, current_cv, value > 0.5);
246 return true;
247 }
248 _ => {}
249 }
250 }
251 }
252 }
253 }
254 }
255 false
256 }
257}
258
259impl ModuleIntrospection for Quantizer {
260 fn param_infos(&self) -> Vec<ParamInfo> {
261 let scale_value = match self.scale {
262 Scale::Chromatic => 0.0,
263 Scale::Major => 1.0,
264 Scale::Minor => 2.0,
265 Scale::PentatonicMajor => 3.0,
266 Scale::PentatonicMinor => 4.0,
267 Scale::Dorian => 5.0,
268 Scale::Mixolydian => 6.0,
269 Scale::Blues => 7.0,
270 };
271 vec![ParamInfo::select("scale", "Scale", 8).with_value(scale_value)]
272 }
273
274 fn set_param_by_id(&mut self, id: &str, value: f64) -> bool {
275 match id {
276 "scale" => {
277 let scale = match value as u8 {
278 0 => Scale::Chromatic,
279 1 => Scale::Major,
280 2 => Scale::Minor,
281 3 => Scale::PentatonicMajor,
282 4 => Scale::PentatonicMinor,
283 5 => Scale::Dorian,
284 6 => Scale::Mixolydian,
285 7 => Scale::Blues,
286 _ => return false,
287 };
288 self.set_scale(scale);
289 true
290 }
291 _ => false,
292 }
293 }
294}
295
296impl ModuleIntrospection for GroundLoop {
297 fn param_infos(&self) -> Vec<ParamInfo> {
298 vec![ParamInfo::select("frequency", "Mains Frequency", 2)
299 .with_default(1.0)
300 .with_value(if self.frequency == 60.0 { 1.0 } else { 0.0 })]
301 }
302
303 fn set_param_by_id(&mut self, id: &str, value: f64) -> bool {
304 match id {
305 "frequency" => {
306 self.frequency = if value > 0.5 { 60.0 } else { 50.0 };
307 true
308 }
309 _ => false,
310 }
311 }
312}
313
314impl ModuleIntrospection for Saturator {
315 fn param_infos(&self) -> Vec<ParamInfo> {
316 vec![ParamInfo::new("drive", "Drive")
317 .with_range(1.0, 10.0)
318 .with_default(1.0)
319 .with_value(self.drive)
320 .with_curve(ParamCurve::Exponential)
321 .with_control(ControlType::Knob)
322 .with_format(ValueFormat::Ratio)]
323 }
324
325 fn set_param_by_id(&mut self, id: &str, value: f64) -> bool {
326 match id {
327 "drive" => {
328 self.drive = value.clamp(1.0, 10.0);
329 true
330 }
331 _ => false,
332 }
333 }
334}
335
336impl ModuleIntrospection for Wavefolder {
337 fn param_infos(&self) -> Vec<ParamInfo> {
338 vec![
339 ParamInfo::new("threshold", "Fold Threshold")
340 .with_range(0.1, 5.0)
341 .with_default(1.0)
342 .with_value(self.threshold)
343 .with_curve(ParamCurve::Exponential)
344 .with_control(ControlType::Knob)
345 .with_unit("V")
346 .with_format(ValueFormat::Decimal { places: 2 }),
347 ParamInfo::select("oversample", "Oversampling", 3)
348 .with_value(oversample_to_index(self.oversample_factor())),
349 ]
350 }
351
352 fn set_param_by_id(&mut self, id: &str, value: f64) -> bool {
353 match id {
354 "threshold" => {
355 self.threshold = value.clamp(0.1, 5.0);
356 true
357 }
358 "oversample" => {
359 self.set_oversample(oversample_from_index(value));
360 true
361 }
362 _ => false,
363 }
364 }
365}
366
367impl ModuleIntrospection for SamplePlayer {
368 fn param_infos(&self) -> Vec<ParamInfo> {
369 vec![ParamInfo::percent("start", "Start Position")
370 .with_default(0.0)
371 .with_value(self.start_position())]
372 }
373
374 fn set_param_by_id(&mut self, id: &str, value: f64) -> bool {
375 match id {
376 "start" => {
377 self.set_start(value);
378 true
379 }
380 _ => false,
381 }
382 }
383}
384
385impl ModuleIntrospection for Ducker {
386 fn param_infos(&self) -> Vec<ParamInfo> {
387 vec![
394 ParamInfo::percent("depth", "Duck Amount")
395 .with_default(1.0)
396 .with_value(self.amount()),
397 ParamInfo::percent("thresh", "Threshold")
398 .with_default(0.2)
399 .with_value(self.threshold()),
400 ]
401 }
402
403 fn set_param_by_id(&mut self, id: &str, value: f64) -> bool {
404 match id {
405 "depth" => {
406 self.set_amount(value);
407 true
408 }
409 "thresh" => {
410 self.set_threshold(value);
411 true
412 }
413 _ => false,
414 }
415 }
416}
417
418#[cfg(test)]
423mod tests {
424 use super::*;
425
426 #[test]
427 fn test_offset_introspection() {
428 let mut offset = Offset::new(2.5);
429 let params = offset.param_infos();
430 assert_eq!(params.len(), 1);
431 assert_eq!(params[0].id, "offset");
432 assert_eq!(params[0].value, 2.5);
433
434 assert!(offset.set_param_by_id("offset", -3.0));
435 assert_eq!(offset.param_infos()[0].value, -3.0);
436 assert!(!offset.set_param_by_id("invalid", 0.0));
437 }
438
439 #[test]
440 fn test_step_sequencer_introspection() {
441 let mut seq = StepSequencer::new();
442 seq.set_step(0, 1.0, true);
443 seq.set_step(1, -0.5, false);
444
445 let params = seq.param_infos();
446 assert_eq!(params.len(), 16);
447
448 let step0_cv = params.iter().find(|p| p.id == "step_0_cv").unwrap();
449 assert_eq!(step0_cv.value, 1.0);
450
451 let step0_gate = params.iter().find(|p| p.id == "step_0_gate").unwrap();
452 assert_eq!(step0_gate.value, 1.0);
453
454 assert!(seq.set_param_by_id("step_2_cv", 2.5));
455 assert_eq!(seq.get_step(2).unwrap().0, 2.5);
456 }
457
458 #[test]
459 fn test_quantizer_introspection() {
460 let mut quant = Quantizer::major();
461 assert_eq!(quant.param_infos()[0].value, 1.0);
462
463 assert!(quant.set_param_by_id("scale", 2.0));
464 assert_eq!(quant.param_infos()[0].value, 2.0);
465 }
466
467 #[test]
468 fn test_noise_generator_introspection() {
469 let mut noise = NoiseGenerator::with_correlation(0.5);
470 assert!((noise.param_infos()[0].value - 0.5).abs() < 0.001);
471
472 assert!(noise.set_param_by_id("correlation", 0.8));
473 assert!((noise.param_infos()[0].value - 0.8).abs() < 0.001);
474 }
475
476 #[test]
477 fn test_saturator_introspection() {
478 let mut sat = Saturator::new(1.0);
479 assert_eq!(sat.param_infos()[0].id, "drive");
480
481 assert!(sat.set_param_by_id("drive", 5.0));
482 assert_eq!(sat.param_infos()[0].value, 5.0);
483 }
484
485 #[test]
486 fn test_wavefolder_introspection() {
487 let mut wf = Wavefolder::new(1.0);
488 assert_eq!(wf.param_infos()[0].id, "threshold");
489
490 assert!(wf.set_param_by_id("threshold", 2.0));
491 assert_eq!(wf.param_infos()[0].value, 2.0);
492 }
493
494 #[test]
495 fn test_ground_loop_introspection() {
496 let mut gl = GroundLoop::hz_50(44100.0);
497 assert_eq!(gl.param_infos()[0].value, 0.0);
498
499 assert!(gl.set_param_by_id("frequency", 1.0));
500 assert_eq!(gl.param_infos()[0].value, 1.0);
501 }
502
503 #[test]
504 fn test_cv_controlled_modules_have_no_params() {
505 assert!(Vco::default().param_infos().is_empty());
506 assert!(Lfo::default().param_infos().is_empty());
507 assert!(Svf::default().param_infos().is_empty());
508 assert!(Adsr::default().param_infos().is_empty());
509 assert!(Vca::default().param_infos().is_empty());
510 assert!(Clock::default().param_infos().is_empty());
511 assert!(LogicAnd::default().param_infos().is_empty());
512 assert!(MidSideEncode::default().param_infos().is_empty());
514 assert!(MidSideDecode::default().param_infos().is_empty());
515 }
516
517 #[test]
518 fn test_sample_player_introspection() {
519 let mut player = SamplePlayer::default();
520 let params = player.param_infos();
521 assert_eq!(params.len(), 1);
522 assert_eq!(params[0].id, "start");
523
524 assert!(player.set_param_by_id("start", 0.5));
525 assert!((player.param_infos()[0].value - 0.5).abs() < 1e-9);
526 assert!(!player.set_param_by_id("invalid", 0.0));
527 }
528
529 #[test]
530 fn test_ducker_introspection() {
531 let mut ducker = Ducker::default();
532 let params = ducker.param_infos();
533 assert_eq!(params.len(), 2);
534 assert_eq!(params[0].id, "depth");
536 assert_eq!(params[1].id, "thresh");
537
538 assert!(ducker.set_param_by_id("depth", 0.25));
539 assert!(ducker.set_param_by_id("thresh", 0.6));
540 let p = ducker.param_infos();
541 assert!((p[0].value - 0.25).abs() < 1e-9);
542 assert!((p[1].value - 0.6).abs() < 1e-9);
543 assert!(!ducker.set_param_by_id("invalid", 0.0));
544 }
545}