Skip to content

Commit 69d39f6

Browse files
committed
Reworked filters
1 parent 4033790 commit 69d39f6

12 files changed

Lines changed: 616 additions & 510 deletions
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
==============================================================================
3+
4+
This file is part of the YUP library.
5+
Copyright (c) 2026 - kunitoki@gmail.com
6+
7+
YUP is an open source library subject to open-source licensing.
8+
9+
The code included in this file is provided under the terms of the ISC license
10+
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
11+
to use, copy, modify, and/or distribute this software for any purpose with or
12+
without fee is hereby granted provided that the above copyright notice and
13+
this permission notice appear in all copies.
14+
15+
YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
16+
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
17+
DISCLAIMED.
18+
19+
==============================================================================
20+
*/
21+
22+
#pragma once
23+
24+
namespace yup
25+
{
26+
27+
//==============================================================================
28+
/** State for a one-pole analog-model filter.
29+
30+
The state is designed to be used with the coefficients defined in AnalogOnePoleCoefficients.
31+
*/
32+
template <typename CoeffType>
33+
struct AnalogOnePoleState
34+
{
35+
CoeffType z1 = static_cast<CoeffType> (0);
36+
37+
void reset() noexcept
38+
{
39+
z1 = static_cast<CoeffType> (0);
40+
}
41+
42+
CoeffType processLowPass (CoeffType input, const AnalogOnePoleCoefficients<CoeffType>& coefficients) noexcept
43+
{
44+
const auto v = (input - z1) * coefficients.alpha;
45+
const auto output = v + z1;
46+
z1 = output + v;
47+
48+
return output;
49+
}
50+
51+
CoeffType processHighPass (CoeffType input, const AnalogOnePoleCoefficients<CoeffType>& coefficients) noexcept
52+
{
53+
return input - processLowPass (input, coefficients);
54+
}
55+
56+
CoeffType getFeedbackOutput (const AnalogOnePoleCoefficients<CoeffType>& coefficients) const noexcept
57+
{
58+
return coefficients.beta * z1;
59+
}
60+
};
61+
62+
//==============================================================================
63+
/** State for a two-pole analog-model filter.
64+
65+
The state is designed to be used with the coefficients defined in AnalogTwoPoleCoefficients.
66+
*/
67+
template <typename CoeffType>
68+
struct AnalogTwoPoleState
69+
{
70+
CoeffType s1 = static_cast<CoeffType> (0);
71+
CoeffType s2 = static_cast<CoeffType> (0);
72+
73+
void reset() noexcept
74+
{
75+
s1 = static_cast<CoeffType> (0);
76+
s2 = static_cast<CoeffType> (0);
77+
}
78+
};
79+
80+
//==============================================================================
81+
/** Computes the low-pass output of a one-pole analog-model filter.
82+
83+
@param input The input sample to process
84+
@param state The current state of the filter
85+
@param coefficients The coefficients defining the filter behavior
86+
87+
@return The low-pass output sample
88+
*/
89+
template <typename CoeffType>
90+
CoeffType getAnalogOnePoleLowPassOutput (CoeffType input, CoeffType state, const AnalogOnePoleCoefficients<CoeffType>& coefficients) noexcept
91+
{
92+
return coefficients.alpha * input + (static_cast<CoeffType> (1) - coefficients.alpha) * state;
93+
}
94+
95+
/** Computes the high-pass output of a one-pole analog-model filter.
96+
97+
@param input The input sample to process
98+
@param state The current state of the filter
99+
@param coefficients The coefficients defining the filter behavior
100+
101+
@return The high-pass output sample
102+
*/
103+
template <typename CoeffType>
104+
CoeffType getAnalogOnePoleHighPassOutput (CoeffType input, CoeffType state, const AnalogOnePoleCoefficients<CoeffType>& coefficients) noexcept
105+
{
106+
return (static_cast<CoeffType> (1) - coefficients.alpha) * (input - state);
107+
}
108+
109+
/** Computes the next state of a one-pole analog-model filter.
110+
111+
@param input The input sample to process
112+
@param state The current state of the filter
113+
@param coefficients The coefficients defining the filter behavior
114+
115+
@return The next state of the filter
116+
*/
117+
template <typename CoeffType>
118+
CoeffType getAnalogOnePoleNextState (CoeffType input, CoeffType state, const AnalogOnePoleCoefficients<CoeffType>& coefficients) noexcept
119+
{
120+
return static_cast<CoeffType> (2) * coefficients.alpha * input
121+
+ (static_cast<CoeffType> (1) - static_cast<CoeffType> (2) * coefficients.alpha) * state;
122+
}
123+
124+
//==============================================================================
125+
/** Processes a single sample through a two-pole analog-model filter.
126+
127+
This function implements the processing for a trapezoidal-integrator two-pole state-variable filter.
128+
The output is a combination of low-pass, high-pass, and band-pass responses based on the coefficients provided.
129+
130+
@param input The input sample to process
131+
@param coefficients The coefficients defining the filter behavior
132+
@param state The state of the filter, which will be updated during processing
133+
134+
@return The processed output sample
135+
*/
136+
template <typename SampleType, typename CoeffType>
137+
SampleType processAnalogTwoPole (
138+
SampleType input,
139+
const AnalogTwoPoleCoefficients<CoeffType>& coefficients,
140+
AnalogTwoPoleState<CoeffType>& state) noexcept
141+
{
142+
const auto inputValue = static_cast<CoeffType> (input);
143+
const auto highpass = (inputValue - coefficients.r2 * state.s1 - coefficients.g * state.s1 - state.s2) * coefficients.h;
144+
const auto bandpass = coefficients.g * highpass + state.s1;
145+
const auto bandpassFeedback = clipAnalogResonance (bandpass);
146+
const auto lowpass = coefficients.g * bandpass + state.s2;
147+
148+
state.s1 = coefficients.g * highpass + bandpassFeedback;
149+
state.s2 = coefficients.g * bandpassFeedback + lowpass;
150+
151+
const auto output = (coefficients.lowOut * lowpass + coefficients.bandOut * bandpass + coefficients.highOut * highpass)
152+
* coefficients.gainCorrection;
153+
154+
return static_cast<SampleType> (output);
155+
}
156+
157+
//==============================================================================
158+
/** Computes the complex frequency response of a two-pole analog-model filter.
159+
160+
@param coefficients The coefficients defining the filter behavior
161+
@param frequency The frequency at which to evaluate the response
162+
@param sampleRate The sample rate of the system
163+
164+
@return The complex frequency response
165+
*/
166+
template <typename CoeffType>
167+
Complex<CoeffType> getAnalogTwoPoleComplexResponse (
168+
const AnalogTwoPoleCoefficients<CoeffType>& coefficients,
169+
CoeffType frequency,
170+
double sampleRate) noexcept
171+
{
172+
using ComplexType = Complex<CoeffType>;
173+
174+
const auto highpassState1 = -coefficients.h * (coefficients.r2 + coefficients.g);
175+
const auto highpassState2 = -coefficients.h;
176+
const auto highpassInput = coefficients.h;
177+
178+
const auto bandpassState1 = coefficients.g * highpassState1 + static_cast<CoeffType> (1);
179+
const auto bandpassState2 = coefficients.g * highpassState2;
180+
const auto bandpassInput = coefficients.g * highpassInput;
181+
182+
const auto lowpassState1 = coefficients.g * bandpassState1;
183+
const auto lowpassState2 = coefficients.g * bandpassState2 + static_cast<CoeffType> (1);
184+
const auto lowpassInput = coefficients.g * bandpassInput;
185+
186+
const auto state11 = coefficients.g * highpassState1 + bandpassState1;
187+
const auto state12 = coefficients.g * highpassState2 + bandpassState2;
188+
const auto state1Input = coefficients.g * highpassInput + bandpassInput;
189+
190+
const auto state21 = coefficients.g * bandpassState1 + lowpassState1;
191+
const auto state22 = coefficients.g * bandpassState2 + lowpassState2;
192+
const auto state2Input = coefficients.g * bandpassInput + lowpassInput;
193+
194+
const auto outputState1 = (coefficients.lowOut * lowpassState1 + coefficients.bandOut * bandpassState1 + coefficients.highOut * highpassState1)
195+
* coefficients.gainCorrection;
196+
const auto outputState2 = (coefficients.lowOut * lowpassState2 + coefficients.bandOut * bandpassState2 + coefficients.highOut * highpassState2)
197+
* coefficients.gainCorrection;
198+
const auto outputInput = (coefficients.lowOut * lowpassInput + coefficients.bandOut * bandpassInput + coefficients.highOut * highpassInput)
199+
* coefficients.gainCorrection;
200+
201+
const auto z = polar (static_cast<CoeffType> (1), frequencyToAngular (frequency, static_cast<CoeffType> (sampleRate)));
202+
const auto determinant = (z - state11) * (z - state22) - state12 * state21;
203+
const auto responseState1 = ((z - state22) * state1Input + state12 * state2Input) / determinant;
204+
const auto responseState2 = (state21 * state1Input + (z - state11) * state2Input) / determinant;
205+
206+
return ComplexType (outputInput) + outputState1 * responseState1 + outputState2 * responseState2;
207+
}
208+
209+
} // namespace yup
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
==============================================================================
3+
4+
This file is part of the YUP library.
5+
Copyright (c) 2026 - kunitoki@gmail.com
6+
7+
YUP is an open source library subject to open-source licensing.
8+
9+
The code included in this file is provided under the terms of the ISC license
10+
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
11+
to use, copy, modify, and/or distribute this software for any purpose with or
12+
without fee is hereby granted provided that the above copyright notice and
13+
this permission notice appear in all copies.
14+
15+
YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
16+
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
17+
DISCLAIMED.
18+
19+
==============================================================================
20+
*/
21+
22+
#pragma once
23+
24+
namespace yup
25+
{
26+
27+
//==============================================================================
28+
/** Clips the input value to the range [-4, 4] using a hyperbolic tangent function.
29+
30+
This function is used to limit the feedback in analog-model filters to prevent
31+
instability while preserving the character of the resonance.
32+
33+
@param input The input value to clip
34+
35+
@return The clipped output value
36+
*/
37+
template <typename CoeffType>
38+
CoeffType clipAnalogResonance (CoeffType input) noexcept
39+
{
40+
return std::tanh (input * static_cast<CoeffType> (0.25)) * static_cast<CoeffType> (4);
41+
}
42+
43+
/** Analog saturator structure.
44+
45+
This structure implements a simple symmetric atan-based waveshaper for analog
46+
saturation effects. The drive parameter controls the amount of saturation, while
47+
preScale and postScale allow for adjusting the input and output levels to
48+
achieve the desired tonal characteristics.
49+
*/
50+
template <typename SampleType, typename CoeffType>
51+
struct AnalogSaturator
52+
{
53+
void setCoefficients (const AnalogSaturatorCoefficients<CoeffType>& newCoefficients) noexcept
54+
{
55+
coefficients = newCoefficients;
56+
}
57+
58+
SampleType process (SampleType input) const noexcept
59+
{
60+
if (coefficients.drive <= static_cast<CoeffType> (0))
61+
return input;
62+
63+
return static_cast<SampleType> (
64+
std::atan (static_cast<CoeffType> (input) * coefficients.preScale) * coefficients.postScale);
65+
}
66+
67+
AnalogSaturatorCoefficients<CoeffType> coefficients;
68+
};
69+
70+
} // namespace yup

modules/yup_dsp/designers/yup_FilterDesigner.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -387,12 +387,12 @@ BiquadCoefficients<CoeffType> FilterDesigner<CoeffType>::designZoelzer (
387387

388388
template <typename CoeffType>
389389
int FilterDesigner<CoeffType>::designButterworth (
390+
std::vector<BiquadCoefficients<CoeffType>>& coefficients,
390391
FilterModeType filterMode,
391392
int order,
392393
CoeffType frequency,
393394
CoeffType frequency2,
394-
double sampleRate,
395-
std::vector<BiquadCoefficients<CoeffType>>& coefficients) noexcept
395+
double sampleRate) noexcept
396396
{
397397
// Validate inputs
398398
jassert (order >= 2 && order <= 16);
@@ -540,11 +540,11 @@ int FilterDesigner<CoeffType>::designButterworth (
540540

541541
template <typename CoeffType>
542542
int FilterDesigner<CoeffType>::designLinkwitzRiley (
543+
std::vector<BiquadCoefficients<CoeffType>>& lowCoeffs,
544+
std::vector<BiquadCoefficients<CoeffType>>& highCoeffs,
543545
int order,
544546
CoeffType crossoverFreq,
545-
double sampleRate,
546-
std::vector<BiquadCoefficients<CoeffType>>& lowCoeffs,
547-
std::vector<BiquadCoefficients<CoeffType>>& highCoeffs) noexcept
547+
double sampleRate) noexcept
548548
{
549549
jassert (order >= 2 && order <= 16);
550550
jassert ((order & 1) == 0); // Must be even

0 commit comments

Comments
 (0)