1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
|
# filtergen.tcl --
# Package for digital filters
# filterButterworth:
# Generate the coefficients for a low-pass or high-pass Butterworth filter
# filter:
# Filter an entire series of data
# filterObject:
# Class to create filters
#
# Derived from: https://www.meme.net.au/butterworth.html
#
# Here is the license notice from this webpage:
#
# @licstart The following is the entire license notice for the
# JavaScript code in this page.
#
# Copyright (C) 2013 Glenn McIntosh
#
# The JavaScript code in this page is free software: you can
# redistribute it and/or modify it under the terms of the GNU
# General Public License (GNU GPL) as published by the Free Software
# Foundation, either version 3 of the License, or (at your option)
# any later version. The code is distributed WITHOUT ANY WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
#
# As additional permission under GNU GPL version 3 section 7, you
# may distribute non-source (e.g., minimized or compacted) forms of
# that code without the copy of the GNU GPL normally required by
# section 4, provided you include this license notice and a URL
# through which recipients can access the Corresponding Source.
#
# @licend The above is the entire license notice
# for the JavaScript code in this page.
#
package require Tcl 8.6 9
package require TclOO
namespace eval ::math::filters {}
# filterButterworth --
# Generate the coefficients for a low-pass/high-pass Butterworth filter
#
# Arguments:
# lowpass Low-pass if 1, high-pass if 0
# order Order of the filter
# samplefreq Sample frequency
# cutofffreq Cut-off frequency (3 dB point)
#
# Returns:
# List (nexted list) of coefficients for x and y and the scale factor
#
proc ::math::filters::filterButterworth {lowpass order samplefreq cutofffreq} {
##nagelfar ignore
if { ![string is integer $order] || $order <= 0 } {
return -code error "The order must be a positive integer"
}
if { $samplefreq <= 0.0 || $cutofffreq <= 0.0 } {
return -code error "The frequencies must be positive"
}
if { $samplefreq < $cutofffreq } {
return -code error "The cutoff frequency must be lower than the sample frequency"
}
set pi [expr {acos(-1.0)}]
set cutoff [expr {-$cutofffreq / double($samplefreq) * 2.0 * $pi}]
set yf0 [lrepeat [expr {$order+1}] 0.0]
set yf1 $yf0
set xf $yf0
lset yf0 0 -1.0
lset yf1 0 0.0
lset xf 0 1.0
set scale 1.0
set invert [expr {$lowpass == 1? 1.0 : -1.0}]
for {set i 1} {$i <= $order} {incr i} {
set angle [expr {($i-0.5) / $order * $pi}]
set sinsin [expr {1.0 - sin($cutoff) * sin($angle)}]
set rcof0 [expr {-cos($cutoff) / $sinsin}]
set rcof1 [expr { sin($cutoff) * cos($angle) / $sinsin}]
lset yf0 $i 0.0
lset yf1 $i 0.0
for {set j $i} {$j > 0} {incr j -1} {
set yf0jm1 [lindex $yf0 [expr {$j-1}]]
set yf1jm1 [lindex $yf1 [expr {$j-1}]]
set yf0j [lindex $yf0 $j]
set yf1j [lindex $yf1 $j]
lset yf0 $j [expr {$yf0j + $rcof0 * $yf0jm1 + $rcof1 * $yf1jm1}]
lset yf1 $j [expr {$yf1j + $rcof0 * $yf1jm1 - $rcof1 * $yf0jm1}]
}
set scale [expr {$scale * $sinsin * 2.0 / (1.0 - cos($cutoff) * $invert)}]
set xfim1 [lindex $xf [expr {$i-1}]]
lset xf $i [expr {$xfim1 * $invert * ($order-$i+1)/double($i)}]
}
set scale [expr {sqrt($scale)}]
for {set i 1} {$i <= $order} {incr i} {
set yf0i [lindex $yf0 $i]
lset yf0 $i [expr {$yf0i * $scale}]
}
return [list $xf [lrange $yf0 1 end] $scale]
}
# filter --
# Filter the data series based on the given coefficients
#
# Arguments:
# coeff Filter coefficients, as generated by filtergen
# data Data to be filtered
#
# Returns:
# The filtered data
#
# Note:
# The initial part of the filtered data is a list of zeroes
#
proc ::math::filters::filter {coeff data} {
lassign $coeff xcoeff ycoeff scale
set filtered {}
set yv [lrepeat [llength $ycoeff] [expr {0.0}]]
set noxcoeff [llength $xcoeff]
set xcoeff [lreverse $xcoeff]
set ycoeff [lreverse $ycoeff]
for {set i 0} {$i <= [llength $data]-$noxcoeff} {incr i} {
set xv [lrange $data $i [expr {$i+$noxcoeff-1}]]
set f [expr {0.0}]
foreach x $xv c $xcoeff {
set f [expr {$f + $c * $x}]
}
foreach y $yv c $ycoeff {
set f [expr {$f + $c * $y}]
}
set f [expr {$f / $scale}]
lappend filtered $f
set yv [concat [lrange $yv 1 end] $f]
}
return $filtered
}
# filterObject --
# Create an object that can filter incoming data
#
# Arguments:
# coeff Filter coefficients, as generated by filtergen
# yinit (Optional) initial y-values
#
::oo::class create ::math::filters::filterObject {
variable xcoeff
variable ycoeff
variable yv
variable xv
variable xv_org
variable yv_org
#
# Constructor:
# - the arguments coeff and, optionally, yinit
# - prepare everything
#
constructor {coeff {yinit {}}} {
variable xcoeff
variable ycoeff
variable scale
variable yv
variable xv
variable yv_org
variable xv_org
lassign $coeff xcoeff ycoeff scale
set xcoeff [lreverse $xcoeff]
set ycoeff [lreverse $ycoeff]
if { $yinit eq {} } {
set yv [lrepeat [llength $ycoeff] [expr {0.0}]]
} else {
if { [llength $yinit] != [llength $ycoeff] } {
return -code error "Length of initial y-values must be equal to the number of y coefficients"
}
set yv $yinit
}
set xv [lrepeat [llength $xcoeff] [expr {0.0}]]
set xv_org $xv
set yv_org $yv
}
method filter {x} {
variable xcoeff
variable ycoeff
variable scale
variable yv
variable xv
set xv [concat [lrange $xv 1 end] $x]
set f [expr {0.0}]
foreach x $xv c $xcoeff {
set f [expr {$f + $c * $x}]
}
foreach y $yv c $ycoeff {
set f [expr {$f + $c * $y}]
}
set f [expr {$f / $scale}]
set yv [concat [lrange $yv 1 end] $f]
return $f
}
method reset {} {
variable yv
variable xv
variable xv_org
variable yv_org
set xv $xv_org
set yv $yv_org
}
}
# Publish the package
namespace eval ::math::filters {
namespace export filterButterworth filter filterObject
}
package provide math::filters 0.3
|