Skip to content

Commit 3ed388e

Browse files
zsoerenmwheeheeemartinholters
authored
Add complex valued bandpass filter (#468)
Co-authored-by: wheeheee <104880306+wheeheee@users.noreply.github.com> Co-authored-by: Martin Holters <martin.holters@hsu-hh.de>
1 parent 7652e2d commit 3ed388e

File tree

4 files changed

+104
-26
lines changed

4 files changed

+104
-26
lines changed

docs/src/filters.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ bilinear
119119
Lowpass
120120
Highpass
121121
Bandpass
122+
ComplexBandpass
122123
Bandstop
123124
```
124125

src/Filters/Filters.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export FilterType,
3737
Lowpass,
3838
Highpass,
3939
Bandpass,
40+
ComplexBandpass,
4041
Bandstop,
4142
analogfilter,
4243
digitalfilter,

src/Filters/design.jl

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -234,61 +234,84 @@ Elliptic(n::Integer, rp::Real, rs::Real) = Elliptic(Float64, n, rp, rs)
234234
# returns frequency in half-cycles per sample ∈ (0, 1)
235235
function normalize_freq(w::Real, fs::Real)
236236
w <= 0 && throw(DomainError(w, "frequencies must be positive"))
237-
f = 2*w/fs
237+
f = 2 * w / fs
238238
f >= 1 && throw(DomainError(f, "frequencies must be less than the Nyquist frequency $(fs/2)"))
239239
f
240240
end
241-
242-
struct Lowpass{T} <: FilterType
243-
w::T
241+
function normalize_complex_freq(w::Real, fs::Real)
242+
f = 2 * w / fs
243+
f >= 2 && throw(DomainError(f, "frequencies must be less than the sampling frequency $(fs)"))
244+
f
244245
end
245246

246247
"""
247248
Lowpass(Wn::Real)
248249
249250
Low pass filter with cutoff frequency `Wn`.
250251
"""
251-
Lowpass(w::Real) = Lowpass{typeof(w/1)}(w)
252-
253-
struct Highpass{T} <: FilterType
252+
struct Lowpass{T<:Real} <: FilterType
254253
w::T
254+
Lowpass{T}(w::Real) where {T<:Real} = new{T}(w)
255+
Lowpass(w::Real) = Lowpass{typeof(w / 1)}(w)
255256
end
256257

257258
"""
258259
Highpass(Wn::Real)
259260
260261
High pass filter with cutoff frequency `Wn`.
261262
"""
262-
Highpass(w::Real) = Highpass{typeof(w/1)}(w)
263-
264-
struct Bandpass{T} <: FilterType
265-
w1::T
266-
w2::T
263+
struct Highpass{T<:Real} <: FilterType
264+
w::T
265+
Highpass{T}(w::Real) where {T<:Real} = new{T}(w)
266+
Highpass(w::Real) = Highpass{typeof(w / 1)}(w)
267267
end
268268

269269
"""
270270
Bandpass(Wn1::Real, Wn2::Real)
271271
272272
Band pass filter with pass band frequencies (`Wn1`, `Wn2`).
273273
"""
274-
function Bandpass(w1::Real, w2::Real)
275-
w1 < w2 || throw(ArgumentError("w1 must be less than w2"))
276-
Bandpass{Base.promote_typeof(w1/1, w2/1)}(w1, w2)
274+
struct Bandpass{T<:Real} <: FilterType
275+
w1::T
276+
w2::T
277+
function Bandpass{T}(w1::Real, w2::Real) where {T<:Real}
278+
w1 < w2 || throw(ArgumentError("w1 must be less than w2"))
279+
new{T}(w1, w2)
280+
end
281+
Bandpass(w1::T, w2::V) where {T<:Real,V<:Real} =
282+
Bandpass{typeof(one(promote_type(T, V)) / 1)}(w1, w2)
277283
end
278284

279-
struct Bandstop{T} <: FilterType
285+
"""
286+
ComplexBandpass(Wn1, Wn2)
287+
288+
Complex band pass filter with pass band frequencies (`Wn1`, `Wn2`).
289+
"""
290+
struct ComplexBandpass{T<:Real} <: FilterType
280291
w1::T
281292
w2::T
293+
function ComplexBandpass{T}(w1::Real, w2::Real) where {T<:Real}
294+
w1 < w2 || throw(ArgumentError("w1 must be less than w2"))
295+
new{T}(w1, w2)
296+
end
297+
ComplexBandpass(w1::T, w2::V) where {T,V} =
298+
ComplexBandpass{typeof(one(promote_type(T, V)) / 1)}(w1, w2)
282299
end
283300

284301
"""
285302
Bandstop(Wn1::Real, Wn2::Real)
286303
287304
Band stop filter with stop band frequencies (`Wn1`, `Wn2`).
288305
"""
289-
function Bandstop(w1::Real, w2::Real)
290-
w1 < w2 || throw(ArgumentError("w1 must be less than w2"))
291-
Bandstop{Base.promote_typeof(w1/1, w2/1)}(w1, w2)
306+
struct Bandstop{T<:Real} <: FilterType
307+
w1::T
308+
w2::T
309+
function Bandstop{T}(w1::Real, w2::Real) where {T<:Real}
310+
w1 < w2 || throw(ArgumentError("w1 must be less than w2"))
311+
new{T}(w1, w2)
312+
end
313+
Bandstop(w1::T, w2::V) where {T,V} =
314+
Bandstop{typeof(one(promote_type(T, V)) / 1)}(w1, w2)
292315
end
293316

294317
#
@@ -472,8 +495,10 @@ function bilinear(f::ZeroPoleGain{:s,Z,P,K}, fs::Real) where {Z,P,K}
472495
end
473496

474497
# Pre-warp filter frequencies for digital filtering
475-
prewarp(ftype::Union{Lowpass, Highpass}, fs::Real) = (typeof(ftype))(prewarp(normalize_freq(ftype.w, fs)))
476-
prewarp(ftype::Union{Bandpass, Bandstop}, fs::Real) = (typeof(ftype))(prewarp(normalize_freq(ftype.w1, fs)), prewarp(normalize_freq(ftype.w2, fs)))
498+
prewarp(ftype::Lowpass, fs::Real) = Lowpass(prewarp(normalize_freq(ftype.w, fs)))
499+
prewarp(ftype::Highpass, fs::Real) = Highpass(prewarp(normalize_freq(ftype.w, fs)))
500+
prewarp(ftype::Bandpass, fs::Real) = Bandpass(prewarp(normalize_freq(ftype.w1, fs)), prewarp(normalize_freq(ftype.w2, fs)))
501+
prewarp(ftype::Bandstop, fs::Real) = Bandstop(prewarp(normalize_freq(ftype.w1, fs)), prewarp(normalize_freq(ftype.w2, fs)))
477502
# freq in half-samples per cycle
478503
prewarp(f::Real) = 4*tan(pi*f/2)
479504

@@ -569,19 +594,31 @@ FIRWindow(; transitionwidth::Real=throw(ArgumentError("must specify transitionwi
569594
FIRWindow(kaiser(kaiserord(transitionwidth, attenuation)...), scale)
570595

571596
# Compute coefficients for FIR prototype with specified order
572-
function firprototype(n::Integer, ftype::Lowpass, fs::Real)
597+
function _firprototype(n::Integer, ftype::Lowpass, fs::Real, ::Type{T}) where {T<:Number}
573598
w = normalize_freq(ftype.w, fs)
574599

575-
[w*sinc(w*(k-(n+1)/2)) for k = 1:n]
600+
promote_type(typeof(w), T)[w*sinc(w*(k-(n+1)/2)) for k = 1:n]
576601
end
577602

603+
firprototype(n::Integer, ftype::Lowpass, fs::Real) =
604+
_firprototype(n, ftype, fs, typeof(fs))
605+
578606
function firprototype(n::Integer, ftype::Bandpass, fs::Real)
579607
w1 = normalize_freq(ftype.w1, fs)
580608
w2 = normalize_freq(ftype.w2, fs)
581609

582610
[w2*sinc(w2*(k-(n+1)/2)) - w1*sinc(w1*(k-(n+1)/2)) for k = 1:n]
583611
end
584612

613+
function firprototype(n::Integer, ftype::ComplexBandpass, fs::T) where {T<:Real}
614+
w1 = normalize_complex_freq(ftype.w1, fs)
615+
w2 = normalize_complex_freq(ftype.w2, fs)
616+
w_center = (w2 + w1) / 2
617+
w_cutoff = (w2 - w1) / 2
618+
lp = Lowpass(w_cutoff)
619+
_firprototype(n, lp, 2, Complex{T}) .*= cispi.(w_center * (0:(n-1)))
620+
end
621+
585622
function firprototype(n::Integer, ftype::Highpass, fs::Real)
586623
w = normalize_freq(ftype.w, fs)
587624
isodd(n) || throw(ArgumentError("FIRWindow highpass filters must have an odd number of coefficients"))
@@ -602,14 +639,14 @@ function firprototype(n::Integer, ftype::Bandstop, fs::Real)
602639
end
603640

604641
scalefactor(coefs::Vector, ::Union{Lowpass, Bandstop}, fs::Real) = sum(coefs)
605-
function scalefactor(coefs::Vector{T}, ::Highpass, fs::Real) where T
642+
function scalefactor(coefs::Vector{T}, ::Highpass, fs::Real) where {T<:Number}
606643
c = zero(T)
607644
for k = 1:length(coefs)
608645
c += ifelse(isodd(k), coefs[k], -coefs[k])
609646
end
610647
c
611648
end
612-
function scalefactor(coefs::Vector{T}, ftype::Bandpass, fs::Real) where T
649+
function scalefactor(coefs::Vector{T}, ftype::Bandpass, fs::Real) where {T<:Number}
613650
n = length(coefs)
614651
freq = normalize_freq(middle(ftype.w1, ftype.w2), fs)
615652
c = zero(T)
@@ -618,11 +655,20 @@ function scalefactor(coefs::Vector{T}, ftype::Bandpass, fs::Real) where T
618655
end
619656
c
620657
end
658+
function scalefactor(coefs::Vector{T}, ftype::ComplexBandpass, fs::Real) where T<:Number
659+
n = length(coefs)
660+
freq = normalize_complex_freq(middle(ftype.w1, ftype.w2), fs)
661+
c = zero(T)
662+
for k = 1:n
663+
c = muladd(coefs[k], cispi(-freq * (k - (n + 1) / 2)), c)
664+
end
665+
return abs(c)
666+
end
621667

622668
function digitalfilter(ftype::FilterType, proto::FIRWindow; fs::Real=2)
623669
coefs = firprototype(length(proto.window), ftype, fs)
624670
@assert length(proto.window) == length(coefs)
625-
out = coefs .* proto.window
671+
out = (coefs .*= proto.window)
626672
proto.scale ? rmul!(out, 1/scalefactor(out, ftype, fs)) : out
627673
end
628674

test/filter_design.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,36 @@ end
10121012
winfirtaps_scipy = readdlm(joinpath(dirname(@__FILE__), "data", "digitalfilter_hamming_129_bandpass_fc0.1_0.2_fs1.0.txt"),'\t')
10131013
@test winfirtaps_jl winfirtaps_scipy
10141014

1015+
@test_throws ArgumentError ComplexBandpass(2, 1)
1016+
1017+
winfirtaps_jl = digitalfilter(ComplexBandpass(0.1, 0.2), FIRWindow(hamming(128)))
1018+
winfirfreq_db = 20log10.(abs.(fft([winfirtaps_jl; zeros(400)])))
1019+
f = range(0; step=2/length(winfirfreq_db), length=length(winfirfreq_db))
1020+
# above -6dB in the whole passband
1021+
@test minimum(winfirfreq_db[0.1 .< f .< 0.2]) > -6.02
1022+
# close to 0dB in the passband a bit away from the edges
1023+
@test all(-0.05 .< winfirfreq_db[0.125 .< f .< 0.175] .< 0.05)
1024+
# exactly unity in the middle of the passband
1025+
@test abs(sum(winfirtaps_jl .* cispi.(-0.15 * axes(winfirtaps_jl,1)))) 1
1026+
# below -6dB outside passband
1027+
@test maximum(winfirfreq_db[.!(0.1 .< f .< 0.2)]) < -6.02
1028+
# reasonable attenuation outside passband a bit away from the edges
1029+
@test maximum(winfirfreq_db[.!(0.07 .< f .< 0.23)]) < -50
1030+
1031+
winfirtaps_jl = digitalfilter(ComplexBandpass(30, 40), FIRWindow(hamming(511)); fs=100)
1032+
winfirfreq_db = 20log10.(abs.(fft([winfirtaps_jl; zeros(400)])))
1033+
f = range(0; step=100/length(winfirfreq_db), length=length(winfirfreq_db))
1034+
# above -6dB in the whole passband
1035+
@test minimum(winfirfreq_db[30 .< f .< 40]) > -6.02
1036+
# close to 0dB in the passband a bit away from the edges
1037+
@test all(-0.01 .< winfirfreq_db[31 .< f .< 39] .< 0.01)
1038+
# exactly unity in the middle of the passband
1039+
@test abs(sum(winfirtaps_jl .* cispi.(-2*35/100 * axes(winfirtaps_jl,1)))) 1
1040+
# below -6dB outside passband
1041+
@test maximum(winfirfreq_db[.!(30 .< f .< 40)]) < -6.02
1042+
# reasonable attenuation outside passband a bit away from the edges
1043+
@test maximum(winfirfreq_db[.!(28 .< f .< 42)]) < -60
1044+
10151045
@test_throws ArgumentError digitalfilter(Bandstop(0.1, 0.2),FIRWindow(hamming(128), scale=false); fs=1)
10161046

10171047
winfirtaps_jl = digitalfilter(Bandstop(0.1, 0.2),FIRWindow(hamming(129), scale=false); fs=1)

0 commit comments

Comments
 (0)