wavecraft_dsp/traits.rs
1//! Core DSP traits for user-implemented audio processors.
2//!
3//! This module defines the primary extension points for users building plugins
4//! with Wavecraft. The `Processor` trait is the main interface for custom DSP code.
5
6/// Transport information for timing-aware DSP.
7///
8/// Provides context about playback state, tempo, and position.
9#[derive(Debug, Clone, Copy, Default)]
10pub struct Transport {
11 /// Current tempo in BPM (beats per minute).
12 pub tempo: Option<f64>,
13
14 /// Current playback position in samples.
15 pub pos_samples: i64,
16
17 /// True if the host is playing.
18 pub playing: bool,
19}
20
21/// Trait for defining processor parameters.
22///
23/// This trait provides metadata about a processor's parameters,
24/// enabling automatic UI generation and nih-plug integration.
25///
26/// This is typically implemented via `#[derive(ProcessorParams)]` rather than manually.
27pub trait ProcessorParams: Default + Send + Sync + 'static {
28 /// Returns the parameter specifications for this processor.
29 fn param_specs() -> &'static [ParamSpec];
30
31 /// Returns how many plain values [`Self::apply_plain_values`] expects.
32 ///
33 /// By default this is derived from [`Self::param_specs`]. Override this for
34 /// parameter containers that can determine split counts without touching
35 /// `param_specs()` (for example to avoid alloc/leak helper paths in runtime
36 /// parameter splitting).
37 fn plain_value_count() -> usize {
38 Self::param_specs().len()
39 }
40
41 /// Builds parameter values initialized from each [`ParamSpec::default`].
42 ///
43 /// By default this falls back to `Self::default()`. Implementations should
44 /// override this when struct field defaults differ from declared
45 /// `param_specs()` defaults (common with `#[derive(Default)]` on numeric
46 /// fields, which initializes them to zero).
47 fn from_param_defaults() -> Self {
48 Self::default()
49 }
50
51 /// Applies plain parameter values in `param_specs()` order.
52 ///
53 /// This is used by host/plugin bridges to populate runtime processor params
54 /// from external automation/UI state. Implementations may ignore unknown or
55 /// missing values.
56 fn apply_plain_values(&mut self, _values: &[f32]) {}
57}
58
59/// Specification for a single processor parameter.
60#[derive(Debug, Clone)]
61pub struct ParamSpec {
62 /// Display name of the parameter (e.g., "Frequency").
63 pub name: &'static str,
64
65 /// ID suffix for this parameter (e.g., "frequency").
66 /// Full ID will be prefixed with processor name: "my_filter_frequency"
67 pub id_suffix: &'static str,
68
69 /// Value range for this parameter.
70 pub range: ParamRange,
71
72 /// Default value.
73 pub default: f64,
74
75 /// Unit string (e.g., "dB", "Hz", "%").
76 pub unit: &'static str,
77
78 /// Optional group name for UI organization (e.g., "Input", "Processing", "Output").
79 pub group: Option<&'static str>,
80}
81
82/// Parameter value range definition.
83#[derive(Debug, Clone)]
84pub enum ParamRange {
85 /// Linear range from min to max.
86 Linear { min: f64, max: f64 },
87
88 /// Skewed range with exponential/logarithmic scaling.
89 /// `factor > 1.0` produces logarithmic-style skew; `factor < 1.0` produces exponential-style skew.
90 Skewed { min: f64, max: f64, factor: f64 },
91
92 /// Integer stepped range (for enums, switches).
93 Stepped { min: i32, max: i32 },
94
95 /// Enumerated parameter with named variants.
96 ///
97 /// Index 0 corresponds to the first variant, 1 to the second, etc.
98 Enum { variants: &'static [&'static str] },
99}
100
101/// Unit type has no parameters.
102impl ProcessorParams for () {
103 fn param_specs() -> &'static [ParamSpec] {
104 &[]
105 }
106}
107
108/// Trait for user-implemented DSP processors.
109///
110/// Implement this trait to define custom audio processing logic.
111/// All methods must be real-time safe (no allocations, locks, or syscalls).
112///
113/// # Example
114///
115/// ```rust,no_run
116/// use wavecraft_dsp::{ParamRange, ParamSpec, Processor, ProcessorParams, Transport};
117///
118/// #[derive(Default)]
119/// struct MyGainParams {
120/// level: f32,
121/// }
122///
123/// impl ProcessorParams for MyGainParams {
124/// fn param_specs() -> &'static [ParamSpec] {
125/// &[ParamSpec {
126/// name: "Level",
127/// id_suffix: "level",
128/// range: ParamRange::Linear { min: 0.0, max: 2.0 },
129/// default: 1.0,
130/// unit: "x",
131/// group: None,
132/// }]
133/// }
134/// }
135///
136/// struct MyGain {
137/// sample_rate: f32,
138/// }
139///
140/// impl Processor for MyGain {
141/// type Params = MyGainParams;
142///
143/// fn process(
144/// &mut self,
145/// buffer: &mut [&mut [f32]],
146/// _transport: &Transport,
147/// params: &Self::Params,
148/// ) {
149/// for channel in buffer.iter_mut() {
150/// for sample in channel.iter_mut() {
151/// *sample *= params.level;
152/// }
153/// }
154/// }
155/// }
156/// ```
157pub trait Processor: Send + 'static {
158 /// Associated parameter type for this processor.
159 ///
160 /// Use `()` for processors with no parameters, or define a struct
161 /// with `#[derive(ProcessorParams)]`.
162 type Params: ProcessorParams + Default + Send + Sync + 'static;
163
164 /// Process a buffer of audio samples.
165 ///
166 /// The buffer is provided as a slice of mutable slices, one per channel.
167 /// Modify samples in-place to apply your DSP effect.
168 ///
169 /// # Arguments
170 /// * `buffer` - Audio channels as `[L, R, ...]` where each channel is `[samples]`
171 /// * `transport` - Playback timing information
172 /// * `params` - Current parameter values
173 ///
174 /// # Real-Time Safety
175 /// This method is called on the audio thread. It MUST be real-time safe:
176 /// - No allocations (`Vec::push`, `String`, `Box::new`)
177 /// - No locks (`Mutex`, `RwLock`)
178 /// - No syscalls (file I/O, logging, network)
179 /// - No panics (use `debug_assert!` only)
180 fn process(&mut self, buffer: &mut [&mut [f32]], transport: &Transport, params: &Self::Params);
181
182 /// Called when the sample rate changes.
183 ///
184 /// Use this to update internal state that depends on sample rate
185 /// (e.g., filter coefficients, delay line sizes).
186 ///
187 /// # Arguments
188 /// * `sample_rate` - New sample rate in Hz (e.g., 44100.0, 48000.0)
189 ///
190 /// # Default
191 /// No-op by default. Override if your processor needs sample rate.
192 fn set_sample_rate(&mut self, _sample_rate: f32) {}
193
194 /// Reset processor state.
195 ///
196 /// Called when the host stops playback or when the user resets the plugin.
197 /// Use this to clear delay lines, reset filters, etc.
198 ///
199 /// # Default
200 /// No-op by default. Override if your processor maintains state.
201 fn reset(&mut self) {}
202}