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