Vital
Loading...
Searching...
No Matches
comb_filter.cpp
Go to the documentation of this file.
1#include "comb_filter.h"
2#include "futils.h"
3#include "memory.h"
4
5namespace vital {
6
7 namespace {
11 constexpr float kFlangeScale = 0.70710678119f;
12
19 force_inline poly_float getLowGain(poly_float blend) {
20 return utils::clamp(-blend + 2.0f, 0.0f, 1.0f);
21 }
22
29 force_inline poly_float getHighGain(poly_float blend) {
30 return utils::clamp(blend, 0.0f, 1.0f);
31 }
32
49 force_inline poly_float tickComb(poly_float audio_in, Memory* memory,
50 OnePoleFilter<>& filter1, OnePoleFilter<>& filter2,
51 poly_float period, poly_float feedback, poly_float scale,
52 poly_float filter_coefficient, poly_float filter2_coefficient,
53 poly_float low_gain, poly_float high_gain) {
54 // Read from delay buffer
55 poly_float read = memory->get(period);
56
57 // Combine input and feedback
58 poly_float combine = utils::mulAdd(scale * audio_in, read, feedback);
59
60 // Filter and blend low/high
61 poly_float low_output = filter1.tickBasic(combine, filter_coefficient);
62 poly_float high_output = combine - low_output;
63 poly_float stage1_output = utils::mulAdd(low_gain * low_output, high_gain, high_output);
64
65 // Secondary filter pass
66 poly_float stage2_output = filter2.tickBasic(stage1_output, filter2_coefficient);
67 poly_float result = stage1_output - stage2_output;
68
69 // Push the result into the memory buffer
70 memory->push(futils::hardTanh(result));
71
73 return result;
74 }
75
92 force_inline poly_float tickPositiveFlange(poly_float audio_in, Memory* memory,
93 OnePoleFilter<>& filter1, OnePoleFilter<>& filter2,
94 poly_float period, poly_float feedback, poly_float scale,
95 poly_float filter_coefficient, poly_float filter2_coefficient,
96 poly_float low_gain, poly_float high_gain) {
97 // Read from memory
98 poly_float read = memory->get(period);
99
100 // Filter feedback
101 poly_float low_output = filter1.tickBasic(read, filter_coefficient);
102 poly_float high_output = read - low_output;
103 poly_float stage1_output = utils::mulAdd(low_gain * low_output, high_gain, high_output);
104 poly_float stage2_output = filter2.tickBasic(stage1_output, filter2_coefficient);
105 poly_float filter_output = stage1_output - stage2_output;
106 VITAL_ASSERT(utils::isFinite(filter_output));
107
108 // Mix input with feedback
109 poly_float scaled_input = audio_in * kFlangeScale;
110 memory->push(scaled_input + futils::hardTanh(filter_output * feedback));
111
112 return scaled_input * scale + filter_output;
113 }
114
131 force_inline poly_float tickNegativeFlange(poly_float audio_in, Memory* memory,
132 OnePoleFilter<>& filter1, OnePoleFilter<>& filter2,
133 poly_float period, poly_float feedback, poly_float scale,
134 poly_float filter_coefficient, poly_float filter2_coefficient,
135 poly_float low_gain, poly_float high_gain) {
136 // Use half the period for the negative flange
137 poly_float read = memory->get(period * 0.5f);
138
139 // Filter feedback
140 poly_float low_output = filter1.tickBasic(read, filter_coefficient);
141 poly_float high_output = read - low_output;
142 poly_float stage1_output = utils::mulAdd(low_gain * low_output, high_gain, high_output);
143 poly_float stage2_output = filter2.tickBasic(stage1_output, filter2_coefficient);
144 poly_float filter_output = stage1_output - stage2_output;
145 VITAL_ASSERT(utils::isFinite(filter_output));
146
147 poly_float scaled_input = audio_in * kFlangeScale;
148 memory->push(scaled_input - futils::hardTanh(filter_output * feedback));
149 return scaled_input * scale - filter_output;
150 }
151 } // namespace
152
158 CombFilter::CombFilter(int size) : Processor(CombFilter::kNumInputs, 1) {
160 memory_ = std::make_unique<Memory>(size);
161 feedback_ = 0.0f;
163 scale_ = 0.0f;
164 low_gain_ = 0.0f;
165 high_gain_ = 0.0f;
166 filter_midi_cutoff_ = 0.0f;
168
169 filter_coefficient_ = 0.0f;
171 }
172
179 this->feedback_style_ = other.feedback_style_;
180 this->memory_ = std::make_unique<Memory>(*other.memory_);
181 this->feedback_ = 0.0f;
183 this->filter_coefficient_ = 0.0f;
184 this->filter2_coefficient_ = 0.0f;
185 this->scale_ = 0.0f;
186 this->filter_midi_cutoff_ = 0.0f;
187 this->filter2_midi_cutoff_ = 0.0f;
189 }
190
195
201 void CombFilter::reset(poly_mask reset_mask) {
202 mono_float max_period = max_period_[0];
203 for (int i = 1; i < poly_float::kSize; ++i)
204 max_period = utils::max(max_period, max_period_[i]);
205
206 int clear_samples = std::min(memory_->getSize() - 1, ((int)max_period) + 1);
207 memory_->clearMemory(clear_samples, reset_mask);
208
209 scale_ = utils::maskLoad(scale_, 0.0f, reset_mask);
210 low_gain_ = utils::maskLoad(low_gain_, 0.0f, reset_mask);
211 high_gain_ = utils::maskLoad(high_gain_, 0.0f, reset_mask);
212
213 feedback_filter_.reset(reset_mask);
214 feedback_filter2_.reset(reset_mask);
215 }
216
223
229 void CombFilter::setupFilter(const FilterState& filter_state) {
230 feedback_style_ = getFeedbackStyle(filter_state.style);
231 poly_float resonance = utils::clamp(filter_state.resonance_percent, 0.0f, 1.0f);
232
233 // Set feedback and scale based on style
234 if (feedback_style_ == kComb) {
238 }
239 else {
241 scale_ = poly_float(1.0f) / (feedback_ + 1.0f);
242 }
243
244 poly_float midi_cutoff = filter_state.midi_cutoff;
245 float min_nyquist = getSampleRate() * kMinNyquistMult;
246
247 poly_float blend = filter_state.pass_blend;
248 poly_float min_cutoff = midi_cutoff - 4 * kNotesPerOctave;
249
250 // Determine the filter style (band spread vs. low-high blend)
251 FilterStyle filter_style = getFilterStyle(filter_state.style);
252 if (filter_style == kBandSpread) {
253 // Compute band offsets from center
254 poly_float midi_blend_transpose = filter_state.transpose;
255 poly_float center_midi_cutoff = midi_cutoff + midi_blend_transpose;
256 poly_float midi_band_range = (blend * 0.5f * kBandOctaveRange + kBandOctaveMin) * kNotesPerOctave;
257
258 filter_midi_cutoff_ = center_midi_cutoff + midi_band_range;
259 filter2_midi_cutoff_ = utils::max(min_cutoff, center_midi_cutoff - midi_band_range);
260
263
264 filter1_cutoff = utils::clamp(filter1_cutoff, 1.0f, getSampleRate() / 2.1f);
265 filter2_cutoff = utils::clamp(filter2_cutoff, 1.0f, getSampleRate() / 2.1f);
266
269
270 low_gain_ = filter2_cutoff / filter1_cutoff + 1.0f;
271 high_gain_ = 0.0f;
272
275 }
276 else {
277 // Low/high blend calculation
278 low_gain_ = getLowGain(blend);
279 high_gain_ = getHighGain(blend);
280
281 poly_float midi_blend_transpose = filter_state.transpose;
282 filter_midi_cutoff_ = midi_cutoff + midi_blend_transpose;
283 filter2_midi_cutoff_ = min_cutoff;
284
287
288 filter_cutoff = utils::clamp(filter_cutoff, 1.0f, min_nyquist);
289 filter2_cutoff = utils::clamp(filter2_cutoff, 1.0f, min_nyquist);
290
293 }
294 }
295
301 void CombFilter::process(int num_samples) {
303
306
307 if (style == kComb)
308 processFilter<tickComb>(num_samples);
309 else if (style == kPositiveFlange)
311 else if (style == kNegativeFlange)
313 else
314 VITAL_ASSERT(false);
315 }
316
323 template<poly_float(*tick)(poly_float, Memory*, OnePoleFilter<>&, OnePoleFilter<>&,
324 poly_float, poly_float, poly_float, poly_float,
325 poly_float, poly_float, poly_float)>
326 void CombFilter::processFilter(int num_samples) {
327 poly_float current_feedback = feedback_;
328 poly_float current_filter_coefficient = filter_coefficient_;
329 poly_float current_filter2_coefficient = filter2_coefficient_;
330 poly_float current_scale = scale_;
331 poly_float current_low_gain = low_gain_;
332 poly_float current_high_gain = high_gain_;
333
335
336 // Compute min frequency for bounding
338 filter_state_.midi_cutoff_buffer[num_samples - 1]);
339 poly_float min_frequency = utils::midiNoteToFrequency(min_midi_cutoff);
340 float min_nyquist = getSampleRate() * kMinNyquistMult;
341 max_period_ = poly_float(getSampleRate()) / utils::clamp(min_frequency, 1.0f, min_nyquist);
342
343 poly_float min_period = Memory::kMinPeriod;
345 min_period *= 2.0f;
346 max_period_ = utils::clamp(max_period_, min_period, memory_->getMaxPeriod() - 5.0f);
347
348 // Check reset
349 poly_mask reset_mask = getResetMask(kReset);
350 if (reset_mask.anyMask()) {
351 reset(reset_mask);
352
353 current_feedback = utils::maskLoad(current_feedback, feedback_, reset_mask);
354 current_filter_coefficient = utils::maskLoad(current_filter_coefficient, filter_coefficient_, reset_mask);
355 current_filter2_coefficient = utils::maskLoad(current_filter2_coefficient, filter2_coefficient_, reset_mask);
356 current_scale = utils::maskLoad(current_scale, scale_, reset_mask);
357 current_low_gain = utils::maskLoad(current_low_gain, low_gain_, reset_mask);
358 current_high_gain = utils::maskLoad(current_high_gain, high_gain_, reset_mask);
359 }
360
361 const poly_float* audio_in = input(kAudio)->source->buffer;
362 poly_float* audio_out = output()->buffer;
363
364 mono_float tick_increment = 1.0f / num_samples;
365 poly_float delta_feedback = (feedback_ - current_feedback) * tick_increment;
366 poly_float delta_coefficient = (filter_coefficient_ - current_filter_coefficient) * tick_increment;
367 poly_float delta_scale = (scale_ - current_scale) * tick_increment;
368 poly_float delta_coefficient2 = (filter2_coefficient_ - current_filter2_coefficient) * tick_increment;
369 poly_float delta_low_gain = (low_gain_ - current_low_gain) * tick_increment;
370 poly_float delta_high_gain = (high_gain_ - current_high_gain) * tick_increment;
371
372 const poly_float* midi_cutoff_buffer = filter_state_.midi_cutoff_buffer;
373 poly_float base_midi = midi_cutoff_buffer[num_samples - 1];
374 poly_float base_frequency = utils::midiNoteToFrequency(base_midi);
375
376 poly_float sample_rate = getSampleRate();
377 poly_float max_period = memory_->getMaxPeriod() - 5.0f;
378 Memory* memory = memory_.get();
379
380 for (int i = 0; i < num_samples; ++i) {
381 // Compute frequency from MIDI buffer
382 poly_float midi_offset = midi_cutoff_buffer[i] - base_midi;
383 poly_float frequency = base_frequency * futils::midiOffsetToRatio(midi_offset);
384 poly_float period = sample_rate / frequency;
385 period = utils::clamp(period, min_period, max_period);
386
387 // Interpolate the filter parameters
388 current_feedback += delta_feedback;
389 current_filter_coefficient += delta_coefficient;
390 current_filter2_coefficient += delta_coefficient2;
391 current_scale += delta_scale;
392 current_low_gain += delta_low_gain;
393 current_high_gain += delta_high_gain;
394
395 // Execute the per-sample tick function
396 audio_out[i] = tick(audio_in[i], memory, feedback_filter_, feedback_filter2_,
397 period, current_feedback, current_scale,
398 current_filter_coefficient, current_filter2_coefficient,
399 current_low_gain, current_high_gain);
400 }
401 }
402
403} // namespace vital
A Processor implementing a comb-based filter with multiple feedback styles.
Definition comb_filter.h:18
poly_float scale_
Scaling multiplier applied to the incoming audio or feedback path.
Definition comb_filter.h:246
static constexpr mono_float kInputScale
Scaling factor for the comb filter input signal.
Definition comb_filter.h:84
static constexpr mono_float kBandOctaveRange
Range of band spread in octaves.
Definition comb_filter.h:69
virtual void process(int num_samples) override
Processes a block of samples, choosing the appropriate feedback style to apply.
Definition comb_filter.cpp:301
poly_float filter2_midi_cutoff_
MIDI note value controlling the secondary filter’s cutoff frequency (in band-spread mode).
Definition comb_filter.h:256
poly_float feedback_
Current feedback amount for the comb/flange filter.
Definition comb_filter.h:221
std::unique_ptr< Memory > memory_
Pointer to the Memory buffer used for the comb delay line.
Definition comb_filter.h:206
static constexpr mono_float kBandOctaveMin
Minimum band spread in octaves.
Definition comb_filter.h:74
poly_float max_period_
The computed maximum delay period based on input frequency and sample rate.
Definition comb_filter.h:216
void setupFilter(const FilterState &filter_state) override
Sets up the CombFilter state based on a FilterState struct.
Definition comb_filter.cpp:229
void hardReset() override
Resets the filter completely for all voices.
Definition comb_filter.cpp:220
FeedbackStyle feedback_style_
Current feedback style (comb, positive flange, negative flange).
Definition comb_filter.h:211
CombFilter(int size=kMinPeriod)
Constructs a CombFilter with a given memory buffer size.
Definition comb_filter.cpp:158
OnePoleFilter feedback_filter_
One-pole filter for the feedback path (first stage).
Definition comb_filter.h:261
static FilterStyle getFilterStyle(int style)
Converts an integer to a valid FilterStyle, taking advantage of the style integer layout.
Definition comb_filter.h:57
static constexpr mono_float kMaxFeedback
Maximum allowable feedback amount.
Definition comb_filter.h:89
poly_float filter2_coefficient_
Secondary coefficient for the band-spread or second filter stage.
Definition comb_filter.h:231
poly_float filter_coefficient_
Coefficient for the one-pole feedback filter (low pass).
Definition comb_filter.h:226
FilterStyle
Types of filter styles (blend of low/high, band spread).
Definition comb_filter.h:35
@ kBandSpread
Spread the band around center frequency.
Definition comb_filter.h:37
virtual ~CombFilter()
Destructor. Cleans up allocated memory.
Definition comb_filter.cpp:194
poly_float filter_midi_cutoff_
MIDI note value controlling the main filter’s cutoff frequency.
Definition comb_filter.h:251
OnePoleFilter feedback_filter2_
One-pole filter for the feedback path (second stage in certain styles).
Definition comb_filter.h:266
FeedbackStyle
Types of feedback for the comb filter (comb, positive/negative flange).
Definition comb_filter.h:24
@ kComb
Standard comb filtering.
Definition comb_filter.h:25
@ kPositiveFlange
Positive flanging effect.
Definition comb_filter.h:26
@ kNegativeFlange
Negative flanging effect.
Definition comb_filter.h:27
void reset(poly_mask reset_mask) override
Resets the filter state, clearing memory and reinitializing variables.
Definition comb_filter.cpp:201
poly_float high_gain_
Gain applied to the high output in the low/high blend mode.
Definition comb_filter.h:241
void processFilter(int num_samples)
A templated function to handle processing for each feedback style implementation.
Definition comb_filter.cpp:326
poly_float low_gain_
Gain applied to the low output in the low/high blend mode.
Definition comb_filter.h:236
static FeedbackStyle getFeedbackStyle(int style)
Converts an integer to a valid FeedbackStyle, wrapping around kNumFeedbackStyles.
Definition comb_filter.h:47
A specialized MemoryTemplate for poly_float::kSize channels.
Definition memory.h:174
force_inline poly_float get(poly_float past) const
Retrieves a poly_float of samples from the memory using cubic interpolation.
Definition memory.h:194
static constexpr mono_float kMinPeriod
Definition memory.h:35
force_inline void reset(poly_mask reset_mask)
Resets the filter state for the voices indicated by a mask.
Definition one_pole_filter.h:36
static force_inline poly_float computeCoefficient(poly_float cutoff_frequency, int sample_rate)
Computes the filter coefficient for a given cutoff frequency and sample rate.
Definition one_pole_filter.h:131
Base class for all signal-processing units in Vital.
Definition processor.h:212
force_inline Input * input(unsigned int index=0) const
Retrieves the Input pointer at a given index.
Definition processor.h:587
force_inline int getSampleRate() const
Retrieves the current (effective) sample rate.
Definition processor.h:326
bool inputMatchesBufferSize(int input=0)
Checks whether the buffer size of a particular input matches the size needed by this Processor.
Definition processor.cpp:42
force_inline poly_mask getResetMask(int input_index) const
Retrieves a mask indicating which voices triggered a note-on event. Compares the input's trigger_valu...
Definition processor.h:360
force_inline Output * output(unsigned int index=0) const
Retrieves the Output pointer at a given index.
Definition processor.h:616
Holds the parameters necessary to configure a SynthFilter at runtime.
Definition synth_filter.h:92
poly_float transpose
Transpose in semitones (applied to midi_cutoff)
Definition synth_filter.h:120
void loadSettings(Processor *processor)
Loads state from a Processor’s input signals (MIDI cutoff, drive, style, etc.).
Definition synth_filter.cpp:30
const poly_float * midi_cutoff_buffer
Pointer to the buffer storing per-sample MIDI cutoff.
Definition synth_filter.h:111
poly_float pass_blend
Blend parameter in [0..2], controlling pass type.
Definition synth_filter.h:117
int style
Filter style enum (e.g., k12Db, k24Db)
Definition synth_filter.h:116
poly_float midi_cutoff
MIDI note-based cutoff value.
Definition synth_filter.h:110
poly_float resonance_percent
Resonance parameter in [0..1].
Definition synth_filter.h:112
Abstract base class for Vital’s synthesizer filters.
Definition synth_filter.h:19
@ kReset
Reset signal.
Definition synth_filter.h:56
@ kAudio
Audio input index.
Definition synth_filter.h:55
FilterState filter_state_
Internal storage of the most recent FilterState, used by derived filters.
Definition synth_filter.h:151
#define VITAL_ASSERT(x)
Definition common.h:11
#define force_inline
Definition common.h:23
cr::Value resonance
Resonance factor for this formant.
Definition formant_filter.cpp:18
cr::Value midi_cutoff
MIDI note for the filter's cutoff.
Definition formant_filter.cpp:19
Contains faster but less accurate versions of utility math functions, such as exponential,...
Declares classes for time-domain memory storage and retrieval with cubic interpolation.
const poly_mask kFullMask
A mask covering all lanes of a poly_float vector.
Definition synth_constants.h:257
force_inline poly_float hardTanh(poly_float value)
Another saturation function using half-range tanh.
Definition futils.h:373
force_inline poly_float midiOffsetToRatio(poly_float note_offset)
Converts a MIDI note offset to a frequency ratio.
Definition futils.h:184
force_inline poly_float clamp(poly_float value, mono_float min, mono_float max)
Clamps each lane of a vector to [min, max].
Definition poly_utils.h:306
force_inline bool isFinite(poly_float value)
Checks if all lanes in a poly_float are finite.
Definition poly_utils.h:610
force_inline poly_float mulAdd(poly_float a, poly_float b, poly_float c)
Performs a fused multiply-add on SIMD data: (a * b) + c.
Definition poly_utils.h:61
force_inline poly_float min(poly_float left, poly_float right)
Returns the minimum of two poly_floats lane-by-lane.
Definition poly_utils.h:334
force_inline poly_float frequencyToMidiNote(poly_float value)
Converts a frequency to a MIDI note (vectorized).
Definition poly_utils.h:130
force_inline poly_float max(poly_float left, poly_float right)
Returns the maximum of two poly_floats lane-by-lane.
Definition poly_utils.h:327
force_inline poly_float maskLoad(poly_float zero_value, poly_float one_value, poly_mask reset_mask)
Selects between two values (zero_value or one_value) based on a mask in each lane.
Definition poly_utils.h:351
force_inline poly_float midiNoteToFrequency(poly_float value)
Converts a MIDI note to a frequency (vectorized).
Definition poly_utils.h:123
force_inline poly_float sqrt(poly_float value)
Computes the square root of each element in a poly_float.
Definition poly_utils.h:169
force_inline poly_float interpolate(poly_float from, poly_float to, mono_float t)
Performs a linear interpolation between two poly_floats using a scalar t in [0..1].
Definition poly_utils.h:182
Contains classes and functions used within the Vital synthesizer framework.
constexpr int kNotesPerOctave
Number of semitones per octave.
Definition common.h:51
constexpr mono_float kMinNyquistMult
Minimum ratio relative to Nyquist frequency.
Definition common.h:42
float mono_float
Definition common.h:33
const Output * source
The output from which this input reads samples.
Definition processor.h:134
poly_float * buffer
Pointer to the output buffer.
Definition processor.h:110
Represents a vector of floating-point values using SIMD instructions.
Definition poly_values.h:600
static force_inline simd_type vector_call abs(simd_type value)
Computes the absolute value of each element in the SIMD float register.
Definition poly_values.h:935
Represents a vector of integer values using SIMD instructions.
Definition poly_values.h:56
static force_inline uint32_t vector_call anyMask(simd_type value)
Returns a bitmask that indicates which bytes/elements in the register are non-zero.
Definition poly_values.h:352