Here's a short explanation of how the effects framework is organized. This framework is for adding your own effects if you decide not to use the multi-effects that are preloaded and if you don't fancy programming the XMOS board from scratch.
The effects framework is a multi-threaded application that handles moving data over the I2S bus, responding to external parameter control (via the UART/I2C interface), and marshaling data to each of the data processing loops.
There's eight threads that are created when the framework application boots up (note that applications are stored in FLASH and transferred to the MCU's RAM by the MCU's boot loader):
Thread 1 - Application thread: Receives data from the UART thread and sends parameter id/values to each data processing thread.
Thread 2 - I2S thread: Implements the I2S interface for the CODEC (ADC/DAC). Sends data to processing loop 1 and receives data from processing loop 5.
Thread 3 - Processing loop 1: Contains your own DSP algorithms. Receives data from the I2S thread and sends data to processing loop 2.
Thread 4 - Processing loop 2: Contains your own DSP algorithms. Receives data from processing loop 1 and sends data to processing loop 3.
Thread 5 - Processing loop 3: Contains your own DSP algorithms. Receives data from processing loop 2 and sends data to processing loop 4.
Thread 6 - Processing loop 4: Contains your own DSP algorithms. Receives data from processing loop 3 and sends data to processing loop 5.
Thread 7 - Processing loop 5: Contains your own DSP algorithms. Receives data from processing loop 4 and sends data to the I2S thread.
Thread 8 - UART/I2C thread: Implements the UART/I2C interface. Received data is sent to the application thread.
The effects framework defines these functions that the effects programmer must implement:
void processing_loop1( int* sample_L, int* sample_R, int param_id, int param_val );
void processing_loop2( int* sample_L, int* sample_R, int param_id, int param_val );
void processing_loop3( int* sample_L, int* sample_R, int param_id, int param_val );
void processing_loop4( int* sample_L, int* sample_R, int param_id, int param_val );
void processing_loop5( int* sample_L, int* sample_R, int param_id, int param_val );
These functions get called once for each audio cycle (192 KHz). The framework moves audio data from the I2S thread to each processing loop and back to the I2S thread automatically. The audio samples arrive to and exit from the processing loop via the pointer variables sample_L and sample_R. If a parameter arrives from the UART/I2C thread then application thread will distribute the parameter ID/value to each processing loop on the next audio cycle after UAR/I2C data input completion. From the perspective of each processing loop (1 through 5) the param_id will be non-zero if it's a new parameter arrival or it will be zero if no parameter activity has occurred.
Here's an example of a simple application organized as follows:
1) Processing loop 1 - blocks the DC component in the audio signal for the left channel, leaves the right channel unaffected.
2) Processing loop 2 - Does nothing. Samples pass through this loop unaffected.
3) Processing loop 3 - Does nothing. Samples pass through this loop unaffected.
4) Processing loop 4 - Does nothing. Samples pass through this loop unaffected.
5) Processing loop 5 performs a low-pass FIR filter in the left channel.
static int loop1_dcblock_q25( int sample_q25 )
{
{ // Differentiator: y[n] = x[n] - x[n-1]
int temp = sample_q25; static int prev_sample = 0;
sample_q25 = sample_q25 - prev_sample; prev_sample = temp;
}
{ // Leaky Integrator: y[n] = pole * y[n-1] + x[n]
int temp = sample_q25; static int prev_result = 0;
sample_q25 = prev_result = xsignalproc_mult_r25x25y31(prev_result,Q31(0.998)) + sample_q25;
}
return sample_q25;
}
void processing_loop1( int* sample_L, int* sample_R, int param_id, int param_val )
{
*sample_L /= 64; // Q31 --> Q25
*sample_L = _loop_1_dcblock( *sample_L );
}
void processing_loop2( int* sample_L, int* sample_R, int param_id, int param_val )
{
}
void processing_loop3( int* sample_L, int* sample_R, int param_id, int param_val )
{
}
void processing_loop4( int* sample_L, int* sample_R, int param_id, int param_val )
{
}
static int loop5_antialias( int sample_q25 )
{
static int coef[] = { Q31(+0.000063008965),Q31(+0.002800992631),Q31(+0.026655281619),
Q31(+0.104794330580),Q31(+0.223294720691),Q31(+0.284783331029),
Q31(+0.223294720691),Q31(+0.104794330580),Q31(+0.026655281619),
Q31(+0.002800992631),Q31(+0.000063008965) };
static int hist[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0};
return xsignalproc_fir_n10_r25x25c31( sample_q25, coef, hist );
}
void processing_loop5( int* sample_L, int* sample_R, int param_id, int param_val )
{
*sample_L = loop5_antialias( *sample_L );
*sample_L *= 64; // Q25 --> Q31
}
Well, hope this makes some sense! Let me know if it's confusing or if you have questions :-)