Skip to content

Commit bfb78ef

Browse files
committed
Merge pull request #108 from JuliaDSP/sjk/filter-compose
Allow scaling and composing filter coefficients using *
2 parents 947ce5a + 013ad61 commit bfb78ef

File tree

2 files changed

+155
-38
lines changed

2 files changed

+155
-38
lines changed

src/Filters/coefficients.jl

Lines changed: 103 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
abstract FilterCoefficients
44

5+
realtype(x::DataType) = x
6+
realtype{T}(::Type{Complex{T}}) = T
7+
complextype(T::DataType) = Complex{T}
8+
complextype{T}(::Type{Complex{T}}) = Complex{T}
9+
510
#
611
# Zero-pole gain form
712
#
@@ -12,6 +17,19 @@ immutable ZeroPoleGain{Z<:Number,P<:Number,K<:Number} <: FilterCoefficients
1217
k::K
1318
end
1419

20+
Base.promote_rule{Z1,P1,K1,Z2,P2,K2}(::Type{ZeroPoleGain{Z1,P1,K1}}, ::Type{ZeroPoleGain{Z2,P2,K2}}) =
21+
ZeroPoleGain{promote_type(Z1,Z2),promote_type(P1,P2),promote_type(K1,K2)}
22+
Base.convert{Z,P,K}(::Type{ZeroPoleGain{Z,P,K}}, f::ZeroPoleGain{Z,P,K}) = f
23+
Base.convert{Z,P,K}(::Type{ZeroPoleGain{Z,P,K}}, f::ZeroPoleGain) =
24+
ZeroPoleGain{Z,P,K}(f.z, f.p, f.k)
25+
26+
*(f::ZeroPoleGain, g::Number) = ZeroPoleGain(f.z, f.p, f.k*g)
27+
*(g::Number, f::ZeroPoleGain) = ZeroPoleGain(f.z, f.p, f.k*g)
28+
*(f1::ZeroPoleGain, f2::ZeroPoleGain) =
29+
ZeroPoleGain([f1.z; f2.z], [f1.p; f2.p], f1.k*f2.k)
30+
*(f1::ZeroPoleGain, fs::ZeroPoleGain...) =
31+
ZeroPoleGain(vcat(f1.z, [f.z for f in fs]...), vcat(f1.p, [f.p for f in fs]...), f1.k*prod([f.k for f in fs]))
32+
1533
#
1634
# Transfer function form
1735
#
@@ -34,77 +52,100 @@ function PolynomialRatio{T<:Number,S<:Number}(b::Union(T,Vector{T}), a::Union(S,
3452
PolynomialRatio{promote_type(T,S)}(Poly(b[end:-1:findfirst(b)]), Poly(a[end:-1:findfirst(a)]))
3553
end
3654

37-
function Base.convert(::Type{PolynomialRatio}, f::ZeroPoleGain)
55+
Base.promote_rule{T,S}(::Type{PolynomialRatio{T}}, ::Type{PolynomialRatio{S}}) = PolynomialRatio{promote_type(T,S)}
56+
Base.convert{T}(::Type{PolynomialRatio{T}}, f::PolynomialRatio{T}) = f
57+
Base.convert{T}(::Type{PolynomialRatio{T}}, f::PolynomialRatio) = PolynomialRatio{T}(f.b, f.a)
58+
59+
function Base.convert{T<:Real}(::Type{PolynomialRatio{T}}, f::ZeroPoleGain)
3860
b = f.k*poly(f.z)
3961
a = poly(f.p)
40-
PolynomialRatio(Poly(real(b.a)), Poly(real(a.a)))
62+
PolynomialRatio{T}(Poly(real(b.a)), Poly(real(a.a)))
4163
end
64+
Base.convert{Z,P,K}(::Type{PolynomialRatio}, f::ZeroPoleGain{Z,P,K}) =
65+
convert(PolynomialRatio{promote_type(realtype(Z),realtype(P),K)}, f)
4266

43-
function Base.convert{T}(::Type{ZeroPoleGain}, f::PolynomialRatio{T})
67+
function Base.convert{Z,P,K}(::Type{ZeroPoleGain{Z,P,K}}, f::PolynomialRatio)
4468
k = real(f.b[end])
45-
b = f.b / k
46-
z = convert(Vector{Complex{T}}, roots(b))
47-
p = convert(Vector{Complex{T}}, roots(f.a))
48-
ZeroPoleGain(z, p, k)
69+
ZeroPoleGain{Z,P,K}(roots(f.b / k), roots(f.a), k)
4970
end
71+
Base.convert{T}(::Type{ZeroPoleGain}, f::PolynomialRatio{T}) =
72+
convert(ZeroPoleGain{complextype(T),complextype(T),T}, f)
73+
74+
*(f::PolynomialRatio, g::Number) = PolynomialRatio(g*f.b, f.a)
75+
*(g::Number, f::PolynomialRatio) = PolynomialRatio(g*f.b, f.a)
76+
*(f1::PolynomialRatio, f2::PolynomialRatio) =
77+
PolynomialRatio(f1.b*f2.b, f1.a*f2.a)
78+
*(f1::PolynomialRatio, fs::PolynomialRatio...) =
79+
PolynomialRatio(f1.b*prod([f.b for f in fs]), f1.a*prod([f.a for f in fs]))
5080

5181
coefb(f::PolynomialRatio) = reverse(f.b.a)
5282
coefa(f::PolynomialRatio) = reverse(f.a.a)
5383

5484
#
5585
# Biquad filter in transfer function form
56-
# A separate immutable to improve efficiency of filtering using SecondOrderSectionss
86+
# A separate immutable to improve efficiency of filtering using SecondOrderSections
5787
#
5888

59-
immutable Biquad{T} <: FilterCoefficients
89+
immutable Biquad{T<:Number} <: FilterCoefficients
6090
b0::T
6191
b1::T
6292
b2::T
6393
a1::T
6494
a2::T
6595
end
66-
Biquad{T}(b0::T, b1::T, b2::T, a0::T, a1::T, a2::T, g::Real=1) =
96+
Biquad{T}(b0::T, b1::T, b2::T, a0::T, a1::T, a2::T, g::Number=1) =
6797
Biquad(g*b0/a0, g*b1/a0, g*b2/a0, a1/a0, a2/a0)
6898

69-
Base.convert(::Type{ZeroPoleGain}, f::Biquad) = convert(ZeroPoleGain, convert(PolynomialRatio, f))
99+
Base.promote_rule{T,S}(::Type{Biquad{T}}, ::Type{Biquad{S}}) = Biquad{promote_type(T,S)}
100+
Base.convert{T}(::Type{Biquad{T}}, f::Biquad{T}) = f
101+
Base.convert{T}(::Type{Biquad{T}}, f::Biquad) = Biquad{T}(f.b0, f.b1, f.b2, f.a1, f.a2)
70102

71-
function Base.convert{T}(::Type{PolynomialRatio}, f::Biquad{T})
103+
Base.convert{Z,P,K}(::Type{ZeroPoleGain{Z,P,K}}, f::Biquad) =
104+
convert(ZeroPoleGain{Z,P,K}, convert(PolynomialRatio, f))
105+
Base.convert(::Type{ZeroPoleGain}, f::Biquad) =
106+
convert(ZeroPoleGain, convert(PolynomialRatio, f))
107+
108+
function Base.convert{T}(::Type{PolynomialRatio{T}}, f::Biquad)
72109
if f.b2 == zero(T) && f.a2 == zero(T)
73110
if f.b1 == zero(T) && f.a1 == zero(T)
74-
b = [f.b0]
75-
a = [one(T)]
111+
b = T[f.b0]
112+
a = T[one(T)]
76113
else
77-
b = [f.b0, f.b1]
78-
a = [one(T), f.a1]
114+
b = T[f.b0, f.b1]
115+
a = T[one(T), f.a1]
79116
end
80117
else
81-
b = [f.b0, f.b1, f.b2]
82-
a = [one(T), f.a1, f.a2]
118+
b = T[f.b0, f.b1, f.b2]
119+
a = T[one(T), f.a1, f.a2]
83120
end
84121

85122
PolynomialRatio(b, a)
86123
end
124+
Base.convert{T}(::Type{PolynomialRatio}, f::Biquad{T}) = convert(PolynomialRatio{T}, f)
87125

88-
Base.convert(::Type{Biquad}, f::ZeroPoleGain) = convert(Biquad, convert(PolynomialRatio, f))
89-
90-
function Base.convert{T}(::Type{Biquad}, f::PolynomialRatio{T})
126+
function Base.convert{T}(::Type{Biquad{T}}, f::PolynomialRatio)
91127
a, b = f.a, f.b
92128
xs = max(length(b), length(a))
93129

94130
if xs == 3
95-
Biquad(b[2], b[1], b[0], a[1], a[0])
131+
Biquad{T}(b[2], b[1], b[0], a[1], a[0])
96132
elseif xs == 2
97-
Biquad(b[1], b[0], zero(T), a[0], zero(T))
133+
Biquad{T}(b[1], b[0], zero(T), a[0], zero(T))
98134
elseif xs == 1
99-
Biquad(b[0], zero(T), zero(T), zero(T), zero(T))
135+
Biquad{T}(b[0], zero(T), zero(T), zero(T), zero(T))
100136
elseif xs == 0
101137
throw(ArgumentError("cannot convert an empty PolynomialRatio to Biquad"))
102138
else
103139
throw(ArgumentError("cannot convert a filter of length > 3 to Biquad"))
104140
end
105141
end
142+
Base.convert{T}(::Type{Biquad}, f::PolynomialRatio{T}) = convert(Biquad{T}, f)
143+
144+
Base.convert{T}(::Type{Biquad{T}}, f::ZeroPoleGain) = convert(Biquad{T}, convert(PolynomialRatio, f))
145+
Base.convert(::Type{Biquad}, f::ZeroPoleGain) = convert(Biquad, convert(PolynomialRatio, f))
106146

107147
*(f::Biquad, g::Number) = Biquad(f.b0*g, f.b1*g, f.b2*g, f.a1, f.a2)
148+
*(g::Number, f::Biquad) = Biquad(f.b0*g, f.b1*g, f.b2*g, f.a1, f.a2)
108149

109150
#
110151
# Second-order sections (array of biquads)
@@ -115,27 +156,40 @@ immutable SecondOrderSections{T,G} <: FilterCoefficients
115156
g::G
116157
end
117158

118-
realtype(x::DataType) = x
119-
realtype{T}(::Type{Complex{T}}) = T
120-
complextype(T::DataType) = Complex{T}
121-
complextype{T}(::Type{Complex{T}}) = Complex{T}
159+
Base.promote_rule{T1,G1,T2,G2}(::Type{SecondOrderSections{T1,G1}}, ::Type{SecondOrderSections{T2,G2}}) =
160+
SecondOrderSections{promote_type(T1,T2),promote_type(G1,G2)}
161+
Base.convert{T,G}(::Type{SecondOrderSections{T,G}}, f::SecondOrderSections{T,G}) = f
162+
Base.convert{T,G}(::Type{SecondOrderSections{T,G}}, f::SecondOrderSections) =
163+
SecondOrderSections{T,G}(f.biquads, f.g)
122164

123-
function Base.convert{T}(::Type{ZeroPoleGain}, f::SecondOrderSections{T})
124-
t = complextype(T)
125-
z = t[]
126-
p = t[]
165+
function Base.convert{Z,P,K}(::Type{ZeroPoleGain{Z,P,K}}, f::SecondOrderSections)
166+
z = Z[]
167+
p = P[]
127168
k = f.g
128169
for biquad in f.biquads
129170
biquadzpk = convert(ZeroPoleGain, biquad)
130171
append!(z, biquadzpk.z)
131172
append!(p, biquadzpk.p)
132173
k *= biquadzpk.k
133174
end
134-
ZeroPoleGain(z, p, k)
175+
ZeroPoleGain{Z,P,K}(z, p, k)
135176
end
177+
Base.convert{T,G}(::Type{ZeroPoleGain}, f::SecondOrderSections{T,G}) =
178+
convert(ZeroPoleGain{complextype(T),complextype(T),G}, f)
136179

137-
Base.convert(to::Union(Type{PolynomialRatio}, Type{Biquad}), f::SecondOrderSections) =
138-
convert(to, convert(ZeroPoleGain, f))
180+
function Base.convert{T}(::Type{Biquad{T}}, f::SecondOrderSections)
181+
if length(f.biquads) != 1
182+
throw(ArgumentError("only a single second order section may be converted to a biquad"))
183+
end
184+
convert(Biquad{T}, f.biquads[1]*f.g)
185+
end
186+
Base.convert{T,G}(::Type{Biquad}, f::SecondOrderSections{T,G}) =
187+
convert(Biquad{promote_type(T,G)}, f)
188+
189+
Base.convert{T}(::Type{PolynomialRatio{T}}, f::SecondOrderSections) =
190+
convert(PolynomialRatio{T}, convert(ZeroPoleGain, f))
191+
Base.convert(::Type{PolynomialRatio}, f::SecondOrderSections) =
192+
convert(PolynomialRatio, convert(ZeroPoleGain, f))
139193

140194
# Group each pole in p with its closest zero in z
141195
# Remove paired poles from p and z
@@ -252,4 +306,18 @@ function Base.convert{Z,P}(::Type{SecondOrderSections}, f::ZeroPoleGain{Z,P})
252306
SecondOrderSections(biquads, f.k)
253307
end
254308

309+
Base.convert{T,G}(::Type{SecondOrderSections{T,G}}, f::Biquad) = SecondOrderSections{T,G}([f], one(G))
310+
Base.convert{T}(::Type{SecondOrderSections}, f::Biquad{T}) = convert(SecondOrderSections{T,Int}, f)
255311
Base.convert(::Type{SecondOrderSections}, f::FilterCoefficients) = convert(SecondOrderSections, convert(ZeroPoleGain, f))
312+
313+
*(f::SecondOrderSections, g::Number) = SecondOrderSections(f.biquads, f.g*g)
314+
*(g::Number, f::SecondOrderSections) = SecondOrderSections(f.biquads, f.g*g)
315+
*(f1::SecondOrderSections, f2::SecondOrderSections) =
316+
SecondOrderSections([f1.biquads; f2.biquads], f1.g*f2.g)
317+
*(f1::SecondOrderSections, fs::SecondOrderSections...) =
318+
SecondOrderSections(vcat(f1.biquads, [f.biquads for f in fs]...), f1.g*prod([f.g for f in fs]))
319+
320+
*(f1::Biquad, f2::Biquad) = SecondOrderSections([f1, f2], 1)
321+
*(f1::Biquad, fs::Biquad...) = SecondOrderSections([f1, fs...], 1)
322+
*(f1::SecondOrderSections, f2::Biquad) = SecondOrderSections([f1.biquads; f2], f1.g)
323+
*(f1::Biquad, f2::SecondOrderSections) = SecondOrderSections([f1; f2.biquads], f2.g)

test/filter_conversion.jl

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
require(joinpath(dirname(@__FILE__), "FilterTestHelpers.jl"))
2-
using DSP, Base.Test, FilterTestHelpers
2+
using DSP, Base.Test, FilterTestHelpers, Polynomials
33

44
# Test conversion to SOS against MATLAB
55
# Test poles/zeros generated with:
@@ -131,23 +131,72 @@ for proto in (Butterworth(3), Chebyshev1(3, 1), Chebyshev2(3, 1))
131131
end
132132
end
133133

134+
# Test setting filter gain
135+
x = randn(100)
136+
f1 = digitalfilter(Lowpass(0.3), Butterworth(2))
137+
y = filt(f1, x)
138+
for ty in (ZPKFilter, PolynomialRatio, Biquad, SecondOrderSections)
139+
@test_approx_eq filt(3*convert(ty, f1), x) 3*y
140+
@test_approx_eq filt(convert(ty, f1)*3, x) 3*y
141+
end
142+
143+
# Test composing filters
144+
f2 = digitalfilter(Highpass(0.5), Butterworth(1))
145+
y = filt(f2, y)
146+
for ty in (ZPKFilter, PolynomialRatio, Biquad, SecondOrderSections)
147+
@test_approx_eq filt(convert(ty, f1)*convert(ty, f2), x) y
148+
end
149+
150+
f3 = digitalfilter(Bandstop(0.35, 0.4), Butterworth(1))
151+
y = filt(f3, y)
152+
for ty in (ZPKFilter, PolynomialRatio, Biquad, SecondOrderSections)
153+
@test_approx_eq filt(convert(ty, f1)*convert(ty, f2)*convert(ty, f3), x) y
154+
end
155+
@test_approx_eq filt(convert(Biquad, f1)*(convert(Biquad, f2)*convert(Biquad, f3)), x) y
156+
@test_approx_eq filt((convert(Biquad, f1)*convert(Biquad, f2))*convert(Biquad, f3), x) y
157+
134158
# Test some otherwise untested code paths
159+
@test promote_type(ZeroPoleGain{Complex64,Complex128,Float32}, ZeroPoleGain{Complex128,Complex64,Float64}) == ZeroPoleGain{Complex128,Complex128,Float64}
160+
@test convert(ZeroPoleGain{Float64,Complex128,Float64}, f1) === f1
161+
f1f = convert(ZeroPoleGain{Complex64,Complex64,Float32}, f1)
162+
@test f1f.z == convert(Vector{Complex64}, f1.z)
163+
@test f1f.p == convert(Vector{Complex64}, f1.p)
164+
@test f1f.k == convert(Float32, f1.k)
165+
166+
@test_throws ArgumentError PolynomialRatio(Float64[], Float64[])
167+
@test promote_type(PolynomialRatio{Float32}, PolynomialRatio{Int}) == PolynomialRatio{Float32}
168+
f1f = convert(PolynomialRatio{Float32}, f1)
169+
f1p = convert(PolynomialRatio, f1)
170+
@test f1f.b == convert(Poly{Float32}, f1p.b)
171+
@test f1f.a == convert(Poly{Float32}, f1p.a)
172+
135173
b = Biquad(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 2)
136174
@test b.b0 === 0.0
137175
@test b.b1 === 2*1/3
138176
@test b.b2 === 2*2/3
139177
@test b.a1 === 4/3
140178
@test b.a2 === 5/3
179+
@test promote_type(Biquad{Float32}, Biquad{Int}) == Biquad{Float32}
180+
@test convert(Biquad{Float32}, b) == Biquad{Float32}(0.0, 2*1/3, 2*2/3, 4/3, 5/3)
181+
zpkfilter_eq(convert(ZeroPoleGain{Complex128,Complex128,Float64}, b), convert(ZeroPoleGain, b), 0.0)
141182
f = convert(PolynomialRatio, Biquad(2.0, 0.0, 0.0, 0.0, 0.0))
142183
@test coefb(f) == [2.0]
143184
@test coefa(f) == [1.0]
144185
@test convert(Biquad, PolynomialRatio([4.0], [2.0])) == Biquad(2.0, 0.0, 0.0, 0.0, 0.0)
145186
@test Biquad(2.0, 0.0, 0.0, 0.0, 0.0)*2 == Biquad(4.0, 0.0, 0.0, 0.0, 0.0)
146-
147-
@test_throws ArgumentError PolynomialRatio(Float64[], Float64[])
187+
@test convert(Biquad{Float64}, f1) == convert(Biquad, f1)
148188
f = PolynomialRatio(Float64[1.0], Float64[1.0])
149189
empty!(f.b.a)
150190
empty!(f.a.a)
151191
@test_throws ArgumentError convert(Biquad, f)
152192
@test_throws ArgumentError convert(SOSFilter, ZPKFilter([0.5 + 0.5im, 0.5 + 0.5im], [0.5 + 0.5im, 0.5 - 0.5im], 1))
153193
@test_throws ArgumentError convert(SOSFilter, ZPKFilter([0.5 + 0.5im, 0.5 - 0.5im], [0.5 + 0.5im, 0.5 + 0.5im], 1))
194+
195+
@test promote_type(SecondOrderSections{Float64,Float32}, SecondOrderSections{Float32,Float64}) == SecondOrderSections{Float64,Float64}
196+
@test convert(SecondOrderSections{Float32,Float32}, convert(SecondOrderSections, b)).biquads == convert(SecondOrderSections, convert(Biquad{Float32}, b)).biquads
197+
@test convert(Biquad, convert(SecondOrderSections, b)) == b
198+
@test_throws ArgumentError convert(Biquad, convert(SecondOrderSections, f1*f2))
199+
f1p = convert(PolynomialRatio{Float32}, convert(PolynomialRatio, convert(SecondOrderSections, f1)))
200+
f1f = convert(PolynomialRatio{Float32}, convert(SecondOrderSections, f1))
201+
@test f1p.b == f1f.b
202+
@test f1p.a == f1f.a

0 commit comments

Comments
 (0)