Generic Programming in C++ for Filters
Generic Programming in C++ for Filters
• Maxwell’s Equations:
• Partial differential equations, very difficult to solve directly
• Solved by numerical approaches
• Numerical approaches
• Convert problems described by complex differential equations into ‘lots of’
smaller but more simple problems.
• ‘lots of’ can mean millions of much simpler equations that must be solved in
parallel
HYPOTHETICAL PROBLEM
• Aircraft ‘skin’ modelled by considering its surfaces
• Many panels used to describe each surface
• The penetration of fields through the skin is modelled by equations that link surface
polygons
• Each filter:
• Needs to know which panel it connects, i.e. its input and output
• Contain the transfer function / equation that links these
• Probably represented by some sort of data structure or class, almost certainly then have a large array of filters
• The point:
• Engineering problems get very large, very quickly.
• Clever ways of storing and processing very large sets of objects / arrays / matrices are needed
FILTER IMPLEMENTATION
• How to store?
• Data structures / Classes
• Representing links – filter has polygon 1 as input and 23012 as output, represent using ID, pointers, etc
• Need to minimise storage requirement due to number of instances
• Equation implementation:
• How is action of each filter stored & evaluated – class member functions, etc
• Many different types of filter, how to reuse filter definition code but change ‘action’ code without rewriting
• Future proof for future modifications – actions required may evolve
• Computational efficiency
• How to efficiently evaluate all filters – important due to number of instances
• Efficient implementation – get most from each core
• Parallelisation – make sure all available cores are used
• Variable types
• Might sound trivial but…
• 32 or 64 bit integers, signed or unsigned – 64 bit required if you want to index more than 2^32 filters but will double storage requirements
• Similar situation with floats vs doubles
HYPOTHETICAL SOLUTION class Panel;
class Filter;
main() {
• Use C++ as OOP solves some of the problems
• E.g. can have different types of filter with polymorphism and inheritance int numPanels = X;
int numFilters = Y;
int numTimeSteps = Z;
• Filter class would be an abstract class with a virtual doFiltering() member Panel* panels[numPanel];
Filter* filters[numFilters];
function
// Read patches / filters from file
• In this case, the virtual function in Filter means that all filters must have a class Filter2 : public Filter {
doFiltering() function, but this function is not defined for the generic filter public:
void evaluate() {
case // filter code for filter type 2
}
};
• Filter1 and Filter2 each have evaluate() functions which are different
• Example of polymorphism main() {
Filter *filters[2];
main() {
• Back to the original example
int numPanels = X;
int numFilters = Y;
• The array of Filters int numTimeSteps = Z;
• Size is potentially unknown when the program is compiled – Y is probably read from an input file
• The type of each filter is also unknown, as this will be read from the input file Panel* panels[numPanels];
Filter* filters[numFilters];
• The compiler cannot decide which version of evaluate is used // Read patches / filters from file
• It isnt known until the program runs and the input file read
for( int i = 0; i < numTimeSteps; i++ ) {
for( int j = 0; j < numFilters; j++ ) {
• A decision must be made when the program runs – at runtime filters[j]->evaluate();
}
• Known as runtime-polymorphism }
• Effectively using pointers to functions to select which function runs }
Filter1. Filter2( double _d1, double _d2 ) : Filter( _d1, _d2 ) {} Polymorphic code – use
double evaluate( double x ) {
return d1*x*x + d2*x*x*x; random numbers to decide
}
}; whether filters should be type
1 or type 2 and store general
Example Filter classes Filter pointers to new objects.
[Link] [Link]
GHIDRA DECOMPILATION
• Compile both with g++, and decompile with Ghidra
• Would expect that Polymorphic code is more complicated and slower – it has to decide which variant of class it is dealing with when evaluate
is called.
• In fact, very little difference. Polymorphic uses function pointers in class array, adds a small offset to the address of each Filter to get the
address of the evaluate function. Filters just lined in in Filter array – doesn’t really make much difference to code
• The code here is not optimised – the compiler basically uses my code as a template for the machine code. It replicates my C++ in machine
code, structures, loops, etc
• If you compare the two programs by looking at disassembly and decompiled code, there is very little difference. Both “evaluate” loops take
similar number of assembly instructions (105 for MM and 110 for PM) and the decompiled code is similar for both
• Small amount of extra code for PM, but not really significant
[Link]
[Link]
USE OF OPTIMISER
• A second version of the program is compiled, this time allowing the compiler to optimise my code (g++ -O3).
• If the decision about which code would be run could be made at compile time, the
compiler/optimiser can only insert the code that is actually used (and no decision making
code
Executable
• Obviously this isn’t always possible (like in our case with random choices), but in many
cases you have flexible classes to allow different actions in different parts of the program
but the decision about which action will be taken can be predetermined
• Some of this is about program design – make it clear what you want
• Some of it is about using OOP features that allow the compiler to more easily optimise…
GENERIC PROGRAMMING
Reduce code, and algorithms, to their most general and useful form.
Don’t write code specific to one application of an algorithm, write code that describes the general
algorithm that can subsequently be applied in any application
A SIMPLIFIED PROBLEM
• Assume the ‘filters’ from the example can contain functions, and that these
functions might need to be integrated.
• Different filters have different functions
• There are different ways of integrating – trading off speed and accuracy
Function to
𝐼 = න 𝑓(𝑥) 𝑑𝑥
integrate 𝑎
Numerical
Lower Limit Integrator Function ()
Answer
Upper Limit
Integrator function should be able to integrate any function, between any limits
INTEGRATOR FUNCTION DEFINITION 𝑏
• Problems:
double integrate_functionA( double a, double b );
• You create a copy of the function for every different double integrate_functionB( double a, double b );
integration routine double integrate_functionC( double a, double b );
• If you then want to implement two different integration types if( [Link] == FTYPE_A ) {
(1st order, 2nd order, etc), you need two sets of integration I = integrate_functionA( a, b );
functions 1st order A,B,C,… 2nd order A,B,C,… } else if( [Link] == FTYPE_B ) {
I = integrate_functionB( a, b );
• Quickly becomes unmanageable } else if( [Link] == FTYPE_C ) {
I = integrate_functionC( a, b );
• Makes updating code difficult – update/fix might need to be }
applied across many functions
• Possibly messy code, you might need lots of if/switch
statements
FUNCTION TO INTEGRATE – C-SOLUTION
𝑏
• Having a function that does some subprocess which
varies isn’t an uncommon scenario 𝐼 = න 𝑓(𝑥) 𝑑𝑥
𝑎
• Dealt with in C using callbacks / function pointers
• One integrate() can integrate any function double functionA( double x ) {
return 3.*x + 1.;
• The function to integrate is passed to integrate() as a }
pointer – its the memory address
double functionB( double x ) {
return x*x + 4.;
• Now means can add as many additional functions as }
want, without needing to write a new integrate function
double integrate( double (*fptr)(double), double a, double b ) {
return 0.5*(b-a)*fptr(a)*fptr(b);
}
• Can also add new integrate() variants and these
integrate( &functionA, 1. 5. );
should! be able to integrate any of existing functions integrate( &functionB, 1. 5. );
INTEGRATING FUNCTIONS WITH ARGUMENTS
𝑏
• 𝑓 𝑥 = 𝑝1 𝑥 2 + 𝑝2 𝑥 + 𝑝3
𝐼 = න 𝑓(𝑥) 𝑑𝑥
• What if the function takes other parameters – e.g. 𝑝1 ,
𝑎
𝑝2 and 𝑝3 in this case?
double functionC( double x, double p1, double p2, double p3 ) {
return p1*x*x + p2*x + p3;
}
• The previous function pointer method doesn’t work:
• Function pointer definition is: double (*fptr)(double)
• Is restricted to a function with one double argument and
returning a double
• Force it manually
• Most general way is to have your functions take an array of void pointers as their only argument.
• Each void pointer can be typecast to the correct pointer type
• The cast pointer is then used to retrieve that argument
• Important to know:
• How many arguments (how many values to retrieve)
• What type – this affects size of arguments, and therefore
where each argument in in memory
[Link]
A BASIC C++ APPROACH
class Function {
• OOP approach is to create a function object public:
double evaluate( double x );
• Object has member functions, can use polymorphism
double evaluate( double x, double p1, double p2, double p3 );
• Multiple evaluate functions with different arguments
float evaluate( float x );
float evaluate( float x, float p1, float p2, float p3 );
• Might be a better approach, but each “evaluate” function needs to be };
written – copy/paste, etc
• What the program does is clear, how it does it is flexible Worth pointing out that C++ is an evolving
language, template meta-progamming and STL
(two key topics for us) has been around since ’98
• Allows code that can be modified, extended, scaled later but certain other features were only added more
recently
• There are features of OOP languages that can help, that’s what we
are looking at
• Having a function that returns correct type / takes correct argument can be important – affects
variable size in memory (see later) and result precision
• End up having to rewrite same function multiple times, if you think of all different types of int
(int32_t, int64_t, uint32_t, uint64_t, etc) this can add up to a lot of code is some circumstances
• This problem occurs with many standard library functions – min, max, etc, - all need to work
with different types
INTRODUCING TEMPLATES
// what follows contains an undefined type T
• Templates allow you to define the function: template <typename T>
• without specifying variable types // function that uses instances of T
T evaluate( T x ) {
• but allowing the specification of links between different variables, return (x < 0.)? T(0.):T(1.);
i.e. specifying that the arguments and return values are same type }
• These are simple examples with one type, which is called T in // Constructor
Function(T val1, T val2) : value1(val1), value2(val2) {}
this case };
INTRODUCING TEMPLATES
private: definition – it just says that a generic
variable, of type T, will be found in the class.
T value1; The compiler then knows it must try to work
// what follows contains an undefined type T
• Templates allow you to define the function: out what T is each time the class is used (it
T •value2;
without specifying variable types
template <typename T>
can be different for each class instance).
// function that uses instances of T
T evaluate( T x ) {
• but allowing the specification of links between different variables, return (x < 0.)? T(0.):T(1.);
i.e. specifying that the arguments and return values are same type }
// Constructor
• You can create a function, or class, with as-yet undefined
Function(T
variable types val1, T val2) : value1(val1), value2(val2) {}
}; • Function/class must be prefixed with
template <typename T>
PROGRESS…
class Function {
public:
T evaluate( T x ) {
return T(0);
};
• This is what we had before: };
double evaluate( T x ) {
return p1*x*x + p2*x + p3;
};
};
PROGRESS…
template <typename T>
class Function {
public:
T evaluate( T x ) {
return T(0);
• This is what we had before: };
• Function base class };
• Function 1 & 2 inherit from this
class Function1 : public Function<double> {
public:
double evaluate( double x ) {
• What about now? return 3.*x;
• Can template the base class to provide a generic }
};
evaluate function
template <typename U>
class Function1 : public Function<U> {
• But how does inheritance work? public:
• Derived classes can specify the template type for U evaluate( U x ) {
the parent class, either by fixing it or by linking it return 3.*x;
}
to another template };
PROGRESS… template <typename T>
class Function {
public:
T evaluate( T x ) {
return T(0);
• This is what we had before: };
};
• Function base class
• Function 1 & 2 inherit from this
template <typename U, typename V>
class Function2 : public Function<U> {
private:
• What about now? V p1, p2, p3;
U evaluate( U x ) {
return p1*x*x + p2*x + p3;
• But how does inheritance work? }
};
• Derived classes can specify the template type for
the parent class, either by fixing it or by linking it
to another template
Function_1.cpp
WHAT MIGHT THE INTEGRATION CODE LOOK LIKE?
template <typename U>
class Function1 : public Function<U> {
• Integrate function takes a function as an public:
U evaluate( U x ) {
argument return 3.*x;
}
• Templating this allows any function to be };
passed template <typename U, typename V>
• Requirement is just that T must contain an class Function2 : public Function<U> {
private:
evaluate( ) function V p1, p2, p3;
public:
Function2( V _p1, V _p2, V _p3 ) : p1(_p1), p2(_p2),
p3(_p3){}
• Also takes numerical arguments for limits
U evaluate( U x ) {
return p1*x*x + p2*x + p3;
}
};
• Final point on this, is that code can be made to
less messy. template <typename T, typename U>
U integrate( T f, U a, U b ) {
return 0.5*(b-a)*[Link](a)*[Link](b);
}
WHAT MIGHT THE INTEGRATION CODE LOOK LIKE?
𝑏
• Integrate function takes a function as an
argument
𝐼 = න 𝑓(𝑥) 𝑑𝑥
• Templating this allows any function to be 𝑎
passed 𝐼 = 0.5 ∗ b − a × 𝑓(a) × 𝑓(b)
• Requirement is just that T must contain an
evaluate( ) function template <typename T, typename U>
U integrate( T f, U a, U b ) {
return 0.5*(b-a)*[Link](a)*[Link](b);
}
• Also takes numerical arguments for limits
• Final point on this, is that code can be made to template <typename T, typename U>
less messy. U integrate( T f, U a, U b ) {
return 0.5*(b-a)*f(a)*f(b);
}
FUNCTORS
template <typename T>
class Function {
public:
T evaluate( T x ) {
return T(0);
U operator() ( U x ) {
return evaluate( x );
}
};
template <typename T, typename U>
U integrate( T f, U a, U b ) {
return 0.5*(b-a)*f(a)*f(b);
}
ABSTRACT CLASSES
template <typename T>
class Function {
public:
T evaluate( T x ) {
return T(0);
• Have intentionally left a default evaluate function in the };
parent function class
U operator() ( U x ) {
• This just returns 0 in whatever type the template is using return evaluate( x );
}
};
• Issue with this is that a Function class could be created,
but it wouldn’t do anything
• You may not want to allow someone to create an empty
function
template <typename T>
class Function {
• Can make Function an abstract class public:
• This means it can be used as basis for derived classes but virtual T evaluate( T x ) = 0;
cannot exist itself
T operator() ( T x ) {
• Done by making at least one of its functions virtual – that return evaluate( x );
means say the function exists (declare it), but decline to }
define it. };
• All non-abstract derived classes must implement this function
CODE OPTIMISATION
Optimising for speed
INLINING for( i = 0; i < nFilter; i++ ) {
double output = filters[i].evaluate( input );
}
• Thinking back to original example: a huge array of filters
(millions?)
• Must be a “evaluate” function in the filter classes, similar to
the integrate class Approximate description! (see cybersecurity
• Typical code might involve a loop forcing each to evaluate module for more details) The point is: there are
quite a few additional instructions required to
• What happens when a function is called? setup and close-down the function
• Arguments for function are pushed onto stack
• Function called If the function code is large, this might be ok.
• Various memory allocation steps for new function’s variables (stack
frame creation)
• Function code executes If the function code is a line or two, you can
• Return value stored somewhere more than double the amount of code that
• Function’s stack frame removed must be run.
• Jump back to calling function
• Arguments removed from stack
If this is happening millions of times, it can cost
quite a bit of time!
INLINING for( i = 0; i < nFilter; i++ ) {
double output = filters[i].evaluate( input );
}
• Imagine the filter was just an integrator:
• Here there would be two function calls
• But only one useful line of code: 0.5*(b-a)*f(a)*f(b); template <typename T>
• Potentially quite inefficient class Filter3 : public Filter {
private:
Function1<T> fn;
• Inlining is a method used to address this public:
T evaluate( T t ) {
• Compiler doesn’t insert function call
return integrate( fn, t, t+100. );
• Copies useful code from called function into calling function
}
};
• Key point:
• Inlining involves the compiler copying code for a function
into another function template <typename T, typename U>
• The compiler must know which function will be called inline U integrate( T f, U a, U b ) {
• This isn’t always the case! return 0.5*(b-a)*f(a)*f(b);
}
REMINDER…
template <typename T>
class Filter {
public:
virtual T evaluate( T t ) = 0;
};
• Runtime polymorphism
template <typename T>
• Is used to provide flexibility at runtime
class Filter1 : public Filter<T> {
• In this case, when we write the code we know we will have a set of filters, but public:
not the type of each filter. This is because the filter list will be read from a file T evaluate( T t ) {
when the program runs (at runtime). // do some filtering
• Runtime polymorphism allows an array of filter pointers to be defined, but }
each is assigned to a specific filter type object only at runtime. };
• The remaining code (evaluate) is type agnostic – all filters have an evaluate
function, don’t need to worry about the type
class Panel;
class Filter;
• Each filter type has its own evaluate function:
main() {
• The compiler cannot know which function is going to be called in each loop
cycle because it doesn’t know which type of filter it is dealing with
int numPanels = X;
• This must be decided at runtime int numFilters = Y;
int numTimeSteps = Z;
intjungle.
cycle because it doesn’t know which type of filter it is dealing with
numPanels = X;
• This must be decided at runtime int numFilters = Y;
int numTimeSteps = Z;
• Implications
It can be possible to avoid
Panel*it,panels
but =you need to be careful.
new Panel[numPanels];
Filter* filters = new Filter[numFilters];
• Cannot inline function – compiler must copy/paste code, doesn’t know which
code to copy/paste // Read patches / filters from file
• There must be additional hidden code that decides which evaluate() to call,
each time call is made for( int i = 0; i < numTimeSteps; i++ ) {
for( int j = 0; j < numFilters; j++ ) {
filters[j].evaluate();
}
}
}
#include <iostream>
#include <iostream>
using namespace std;
using namespace std;
class Function {
class Function {
public:
public:
inline double evaluate( double x ) {
inline double evaluate( double x ) {
return 3*x; Example program return 3*x;
}
}
inline double operator() (double x) {
inline double operator() (double x) {
return evaluate( x );
} Removed templating, }
return evaluate( x );
};
etc for simplicity – just };
want to demonstrate
inline double integrate( Function f, double a, double b ) {
return 0.5*(b-a)*f(a)*f(b); effect of inlining inline double integrate( Function f, double a, double b ) {
return 0.5*(b-a)*f(a)*f(b);
}
}
class Filter {
private: Left is non-inlined class Filter {
private:
Function fn;
public:
version, right is the Function fn;
public:
inline double evaluate( double t ) { same with inline inline double evaluate( double t ) {
return integrate( fn, t, t+1e-5 );
} keywords added return integrate( fn, t, t+1e-5 );
}
};
};
main() {
main() {
Filter f;
Filter f;
double total = 0.;
double total = 0.;
for( int i = 0; i < 100000; i++ ) {
for( int i = 0; i < 100000; i++ ) {
total += [Link]( double(i/1e6) );
total += [Link]( double(i/1e6) );
}
}
cout << total;
cout << total;
}
[Link] } Inlining_inline.cpp
INSTANCE VS POINTER
• A related example is passing an object, a pointer
or a reference to a function
integrate()
main()
operator() ()
Function::evaluate() Compiled code has exactly the same structure as that I wrote
Lots of function calls
I didn’t try to make this happen – just used default options on g++ and didn’t use inline hints
WHAT DOES A FUNCTION LOOK LIKE TO A
PROCESSOR?
• Previous slide shows that without inlining we get a lot of function call, but what exactly is the
overhead associated with each?
• In Ghidra, I can see the assembly code alongside the decompiled code.
• Assembly is the instructions that the processor will actually execute
• Decompiled code is the equivalent human readable source code
Integral calculation
Function return
INSTANCE VS POINTER
• A related example is passing an object, a pointer
or a reference to a function
cout << total << " time: " << t << endl;
}
• Non-inlined program: 123ms
• Inlined program 22ms
• >5.5X slower!
AN ADMISSION (AND A WARNING!)
• I said that I was showing you effect of inlining – this was true. I also talked about use of the
inline keyword but the truth here is that it didn’t actually do anything!
• I did two things:
• Used inline / didn’t use inline
• I passed an option to the compiler on the comment line (-O1) – this means use optimisation
level 1
• The compiler by default uses optimisation level 0 (i.e. no optimisation). For this program, that
appears that it will not inline at this level, even if you use the inline keyword.
• When you enable optimisation level 1, it chooses to inline. Whether you use the inline keyword
or not!
• Important point 1: I did mention this earlier – but inline is just a hint, the compiler will still do [1]
what it thinks is appropriate. [Link]
• Important point 2: If speed really is an issue for some of your code:
n-
• Profile the code, this can be as simple as putting timing calculation code in (temporarily). IDEs
us/visualstudio/profiling/profi
like Visual Studio have code profiling features [1]. ling-feature-tour?view=vs-
• Use a decompiler like Ghidra to see what the compiler actually generated – it might be quite 2022&pivots=programming-
different to the way you structured your original source code language-dotnet
OTHER TEMPLATE TYPES
Additional types of template
ADDITIONAL / ALTERNATIVE PARAMETERS
• Possible to have multiple parameters in a template template <typename T, typename U>
• T and U are potentially different types here U evaluate( T x, U p1 ) {
• Return type is same as that of second parameter (x > 0 ) ? p1 : -p1;
}
• Template parameter can be numeric
• It says that function/class will contain a number, but not what
that number is
• Why do this? These parameters are inserted by the compiler, template <typename T, int n>
they are not variables.
• Affects how code is compiled
class Function {
• In this case, it means the array is statically allocated, not
private:
dynamically allocated at runtime T workingArray[n];
• A bit like a fancy #define public:
T evaluate( T x );
};
• Parameter can also be a bool
TEMPLATE INSTANTIATION - NUMERIC
• The compiler must be able to determine all template<int order>
template parameters at compile time, they float power( float x ){ // Remember for different orders
cannot be variables whose value will be we have a different
// function
determined when the program runs return( pow(x,order) );
}
main(){
• You cannot do this -> int n;
• In this case, n is not known until the cout<<"\nPlease enter the order";
program runs.
cin>>n;
• But, the template parameter must be
cout<<"\n"<<power<n>(7); // Here n is a variable !!!
resolved by the compiler Must be a compile time constant!
}
TEMPLATE INSTANTIATION - NUMERIC
• The compiler must be able to determine all
template<int order>
template parameters at compile time, they float power( float x ){ // Remember for different orders
cannot be variables whose value will be we have a different
determined when the program runs // function
return( pow(x,order) );
}
main(){
• This is possible, as all instances of the int n;
templated function can be generated at cout<<"\nPlease enter the order";
compile time
cin>>n;
• But both versions of the function are
included in the compiled program and the switch(n) {
cout<<"\n"<<[Link]<<"" "<<pf.T;
}
TEMPLATE PARAMETERS ARE NOT MEMBER
VARIABLES template<typename T, int order>
• You cannot access the template parameter as if class Power {
it was a variable.
public:
T operator()(T x) {
return( pow( x,order ) );
• If you want to know what the “power” parameter }
was in this case, you’d need to add an accessor
function which referred to the parameter int getOrder() {
return order;
}
};
• This works because the compiler will insert the
templated value of order when the code for main(){
getOrder is compiled Power <double,3> pf;
cout<<"\n"<<[Link]()<<""
"<<pf.T;
}
OTHER POINTS template <typename T = double> class Function {
private:
T value1;
• Default template types T value2;
• Template variables
template <typename T> constexpr T pi = T(3.141592);
• E.g. define a number which can
become a float/double as required cout << pi<double> << " " << pi<float> << "\n";
m =0 n =1
• The current output is related to previous inputs and outputs as a
sum. As an “algorithm” or “difference
function”, the output at the current
time (sample i) is related to the N
• The number of previous inputs and outputs is variable (represented previous outputs and M previous
by M and N in this case) inputs
class IIR {
public:
IIR( int _M,int _N, double* _a, double* _b );
double operator()( double ip ); // Constructor definition
IIR::IIR( int _M,int _N, double* _a, double* _b ) : M( _M), N (_N) {
private: a = new float[M];
// Arrays to store previous input/output b = new float[N];
samples
int M, N; vin = new float[M];
double *vin; vout = new float[N];
double *vout;
for( int m = 0; m < M; m++ ){
// Arrays to store filter coefficients a[m] = _a[m];
double *a; vin[m] = 0;
double *b; }
};
for( int n = 0; n < N; n++ ){
b[n] = _b[n];
vout[n] = 0;
}
}
ISSUES WITH THE BASIC IMPLEMENTATION
• M and N can be anything – up to the user to decide
• The reason for mentioning this is not filter design theory, its just a hypothetical example to illustrate recursive templates
a0 + a1 z + a2 z 2 .... + aM z M u0 + u1 z + u2 z 2 p0 + p1 z + p2 z 2
Vout ( z ) = Vin ( z ) = 2
.... V ( z)
2 in
1 + b1 z + b2 z .... + bN z
2 N
1 + v1 z + v2 z 1 + q1 z + q2 z
IIR FILTERS USING RECURSIVE BIQUADS
input to
BQc
The individual filter input to
actions can be BQb BQc
represented by input to
something similar to BQa BQb
the Filter class from BQa
earlier
How do you create a class that chains or cascades with instances of itself?
How do you code this so that it is easy to initialise and use these chains?
IIR FILTERS USING RECURSIVE BIQUADS
input to
BQc
input to
BQb 𝑁=1 BQc
input to
BQa 𝑁=2 BQb
𝑁=3 BQa
template<int N, typename Filter, typename T, typename>
• Each level represented by a cascade structure, struct Cascade {
each containing a digital filter // Digital filter, this can be a variant of the earlier filter class
Filter filter;
• Each cascade also contains the cascade // "The object whose output is my input"
immediately after it – it is able to call the next filter Cascade<N-1,operation,T> next;
in the chain
T operator()( T t ){ // "My output" - my operation on my input
return( filter( next( t ) );
}
};
BIQUAD FILTER CLASS
// create a class that acts as the digital filter for each level
• Need a filter class, call it template<typename T>
class BiQuad {
BiQuad private:
T u2, u1, u0; // three coefficients for numerator
T v2, v1; // two coefficients for numerator
T vin1, vin2; // past values for vin
T vout1, vout2; // past values for vout
• Implements filter function
public:
// constructor that sets up coefficient values
𝑢0 + 𝑢1 𝑧 + 𝑢2 𝑧 2 BiQuad( T _u0, T _u1, T _u2, T _v1, T _v2 )
𝑉𝑜𝑢𝑡 = V : u0(_u0), u1(_u1), u2(_u2), v1(_v1), v2(_v2), vin1(0), vin2(0), vout1(0), vout2(0) {
1 + 𝑣1 𝑧 + 𝑣2 𝑧 2 in }
1 is defined
• This is used when N=1 template<typename Filter, typename T>// N is no longer a template parameter
• This instance stops the cascade, there class Cascade<1,Filter,T> { // N is specified for this version of class
private:
is no “next” instance // Instance of filter for this level
Filter f;
// no next class
public:
T operator() ( T vin ) {
return f( vin );
}
};
[Link]