I am writing a simple dsp library in c89. It is a goal to use this version of the language for portability to older machines. I am unit testing my library and want to measure the amplitude response of each filter. I have created the following method for computing the amplitude response using the FFTW library.
#include "algaec.h"
#include <fftw3.h>
void algae__biquad_compute_amplitude_response(algae__sample_t *amplitude_response,
const size_t number_of_bins,
algae__biquad_t *filter,
const algae__frequency_t sample_rate,
const size_t blocksize) {
algae__sample_block_empty(amplitude_response, blocksize);
enum complex { RE, IM };
const size_t N = 2 * number_of_bins;
fftw_complex *in;
fftw_complex *out;
fftw_plan p;
in = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * N);
out = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * N);
p = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
algae__sample_t impulse[blocksize];
algae__sample_block_empty(impulse, blocksize);
impulse[0] = 1;
algae__biquad_process(filter, impulse, impulse, blocksize);
size_t idx;
for (idx = 0; idx < N; idx++) {
in[idx][RE] = impulse[idx];
in[idx][IM] = 0;
}
fftw_execute(p);
for (idx = 0; idx < number_of_bins; idx++) {
amplitude_response[idx] =
(sqrt(out[idx][RE] * out[idx][RE] + out[idx][IM] * out[idx][IM]));
}
fftw_destroy_plan(p);
fftw_free(in);
fftw_free(out);
}
I also want to test a one pole IIR filter. In order to do that I will now need to define the following:
void algae__onepole_compute_amplitude_response(algae__sample_t *amplitude_response,
const size_t number_of_bins,
algae__onepole_t *filter,
const algae__frequency_t sample_rate,
const size_t blocksize) {
algae__sample_block_empty(amplitude_response, blocksize);
enum complex { RE, IM };
const size_t N = 2 * number_of_bins;
fftw_complex *in;
fftw_complex *out;
fftw_plan p;
in = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * N);
out = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * N);
p = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
algae__sample_t impulse[blocksize];
algae__sample_block_empty(impulse, blocksize);
impulse[0] = 1;
algae__onepole_process(filter, impulse, impulse, blocksize);
size_t idx;
for (idx = 0; idx < N; idx++) {
in[idx][RE] = impulse[idx];
in[idx][IM] = 0;
}
fftw_execute(p);
for (idx = 0; idx < number_of_bins; idx++) {
amplitude_response[idx] =
(sqrt(out[idx][RE] * out[idx][RE] + out[idx][IM] * out[idx][IM]));
}
fftw_destroy_plan(p);
fftw_free(in);
fftw_free(out);
}
Ultimately I will have many more filter types. I will want to test the amplitude response of all of them to ensure that I have implemented them correctly. If I were using c++, I could use templates to make both algae__compute_amplitude_response and algae__process generic with respect to the type of filter and have the compiler generate the right implementation automatically while having only one code path I have to maintain. Is there any way to avoid repeating myself here?
So far I have researched the following options. None of them seem appealing...
- I use preprocessor macros to define a generic
processmethod and a genericcompute_amplitude_responsemethod... I'm not entirely clear on how this would work and worried about what it would do to the readability of my production code. - I create a tagged union of my different filter structs and create a
processmethod that identifies the type of the struct and then redirects to the proper method... This would affect the structure of my production code for the sake of testing (not necessarily a deal-breaker but a bit sad). It would also affect the implementation and performance of my production code as the union-ed struct would take up the same amount of memory as its largest member. For a onepole filter vs a biquad this is the different between 2 floats and 9 floats. - something scary with void pointers??? Perhaps defining a struct that is the combination of a void pointer and a type to tag it. This one feels like opening a can worms and could make my production API very confusing.
Are there any options I am missing here?-
You can do this with function pointers, but first you need to make sure the functions in question have the same signature type.
First, change
algae__onepole_processandalgae__biquad_processso that the first parameter to each has typevoid *, i.e.:This means that you'll need to copy the
filterparameter in each to a pointer of the appropriate type.Then you create a single function to do what both of the above do, changing the type of type of the
filterparameter tovoid *and adding a function pointer for the process function you want to call:Which will then use the function pointer instead of the specific function: