# analog-filter.rb -- analog-filter.scm --> analog-filter.rb -*- snd-ruby -*- # Translator/Author: Michael Scholz # Created: Tue Aug 01 22:58:31 CEST 2006 # Changed: Wed Nov 17 22:07:58 CET 2010 # Commentary: # # ;;; various even order analog filters, based primarily on Anders Johansson's (GPL'd) code # # module Analog_filter # make_butterworth_lowpass(n, fc) # make_butterworth_highpass(n, fc) # make_butterworth_bandpass(n, fl, fh) # make_butterworth_bandstop(n, fl, fh) # # make_chebyshev_lowpass(n, fc, ripple = 1.0) # make_chebyshev_highpass(n, fc, ripple = 1.0) # make_chebyshev_bandpass(n, fl, fh, ripple = 1.0) # make_chebyshev_bandstop(n, fl, fh, ripple = 1.0) # # make_inverse_chebyshev_lowpass(n, fc, loss_dB = 60.0) # make_inverse_chebyshev_highpass(n, fc, loss_dB = 60.0) # make_inverse_chebyshev_bandpass(n, fl, fh, loss_dB = 60.0) # make_inverse_chebyshev_bandstop(n, fl, fh, loss_dB = 60.0) # # make_bessel_lowpass(n, fc) # make_bessel_highpass(n, fc) # make_bessel_bandpass(n, fl, fh) # make_bessel_bandstop(n, fl, fh) # # make_elliptic_lowpass(n, fc, ripple = 1.0, loss_dB = 60.0) # make_elliptic_highpass(n, fc, ripple = 1.0, loss_dB = 60.0) # make_elliptic_bandpass(n, fl, fh, ripple = 1.0, loss_dB = 60.0) # make_elliptic_bandstop(n, fl, fh, ripple = 1.0, loss_dB = 60.0) # # Code: require "clm" require "dsp" module Analog_filter def analog2digital(n, num, den, fz) g = 1.0 wc = tan(PI * fz) wcc = wc * wc c = Vct.new(2 * n) j = 0 k = 0 0.step(n - 1, 2) do |i| nt0 = num[j + 0] / wcc nt1 = num[j + 1] / wc nt2 = num[j + 2] dt0 = den[j + 0] / wcc dt1 = den[j + 1] / wc dt2 = den[j + 2] kd = dt0 + dt1 + dt2 kn = nt0 + nt1 + nt2 c[k + 0] = (2.0 * dt2 - 2.0 * dt0) / kd c[k + 1] = (dt0 + (-dt1) + dt2) / kd c[k + 2] = (2.0 * nt2 - 2.0 * nt0) / kn c[k + 3] = (nt0 + (-nt1) + nt2) / kn g *= (kn / kd) j += 3 k += 4 end a = [] b = [] k = 0 0.step(n - 1, 2) do |i| a.unshift(vct(c[k + 3], c[k + 2], c[k + 3])) b.unshift(vct(1.0, c[k], c[k + 1])) k += 4 end [cascade2canonical(a).scale!(g), cascade2canonical(b)] end def prototype2highpass(n, num, den) g = 1.0 numt = Vct.new(num.length) dent = Vct.new(den.length) i = 0 0.step(n - 1, 2) do |k| g *= (num[i + 2] / den[i + 2]) numt[i + 0] = 1.0 numt[i + 1] = num[i + 1] / num[i + 2] numt[i + 2] = num[i + 0] / num[i + 2] dent[i + 0] = 1.0 dent[i + 1] = den[i + 1] / den[i + 2] dent[i + 2] = den[i + 0] / den[i + 2] i += 3 end numt[0] = g [numt, dent] end # # === BUTTERWORTH === # def butterworth_prototype(n) len = (n * 3) / 2 num = Vct.new(len) den = Vct.new(len) n2 = 2.0 * n j = 0 1.step(n - 1, 2) do |w| num[j + 0] = 0.0 num[j + 1] = 0.0 num[j + 2] = 1.0 den[j + 0] = 1.0 den[j + 1] = 2.0 * cos((w * PI) / n2) den[j + 2] = 1.0 j += 3 end [num, den] end # n = order, fc = cutoff freq (srate = 1.0) add_help(:make_butterworth_lowpass, "make_butterworth_lowpass(n, fc): \ returns a lowpass Buttterworth filter; N = order, \ FC = cutoff freq (srate = 1.0): make_butterworth_lowpass(8, 0.1)") def make_butterworth_lowpass(n, fc) if n.odd? then n += 1 end proto = butterworth_prototype(n) coeffs = analog2digital(n, proto[0], proto[1], fc) make_filter(:xcoeffs, coeffs[0], :ycoeffs, coeffs[1]) end add_help(:make_butterworth_highpass, "make_butterworth_highpass(n, fc): \ returns a highpass Buttterworth filter; N = order, \ FC = cutoff freq (srate = 1.0): make_butterworth_highpass(8, 0.1)") def make_butterworth_highpass(n, fc) if n.odd? then n += 1 end proto = butterworth_prototype(n) hproto = prototype2highpass(n, proto[0], proto[1]) coeffs = analog2digital(n, hproto[0], hproto[1], fc) make_filter(:xcoeffs, coeffs[0], :ycoeffs, coeffs[1]) end add_help(:make_butterworth_bandpass, "make_butterworth_bandpass(n, fl, fh): \ returns a bandpass Buttterworth filter; N = order, \ FL and FH are (1.0-based) edge freqs: make_butterworth_bandpass(4, 0.1, 0.2)") def make_butterworth_bandpass(n, fl, fh) lp = make_butterworth_lowpass(n, fh) hp = make_butterworth_highpass(n, fl) lambda do |y| filter(lp, filter(hp, y)) end end add_help(:make_butterworth_bandstop, "make_butterworth_bandstop(n, fl, fh): \ returns a bandstop Buttterworth filter; N = order, \ FL and FH are (1.0-based) edge freqs: make_butterworth_bandstop(4, 0.1, 0.2)") def make_butterworth_bandstop(n, fl, fh) lp = make_butterworth_lowpass(n, fl) hp = make_butterworth_highpass(n, fh) lambda do |y| filter(lp, y) + filter(hp, y) end end # # === CHEBYSHEV === # # ripple in dB (positive) def chebyshev_prototype(n, ripple = 1.0) e = sqrt((10.0 ** (0.1 * ripple)) - 1.0) v0 = asinh(1.0 / e) / n.to_f len = (n * 3) / 2 n2 = 2.0 * n sinhv0 = sinh(v0) coshv0 = cosh(v0) num = Vct.new(len) den = Vct.new(len) j = 0 1.step(n - 1, 2) do |l| lpi = l * PI u = -(sinhv0 * sin(lpi / n2)) w = coshv0 * cos(lpi / n2) num[j + 0] = 0.0 num[j + 1] = 0.0 num[j + 2] = 1.0 den[j + 0] = 1.0 den[j + 1] = -2.0 * u den[j + 2] = u * u + w * w j += 3 end num[2] = (2.0 ** (2 - n)) / (3.2 ** (log(ripple) / log(10.0))) [num, den] end # n = order, fc = cutoff freq (srate = 1.0) add_help(:make_chebyshev_lowpass, "make_chebyshev_lowpass(n, fc, ripple=1.0): \ returns a lowpass Chebyshev filter; N = order, \ FC = cutoff freq (srate = 1.0): make_chebyshev_lowpass(8, 0.1)") def make_chebyshev_lowpass(n, fc, ripple = 1.0) if n.odd? then n += 1 end proto = chebyshev_prototype(n, ripple) coeffs = analog2digital(n, proto[0], proto[1], fc) make_filter(:xcoeffs, coeffs[0], :ycoeffs, coeffs[1]) end add_help(:make_chebyshev_highpass, "make_chebyshev_highpass(n, fc, ripple=1.0): \ returns a highpass Chebyshev filter; N = order, \ FC = cutoff freq (srate = 1.0): make_chebyshev_highpass(8, 0.1, 0.01)") def make_chebyshev_highpass(n, fc, ripple = 1.0) if n.odd? then n += 1 end proto = chebyshev_prototype(n, ripple) hproto = prototype2highpass(n, proto[0], proto[1]) coeffs = analog2digital(n, hproto[0], hproto[1], fc) make_filter(:xcoeffs, coeffs[0], :ycoeffs, coeffs[1]) end add_help(:make_chebyshev_bandpass, "make_chebyshev_bandpass(n, fl, fh, ripple=1.0): \ returns a bandpass Chebyshev filter; N = order, \ FL and FH = edge freq (srate = 1.0): make_chebyshev_highpass(8, 0.1, 0.01)") def make_chebyshev_bandpass(n, fl, fh, ripple = 1.0) lp = make_chebyshev_lowpass(n, fh, ripple) hp = make_chebyshev_highpass(n, fl, ripple) lambda do |y| filter(lp, filter(hp, y)) end end add_help(:make_chebyshev_bandstop, "make_chebyshev_bandstop(n, fl, fh, ripple=1.0): \ returns a bandstop Chebyshev filter; N = order, \ FL and FH = edge freqs (srate = 1.0): make_chebyshev_bandstop(8, 0.1, 0.4, 0.01)") def make_chebyshev_bandstop(n, fl, fh, ripple = 1.0) lp = make_chebyshev_lowpass(n, fl, ripple) hp = make_chebyshev_highpass(n, fh, ripple) lambda do |y| filter(lp, y) + filter(hp, y) end end # # === INVERSE CHEBYSHEV === # def inverse_chebyshev_prototype(n, loss_dB = 60.0) e = sqrt(1.0 / (10.0 ** (0.1 * loss_dB) - 1.0)) v0 = asinh(1.0 / e) / n.to_f len = (n * 3) / 2 n2 = 2.0 * n num = Vct.new(len) den = Vct.new(len) pl = 0.0 j = 0 1.0.step(n - 1, 2.0) do |l| lpi = l * PI u = -(sinh(v0) * sin(lpi / n2)) w = cosh(v0) * cos(lpi / n2) t = 1.0 / sin(((l + pl) * PI) / n2) num[j + 0] = 1.0 num[j + 1] = 0.0 num[j + 2] = t * t den[j + 0] = 1.0 den[j + 1] = (-2.0 * u) / (u * u + w * w) den[j + 2] = 1.0 / (u * u + w * w) j += 3 end [num, den, 1.122 ** -loss_dB] end # n = order, fc = cutoff freq (srate = 1.0) add_help(:make_inverse_chebyshev_lowpass, "make_inverse_chebyshev_lowpass(n, fc, loss_dB=60.0): \ returns a lowpass inverse-Chebyshev filter; N = order, \ FC = cutoff freq (srate = 1.0): make_inverse_chebyshev_lowpass(10, 0.4, 120)") def make_inverse_chebyshev_lowpass(n, fc, loss_dB = 60.0) if n.odd? then n += 1 end proto = inverse_chebyshev_prototype(n, loss_dB) coeffs = analog2digital(n, proto[0], proto[1], fc) make_filter(:xcoeffs, coeffs[0].scale!(proto[2]), :ycoeffs, coeffs[1]) end add_help(:make_inverse_chebyshev_highpass, "make_inverse_chebyshev_highpass(n, fc, loss_dB=60.0): \ returns a highpass inverse-Chebyshev filter; N = order, \ FC = cutoff freq (srate = 1.0): make_inverse_chebyshev_highpass(10, 0.1, 120)") def make_inverse_chebyshev_highpass(n, fc, loss_dB = 60.0) if n.odd? then n += 1 end proto = inverse_chebyshev_prototype(n, loss_dB) hproto = prototype2highpass(n, proto[0], proto[1]) coeffs = analog2digital(n, hproto[0], hproto[1], fc) make_filter(:xcoeffs, coeffs[0].scale!(proto[2]), :ycoeffs, coeffs[1]) end add_help(:make_inverse_chebyshev_bandpass, "make_inverse_chebyshev_bandpass(n, fl, fh, loss_dB=60.0): \ returns a bandpass inverse-Chebyshev filter; N = order, \ FL and FH are edge freqs (srate = 1.0): make_inverse_chebyshev_bandpass(8, 0.1, 0.4)") def make_inverse_chebyshev_bandpass(n, fl, fh, loss_dB = 60.0) lp = make_inverse_chebyshev_lowpass(n, fh, loss_dB) hp = make_inverse_chebyshev_highpass(n, fl, loss_dB) lambda do |y| filter(lp, filter(hp, y)) end end add_help(:make_inverse_chebyshev_bandstop, "make_inverse_chebyshev_bandstop(n, fl, fh, loss_dB=60.0): \ returns a bandstop inverse-Chebyshev filter; N = order, \ FL and FH are edge freqs (srate = 1.0): make_inverse_chebyshev_bandstop(8, 0.1, 0.4, 90)") def make_inverse_chebyshev_bandstop(n, fl, fh, loss_dB = 60.0) lp = make_inverse_chebyshev_lowpass(n, fl, loss_dB) hp = make_inverse_chebyshev_highpass(n, fh, loss_dB) lambda do |y| filter(lp, y) + filter(hp, y) end end if provided? :gsl # requires with-gsl if defined? gsl_roots # gsl_roots isn't defined for ruby in snd-xen.c # # === BESSEL(-Thompson) === # def fact(n) x = 1 2.upto(n) do |i| x *= i end x end def bessel_i(n) Vct.new(n + 1) do |i| fact(2 * n - i) / ((2 ** (n - i)) * fact(i) * fact(n - i)) end end def bessel_prototype(n) len = (n * 3) / 2 num = Vct.new(len) den = Vct.new(len) b2 = bessel_i(n) p = gsl_roots(b2.to_a) p.map! do |x| x / (b2[0] ** (1.0 / n)) end j = 0 0.step(n - 1, 2) do |i| num[j + 0] = 0.0 num[j + 0] = 0.0 num[j + 2] = 1.0 den[j + 0] = 1.0 den[j + 0] = -2.0 * p[i].real den[j + 2] = (p[i] * p[i + 1]).real j += 3 end [num, den] end add_help(:make_bessel_lowpass, "make_bessel_lowpass(n, fc): \ returns a lowpass Bessel filter; N = order, \ FC = cutoff freq (srate = 1.0): make_bessel_lowpass(4, 0.1)") def make_bessel_lowpass(n, fc) if n.odd? then n += 1 end proto = bessel_prototype(n) coeffs = analog2digital(n, proto[0], proto[1], fc) make_filter(:xcoeffs, coeffs[0], :ycoeffs, coeffs[1]) end add_help(:make_bessel_highpass, "make_bessel_highpass(n, fc): \ returns a highpass Bessel filter; N = order, \ FC = cutoff freq (srate = 1.0): make_bessel_highpass(8, 0.1)") def make_bessel_highpass(n, fc) if n.odd? then n += 1 end proto = bessel_prototype(n) hproto = prototype2highpass(n, proto[0], proto[1]) coeffs = analog2digital(n, hproto[0], hproto[1], fc) make_filter(:xcoeffs, coeffs[0], :ycoeffs, coeffs[1]) end add_help(:make_bessel_bandpass, "make_bessel_bandpass(n, fl, fh): \ returns a bandpass Bessel filter; N = order, \ FL and FH are edge freqs (srate = 1.0): make_bessel_bandpass(4, 0.1, 0.2)") def make_bessel_bandpass(n, fl, fh) lp = make_bessel_lowpass(n, fh) hp = make_bessel_highpass(n, fl) lambda do |y| filter(lp, filter(hp, y)) end end add_help(:make_bessel_bandstop, "make_bessel_bandstop(n, fl, fh): \ returns a bandstop Bessel filter; N = order, \ FL and FH are edge freqs (srate = 1.0): make_bessel_bandstop(8, 0.1, 0.2)") def make_bessel_bandstop(n, fl, fh, ripple = 1.0, loss_dB = 60.0) lp = make_bessel_lowpass(n, fl) hp = make_bessel_highpass(n, fh) lambda do |y| filter(lp, y) + filter(hp, y) end end end # # === ELLIPTIC === # def minimize_function(f, xmin, xmax, arg1 = nil, arg2 = nil) fx = snd_func(f, xmin, arg1, arg2) n = 20 x = Vct.new(n) n.times do |i| step = (xmax - xmin) / (n - 1.0) s = xmin (n - 1).times do |j| x[j] = s s += step end x[n - 1] = xmax n.times do |j| ft = snd_func(f, x[j], arg1, arg2) if ft < fx fx = ft xmax = (j < (n - 1)) ? x[j + 1] : x[n - 1] xmin = j > 0 ? x[j - 1] : x[0] end end end (xmax + xmin) / 2.0 end def findm(m, arg1, arg2) (gsl_ellipk(m) / gsl_ellipk(1.0 - m) - arg1).abs end def findv(u, arg1, arg2) vals = gsl_ellipj(u, arg1) (arg2 - vals[0] / vals[1]).abs end def elliptic_prototype(n, ripple = 1.0, loss_dB = 60.0) e = sqrt((10.0 ** (0.1 * ripple)) - 1.0) k1 = e / sqrt((10.0 ** (0.1 * loss_dB)) - 1.0) k1p = sqrt(1.0 - k1 * k1) kr = m = k = 0.0 len = (n * 3) / 2 num = Vct.new(len) den = Vct.new(len) g = 1.0 eps = 0.0000001 if (1.0 - k1p * k1p).abs > eps kr = n.to_f * (gsl_ellipk(k1 * k1) / gsl_ellipk(k1p * k1p)) end m = minimize_function(:findm, 0.001, 0.999, kr) k = gsl_ellipk(m) cv = Vct.new((0.5 * 3 * (n + 1)).floor) j = 0 0.step(n - 1, 2) do |i| vals = gsl_ellipj(((i + 1) * k) / n.to_f, m) sn, cn, dn = vals[0..2] cv[j + 0] = sn cv[j + 1] = cn cv[j + 2] = dn z = Complex(0.0, -1.0) / (sqrt(m) * sn) pz = (z * make_rectangular(z.real, -z.imag)).real g /= pz num[j + 0] = 1.0 num[j + 1] = -2.0 * z.real num[j + 2] = pz j += 3 end optarg0 = k1p * k1p optarg1 = 1.0 / e minf = minimize_function(:findv, 0.0, 1.0 / e, optarg0, optarg1) v0 = (k * minf) / (n.to_f * gsl_ellipk(k * k1)) vals = gsl_ellipj(v0, 1.0 - m) sn, cn, dn = vals[0..2] j = 0 0.step(n - 1, 2) do |i| p = -(cv[j + 1] * cv[j + 2] * sn * cn + (Complex(0.0, 1.0) * cv[j + 0] * dn)) / (1.0 - (cv[j + 2] * sn * cv[j + 2] * sn)) pp = (p * make_rectangular(p.real, -p.imag)).real g *= pp den[j + 0] = 1.0 den[j + 1] = -2.0 * p.real den[j + 2] = pp j += 3 end g = (g / sqrt(1.0 + e * e)).abs [num, den, g] end # n = order, fc = cutoff freq (srate = 1.0) add_help(:make_elliptic_lowpass, "make_elliptic_lowpass(n, fc, ripple=1.0, loss_dB=60.0): \ returns a lowpass elliptic filter; N = order, \ FC = cutoff freq (srate = 1.0): make_elliptic_lowpass(8, 0.25, 0.01, 90)") def make_elliptic_lowpass(n, fc, ripple = 1.0, loss_dB = 60.0) if n.odd? then n += 1 end proto = elliptic_prototype(n, ripple, loss_dB) coeffs = analog2digital(n, proto[0], proto[1], fc) make_filter(:xcoeffs, coeffs[0].scale!(proto[2]), :ycoeffs, coeffs[1]) end add_help(:make_elliptic_highpass, "make_elliptic_highpass(n, fc, ripple=1.0, loss_dB=60.0): \ returns a highpass elliptic filter; N = order, \ FC = cutoff freq (srate = 1.0): make_elliptic_highpass(8, 0.25, 0.01, 90)") def make_elliptic_highpass(n, fc, ripple = 1.0, loss_dB = 60.0) if n.odd? then n += 1 end proto = elliptic_prototype(n, ripple, loss_dB) hproto = prototype2highpass(n, proto[0], proto[1]) coeffs = analog2digital(n, hproto[0], hproto[1], fc) make_filter(:xcoeffs, coeffs[0].scale!(proto[2]), :ycoeffs, coeffs[1]) end add_help(:make_elliptic_bandpass, "make_elliptic_bandpass(n, fl, fh, ripple=1.0, loss_dB=60.0): \ returns a bandpass elliptic filter; N = order, \ FL and FH are edge freqs (srate = 1.0): make_elliptic_bandpass(6, 0.1, 0.2, 0.1, 90)") def make_elliptic_bandpass(n, fl, fh, ripple = 1.0, loss_dB = 60.0) lp = make_elliptic_lowpass(n, fh, ripple, loss_dB) hp = make_elliptic_highpass(n, fl, ripple, loss_dB) lambda do |y| filter(lp, filter(hp, y)) end end add_help(:make_elliptic_bandstop, "make_elliptic_bandstop(n, fl, fh, ripple=1.0, loss_dB=60.0): \ returns a bandstop elliptic filter; N = order, \ FL and FH are edge freqs (srate = 1.0): make_elliptic_bandstop(6, 0.1, 0.2, 0.1, 90)") def make_elliptic_bandstop(n, fl, fh, ripple = 1.0, loss_dB = 60.0) lp = make_elliptic_lowpass(n, fl, ripple, loss_dB) hp = make_elliptic_highpass(n, fh, ripple, loss_dB) lambda do |y| filter(lp, y) + filter(hp, y) end end end end include Analog_filter # analog-filter.rb ends here