v2.0.0
Loading...
Searching...
No Matches
parksmcclellan.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13// Adapted from the C port of the Parks-McClellan Fortran program by Iowa Hills Software
14// (http://www.iowahills.com/A7ExampleCodePage.html, October 19, 2013); ported to MNE-CPP by
15// Christoph Dinh and Florian Schlembach in February 2014.
16
17//=============================================================================================================
18// INCLUDES
19//=============================================================================================================
20
21#include "parksmcclellan.h"
22
23//=============================================================================================================
24// QT INCLUDES
25//=============================================================================================================
26
27#include <qmath.h>
28
29//=============================================================================================================
30// USED NAMESPACES
31//=============================================================================================================
32
33using namespace UTILSLIB;
34using namespace Eigen;
35
36//=============================================================================================================
37// DEFINES
38//=============================================================================================================
39
40constexpr int BIG = 4096; // Used to define array sizes. Must be somewhat larger than 8 * MaxNumTaps
41constexpr int SMALL = 256;
42constexpr double M_2PI = 6.28318530717958647692;
43constexpr int ITRMAX = 50; // Max Number of Iterations. Some filters require as many as 45 iterations.
44constexpr double MIN_TEST_VAL = 1.0E-6; // Min value used in LeGrangeInterp and GEE
45
46//=============================================================================================================
47// DEFINE MEMBER METHODS
48//=============================================================================================================
49
51: HalfTapCount(0)
52, ExchangeIndex(SMALL)
53, LeGrangeD(SMALL)
54, Alpha(SMALL)
55, CosOfGrid(SMALL)
56, DesPlus(SMALL)
57, Coeff(SMALL)
58, Edge(SMALL)
59, BandMag(SMALL)
60, InitWeight(SMALL)
61, DesiredMag(BIG)
62, Grid(BIG)
63, Weight(BIG)
64, InitDone2(false)
65{
66}
67
68//=============================================================================================================
69
70ParksMcClellan::ParksMcClellan(int NumTaps, double OmegaC, double BW, double ParksWidth, TPassType PassType)
71: HalfTapCount(0)
72, ExchangeIndex(SMALL)
73, LeGrangeD(SMALL)
74, Alpha(SMALL)
75, CosOfGrid(SMALL)
76, DesPlus(SMALL)
77, Coeff(SMALL)
78, Edge(SMALL)
79, BandMag(SMALL)
80, InitWeight(SMALL)
81, DesiredMag(BIG)
82, Grid(BIG)
83, Weight(BIG)
84, InitDone2(false)
85{
86 init(NumTaps, OmegaC, BW, ParksWidth, PassType);
87}
88
89//=============================================================================================================
90
94
95//=============================================================================================================
96
97void ParksMcClellan::init(int NumTaps, double OmegaC, double BW, double ParksWidth, TPassType PassType)
98{
99 // Set per pass type below; an unrecognised type leaves the band table empty
100 // rather than reading an indeterminate count.
101 int j, NumBands = 0;
102
103 if (NumTaps > 256)
104 NumTaps = 256;
105 if (NumTaps < 9)
106 NumTaps = 9;
107 if ((PassType == HPF || PassType == NOTCH) && NumTaps % 2 == 0)
108 NumTaps--;
109
110 // It helps the algorithm a great deal if each band is at least 0.01 wide.
111 // The weights used here came from the orig PM code.
112 if (PassType == LPF) {
113 NumBands = 2;
114 Edge[1] = 0.0; // Omega = 0
115 Edge[2] = OmegaC; // Pass band edge
116 if (Edge[2] < 0.01)
117 Edge[2] = 0.01;
118 if (Edge[2] > 0.98)
119 Edge[2] = 0.98;
120 Edge[3] = Edge[2] + ParksWidth; // Stop band edge
121 if (Edge[3] > 0.99)
122 Edge[3] = 0.99;
123 Edge[4] = 1.0; // Omega = Pi
124
125 BandMag[1] = 1.0;
126 BandMag[2] = 0.0;
127 InitWeight[1] = 1.0;
128 InitWeight[2] = 10.0;
129 }
130
131 if (PassType == HPF) {
132 NumBands = 2;
133 Edge[1] = 0.0; // Omega = 0
134 Edge[3] = OmegaC; // Pass band edge
135 if (Edge[3] > 0.99)
136 Edge[3] = 0.99;
137 if (Edge[3] < 0.02)
138 Edge[3] = 0.02;
139 Edge[2] = Edge[3] - ParksWidth; // Stop band edge
140 if (Edge[2] < 0.01)
141 Edge[2] = 0.01;
142 Edge[4] = 1.0; // Omega = Pi
143
144 BandMag[1] = 0.0;
145 BandMag[2] = 1.0;
146 InitWeight[1] = 10.0;
147 InitWeight[2] = 1.0;
148 }
149
150 if (PassType == BPF) {
151 NumBands = 3;
152 Edge[1] = 0.0; // Omega = 0
153 Edge[3] = OmegaC - BW / 2.0; // Left pass band edge.
154 if (Edge[3] < 0.02)
155 Edge[3] = 0.02;
156 Edge[2] = Edge[3] - ParksWidth; // Left stop band edge
157 if (Edge[2] < 0.01)
158 Edge[2] = 0.01;
159 Edge[4] = OmegaC + BW / 2.0; // Right pass band edge
160 if (Edge[4] > 0.98)
161 Edge[4] = 0.98;
162 Edge[5] = Edge[4] + ParksWidth; // Right stop band edge
163 if (Edge[5] > 0.99)
164 Edge[5] = 0.99;
165 Edge[6] = 1.0; // Omega = Pi
166
167 BandMag[1] = 0.0;
168 BandMag[2] = 1.0;
169 BandMag[3] = 0.0;
170 InitWeight[1] = 10.0;
171 InitWeight[2] = 1.0;
172 InitWeight[3] = 10.0;
173 }
174
175 if (PassType == NOTCH) {
176 NumBands = 3;
177 Edge[1] = 0.0; // Omega = 0
178 Edge[3] = OmegaC - BW / 2.0; // Left stop band edge.
179 if (Edge[3] < 0.02)
180 Edge[3] = 0.02;
181 Edge[2] = Edge[3] - ParksWidth; // Left pass band edge
182 if (Edge[2] < 0.01)
183 Edge[2] = 0.01;
184 Edge[4] = OmegaC + BW / 2.0; // Right stop band edge
185 if (Edge[4] > 0.98)
186 Edge[4] = 0.98;
187 Edge[5] = Edge[4] + ParksWidth; // Right pass band edge
188 if (Edge[5] > 0.99)
189 Edge[5] = 0.99;
190 Edge[6] = 1.0; // Omega = Pi
191
192 BandMag[1] = 1.0;
193 BandMag[2] = 0.0;
194 BandMag[3] = 1.0;
195 InitWeight[1] = 1.0;
196 InitWeight[2] = 10.0;
197 InitWeight[3] = 1.0;
198 }
199
200 // Parks McClellan's edges are based on 2Pi, we are based on Pi.
201 for (j = 1; j <= 2 * NumBands; j++)
202 Edge[j] /= 2.0;
203
204 CalcParkCoeff2(NumBands, NumTaps);
205}
206
207//=============================================================================================================
208
209void ParksMcClellan::CalcParkCoeff2(int NumBands, int TapCount)
210{
211 int j, k, GridCount, GridIndex, BandIndex, NumIterations;
212 double LowFreqEdge, UpperFreq, TempVar, Change;
213 bool OddNumTaps;
214 GridCount = 16; // Grid Density
215
216 if (TapCount % 2)
217 OddNumTaps = true;
218 else
219 OddNumTaps = false;
220
221 HalfTapCount = TapCount / 2;
222 if (OddNumTaps)
223 HalfTapCount++;
224
225 Grid[1] = Edge[1];
226 LowFreqEdge = GridCount * HalfTapCount;
227 LowFreqEdge = 0.5 / LowFreqEdge;
228 j = 1;
229 k = 1;
230 BandIndex = 1;
231 while (BandIndex <= NumBands) {
232 UpperFreq = Edge[k + 1];
233 while (Grid[j] <= UpperFreq) {
234 TempVar = Grid[j];
235 DesiredMag[j] = BandMag[BandIndex];
236 Weight[j] = InitWeight[BandIndex];
237 j++;
238 ;
239 Grid[j] = TempVar + LowFreqEdge;
240 }
241
242 Grid[j - 1] = UpperFreq;
243 DesiredMag[j - 1] = BandMag[BandIndex];
244 Weight[j - 1] = InitWeight[BandIndex];
245 k += 2;
246 BandIndex++;
247 if (BandIndex <= NumBands)
248 Grid[j] = Edge[k];
249 }
250
251 GridIndex = j - 1;
252 if (!OddNumTaps && Grid[GridIndex] > (0.5 - LowFreqEdge))
253 GridIndex--;
254
255 if (!OddNumTaps) {
256 for (j = 1; j <= GridIndex; j++) {
257 Change = cos(M_PI * Grid[j]);
258 DesiredMag[j] = DesiredMag[j] / Change;
259 Weight[j] = Weight[j] * Change;
260 }
261 }
262
263 TempVar = static_cast<double>(GridIndex - 1) / static_cast<double>(HalfTapCount);
264 for (j = 1; j <= HalfTapCount; j++) {
265 ExchangeIndex[j] = static_cast<double>(j - 1) * TempVar + 1.0;
266 }
267 ExchangeIndex[HalfTapCount + 1] = GridIndex;
268
269 NumIterations = Remez2(GridIndex);
271
272 // Calculate the impulse response.
273 if (OddNumTaps) {
274 for (j = 1; j <= HalfTapCount - 1; j++) {
275 Coeff[j] = 0.5 * Alpha[HalfTapCount + 1 - j];
276 }
277 Coeff[HalfTapCount] = Alpha[1];
278 } else {
279 Coeff[1] = 0.25 * Alpha[HalfTapCount];
280 for (j = 2; j <= HalfTapCount - 1; j++) {
281 Coeff[j] = 0.25 * (Alpha[HalfTapCount + 1 - j] + Alpha[HalfTapCount + 2 - j]);
282 }
283 Coeff[HalfTapCount] = 0.5 * Alpha[1] + 0.25 * Alpha[2];
284 }
285
286 // Output section: TapCount may differ from the count the object was constructed with (clamped, or init() later)
287 FirCoeff = RowVectorXd::Zero(TapCount);
288 for (j = 1; j <= HalfTapCount; j++)
289 FirCoeff[j - 1] = Coeff[j];
290 if (OddNumTaps)
291 for (j = 1; j < HalfTapCount; j++)
292 FirCoeff[HalfTapCount + j - 1] = Coeff[HalfTapCount - j];
293 else
294 for (j = 1; j <= HalfTapCount; j++)
295 FirCoeff[HalfTapCount + j - 1] = Coeff[HalfTapCount - j + 1];
296
297 // Parks2Label was on my application's main form.
298 // These replace the original Ouch() function
299 if (NumIterations <= 3) {
300 //TopForm->Parks2Label->Font->Color = clRed;
301 //TopForm->Parks2Label->Caption = "Convergence ?"; // Covergence is doubtful, but possible.
302 } else {
303 //TopForm->Parks2Label->Font->Color = clBlack;
304 //TopForm->Parks2Label->Caption = UnicodeString(NumIterations) + " Iterations";
305 }
306}
307
308//=============================================================================================================
309
310int ParksMcClellan::Remez2(int GridIndex)
311{
312 // This routine is a direct port of the original goto-driven Fortran, so the
313 // compiler cannot prove that every path assigns these before use. Give them
314 // defined starting values rather than relying on the control flow.
315 int j, JET, K, k, NU, JCHNGE, K1, KNZ, KLOW, NUT, KUP;
316 int NUT1 = 0, LUCK, KN, NITER;
317 double Deviation, DNUM, DDEN, TempVar;
318 double DEVL, COMP = 0.0, YNZ = 0.0, Y1, ERR;
319
320 Y1 = 1;
321 LUCK = 0;
322 DEVL = -1.0;
323 NITER = 1; // Init this to 1 to be consistent with the orig code.
324
325TOP_LINE: // We come back to here from 3 places at the bottom.
326 ExchangeIndex[HalfTapCount + 2] = GridIndex + 1;
327
328 for (j = 1; j <= HalfTapCount + 1; j++) {
329 TempVar = Grid[ExchangeIndex[j]];
330 CosOfGrid[j] = cos(TempVar * M_2PI);
331 }
332
333 JET = (HalfTapCount - 1) / 15 + 1;
334 for (j = 1; j <= HalfTapCount + 1; j++) {
335 LeGrangeD[j] = LeGrangeInterp2(j, HalfTapCount + 1, JET);
336 }
337
338 DNUM = 0.0;
339 DDEN = 0.0;
340 K = 1;
341 for (j = 1; j <= HalfTapCount + 1; j++) {
342 k = ExchangeIndex[j];
343 DNUM += LeGrangeD[j] * DesiredMag[k];
344 DDEN += static_cast<double>(K) * LeGrangeD[j] / Weight[k];
345 K = -K;
346 }
347 Deviation = DNUM / DDEN;
348
349 NU = 1;
350 if (Deviation > 0.0)
351 NU = -1;
352 Deviation = -static_cast<double>(NU) * Deviation;
353 K = NU;
354 for (j = 1; j <= HalfTapCount + 1; j++) {
355 k = ExchangeIndex[j];
356 TempVar = static_cast<double>(K) * Deviation / Weight[k];
357 DesPlus[j] = DesiredMag[k] + TempVar;
358 K = -K;
359 }
360
361 if (Deviation <= DEVL)
362 return (NITER); // Ouch
363
364 DEVL = Deviation;
365 JCHNGE = 0;
366 K1 = ExchangeIndex[1];
367 KNZ = ExchangeIndex[HalfTapCount + 1];
368 KLOW = 0;
369 NUT = -NU;
370
371 //Search for the extremal frequencies of the best approximation.
372
373 j = 1;
374 while (j < HalfTapCount + 2) {
375 KUP = ExchangeIndex[j + 1];
376 k = ExchangeIndex[j] + 1;
377 NUT = -NUT;
378 if (j == 2)
379 Y1 = COMP;
380 COMP = Deviation;
381
382 if (k < KUP && !ErrTest(k, NUT, COMP, &ERR)) {
383 L210:
384 COMP = static_cast<double>(NUT) * ERR;
385 for (k++; k < KUP; k++) {
386 if (ErrTest(k, NUT, COMP, &ERR))
387 break; // for loop
388 COMP = static_cast<double>(NUT) * ERR;
389 }
390
391 ExchangeIndex[j] = k - 1;
392 j++;
393 KLOW = k - 1;
394 JCHNGE++;
395 continue; // while loop
396 }
397
398 k--;
399
400 L225:
401 k--;
402 if (k <= KLOW) {
403 k = ExchangeIndex[j] + 1;
404 if (JCHNGE > 0) {
405 ExchangeIndex[j] = k - 1;
406 j++;
407 KLOW = k - 1;
408 JCHNGE++;
409 continue; // while loop
410 } else // JCHNGE <= 0
411 {
412 for (k++; k < KUP; k++) {
413 if (ErrTest(k, NUT, COMP, &ERR))
414 continue; // for loop
415 goto L210;
416 }
417
418 KLOW = ExchangeIndex[j];
419 j++;
420 continue; // while loop
421 }
422 }
423 // Can't use a do while loop here, it would outdent the two continue statements.
424 if (ErrTest(k, NUT, COMP, &ERR) && JCHNGE <= 0)
425 goto L225;
426
427 if (ErrTest(k, NUT, COMP, &ERR)) {
428 KLOW = ExchangeIndex[j];
429 j++;
430 continue; // while loop
431 }
432
433 COMP = static_cast<double>(NUT) * ERR;
434
435 L235:
436 for (k--; k > KLOW; k--) {
437 if (ErrTest(k, NUT, COMP, &ERR))
438 break; // for loop
439 COMP = static_cast<double>(NUT) * ERR;
440 }
441
442 KLOW = ExchangeIndex[j];
443 ExchangeIndex[j] = k + 1;
444 j++;
445 JCHNGE++;
446 } // end while(j<HalfTapCount
447
448 if (j == HalfTapCount + 2)
449 YNZ = COMP;
450
451 while (j <= HalfTapCount + 2) {
452 if (K1 > ExchangeIndex[1])
453 K1 = ExchangeIndex[1];
454 if (KNZ < ExchangeIndex[HalfTapCount + 1])
455 KNZ = ExchangeIndex[HalfTapCount + 1];
456 NUT1 = NUT;
457 NUT = -NU;
458 k = 0;
459 KUP = K1;
460 COMP = YNZ * 1.00001;
461 LUCK = 1;
462
463 for (k++; k < KUP; k++) {
464 if (ErrTest(k, NUT, COMP, &ERR))
465 continue; // for loop
466 j = HalfTapCount + 2;
467 goto L210;
468 }
469 LUCK = 2;
470 break; // break while(j <= HalfTapCount+2) loop
471 } // end while(j <= HalfTapCount+2)
472
473 if (LUCK == 1 || LUCK == 2) {
474 if (LUCK == 1) {
475 if (COMP > Y1)
476 Y1 = COMP;
477 K1 = ExchangeIndex[HalfTapCount + 2];
478 }
479
480 k = GridIndex + 1;
481 KLOW = KNZ;
482 NUT = -NUT1;
483 COMP = Y1 * 1.00001;
484
485 for (k--; k > KLOW; k--) {
486 if (ErrTest(k, NUT, COMP, &ERR))
487 continue; // for loop
488 j = HalfTapCount + 2;
489 COMP = static_cast<double>(NUT) * ERR;
490 LUCK = 3; // last time in this if(LUCK == 1 || LUCK == 2)
491 goto L235;
492 }
493
494 if (LUCK == 2) {
495 if (JCHNGE > 0 && NITER++ < ITRMAX)
496 goto TOP_LINE;
497 else
498 return (NITER);
499 }
500
501 for (j = 1; j <= HalfTapCount; j++) {
502 ExchangeIndex[HalfTapCount + 2 - j] = ExchangeIndex[HalfTapCount + 1 - j];
503 }
504 ExchangeIndex[1] = K1;
505 if (NITER++ < ITRMAX)
506 goto TOP_LINE;
507 } // end if(LUCK == 1 || LUCK == 2)
508
509 KN = ExchangeIndex[HalfTapCount + 2];
510 for (j = 1; j <= HalfTapCount; j++) {
511 ExchangeIndex[j] = ExchangeIndex[j + 1];
512 }
513 ExchangeIndex[HalfTapCount + 1] = KN;
514 if (NITER++ < ITRMAX)
515 goto TOP_LINE;
516
517 return (NITER);
518}
519
520//=============================================================================================================
521
522double ParksMcClellan::LeGrangeInterp2(int K, int N, int M) // D
523{
524 int j, k;
525 double Dee, Q;
526 Dee = 1.0;
527 Q = CosOfGrid[K];
528 for (k = 1; k <= M; k++)
529 for (j = k; j <= N; j += M) {
530 if (j != K)
531 Dee = 2.0 * Dee * (Q - CosOfGrid[j]);
532 }
533 if (std::fabs(Dee) < MIN_TEST_VAL) {
534 if (Dee < 0.0)
535 Dee = -MIN_TEST_VAL;
536 else
537 Dee = MIN_TEST_VAL;
538 }
539 return (1.0 / Dee);
540}
541
542//=============================================================================================================
543
544double ParksMcClellan::GEE2(int K, int N)
545{
546 int j;
547 double P, C, Dee, XF;
548 P = 0.0;
549 XF = Grid[K];
550 XF = cos(M_2PI * XF);
551 Dee = 0.0;
552 for (j = 1; j <= N; j++) {
553 C = XF - CosOfGrid[j];
554 if (std::fabs(C) < MIN_TEST_VAL) {
555 if (C < 0.0)
556 C = -MIN_TEST_VAL;
557 else
558 C = MIN_TEST_VAL;
559 }
560 C = LeGrangeD[j] / C;
561 Dee = Dee + C;
562 P = P + C * DesPlus[j];
563 }
564 if (std::fabs(Dee) < MIN_TEST_VAL) {
565 if (Dee < 0.0)
566 Dee = -MIN_TEST_VAL;
567 else
568 Dee = MIN_TEST_VAL;
569 }
570 return (P / Dee);
571}
572
573//=============================================================================================================
574
575bool ParksMcClellan::ErrTest(int k, int Nut, double Comp, double* Err)
576{
577 *Err = GEE2(k, HalfTapCount + 1);
578 *Err = (*Err - DesiredMag[k]) * Weight[k];
579 if (static_cast<double>(Nut) * *Err - Comp <= 0.0)
580 return (true);
581 else
582 return (false);
583}
584
585//=============================================================================================================
586
588{
589 int j, k, n;
590 double GTempVar, OneOverNumTaps;
591 double Omega, TempVar, FreqN, TempX, GridCos;
592 double GeeArray[SMALL];
593
594 GTempVar = Grid[1];
595 CosOfGrid[HalfTapCount + 2] = -2.0;
596 OneOverNumTaps = 1.0 / static_cast<double>(2 * HalfTapCount - 1);
597 k = 1;
598
599 for (j = 1; j <= HalfTapCount; j++) {
600 FreqN = static_cast<double>(j - 1) * OneOverNumTaps;
601 TempX = cos(M_2PI * FreqN);
602
603 GridCos = CosOfGrid[k];
604 if (TempX <= GridCos) {
605 while (TempX <= GridCos && (GridCos - TempX) >= MIN_TEST_VAL) // MIN_TEST_VAL = 1.0E-6
606 {
607 k++;
608 ;
609 GridCos = CosOfGrid[k];
610 }
611 }
612 if (TempX <= GridCos || (TempX - GridCos) < MIN_TEST_VAL) {
613 GeeArray[j] = DesPlus[k]; // Desired Response
614 } else {
615 Grid[1] = FreqN;
616 GeeArray[j] = GEE2(1, HalfTapCount + 1);
617 }
618 if (k > 1)
619 k--;
620 }
621
622 Grid[1] = GTempVar;
623 for (j = 1; j <= HalfTapCount; j++) {
624 TempVar = 0.0;
625 Omega = static_cast<double>(j - 1) * M_2PI * OneOverNumTaps;
626 for (n = 1; n <= HalfTapCount - 1; n++) {
627 TempVar += GeeArray[n + 1] * cos(Omega * static_cast<double>(n));
628 }
629 TempVar = 2.0 * TempVar + GeeArray[1];
630 Alpha[j] = TempVar;
631 }
632
633 Alpha[1] = Alpha[1] * OneOverNumTaps;
634 for (j = 2; j <= HalfTapCount; j++) {
635 Alpha[j] = 2.0 * Alpha[j] * OneOverNumTaps;
636 }
637}
constexpr double M_2PI
constexpr int BIG
constexpr double MIN_TEST_VAL
constexpr int SMALL
constexpr int ITRMAX
Parks–McClellan equiripple FIR design via the Remez exchange algorithm.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
double LeGrangeInterp2(int K, int N, int M)
int Remez2(int GridIndex)
double GEE2(int K, int N)
Eigen::RowVectorXd FirCoeff
bool ErrTest(int k, int Nut, double Comp, double *Err)
void CalcParkCoeff2(int NBANDS, int NFILT)
void init(int NumTaps, double OmegaC, double BW, double ParksWidth, TPassType PassType)