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...
process
method and a generic compute_amplitude_response
method... I'm not entirely clear on how this would work and worried about what it would do to the readability of my production code.process
method 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.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_process
and algae__biquad_process
so that the first parameter to each has type void *
, i.e.:
void algae__onepole_process(void *filter, algae__sample_t *impulse1,
algae__sample_t *impulse2, size_t blocksize);
void algae__biquad_process(void *filter, algae__sample_t *impulse1,
algae__sample_t *impulse2, size_t blocksize);
This means that you'll need to copy the filter
parameter 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 filter
parameter to void *
and adding a function pointer for the process function you want to call:
void algae__generic_compute_amplitude_response(algae__sample_t *amplitude_response,
const size_t number_of_bins,
void (*process)(void *, algae__sample_t *, algae__sample_t *, size_t),
void *filter,
const algae__frequency_t sample_rate,
const size_t blocksize) {
Which will then use the function pointer instead of the specific function:
process(filter, impulse, impulse, blocksize);