Here's another code example; A stereo 15-band graphic EQ with 4th order filters (two cascaded biquad's) for each band. The frequency band filters are processed in cascaded form therefore the complete EQ frequency response can be processed as 30 cascaded biquad filters.
The filter coefficients are not hard-coded for specific frequency responses (although they could be if desired) but rather are set to 0.0 and later loaded up via MIDI by a host computer allowing band Q, gain, and frequency to be calculated by the host machine as settings change. This method works well when using USB MIDI due to its high data rate and low latency.
Both left and right channels are both processed in one processing thread and easily satisfies timing for a 48 kHz sampling rate. Keep in mind that although thread A1 is doing work the threads A2-A5, B1-B5 and C1-C5 are unused and still have approximately 100 MIPs each do to additional processing.
bash-3.2$ xsim example_eq.xe
1192 ticks (83892 Hz)
#define MIDIPROP_GRAPHICEQ 0x12345600
#define MIDIPROP_GRAPHICEQ_COEFFS_L (MIDIPROP_GRAPHICEQ+0x00)
#define MIDIPROP_GRAPHICEQ_COEFFS_R (MIDIPROP_GRAPHICEQ+0x20)
#define MIDIPROP_GRAPHICEQ_GAINS (MIDIPROP_GRAPHICEQ+0x40)
#define GRAPHICEQ_BAND_COUNT 15
#define GRAPHICEQ_FILTER_COUNT 2
int32_t graphiceq_coeffsL_q28[GRAPHICEQ_BAND_COUNT*GRAPHICEQ_FILTER_COUNT*5] =
{
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 01 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 01 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 02 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 02 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 03 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 03 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 04 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 04 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 05 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 05 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 06 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 06 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 07 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 07 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 08 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 08 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 09 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 09 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 10 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 10 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 11 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 11 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 12 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 12 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 13 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 13 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 14 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 14 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 15 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 15 Biquad 2
};
int32_t graphiceq_coeffsR_q28[GRAPHICEQ_BAND_COUNT*GRAPHICEQ_FILTER_COUNT*5] =
{
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 01 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 01 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 02 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 02 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 03 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 03 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 04 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 04 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 05 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 05 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 06 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 06 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 07 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 07 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 08 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 08 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 09 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 09 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 10 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 10 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 11 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 11 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 12 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 12 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 13 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 13 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 14 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 14 Biquad 2
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 15 Biquad 1
Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), Q28(0.0), // Band 15 Biquad 2
};
int32_t graphiceq_stateL_q28[GRAPHICEQ_BAND_COUNT*GRAPHICEQ_FILTER_COUNT*4];
int32_t graphiceq_stateR_q28[GRAPHICEQ_BAND_COUNT*GRAPHICEQ_FILTER_COUNT*4];
// These pre/post gains are fixed-point Q28 formatted values ranging from (-8.0 to +7.999...)
int32_t graphiceq_pregainL_q28, graphiceq_pregainR_q28;
int32_t graphiceq_postgainL_q28, graphiceq_postgainR_q28;
void thread_a1_initialize( void )
{
graphiceq_pregainL_q28 = graphiceq_pregainR_q28 = 0;
graphiceq_postgainL_q28 = graphiceq_postgainR_q28 = 0;
for( int ii = 0; ii < 15*2*4; ++ii ) graphiceq_stateL_q28[ii] = 0;
for( int ii = 0; ii < 15*2*4; ++ii ) graphiceq_stateR_q28[ii] = 0;
}
void thread_a1_process( int* samples, const int* property, int ticks )
{
static int pass = 0;
// Process left channel sample
samples[0] = lib_dsp_math_multiply( samples[0], graphiceq_pregainL_q28, 28 );
samples[0] = lib_dsp_filters_biquads( samples[0],
graphiceq_coeffsL_q28, graphiceq_stateL_q28,
GRAPHICEQ_BAND_COUNT*GRAPHICEQ_FILTER_COUNT,
28 );
samples[0] = lib_dsp_math_multiply( samples[0], graphiceq_postgainL_q28, 28 );
// Process right channel sample
samples[1] = lib_dsp_math_multiply( samples[1], graphiceq_pregainR_q28, 28 );
samples[1] = lib_dsp_filters_biquads( samples[1],
graphiceq_coeffsR_q28, graphiceq_stateR_q28,
GRAPHICEQ_BAND_COUNT*GRAPHICEQ_FILTER_COUNT,
28 );
samples[1] = lib_dsp_math_multiply( samples[1], graphiceq_postgainR_q28, 28 );
// Check for a property update to the pre and post gain values
if( property[0] == MIDIPROP_GRAPHICEQ_GAINS )
{
graphiceq_pregainL_q28 = property[1];
graphiceq_pregainR_q28 = property[2];
graphiceq_postgainL_q28 = property[3];
graphiceq_postgainR_q28 = property[4];
}
// Check for a property update to the left EQ filter bank
else if( (property[0] & 0xFFFFFFF0) == MIDIPROP_GRAPHICEQ_COEFFS_L )
{
int filter_num = property[0] - MIDIPROP_GRAPHICEQ_COEFFS_L - 1;
set_property( graphiceq_coeffsL_q28 + 5 * filter_num, property );
}
// Check for a property update to the right EQ filter bank
else if( (property[0] & 0xFFFFFFF0) == MIDIPROP_GRAPHICEQ_COEFFS_R )
{
int filter_num = property[0] - MIDIPROP_GRAPHICEQ_COEFFS_L - 1;
set_property( graphiceq_coeffsL_q28 + 5 * filter_num, property );
}
if( ++pass == 2 ) printf( "%u ticks (%u Hz)\n", ticks, 100000000/ticks );
}