bbx_dsp/
block.rs

1//! DSP block system.
2//!
3//! This module defines the [`Block`] trait for DSP processing units and
4//! [`BlockType`] for type-erased block storage in the graph.
5
6use crate::{
7    blocks::{
8        effectors::{
9            ambisonic_decoder::AmbisonicDecoderBlock, binaural_decoder::BinauralDecoderBlock,
10            channel_merger::ChannelMergerBlock, channel_router::ChannelRouterBlock,
11            channel_splitter::ChannelSplitterBlock, dc_blocker::DcBlockerBlock, gain::GainBlock,
12            low_pass_filter::LowPassFilterBlock, matrix_mixer::MatrixMixerBlock, mixer::MixerBlock,
13            overdrive::OverdriveBlock, panner::PannerBlock, vca::VcaBlock,
14        },
15        generators::oscillator::OscillatorBlock,
16        io::{file_input::FileInputBlock, file_output::FileOutputBlock, output::OutputBlock},
17        modulators::{envelope::EnvelopeBlock, lfo::LfoBlock},
18    },
19    channel::ChannelConfig,
20    context::DspContext,
21    parameter::{ModulationOutput, Parameter},
22    sample::Sample,
23};
24
25/// Default input count for `Effector`s.
26pub(crate) const DEFAULT_EFFECTOR_INPUT_COUNT: usize = 1;
27/// Default output count for `Effector`s.
28pub(crate) const DEFAULT_EFFECTOR_OUTPUT_COUNT: usize = 1;
29
30/// Default input count for `Generator`s.
31pub(crate) const DEFAULT_GENERATOR_INPUT_COUNT: usize = 0;
32/// Default output count for `Generator`s.
33pub(crate) const DEFAULT_GENERATOR_OUTPUT_COUNT: usize = 1;
34
35/// Default input count for `Modulator`s.
36pub(crate) const DEFAULT_MODULATOR_INPUT_COUNT: usize = 0;
37/// Default output count for `Modulator`s.
38pub(crate) const DEFAULT_MODULATOR_OUTPUT_COUNT: usize = 1;
39
40/// A unique identifier for a block within a DSP graph.
41///
42/// Used to reference blocks when creating connections or setting up modulation.
43/// The inner `usize` is the block's index in the graph's block list.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub struct BlockId(pub usize);
46
47/// Category of a DSP block for visualization and organization.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum BlockCategory {
50    /// Audio signal generators (oscillators, noise, etc.).
51    Generator,
52    /// Audio signal processors (filters, effects, etc.).
53    Effector,
54    /// Control signal generators (LFOs, envelopes, etc.).
55    Modulator,
56    /// Input/output blocks (file I/O, audio output, etc.).
57    IO,
58}
59
60/// The core trait for DSP processing units.
61///
62/// A block represents a single DSP operation (oscillator, filter, gain, etc.)
63/// that processes audio buffers. Blocks are connected together in a [`Graph`](crate::graph::Graph)
64/// to form a complete signal processing chain.
65pub trait Block<S: Sample> {
66    /// Process audio through this block.
67    ///
68    /// # Arguments
69    ///
70    /// * `inputs` - Slice of input buffer references, one per input port
71    /// * `outputs` - Slice of mutable output buffer references, one per output port
72    /// * `modulation_values` - Values from connected modulator blocks, indexed by [`BlockId`]
73    /// * `context` - The DSP context with sample rate and timing info
74    fn process(&mut self, inputs: &[&[S]], outputs: &mut [&mut [S]], modulation_values: &[S], context: &DspContext);
75
76    /// Returns the number of input ports this block accepts.
77    fn input_count(&self) -> usize;
78
79    /// Returns the number of output ports this block produces.
80    fn output_count(&self) -> usize;
81
82    /// Returns the modulation outputs this block provides.
83    ///
84    /// Only modulator blocks (LFOs, envelopes) return non-empty slices.
85    /// Generator and effector blocks return an empty slice.
86    fn modulation_outputs(&self) -> &[ModulationOutput];
87
88    /// Returns how this block handles multi-channel audio.
89    ///
90    /// Default is [`ChannelConfig::Parallel`] (process each channel independently).
91    /// Override to [`ChannelConfig::Explicit`] for blocks that handle channel
92    /// routing internally (panners, mixers, splitters/mergers).
93    fn channel_config(&self) -> ChannelConfig {
94        ChannelConfig::Parallel
95    }
96
97    /// Configure smoothing time for parameter changes.
98    ///
99    /// # Arguments
100    /// * `sample_rate` - Audio sample rate in Hz
101    /// * `ramp_time_ms` - Smoothing ramp time in milliseconds
102    ///
103    /// Default implementation is a no-op for blocks without smoothing.
104    fn set_smoothing(&mut self, _sample_rate: f64, _ramp_time_ms: f64) {}
105}
106
107/// Type-erased container for all block implementations.
108///
109/// Wraps concrete block types so they can be stored uniformly in a graph.
110/// Each variant corresponds to a specific DSP block type.
111pub enum BlockType<S: Sample> {
112    // I/O
113    /// Reads audio from a file via a [`Reader`](crate::reader::Reader).
114    FileInput(FileInputBlock<S>),
115    /// Writes audio to a file via a [`Writer`](crate::writer::Writer).
116    FileOutput(FileOutputBlock<S>),
117    /// Terminal output block that collects final audio.
118    Output(OutputBlock<S>),
119
120    // GENERATORS
121    /// Waveform oscillator (sine, saw, square, triangle).
122    Oscillator(OscillatorBlock<S>),
123
124    // EFFECTORS
125    /// Decodes ambisonics B-format to speaker layout.
126    AmbisonicDecoder(AmbisonicDecoderBlock<S>),
127    /// Decodes ambisonics B-format to stereo for headphones.
128    BinauralDecoder(BinauralDecoderBlock<S>),
129    /// Merges individual mono inputs into multi-channel output.
130    ChannelMerger(ChannelMergerBlock<S>),
131    /// Routes channels (mono to stereo, stereo to mono, etc.).
132    ChannelRouter(ChannelRouterBlock<S>),
133    /// Splits multi-channel input into individual mono outputs.
134    ChannelSplitter(ChannelSplitterBlock<S>),
135    /// Removes DC offset from the signal.
136    DcBlocker(DcBlockerBlock<S>),
137    /// Adjusts signal level in decibels.
138    Gain(GainBlock<S>),
139    /// SVF-based low-pass filter.
140    LowPassFilter(LowPassFilterBlock<S>),
141    /// NxM mixing matrix for flexible channel routing.
142    MatrixMixer(MatrixMixerBlock<S>),
143    /// Channel-wise mixer that sums multiple sources per channel.
144    Mixer(MixerBlock<S>),
145    /// Asymmetric soft-clipping distortion.
146    Overdrive(OverdriveBlock<S>),
147    /// Stereo panning with equal-power law.
148    Panner(PannerBlock<S>),
149    /// Voltage controlled amplifier (multiplies audio by control signal).
150    Vca(VcaBlock<S>),
151
152    // MODULATORS
153    /// ADSR envelope generator.
154    Envelope(EnvelopeBlock<S>),
155    /// Low-frequency oscillator for modulation.
156    Lfo(LfoBlock<S>),
157}
158
159impl<S: Sample> BlockType<S> {
160    /// Perform the calculation of the underlying `Block`.
161    #[inline]
162    pub fn process(
163        &mut self,
164        inputs: &[&[S]],
165        outputs: &mut [&mut [S]],
166        modulation_values: &[S],
167        context: &DspContext,
168    ) {
169        match self {
170            // I/O
171            BlockType::FileInput(block) => block.process(inputs, outputs, modulation_values, context),
172            BlockType::FileOutput(block) => block.process(inputs, outputs, modulation_values, context),
173            BlockType::Output(block) => block.process(inputs, outputs, modulation_values, context),
174
175            // GENERATORS
176            BlockType::Oscillator(block) => block.process(inputs, outputs, modulation_values, context),
177
178            // EFFECTORS
179            BlockType::AmbisonicDecoder(block) => block.process(inputs, outputs, modulation_values, context),
180            BlockType::BinauralDecoder(block) => block.process(inputs, outputs, modulation_values, context),
181            BlockType::ChannelMerger(block) => block.process(inputs, outputs, modulation_values, context),
182            BlockType::ChannelRouter(block) => block.process(inputs, outputs, modulation_values, context),
183            BlockType::ChannelSplitter(block) => block.process(inputs, outputs, modulation_values, context),
184            BlockType::DcBlocker(block) => block.process(inputs, outputs, modulation_values, context),
185            BlockType::Gain(block) => block.process(inputs, outputs, modulation_values, context),
186            BlockType::LowPassFilter(block) => block.process(inputs, outputs, modulation_values, context),
187            BlockType::MatrixMixer(block) => block.process(inputs, outputs, modulation_values, context),
188            BlockType::Mixer(block) => block.process(inputs, outputs, modulation_values, context),
189            BlockType::Overdrive(block) => block.process(inputs, outputs, modulation_values, context),
190            BlockType::Panner(block) => block.process(inputs, outputs, modulation_values, context),
191            BlockType::Vca(block) => block.process(inputs, outputs, modulation_values, context),
192
193            // MODULATORS
194            BlockType::Envelope(block) => block.process(inputs, outputs, modulation_values, context),
195            BlockType::Lfo(block) => block.process(inputs, outputs, modulation_values, context),
196        }
197    }
198
199    /// Get the input count of the underlying `Block`.
200    #[inline]
201    pub fn input_count(&self) -> usize {
202        match self {
203            // I/O
204            BlockType::FileInput(block) => block.input_count(),
205            BlockType::FileOutput(block) => block.input_count(),
206            BlockType::Output(block) => block.input_count(),
207
208            // GENERATORS
209            BlockType::Oscillator(block) => block.input_count(),
210
211            // EFFECTORS
212            BlockType::AmbisonicDecoder(block) => block.input_count(),
213            BlockType::BinauralDecoder(block) => block.input_count(),
214            BlockType::ChannelMerger(block) => block.input_count(),
215            BlockType::ChannelRouter(block) => block.input_count(),
216            BlockType::ChannelSplitter(block) => block.input_count(),
217            BlockType::DcBlocker(block) => block.input_count(),
218            BlockType::Gain(block) => block.input_count(),
219            BlockType::LowPassFilter(block) => block.input_count(),
220            BlockType::MatrixMixer(block) => block.input_count(),
221            BlockType::Mixer(block) => block.input_count(),
222            BlockType::Overdrive(block) => block.input_count(),
223            BlockType::Panner(block) => block.input_count(),
224            BlockType::Vca(block) => block.input_count(),
225
226            // MODULATORS
227            BlockType::Envelope(block) => block.input_count(),
228            BlockType::Lfo(block) => block.input_count(),
229        }
230    }
231
232    /// Get the output count of the underlying `Block`.
233    #[inline]
234    pub fn output_count(&self) -> usize {
235        match self {
236            // I/O
237            BlockType::FileInput(block) => block.output_count(),
238            BlockType::FileOutput(block) => block.output_count(),
239            BlockType::Output(block) => block.output_count(),
240
241            // GENERATORS
242            BlockType::Oscillator(block) => block.output_count(),
243
244            // EFFECTORS
245            BlockType::AmbisonicDecoder(block) => block.output_count(),
246            BlockType::BinauralDecoder(block) => block.output_count(),
247            BlockType::ChannelMerger(block) => block.output_count(),
248            BlockType::ChannelRouter(block) => block.output_count(),
249            BlockType::ChannelSplitter(block) => block.output_count(),
250            BlockType::DcBlocker(block) => block.output_count(),
251            BlockType::Gain(block) => block.output_count(),
252            BlockType::LowPassFilter(block) => block.output_count(),
253            BlockType::MatrixMixer(block) => block.output_count(),
254            BlockType::Mixer(block) => block.output_count(),
255            BlockType::Overdrive(block) => block.output_count(),
256            BlockType::Panner(block) => block.output_count(),
257            BlockType::Vca(block) => block.output_count(),
258
259            // MODULATORS
260            BlockType::Envelope(block) => block.output_count(),
261            BlockType::Lfo(block) => block.output_count(),
262        }
263    }
264
265    /// Get the modulation outputs (if any) of the underlying `Block`.
266    #[inline]
267    pub fn modulation_outputs(&self) -> &[ModulationOutput] {
268        match self {
269            // I/O
270            BlockType::FileInput(block) => block.modulation_outputs(),
271            BlockType::FileOutput(block) => block.modulation_outputs(),
272            BlockType::Output(block) => block.modulation_outputs(),
273
274            // GENERATORS
275            BlockType::Oscillator(block) => block.modulation_outputs(),
276
277            // EFFECTORS
278            BlockType::AmbisonicDecoder(block) => block.modulation_outputs(),
279            BlockType::BinauralDecoder(block) => block.modulation_outputs(),
280            BlockType::ChannelMerger(block) => block.modulation_outputs(),
281            BlockType::ChannelRouter(block) => block.modulation_outputs(),
282            BlockType::ChannelSplitter(block) => block.modulation_outputs(),
283            BlockType::DcBlocker(block) => block.modulation_outputs(),
284            BlockType::Gain(block) => block.modulation_outputs(),
285            BlockType::LowPassFilter(block) => block.modulation_outputs(),
286            BlockType::MatrixMixer(block) => block.modulation_outputs(),
287            BlockType::Mixer(block) => block.modulation_outputs(),
288            BlockType::Overdrive(block) => block.modulation_outputs(),
289            BlockType::Panner(block) => block.modulation_outputs(),
290            BlockType::Vca(block) => block.modulation_outputs(),
291
292            // MODULATORS
293            BlockType::Envelope(block) => block.modulation_outputs(),
294            BlockType::Lfo(block) => block.modulation_outputs(),
295        }
296    }
297
298    /// Get the channel config of the underlying `Block`.
299    #[inline]
300    pub fn channel_config(&self) -> ChannelConfig {
301        match self {
302            // I/O
303            BlockType::FileInput(block) => block.channel_config(),
304            BlockType::FileOutput(block) => block.channel_config(),
305            BlockType::Output(block) => block.channel_config(),
306
307            // GENERATORS
308            BlockType::Oscillator(block) => block.channel_config(),
309
310            // EFFECTORS
311            BlockType::AmbisonicDecoder(block) => block.channel_config(),
312            BlockType::BinauralDecoder(block) => block.channel_config(),
313            BlockType::ChannelMerger(block) => block.channel_config(),
314            BlockType::ChannelRouter(block) => block.channel_config(),
315            BlockType::ChannelSplitter(block) => block.channel_config(),
316            BlockType::DcBlocker(block) => block.channel_config(),
317            BlockType::Gain(block) => block.channel_config(),
318            BlockType::LowPassFilter(block) => block.channel_config(),
319            BlockType::MatrixMixer(block) => block.channel_config(),
320            BlockType::Mixer(block) => block.channel_config(),
321            BlockType::Overdrive(block) => block.channel_config(),
322            BlockType::Panner(block) => block.channel_config(),
323            BlockType::Vca(block) => block.channel_config(),
324
325            // MODULATORS
326            BlockType::Envelope(block) => block.channel_config(),
327            BlockType::Lfo(block) => block.channel_config(),
328        }
329    }
330
331    /// Configure smoothing time for parameter changes.
332    ///
333    /// Only affects blocks that have internal parameter smoothing.
334    /// Blocks without smoothing will ignore this call.
335    pub fn set_smoothing(&mut self, sample_rate: f64, ramp_time_ms: f64) {
336        match self {
337            BlockType::Panner(block) => block.set_smoothing(sample_rate, ramp_time_ms),
338            BlockType::Gain(block) => block.set_smoothing(sample_rate, ramp_time_ms),
339            BlockType::Overdrive(block) => block.set_smoothing(sample_rate, ramp_time_ms),
340            _ => {} // Blocks without smoothing use default no-op
341        }
342    }
343
344    /// Set a given `Parameter` of the underlying `Block`.
345    pub fn set_parameter(&mut self, parameter_name: &str, parameter: Parameter<S>) -> Result<(), String> {
346        match self {
347            // I/O
348            BlockType::FileInput(_) => Err("File input blocks have no modulated parameters".to_string()),
349            BlockType::FileOutput(_) => Err("File output blocks have no modulated parameters".to_string()),
350            BlockType::Output(_) => Err("Output blocks have no modulated parameters".to_string()),
351
352            // GENERATORS
353            BlockType::Oscillator(block) => match parameter_name.to_lowercase().as_str() {
354                "frequency" => {
355                    block.frequency = parameter;
356                    Ok(())
357                }
358                "pitch_offset" => {
359                    block.pitch_offset = parameter;
360                    Ok(())
361                }
362                _ => Err(format!("Unknown oscillator parameter: {parameter_name}")),
363            },
364
365            // EFFECTORS
366            BlockType::AmbisonicDecoder(_) => Err("Ambisonic decoder has no modulated parameters".to_string()),
367            BlockType::BinauralDecoder(_) => Err("Binaural decoder has no modulated parameters".to_string()),
368            BlockType::ChannelMerger(_) => Err("Channel merger has no modulated parameters".to_string()),
369            BlockType::ChannelRouter(_) => Err("Channel router uses direct field access, not Parameter<S>".to_string()),
370            BlockType::ChannelSplitter(_) => Err("Channel splitter has no modulated parameters".to_string()),
371            BlockType::DcBlocker(_) => Err("DC blocker uses direct field access, not Parameter<S>".to_string()),
372            BlockType::Gain(block) => match parameter_name.to_lowercase().as_str() {
373                "level" | "level_db" => {
374                    block.level_db = parameter;
375                    Ok(())
376                }
377                _ => Err(format!("Unknown gain parameter: {parameter_name}")),
378            },
379            BlockType::LowPassFilter(block) => match parameter_name.to_lowercase().as_str() {
380                "cutoff" | "frequency" => {
381                    block.cutoff = parameter;
382                    Ok(())
383                }
384                "resonance" | "q" => {
385                    block.resonance = parameter;
386                    Ok(())
387                }
388                _ => Err(format!("Unknown low-pass filter parameter: {parameter_name}")),
389            },
390            BlockType::MatrixMixer(_) => Err("Matrix mixer uses set_gain method, not Parameter<S>".to_string()),
391            BlockType::Mixer(_) => Err("Mixer has no modulated parameters".to_string()),
392            BlockType::Overdrive(block) => match parameter_name.to_lowercase().as_str() {
393                "drive" => {
394                    block.drive = parameter;
395                    Ok(())
396                }
397                "level" => {
398                    block.level = parameter;
399                    Ok(())
400                }
401                _ => Err(format!("Unknown overdrive parameter: {parameter_name}")),
402            },
403            BlockType::Panner(block) => match parameter_name.to_lowercase().as_str() {
404                "position" | "pan" => {
405                    block.position = parameter;
406                    Ok(())
407                }
408                "azimuth" => {
409                    block.azimuth = parameter;
410                    Ok(())
411                }
412                "elevation" => {
413                    block.elevation = parameter;
414                    Ok(())
415                }
416                _ => Err(format!("Unknown panner parameter: {parameter_name}")),
417            },
418            BlockType::Vca(_) => Err("VCA has no modulated parameters".to_string()),
419
420            // MODULATORS
421            BlockType::Envelope(block) => match parameter_name.to_lowercase().as_str() {
422                "attack" => {
423                    block.attack = parameter;
424                    Ok(())
425                }
426                "decay" => {
427                    block.decay = parameter;
428                    Ok(())
429                }
430                "sustain" => {
431                    block.sustain = parameter;
432                    Ok(())
433                }
434                "release" => {
435                    block.release = parameter;
436                    Ok(())
437                }
438                _ => Err(format!("Unknown envelope parameter: {parameter_name}")),
439            },
440            BlockType::Lfo(block) => match parameter_name.to_lowercase().as_str() {
441                "frequency" => {
442                    block.frequency = parameter;
443                    Ok(())
444                }
445                "depth" => {
446                    block.depth = parameter;
447                    Ok(())
448                }
449                _ => Err(format!("Unknown LFO parameter: {parameter_name}")),
450            },
451        }
452    }
453
454    /// Returns `true` if this block is a modulator (LFO or Envelope).
455    #[inline]
456    pub fn is_modulator(&self) -> bool {
457        matches!(self, BlockType::Envelope(_) | BlockType::Lfo(_))
458    }
459
460    /// Returns `true` if this block is an output-type block (Output or FileOutput).
461    #[inline]
462    pub fn is_output(&self) -> bool {
463        matches!(self, BlockType::Output(_) | BlockType::FileOutput(_))
464    }
465
466    /// Returns the category of this block.
467    #[inline]
468    pub fn category(&self) -> BlockCategory {
469        match self {
470            BlockType::FileInput(_) | BlockType::FileOutput(_) | BlockType::Output(_) => BlockCategory::IO,
471            BlockType::Oscillator(_) => BlockCategory::Generator,
472            BlockType::AmbisonicDecoder(_)
473            | BlockType::BinauralDecoder(_)
474            | BlockType::ChannelMerger(_)
475            | BlockType::ChannelRouter(_)
476            | BlockType::ChannelSplitter(_)
477            | BlockType::DcBlocker(_)
478            | BlockType::Gain(_)
479            | BlockType::LowPassFilter(_)
480            | BlockType::MatrixMixer(_)
481            | BlockType::Mixer(_)
482            | BlockType::Overdrive(_)
483            | BlockType::Panner(_)
484            | BlockType::Vca(_) => BlockCategory::Effector,
485            BlockType::Envelope(_) | BlockType::Lfo(_) => BlockCategory::Modulator,
486        }
487    }
488
489    /// Returns the display name of this block type.
490    #[inline]
491    pub fn name(&self) -> &'static str {
492        match self {
493            BlockType::FileInput(_) => "File Input",
494            BlockType::FileOutput(_) => "File Output",
495            BlockType::Output(_) => "Output",
496            BlockType::Oscillator(_) => "Oscillator",
497            BlockType::AmbisonicDecoder(_) => "Ambisonic Decoder",
498            BlockType::BinauralDecoder(_) => "Binaural Decoder",
499            BlockType::ChannelMerger(_) => "Channel Merger",
500            BlockType::ChannelRouter(_) => "Channel Router",
501            BlockType::ChannelSplitter(_) => "Channel Splitter",
502            BlockType::DcBlocker(_) => "DC Blocker",
503            BlockType::Gain(_) => "Gain",
504            BlockType::LowPassFilter(_) => "Low Pass Filter",
505            BlockType::MatrixMixer(_) => "Matrix Mixer",
506            BlockType::Mixer(_) => "Mixer",
507            BlockType::Overdrive(_) => "Overdrive",
508            BlockType::Panner(_) => "Panner",
509            BlockType::Vca(_) => "VCA",
510            BlockType::Envelope(_) => "Envelope",
511            BlockType::Lfo(_) => "LFO",
512        }
513    }
514
515    /// Returns all modulated parameters and their source block IDs.
516    ///
517    /// Returns a list of (parameter_name, source_block_id) for each parameter
518    /// that is modulated by another block.
519    ///
520    /// # Note
521    ///
522    /// This method allocates and is NOT realtime-safe. Only call during
523    /// graph setup or from non-audio threads.
524    pub fn get_modulated_parameters(&self) -> Vec<(&'static str, BlockId)> {
525        let mut result = Vec::new();
526
527        match self {
528            BlockType::FileInput(_) | BlockType::FileOutput(_) | BlockType::Output(_) => {}
529
530            BlockType::Oscillator(block) => {
531                if let Parameter::Modulated(id) = &block.frequency {
532                    result.push(("frequency", *id));
533                }
534                if let Parameter::Modulated(id) = &block.pitch_offset {
535                    result.push(("pitch_offset", *id));
536                }
537            }
538
539            BlockType::AmbisonicDecoder(_)
540            | BlockType::BinauralDecoder(_)
541            | BlockType::ChannelMerger(_)
542            | BlockType::ChannelRouter(_)
543            | BlockType::ChannelSplitter(_)
544            | BlockType::DcBlocker(_)
545            | BlockType::MatrixMixer(_)
546            | BlockType::Mixer(_)
547            | BlockType::Vca(_) => {}
548
549            BlockType::Gain(block) => {
550                if let Parameter::Modulated(id) = &block.level_db {
551                    result.push(("level", *id));
552                }
553            }
554
555            BlockType::LowPassFilter(block) => {
556                if let Parameter::Modulated(id) = &block.cutoff {
557                    result.push(("cutoff", *id));
558                }
559                if let Parameter::Modulated(id) = &block.resonance {
560                    result.push(("resonance", *id));
561                }
562            }
563
564            BlockType::Overdrive(block) => {
565                if let Parameter::Modulated(id) = &block.drive {
566                    result.push(("drive", *id));
567                }
568                if let Parameter::Modulated(id) = &block.level {
569                    result.push(("level", *id));
570                }
571            }
572
573            BlockType::Panner(block) => {
574                if let Parameter::Modulated(id) = &block.position {
575                    result.push(("position", *id));
576                }
577                if let Parameter::Modulated(id) = &block.azimuth {
578                    result.push(("azimuth", *id));
579                }
580                if let Parameter::Modulated(id) = &block.elevation {
581                    result.push(("elevation", *id));
582                }
583            }
584
585            BlockType::Envelope(block) => {
586                if let Parameter::Modulated(id) = &block.attack {
587                    result.push(("attack", *id));
588                }
589                if let Parameter::Modulated(id) = &block.decay {
590                    result.push(("decay", *id));
591                }
592                if let Parameter::Modulated(id) = &block.sustain {
593                    result.push(("sustain", *id));
594                }
595                if let Parameter::Modulated(id) = &block.release {
596                    result.push(("release", *id));
597                }
598            }
599
600            BlockType::Lfo(block) => {
601                if let Parameter::Modulated(id) = &block.frequency {
602                    result.push(("frequency", *id));
603                }
604                if let Parameter::Modulated(id) = &block.depth {
605                    result.push(("depth", *id));
606                }
607            }
608        }
609
610        result
611    }
612}
613
614// From implementations for ergonomic block addition via GraphBuilder::add()
615
616// I/O
617impl<S: Sample> From<FileInputBlock<S>> for BlockType<S> {
618    fn from(block: FileInputBlock<S>) -> Self {
619        BlockType::FileInput(block)
620    }
621}
622
623impl<S: Sample> From<FileOutputBlock<S>> for BlockType<S> {
624    fn from(block: FileOutputBlock<S>) -> Self {
625        BlockType::FileOutput(block)
626    }
627}
628
629impl<S: Sample> From<OutputBlock<S>> for BlockType<S> {
630    fn from(block: OutputBlock<S>) -> Self {
631        BlockType::Output(block)
632    }
633}
634
635// Generators
636impl<S: Sample> From<OscillatorBlock<S>> for BlockType<S> {
637    fn from(block: OscillatorBlock<S>) -> Self {
638        BlockType::Oscillator(block)
639    }
640}
641
642// Effectors
643impl<S: Sample> From<AmbisonicDecoderBlock<S>> for BlockType<S> {
644    fn from(block: AmbisonicDecoderBlock<S>) -> Self {
645        BlockType::AmbisonicDecoder(block)
646    }
647}
648
649impl<S: Sample> From<BinauralDecoderBlock<S>> for BlockType<S> {
650    fn from(block: BinauralDecoderBlock<S>) -> Self {
651        BlockType::BinauralDecoder(block)
652    }
653}
654
655impl<S: Sample> From<ChannelMergerBlock<S>> for BlockType<S> {
656    fn from(block: ChannelMergerBlock<S>) -> Self {
657        BlockType::ChannelMerger(block)
658    }
659}
660
661impl<S: Sample> From<ChannelRouterBlock<S>> for BlockType<S> {
662    fn from(block: ChannelRouterBlock<S>) -> Self {
663        BlockType::ChannelRouter(block)
664    }
665}
666
667impl<S: Sample> From<ChannelSplitterBlock<S>> for BlockType<S> {
668    fn from(block: ChannelSplitterBlock<S>) -> Self {
669        BlockType::ChannelSplitter(block)
670    }
671}
672
673impl<S: Sample> From<DcBlockerBlock<S>> for BlockType<S> {
674    fn from(block: DcBlockerBlock<S>) -> Self {
675        BlockType::DcBlocker(block)
676    }
677}
678
679impl<S: Sample> From<GainBlock<S>> for BlockType<S> {
680    fn from(block: GainBlock<S>) -> Self {
681        BlockType::Gain(block)
682    }
683}
684
685impl<S: Sample> From<LowPassFilterBlock<S>> for BlockType<S> {
686    fn from(block: LowPassFilterBlock<S>) -> Self {
687        BlockType::LowPassFilter(block)
688    }
689}
690
691impl<S: Sample> From<MatrixMixerBlock<S>> for BlockType<S> {
692    fn from(block: MatrixMixerBlock<S>) -> Self {
693        BlockType::MatrixMixer(block)
694    }
695}
696
697impl<S: Sample> From<MixerBlock<S>> for BlockType<S> {
698    fn from(block: MixerBlock<S>) -> Self {
699        BlockType::Mixer(block)
700    }
701}
702
703impl<S: Sample> From<OverdriveBlock<S>> for BlockType<S> {
704    fn from(block: OverdriveBlock<S>) -> Self {
705        BlockType::Overdrive(block)
706    }
707}
708
709impl<S: Sample> From<PannerBlock<S>> for BlockType<S> {
710    fn from(block: PannerBlock<S>) -> Self {
711        BlockType::Panner(block)
712    }
713}
714
715impl<S: Sample> From<VcaBlock<S>> for BlockType<S> {
716    fn from(block: VcaBlock<S>) -> Self {
717        BlockType::Vca(block)
718    }
719}
720
721// Modulators
722impl<S: Sample> From<EnvelopeBlock<S>> for BlockType<S> {
723    fn from(block: EnvelopeBlock<S>) -> Self {
724        BlockType::Envelope(block)
725    }
726}
727
728impl<S: Sample> From<LfoBlock<S>> for BlockType<S> {
729    fn from(block: LfoBlock<S>) -> Self {
730        BlockType::Lfo(block)
731    }
732}