Skip to content

Commit e8e559e

Browse files
committed
Second-order section biquadratic IIR filter
1 parent a1726de commit e8e559e

File tree

4 files changed

+376
-0
lines changed

4 files changed

+376
-0
lines changed

include/dsplib.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <dsplib/gccphat.h>
2424
#include <dsplib/resample.h>
2525
#include <dsplib/spectrum.h>
26+
#include <dsplib/iir.h>
2627

2728
#include <dsplib/audio/noise-gate.h>
2829
#include <dsplib/audio/compressor.h>

include/dsplib/iir.h

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
#pragma once
2+
3+
#include <dsplib/array.h>
4+
#include <dsplib/utils.h>
5+
#include <dsplib/inplace.h>
6+
#include <memory>
7+
8+
namespace dsplib {
9+
10+
//------------------------------------------------------------------------------------
11+
//TODO: naming
12+
enum class IIRStructure
13+
{
14+
DirectForm1, //Direct form I
15+
DirectForm2, //Direct form II
16+
DirectForm1Tr, //Direct form I (transposed)
17+
DirectForm2Tr //Direct form II (transposed)
18+
};
19+
20+
//------------------------------------------------------------------------------------
21+
//Infinite impulse response filter
22+
template<typename T>
23+
class IIRFilter
24+
{
25+
public:
26+
explicit IIRFilter(const arr_real& num, const arr_real& den)
27+
: num_{num}
28+
, den_{den}
29+
, n_{den_.size()} {
30+
if (den_.size() != num_.size()) {
31+
DSPLIB_THROW("IIR num/den size is not equal");
32+
}
33+
//TODO: num_ /= den_[0], den_ /= den_[0]
34+
dx_ = dsplib::zeros(n_);
35+
dy_ = dsplib::zeros(n_ - 1);
36+
}
37+
38+
base_array<T> operator()(const base_array<T>& x) {
39+
return this->process(x);
40+
}
41+
42+
T operator()(const T& x) {
43+
return this->process(x);
44+
}
45+
46+
base_array<T> process(const base_array<T>& x) {
47+
auto y = zeros(x.size());
48+
for (int i = 0; i < x.size(); ++i) {
49+
y[i] = process(x[i]);
50+
}
51+
return y;
52+
}
53+
54+
T process(T x) {
55+
//TODO: cyclic buffer?
56+
dx_.slice(1, n_) = dx_.slice(0, n_ - 1);
57+
dx_[0] = x;
58+
T y = 0;
59+
for (int i = 0; i < n_; ++i) {
60+
y += num_[i] * dx_[i];
61+
}
62+
for (int i = 0; i < n_ - 1; ++i) {
63+
y -= den_[i + 1] * dy_[i];
64+
}
65+
dy_.slice(1, n_ - 1) = dy_.slice(0, n_ - 2);
66+
dy_[0] = y;
67+
return y;
68+
}
69+
70+
private:
71+
arr_real num_;
72+
arr_real den_;
73+
base_array<T> dx_;
74+
base_array<T> dy_;
75+
int n_;
76+
};
77+
78+
//------------------------------------------------------------------------------------
79+
class BaseBiquadSection
80+
{
81+
public:
82+
explicit BaseBiquadSection(real_t b0, real_t b1, real_t b2, real_t a1, real_t a2)
83+
: b0_{b0}
84+
, b1_{b1}
85+
, b2_{b2}
86+
, a1_{a1}
87+
, a2_{a2} {
88+
}
89+
90+
virtual ~BaseBiquadSection() {
91+
}
92+
93+
virtual void process(arr_real& x) = 0;
94+
95+
protected:
96+
const real_t b0_;
97+
const real_t b1_;
98+
const real_t b2_;
99+
const real_t a1_;
100+
const real_t a2_;
101+
};
102+
103+
//------------------------------------------------------------------------------------
104+
class BiquadSection1 : public BaseBiquadSection
105+
{
106+
public:
107+
explicit BiquadSection1(real_t b0, real_t b1, real_t b2, real_t a1, real_t a2)
108+
: BaseBiquadSection(b0, b1, b2, a1, a2) {
109+
}
110+
111+
real_t process(const real_t& x) noexcept {
112+
const real_t y = (x * b0_) + (x1_ * b1_) + (x2_ * b2_) - (y1_ * a1_) - (y2_ * a2_);
113+
x2_ = x1_;
114+
x1_ = x;
115+
y2_ = y1_;
116+
y1_ = y;
117+
return y;
118+
}
119+
120+
void process(arr_real& x) noexcept final {
121+
for (auto& v : x) {
122+
v = process(v);
123+
}
124+
}
125+
126+
private:
127+
real_t x1_{0};
128+
real_t x2_{0};
129+
real_t y1_{0};
130+
real_t y2_{0};
131+
};
132+
133+
//------------------------------------------------------------------------------------
134+
class BiquadSection1Tr : public BaseBiquadSection
135+
{
136+
public:
137+
explicit BiquadSection1Tr(real_t b0, real_t b1, real_t b2, real_t a1, real_t a2)
138+
: BaseBiquadSection(b0, b1, b2, a1, a2) {
139+
}
140+
141+
real_t process(const real_t& x) noexcept {
142+
const real_t u0 = (x - x1_);
143+
const real_t y = u0 * b0_ + y1_;
144+
x1_ = x2_ + u0 * a1_;
145+
x2_ = u0 * a2_;
146+
y1_ = y2_ + u0 * b1_;
147+
y2_ = u0 * b2_;
148+
return y;
149+
}
150+
151+
void process(arr_real& x) noexcept final {
152+
for (auto& v : x) {
153+
v = process(v);
154+
}
155+
}
156+
157+
private:
158+
real_t x1_{0};
159+
real_t x2_{0};
160+
real_t y1_{0};
161+
real_t y2_{0};
162+
};
163+
164+
//------------------------------------------------------------------------------------
165+
class BiquadSection2 : public BaseBiquadSection
166+
{
167+
public:
168+
explicit BiquadSection2(real_t b0, real_t b1, real_t b2, real_t a1, real_t a2)
169+
: BaseBiquadSection(b0, b1, b2, a1, a2) {
170+
}
171+
172+
real_t process(const real_t& x) noexcept {
173+
const real_t u0 = x - u2_ * a2_ - u1_ * a1_;
174+
const real_t y = u0 * b0_ + u1_ * b1_ + u2_ * b2_;
175+
u2_ = u1_;
176+
u1_ = u0;
177+
return y;
178+
}
179+
180+
void process(arr_real& x) noexcept final {
181+
for (auto& v : x) {
182+
v = process(v);
183+
}
184+
}
185+
186+
private:
187+
real_t u1_{0};
188+
real_t u2_{0};
189+
};
190+
191+
//------------------------------------------------------------------------------------
192+
class BiquadSection2Tr : public BaseBiquadSection
193+
{
194+
public:
195+
explicit BiquadSection2Tr(real_t b0, real_t b1, real_t b2, real_t a1, real_t a2)
196+
: BaseBiquadSection(b0, b1, b2, a1, a2) {
197+
}
198+
199+
real_t process(const real_t& x) noexcept {
200+
const real_t y = u1_ + (b0_ * x);
201+
u1_ = (b1_ * x) - (a1_ * y) + u2_;
202+
u2_ = (b2_ * x) - (a2_ * y);
203+
return y;
204+
}
205+
206+
void process(arr_real& x) noexcept final {
207+
for (auto& v : x) {
208+
v = process(v);
209+
}
210+
}
211+
212+
private:
213+
real_t u1_{0};
214+
real_t u2_{0};
215+
};
216+
217+
//------------------------------------------------------------------------------------
218+
//IIR filter using biquadratic structures
219+
class BiquadSection
220+
{
221+
public:
222+
explicit BiquadSection(real_t b0, real_t b1, real_t b2, real_t a1, real_t a2,
223+
IIRStructure structure = IIRStructure::DirectForm2Tr) {
224+
switch (structure) {
225+
case dsplib::IIRStructure::DirectForm1:
226+
d_ = std::make_unique<BiquadSection1>(b0, b1, b2, a1, a2);
227+
break;
228+
case dsplib::IIRStructure::DirectForm1Tr:
229+
d_ = std::make_unique<BiquadSection1Tr>(b0, b1, b2, a1, a2);
230+
break;
231+
case dsplib::IIRStructure::DirectForm2:
232+
d_ = std::make_unique<BiquadSection2>(b0, b1, b2, a1, a2);
233+
break;
234+
case dsplib::IIRStructure::DirectForm2Tr:
235+
d_ = std::make_unique<BiquadSection2Tr>(b0, b1, b2, a1, a2);
236+
break;
237+
};
238+
}
239+
240+
explicit BiquadSection(const arr_real& num, const arr_real& den)
241+
: BiquadSection{num[0], num[1], num[2], den[1], den[2]} {
242+
DSPLIB_ASSERT((den.size() == 3) && (num.size() == 3), "biquad coeffs must have length 3");
243+
}
244+
245+
void process(arr_real& x) noexcept {
246+
d_->process(x);
247+
}
248+
249+
private:
250+
std::unique_ptr<BaseBiquadSection> d_;
251+
};
252+
253+
//------------------------------------------------------------------------------------
254+
//Second-order section biquadratic IIR filter structures
255+
//TODO: rename to BiquadFilter?
256+
//TODO: use BiquadIIR filter (fixed sizes algorithm)
257+
//TODO: use matrix instead vector<array>
258+
//TODO: base_array<T> implementation
259+
//TODO: inplace process
260+
class SOSFilter
261+
{
262+
public:
263+
explicit SOSFilter(const std::vector<arr_real>& num, const std::vector<arr_real>& den, arr_real scale)
264+
: scale_{scale} {
265+
DSPLIB_ASSERT((num.size() == den.size()) && (num[0].size() == den[0].size()),
266+
"'num' and 'den' arrays have different lengths");
267+
DSPLIB_ASSERT(scale.size() == num.size() + 1, "'scale' must be scalar or array[n+1]");
268+
269+
for (int i = 0; i < num.size(); ++i) {
270+
iirs_.emplace_back(BiquadSection(num[i], den[i]));
271+
}
272+
}
273+
274+
explicit SOSFilter(const std::vector<arr_real>& num, const std::vector<arr_real>& den, real_t scale = 1.0)
275+
: SOSFilter{num, den, _scale_array(num.size(), scale)} {
276+
}
277+
278+
explicit SOSFilter(const arr_real& num, const arr_real& den, real_t scale = 1.0)
279+
: SOSFilter{{num}, {den}, _scale_array(1, scale)} {
280+
}
281+
282+
void operator()(inplace_t<arr_real> x) {
283+
this->process(x);
284+
}
285+
286+
[[nodiscard]] arr_real operator()(const arr_real& x) {
287+
return this->process(x);
288+
}
289+
290+
//TODO: inplace span<real_t>
291+
void process(inplace_t<arr_real> x) {
292+
arr_real& y = x.get();
293+
y *= scale_[0];
294+
for (int k = 0; k < iirs_.size(); ++k) {
295+
iirs_[k].process(y);
296+
y *= scale_[k + 1];
297+
}
298+
}
299+
300+
[[nodiscard]] arr_real process(const arr_real& x) {
301+
arr_real y(x);
302+
process(inplace(y));
303+
return y;
304+
}
305+
306+
//TODO: get params
307+
//...
308+
309+
private:
310+
const arr_real scale_;
311+
std::vector<BiquadSection> iirs_;
312+
313+
static arr_real _scale_array(int nsect, real_t scale) {
314+
arr_real r = ones(nsect + 1);
315+
r[0] = scale;
316+
return r;
317+
}
318+
};
319+
320+
//TODO: isstable
321+
322+
} // namespace dsplib

include/dsplib/inplace.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#pragma once
2+
3+
namespace dsplib {
4+
5+
template<class T>
6+
class inplace_t
7+
{
8+
public:
9+
explicit inplace_t(T& value)
10+
: value_{value} {
11+
}
12+
13+
T& get() noexcept {
14+
return value_;
15+
}
16+
17+
private:
18+
T& value_;
19+
};
20+
21+
//TODO: span convert support, example inplace(std::vector<real_t>&) -> inplace(span<real_t>&)
22+
template<class T>
23+
inplace_t<T> inplace(T& value) {
24+
return inplace_t<T>(value);
25+
}
26+
27+
} // namespace dsplib

tests/iir_test.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include "tests_common.h"
2+
#include <cmath>
3+
4+
using namespace dsplib;
5+
6+
TEST(IIR, SOSFilter) {
7+
real_t g = 0.00289644590202159;
8+
std::vector<arr_real> den = {{1, 6.98117778863416e-15, 0.00619395865710930},
9+
{1, -1.15315743065558e-14, 0.0576378105614471},
10+
{1, 3.37230243729891e-15, 0.171572875253811},
11+
{1, -1.33226762955019e-15, 0.375524805944922},
12+
{1, -6.10622663543836e-16, 0.729453817281744}};
13+
14+
std::vector<arr_real> num = {{1, 2, 1}, {1, 2, 1}, {1, 2, 1}, {1, 2, 1}, {1, 2, 1}};
15+
16+
SOSFilter flt(num, den, g);
17+
18+
const int fs = 8000;
19+
auto t = arange(fs) / fs;
20+
// auto x = sin(2 * pi * 1000 * t) + sin(2 * pi * 3000 * t) + 0.01 * randn(fs);
21+
auto x = 0.1 * randn(fs);
22+
// auto y = flt(x);
23+
flt(inplace(x));
24+
25+
int p = 0;
26+
}

0 commit comments

Comments
 (0)