| 1 | purpose: IIR digital upsampling filter |
|---|
| 2 | |
|---|
| 3 | \ This file implements x2 and x3 upsampling filters using techniques |
|---|
| 4 | \ described in chapter 10 of _Multirate Signal Processing for Communications |
|---|
| 5 | \ Systems_ by fredric j harris, Prentice Hall 2004, ISBN 0-13-146511-2 |
|---|
| 6 | \ The filters may be cascaded for x6 upsampling (e.g. for 8kHz -> 48kHz) |
|---|
| 7 | |
|---|
| 8 | d# 15 constant mulscale \ Bits for fractional multipliers |
|---|
| 9 | |
|---|
| 10 | 0 [if] |
|---|
| 11 | |
|---|
| 12 | \ iir-cascade-z2 implements a folded cascade of multiple sections |
|---|
| 13 | \ of a second-order IIR filter block in a polynomial in Z^2. |
|---|
| 14 | \ This is the basic building block for half-band all-pass |
|---|
| 15 | \ recursive filters, as described in section 10.2 of the book |
|---|
| 16 | \ cited above. |
|---|
| 17 | \ This code is commented out because we don't use it directly, |
|---|
| 18 | \ instead using the -z1 variant below in a polyphase structure. |
|---|
| 19 | |
|---|
| 20 | code iir-cascade-z2 ( sample weights z #sections -- out ) |
|---|
| 21 | cx pop \ CX: Number of sections |
|---|
| 22 | |
|---|
| 23 | bp bx mov \ Save |
|---|
| 24 | bp pop \ BP: Z list |
|---|
| 25 | si dx mov \ Save SI |
|---|
| 26 | si pop \ SI: Filter weights |
|---|
| 27 | ax pop \ AX: current value |
|---|
| 28 | dx push \ Saved SI on stack |
|---|
| 29 | bx push \ Saved BP on stack |
|---|
| 30 | begin |
|---|
| 31 | ax bx mov \ Save last value |
|---|
| 32 | d# 12 [bp] ax sub \ last - Z[i+1] |
|---|
| 33 | 0 [si] imul \ alpha[i] * (last - Z[i+1]) (kills DX) |
|---|
| 34 | 4 [si] si lea \ Increment alpha pointer |
|---|
| 35 | mulscale # dx ax shrd \ Scale down by multiplier scale factor |
|---|
| 36 | |
|---|
| 37 | d# 4 [bp] ax add \ Z[i] + alpha[i] * (last - Z[i+1]) |
|---|
| 38 | |
|---|
| 39 | 0 [bp] dx mov |
|---|
| 40 | dx 4 [bp] mov |
|---|
| 41 | bx 0 [bp] mov \ Update Z[i] |
|---|
| 42 | |
|---|
| 43 | 8 [bp] bp lea \ Point to next Z[i] |
|---|
| 44 | loopa |
|---|
| 45 | |
|---|
| 46 | 0 [bp] dx mov |
|---|
| 47 | dx 4 [bp] mov |
|---|
| 48 | ax 0 [bp] mov \ Update Z[i] |
|---|
| 49 | |
|---|
| 50 | bp pop \ Restore BP |
|---|
| 51 | si pop \ Restore SI |
|---|
| 52 | ax push \ Return value |
|---|
| 53 | c; |
|---|
| 54 | [then] |
|---|
| 55 | |
|---|
| 56 | \ iir-cascade-z1 is the same basic structure as iir-cascade-z2, |
|---|
| 57 | \ but the Z^-2 delays have been replaced by Z^-1. It is used |
|---|
| 58 | \ in the polyphase upsampler "up2". When the rate is doubled, |
|---|
| 59 | \ a zero sample is inserted between each pair of input samples, |
|---|
| 60 | \ causing every other computation of the Z^-2 polynomial to be |
|---|
| 61 | \ unnecessary. The basic block can thus be restructured to have |
|---|
| 62 | \ a single delay element, and the block only has to run at half |
|---|
| 63 | \ the rate. See section 10.6 (figure 10.48) of the book. |
|---|
| 64 | |
|---|
| 65 | code iir-cascade-z1 ( sample weights z #sections -- out ) |
|---|
| 66 | cx pop \ CX: Number of sections |
|---|
| 67 | |
|---|
| 68 | bp bx mov \ Save |
|---|
| 69 | bp pop \ BP: Z list |
|---|
| 70 | si dx mov \ Save SI |
|---|
| 71 | si pop \ SI: Filter weights |
|---|
| 72 | ax pop \ AX: current value |
|---|
| 73 | dx push \ Saved SI on stack |
|---|
| 74 | bx push \ Saved BP on stack |
|---|
| 75 | begin |
|---|
| 76 | ax bx mov \ Save last value |
|---|
| 77 | d# 4 [bp] ax sub \ last - Z[i+1] |
|---|
| 78 | |
|---|
| 79 | 0 [si] imul \ alpha[i] * (last - Z[i+1]) (kills DX) |
|---|
| 80 | 4 [si] si lea \ Increment alpha pointer |
|---|
| 81 | mulscale # dx ax shrd \ Scale down by multiplier scale factor |
|---|
| 82 | |
|---|
| 83 | d# 0 [bp] ax add \ Z[i] + alpha[i] * (last - Z[i+1]) |
|---|
| 84 | |
|---|
| 85 | bx 0 [bp] mov \ Update Z[i] |
|---|
| 86 | |
|---|
| 87 | 4 [bp] bp lea \ Point to next Z[i] |
|---|
| 88 | loopa |
|---|
| 89 | ax 0 [bp] mov \ Update Z[i] |
|---|
| 90 | |
|---|
| 91 | bp pop \ Restore BP |
|---|
| 92 | si pop \ Restore SI |
|---|
| 93 | ax push \ Return value |
|---|
| 94 | c; |
|---|
| 95 | |
|---|
| 96 | \ iir-cascade-z2g implements the generalized second-order |
|---|
| 97 | \ low pass filter cascade as shown in section 10.5.1 (figure 10.35) |
|---|
| 98 | \ of the book. The generalized form allows arbitrary bandwidths |
|---|
| 99 | \ as opposed to the half-band restriction of the simplifed form. |
|---|
| 100 | \ We use this form for 3x upsampling so we can set the lowpass |
|---|
| 101 | \ frequency to Fnyquist/3. |
|---|
| 102 | |
|---|
| 103 | code iir-cascade-z2g ( sample weights z #sections -- out ) |
|---|
| 104 | cx pop \ CX: Number of sections |
|---|
| 105 | |
|---|
| 106 | bp bx mov \ Save |
|---|
| 107 | bp pop \ BP: Z list |
|---|
| 108 | si dx mov \ Save SI |
|---|
| 109 | si pop \ SI: Filter weights |
|---|
| 110 | ax pop \ AX: current value |
|---|
| 111 | dx push \ Saved SI on stack |
|---|
| 112 | bx push \ Saved BP on stack |
|---|
| 113 | di push \ Saved DI on stack |
|---|
| 114 | begin |
|---|
| 115 | ax bx mov \ Save last value |
|---|
| 116 | d# 12 [bp] ax sub \ last - Z[3] |
|---|
| 117 | |
|---|
| 118 | 4 [si] imul \ c2 * (last - Z[3]) (kills DX) |
|---|
| 119 | mulscale # dx ax shrd \ Scale down by multiplier scale factor |
|---|
| 120 | |
|---|
| 121 | d# 4 [bp] ax add \ next += Z[1] |
|---|
| 122 | |
|---|
| 123 | ax di mov \ Save next in di to free up ax |
|---|
| 124 | |
|---|
| 125 | d# 0 [bp] ax mov \ AX: Z[0] |
|---|
| 126 | ax d# 4 [bp] mov \ Z[1] = Z[0] |
|---|
| 127 | bx d# 0 [bp] mov \ Z[0] = sample |
|---|
| 128 | |
|---|
| 129 | d# 8 [bp] ax sub \ Z[0] - Z[2] |
|---|
| 130 | |
|---|
| 131 | 0 [si] imul \ c1 * (Z[0] - Z[2]) |
|---|
| 132 | mulscale # dx ax shrd \ Scale down by multiplier scale factor |
|---|
| 133 | |
|---|
| 134 | di ax add \ AX: next |
|---|
| 135 | |
|---|
| 136 | 8 [si] si lea \ Increment coefficient pointer |
|---|
| 137 | 8 [bp] bp lea \ Increment Z pointer |
|---|
| 138 | loopa |
|---|
| 139 | |
|---|
| 140 | 0 [bp] dx mov |
|---|
| 141 | dx 4 [bp] mov \ Z[1] = Z[0] |
|---|
| 142 | ax 0 [bp] mov \ Z[0] = next |
|---|
| 143 | |
|---|
| 144 | di pop \ Restore DI |
|---|
| 145 | bp pop \ Restore BP |
|---|
| 146 | si pop \ Restore SI |
|---|
| 147 | ax push \ Return value |
|---|
| 148 | c; |
|---|
| 149 | |
|---|
| 150 | |
|---|
| 151 | \ Convert a decimal fraction to a scaled integer multiplier |
|---|
| 152 | : mul: ( "coef" -- ) |
|---|
| 153 | safe-parse-word push-decimal $number drop pop-base ( n ) |
|---|
| 154 | 1 mulscale lshift d# 1,000,000,000 */ ( n' ) |
|---|
| 155 | , |
|---|
| 156 | ; |
|---|
| 157 | |
|---|
| 158 | \ Clip too-large sample values to +- 15 bits |
|---|
| 159 | code saturate ( s -- s' ) |
|---|
| 160 | ax pop |
|---|
| 161 | d# 32767 # ax cmp > if |
|---|
| 162 | d# 32767 # ax mov |
|---|
| 163 | else |
|---|
| 164 | d# -32767 # ax cmp < if |
|---|
| 165 | d# -32767 # ax mov |
|---|
| 166 | then |
|---|
| 167 | then |
|---|
| 168 | ax push |
|---|
| 169 | c; |
|---|
| 170 | |
|---|
| 171 | 0 [if] |
|---|
| 172 | : saturate ( n -- ) |
|---|
| 173 | dup d# 32767 > if drop d# 32767 then |
|---|
| 174 | dup d# -32767 < if drop d# -32767 then |
|---|
| 175 | ; |
|---|
| 176 | [then] |
|---|
| 177 | |
|---|
| 178 | 2 constant #coefs |
|---|
| 179 | |
|---|
| 180 | \ This is for a 9-pole, 9-zero 2-path all-pass halfband IIR filter |
|---|
| 181 | \ From page 278 of the book |
|---|
| 182 | create weights0 mul: .101467517 mul: .612422841 \ Even path |
|---|
| 183 | create weights1 mul: .342095596 mul: .867647439 \ Odd path |
|---|
| 184 | |
|---|
| 185 | 0 [if] |
|---|
| 186 | \ This is for a 5-pole, 5-zero 2-path all-pass halfband IIR filter |
|---|
| 187 | \ From page 276 of the book |
|---|
| 188 | create weights2 mul: .141348600 \ Even path |
|---|
| 189 | create weights3 mul: .589994800 \ Odd path |
|---|
| 190 | [then] |
|---|
| 191 | |
|---|
| 192 | \ This is for a 5-pole, 5-zero 2-path all-pass third-band IIR filter |
|---|
| 193 | \ G0 and G1 use iir-cascade-z2g, H1 uses iir-cascade-z1 |
|---|
| 194 | \ This is derived from the 5-pole halfband coefficients according |
|---|
| 195 | \ to the formulas on page 291, with fb/fs = 1/6 (fb/fnyquist = 1/3). |
|---|
| 196 | \ The book has a missing sign somewhere, because without the minus |
|---|
| 197 | \ signs on c1 and b, you end up with the corner frequency at 2/6 |
|---|
| 198 | \ instead of 1/6. I had to figure that out the hard way, by trial |
|---|
| 199 | \ and error (looking at spectra of impulse responses). If you |
|---|
| 200 | \ replace b with -b in equation 10.25, the c1's work out automatically. |
|---|
| 201 | |
|---|
| 202 | \ c1 c2 |
|---|
| 203 | create coefg0 mul: -.605502011 mul: .211004022 \ Even path G0 |
|---|
| 204 | create coefg1 mul: -.817448745 mul: .634897489 \ Odd path G1 |
|---|
| 205 | \ b |
|---|
| 206 | create coefh1 mul: -.267949192 |
|---|
| 207 | |
|---|
| 208 | |
|---|
| 209 | 2 1+ /n* constant buflen0 \ 2 stages of Z^-1 plus final feedback |
|---|
| 210 | |
|---|
| 211 | buflen0 buffer: z0 \ First upsampler even path delay buffer |
|---|
| 212 | buflen0 buffer: z1 \ First upsampler odd path delay buffer |
|---|
| 213 | |
|---|
| 214 | 1 1+ 2* /n* constant buflen1 \ 1 stage of Z^-2 plus final feedback |
|---|
| 215 | |
|---|
| 216 | buflen1 buffer: z2 \ 3x upsampler even path G0 delay buffer |
|---|
| 217 | buflen1 buffer: z3 \ 3x upsampler odd path G1 delay buffer |
|---|
| 218 | buflen1 buffer: z4 \ 3x upsampler odd path H1 delay buffer |
|---|
| 219 | |
|---|
| 220 | : init-upsample ( -- ) |
|---|
| 221 | z0 buflen0 erase |
|---|
| 222 | z1 buflen0 erase |
|---|
| 223 | z2 buflen1 erase |
|---|
| 224 | z3 buflen1 erase |
|---|
| 225 | z4 buflen1 erase |
|---|
| 226 | ; |
|---|
| 227 | |
|---|
| 228 | \ This is a polyphase decomposition of upsampling by 2 |
|---|
| 229 | \ The filter prototype for each path is a polynomial in Z^2, |
|---|
| 230 | \ but when you apply the polyphase transform with Nobel's identity, |
|---|
| 231 | \ the paths split and the output summation becomes a commutation. |
|---|
| 232 | : up2 ( sample -- out1 out0 ) |
|---|
| 233 | dup weights0 z0 2 iir-cascade-z1 saturate >r |
|---|
| 234 | weights1 z1 2 iir-cascade-z1 saturate r> |
|---|
| 235 | ; |
|---|
| 236 | |
|---|
| 237 | 0 [if] |
|---|
| 238 | 0 value lastsample |
|---|
| 239 | |
|---|
| 240 | \ This is the base rate (non-polyphase) version of up2 |
|---|
| 241 | \ Both paths run for each sample, with Z^-1 between the |
|---|
| 242 | \ paths, summing the paths to get a lowpass output. |
|---|
| 243 | : filter2 ( sample -- out ) |
|---|
| 244 | lastsample weights1 z3 2 iir-cascade-z2 >r ( sample r: out1 ) |
|---|
| 245 | to lastsample |
|---|
| 246 | lastsample weights0 z2 2 iir-cascade-z2 r> ( out0 out1 ) |
|---|
| 247 | + saturate ( out ) |
|---|
| 248 | ; |
|---|
| 249 | [then] |
|---|
| 250 | |
|---|
| 251 | \ Third-band interpolation filter. You have to run this once |
|---|
| 252 | \ for each output sample, with 0-stuffing between input samples. |
|---|
| 253 | \ It would be nice to polyphase this, but doing so requires a |
|---|
| 254 | \ complex design methodology for the IIR filter sections that |
|---|
| 255 | \ I haven't mastered. |
|---|
| 256 | : interp3 ( sample -- out ) |
|---|
| 257 | dup coefg0 z2 1 iir-cascade-z2g ( sample out0 ) |
|---|
| 258 | swap coefh1 z4 1 iir-cascade-z1 ( out0 outh ) |
|---|
| 259 | coefg1 z3 1 iir-cascade-z2g ( out0 out1 ) |
|---|
| 260 | + saturate ( ) |
|---|
| 261 | ; |
|---|
| 262 | |
|---|
| 263 | \ Upsample by a factor of 3 |
|---|
| 264 | : up3 ( sample -- out2 out1 out0 ) |
|---|
| 265 | interp3 >r 0 interp3 >r 0 interp3 r> r> |
|---|
| 266 | ; |
|---|
| 267 | |
|---|
| 268 | 0 value sample-stride |
|---|
| 269 | variable sample-outp |
|---|
| 270 | : out, ( value -- ) |
|---|
| 271 | sample-outp @ w! sample-stride sample-outp +! |
|---|
| 272 | ; |
|---|
| 273 | |
|---|
| 274 | \ Stride is 2 for mono, 4 for stereo |
|---|
| 275 | \ For stereo you must call it twice with an offset of 2 for |
|---|
| 276 | \ inbuf and outbuf on the second call |
|---|
| 277 | : 8khz>48khz ( inbuf /inbuf outbuf stride -- ) |
|---|
| 278 | to sample-stride ( inbuf /inbuf outbuf ) |
|---|
| 279 | sample-outp ! ( inbuf /inbuf ) |
|---|
| 280 | init-upsample ( inbuf /inbuf ) |
|---|
| 281 | bounds ?do ( ) |
|---|
| 282 | i <w@ ( sample ) |
|---|
| 283 | up2 ( s1 s0 ) |
|---|
| 284 | up3 out, out, out, ( s1 ) |
|---|
| 285 | up3 out, out, out, ( ) |
|---|
| 286 | sample-stride +loop ( ) |
|---|
| 287 | ; |
|---|
| 288 | |
|---|
| 289 | : 16khz>48khz ( inbuf /inbuf outbuf stride -- ) |
|---|
| 290 | to sample-stride ( inbuf /inbuf outbuf ) |
|---|
| 291 | sample-outp ! ( inbuf /inbuf ) |
|---|
| 292 | init-upsample ( inbuf /inbuf ) |
|---|
| 293 | bounds ?do ( ) |
|---|
| 294 | i <w@ ( sample ) |
|---|
| 295 | up3 out, out, out, ( ) |
|---|
| 296 | sample-stride +loop ( ) |
|---|
| 297 | ; |
|---|
| 298 | |
|---|
| 299 | 0 [if] |
|---|
| 300 | \ Here is some Matlab/Octave code to do the "coefg.." coefficient |
|---|
| 301 | \ transformations |
|---|
| 302 | function retval = c1(a, b) |
|---|
| 303 | retval = 2 * b * (1 + a) / (1 + a * b * b); |
|---|
| 304 | end |
|---|
| 305 | function retval = c2(a, b) |
|---|
| 306 | retval = ((b * b) + a) / (1 + a * b * b); |
|---|
| 307 | end |
|---|
| 308 | function retval = cvtb(frac) |
|---|
| 309 | retval = (1 - tan(pi * frac)) / (1 + tan(pi * frac)); |
|---|
| 310 | end |
|---|
| 311 | function cvtfilt (bw) % bw is fb/fs |
|---|
| 312 | a0 = .141348600 |
|---|
| 313 | a1 = .589994800 |
|---|
| 314 | |
|---|
| 315 | b = cvtb(bw) |
|---|
| 316 | c10 = c1(a0, b) |
|---|
| 317 | c20 = c2(a0, b) |
|---|
| 318 | c11 = c1(a1, b) |
|---|
| 319 | c21 = c2(a1, b) |
|---|
| 320 | end |
|---|
| 321 | cvtfilt(1/6); |
|---|
| 322 | [then] |
|---|