News:

SMF for DIYStompboxes.com!

Main Menu

Audio Effects DSP Board

Started by markseel, June 13, 2016, 11:53:46 AM

Previous topic - Next topic

markseel

This board just went out to OSHPark and should be back in a couple of weeks.  It has:

1 - One XMOS 1000 MIPs processor with 512K RAM total and 32/64 bit fixed point DSP support
2 - USB interface for USB audio streaming, effects control, firmware updating, and USB MIDI support
3 - UART/serial TX/RX for effects control, firmware updating, and/or MIDI support *
4 - JTAG interface for programming using xTIMEcomposer from XMOS, full XTAG connectivity support
5 - 48k 24-bit audio CODEC with analog interfaces on-board - can be modified to support up to 192 kHz
6 - High-impedance differential input for guitar (can be used as single-ended)
7 - Differential mono or single-ended stereo line-level output - can be modified for instrument level
8 - Small footprint - 1.0" x 1.6"
9 - Requires only single 5V supply or dual 5V digital/analog supplies - no external components needed *
* 3.3V UART TX/RX provided, MIDI support requires proper MIDI interface circuitry
* USB requires USB connector and ESD protection diodes for D+, D-, and Vbus.



markseel

#1
Firmware Image Availability

XMOS firmware images (FW) for the following are planned:

1) FW #1 - USB Audio, audio mixer, stereo spatialization, and HiRes cabinet simulation
2) FW #2 - USB Audio, audio mixer, stereo spatialization, and stereo guitar multi-effects.
3) FW #3 - #1 and #2 combined with cab-sim limited to LoRes and guitar effects limited to mono.

Firmware can be uploaded via USB/MIDI, serial UART/MIDI, or via the JTAG conntector.

USB audio input allows a mix of guitar input and effects output to be routed to the USB host for monitoring and/or recording. USB output allows for mixing USB host output audio with effects output (for jamming or for re-amping).  These signal path variations are supported via three internal mixers. A stereo 15-band graphic EQ is placed at the end of the audio chain.

Configuration via serial/UART

The UART RX/TX signals can be used to upload property configuration data.
Properties are sent via RX five 32-bit values at a time preceeded with the 16-bit property ID.
An acknowledgment is sent via TX back to the host controller.
The CRC is computed as the XOR of all 22 bytes (2 bytes if ID, 20 bytes of data).
The UART settings are 230400 baud, on stop bit, no parity.

Configuration format and example is shown below:

Property ID = 0x1301
Param 1     = 0x11223344
Param 2     = 0x55667788
Param 3     = 0x99aabbcc
Param 4     = 0x01234567
Param 5     = 0x89abcdef

Step 1: RX signal <---- | Property ID | Param 1     | Param 2     | ...| Param 5     | CRC |
                        | 13, 01      | 11,22,33,44 | 55,66,77,88 |... | 89,ab,cd,ef | ??  |

Step 2: A minimum of a 200 usec delay, host should be ready to RX 200 usec after last TX

Step 3: TX signal ----> | Property ID |
                        | 13, 01      |

The acknowledgement data is equal to the property ID being updated.
A value of 0x0000 indicates an error (invalid property ID or CRC failure).

Configuration via USB MIDI

The parameter upload format uses the serial/UART format described above encapsulated within MIDI sysex messages.
See the 'configuration via serial/UART' above.

MIDI Sysex messages start with 0xF7, end with 0xF0, and contain 7-bit values (octets with MSB=0).
Therfore the 22-byte property upload byte sequence needs to be split into ~25.14 bytes.

Example using ficticious data for illustration only:

Raw property data:          Hex    - 0xA1, 0xB2, 0xC3, 0xC4 ...
                            Binary - 10100001, 10110010, 11000011, 01110100, ...

8-bit to 7-bit for Sysex:   Binary - 01010000, 01101100, 01011000, 001101110, 0100, ...
                            Hex    - 0x50, 0x6C, 0x58, 0x3E, 0x4?, ...

MIDI Sysex property data:   Hex    - 0xF7, 0x50, 0x6C, 0x58, 0x3E, 0x4?, ... 0xF0


Run-time Audio Processing Properties

Each firware supports a set of properties that determines the audio processing behavior.
Properties have a 16-bit identifier formmatted as follows:

0x1ABC where A is the 4-bit property class and BC are the 8-bit property values index.

Property Summary for Firmware #1

=================================================================================================
Property Name       ID       Param1  Param2  Param3  Param4  Param5   Notes
=================================================================================================
Mixer Control       1100     Volume  Mixer1  Mixer2  Mixer3  Reserved
-------------------------------------------------------------------------------------------------
Spatialization      1200     Enable  TBD     TBD     TBD     TBD      Converts mono to stereo   
-------------------------------------------------------------------------------------------------
GraphicEQ Flags     1300     Enable  Level                            Disable is same as bypassed
GraphicEQ Band 1a   1301     B0      B1      B2      A1      A2       Biquad coefficients
GraphicEQ Band 1b   1302     B0      B1      B2      A1      A2       Biquad coefficients
GraphicEQ Band 2a   1303     B0      B1      B2      A1      A2       Biquad coefficients
GraphicEQ Band 2b   1304     B0      B1      B2      A1      A2       Biquad coefficients
...
GraphicEQ Band 15a  132d     B0      B1      B2      A1      A2       Biquad coefficients
GraphicEQ Band 15b  132e     B0      B1      B2      A1      A2       Biquad coefficients
-------------------------------------------------------------------------------------------------
CabSim Data Flags   1400     Enable                                   Disable is same as bypassed
CabSim Data, Next   1401     N+0     N+1     N+2     N+3     N+4      Next set of 5 blockA values
CabSim Data, Last   1402     N+0     N+1     N+2     N+3     N+4      Last set of 5 blockA values
...
CabSim Data, Next   1409     N+0     N+1     N+2     N+3     N+4      Next set of 5 blockE values
CabSim Data, Last   140a     N+0     N+1     N+2     N+3     N+4      Last set of 5 blockE values
=================================================================================================


Property Summary for Firmware #2

TBD, includes mixer, spatialization and graphic EQ properties plus guitar effects properties.


Property Summary for Firmware #3

TBD, includes mixer, spatialization and graphic EQ properties plus a reduced set of cabinet sim
IR and guitar effects properies.


Mixer Control (Firmwares #1 #2 #3, ID 0x1100)

Volume and level are 32-bit Q1.31 values used to scale the signal from 0 to 0.999...
Mixer values are 32-bit Q1.31 fixed point values with ~0.5 representing a 50/50 mix.

[Guitar]-----+-------------------------[Mixer2]---->[USB In]
             |                            /\
             |                            |
             \/                           |
          [Mixer1]--->[Effects]--->[Spatialization]
             /\                           |
             |                            |
         (Left Ch.)       +---------------+
             |            |
             |            \/
[USB Out]----+-------->[Mixer3]--->[GraphicEQ]--->[Volume]--->[Line Out]


Stereo Spatilization (Firmwares #1 #2 #3, ID 0x1200 through TBD)

Converts mono to stereo.


Graphic EQ (Firmwares #1 #2 #3, ID 0x1300 through 0x131b)

15 band graphic EQ.
Each band's frequency response is determined by a two-cascade biquad IIR filter.
Each band's 10-value coefficient data (for two biquads) is set via two properties (5 values each).
Biquad coefficients are formatted as 32-bit Q4.28 fixed-point values.


Cabinet Simulation, HiRes (Firmware #1, ID 0x1400 through 0x1402)

Speaker cabinet and/or acoustic response simuation via time-domain convolution.
Up 62 msec (mono HiRes) of impulse response is supported via 3000 IR coefficients.
Up 31 msec (stereo LoRes) of impulse response is supported via 3000 IR coefficients.
Impulse response data is loaded 5 impulse response values at a time (see serial/UART format).
3000 impulse response values are split into blocks (A, B, C, E, and F) of 600 IR values each.
For stereo LoRes the left channel's 1500 values are loaded first, right channel is second.
'Next' is used for all IR data loading with the exception of the last 5 values using 'Last'.
Impulse response values are formatted as 32-bit Q1.31 fixed-point values.

Cabinet Simulation (Firmware #3, ID 0x1400 through 0x1402)

Speaker cabinet and/or acoustic response simuation via time-domain convolution.
Up 31 msec (mono LoRes) of impulse response supported via 1500 IR coefficients.
Impulse response data is loaded 5 impulse response values at a time (see serial/UART format).
1500 impulse response values are split into blocks (A, B, partial C) of 600 IR values each.
'Next' is used for all IR data loading with the exception of the last 5 values using 'Last'.
Impulse response values are formatted as 32-bit Q1.31 fixed-point values.

markseel

#2
One can create custom audio processing effects by downloading the audio processing framework (to be provided via GitHub - coming soon), adding customer audio processing DSP code, build using XMOS tools (xTIMEcomposer).  The customer firmware can then be burned to FLASH using xTIMEcomposer and the XTAG-2 or XTAG-3 JTAG board ($20 from Digikey), via USB/MIDI, or via serial/UART MIDI (there are special properties defined for firmware upgrading and boot image selection).

The audio processing framework on GitHub implements the USB class 2.0 audio and USB MIDI interfaces, the I2S/CODEC interface, the serial/UART interface, USB/ADC/DAC audio mixing, 15-band graphic EQ, property/parameter transfers, and firmware upgrading.

All one has to do then is add DSP code and any relevant custom property handling code to the five audio processing loops which all run in parallel and implement a five stage audio processing pipeline.  Each processing thread is allocated 100 MIPS of the total 500 MIPs available and executes once per audio cycle (48 kHz).  All threads are executed once per audio cycle.

static void _copy_prop( int dst[5], const int src[5] )
{
    dst[0] = dst[0]; dst[1] = dst[1]; dst[2] = dst[2];
    dst[3] = dst[3]; dst[4] = dst[4];
}

int audio_thread_1( const int input[2], int output[2], int prop_id, const int prop_data[5] )
{
    static int example_property1_params[5]; // Example property ID = 0x1801
    static int example_property2_params[5]; // Example Property ID = 0x1802
   
    switch( prop_id )
    {
        case 0x1801: _copy_prop( example_property1_params, prop_data ); prop_id = 0; break;
        case 0x1802: _copy_prop( example_property2_params, prop_data ); prop_id = 0; break;
    }
   
    output[0] = input[0]; // Pass through left channel unmodified
    output[1] = input[1]; // Pass through right channel unmodified
   
    return prop_id; // Zero indicates that property data was consumed and should be cleared
}

int audio_thread_2( const int input[2], int output[2], int prop_id, const int prop_data[5] )
{
    output[0] = input[0]; // Pass through left channel unmodified
    output[1] = input[1]; // Pass through right channel unmodified
   
    return prop_id; // Zero indicates that property data was consumed and should be cleared
}

int audio_thread_3( const int input[2], int output[2], int prop_id, const int prop_data[5] )
{
    output[0] = input[0]; // Pass through left channel unmodified
    output[1] = input[1]; // Pass through right channel unmodified
   
    return prop_id; // Zero indicates that property data was consumed and should be cleared
}

int audio_thread_4( const int input[2], int output[2], int prop_id, const int prop_data[5] )
{
    output[0] = input[0]; // Pass through left channel unmodified
    output[1] = input[1]; // Pass through right channel unmodified
   
    return prop_id; // Zero indicates that property data was consumed and should be cleared
}

int audio_thread_5( const int input[2], int output[2], int prop_id, const int prop_data[5] )
{
    output[0] = input[0]; // Pass through left channel unmodified
    output[1] = input[1]; // Pass through right channel unmodified   
   
    return prop_id; // Zero indicates that property data was consumed and should be cleared
}
[/font]

Digital Larry

Mark this is very cool.  As you know, I hate writing DSP code so much I'm willing to write thousands of lines of Java just to avoid it.  :icon_wink:  I recently got a Blackfin DSP eval board, thinking to possibly leverage something like Pure Data through "Heavy" I think it is called, to be able to create more powerful FX than one can accomplish on the FV-1.  Do you think there's any possibility that your board would be compatible with that approach?

See: https://enzienaudio.com/

Regards,

DL
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

potul

wow... this looks amazing...

If I only could solder SMD....


markseel

#5
Quote from: Digital Larry on June 13, 2016, 11:02:22 PM
... Do you think there's any possibility that your board would be compatible with that approach?
See: https://enzienaudio.com/

To some degree yes.  Currently there's a one-to-one mapping from an audio thread to an XMOS computation core which hard-limits thread count to XMOS core count.  You'd have to write code that allocated audio processing objects across these threads to hide the underlying HW architecture.  It's not in my plans but someone could do it I suspect.

Quote from: potul on June 14, 2016, 08:59:19 AM
... If I only could solder SMD....

I can assemble a few boards for folks interested it using it.  I can make the board design files publicly available - that's a start.  Not sure how to scale this up.

potul

Quote from: Digital Larry on June 13, 2016, 11:02:22 PM
Mark this is very cool.  As you know, I hate writing DSP code so much I'm willing to write thousands of lines of Java just to avoid it.  :icon_wink:  I recently got a Blackfin DSP eval board, thinking to possibly leverage something like Pure Data through "Heavy" I think it is called, to be able to create more powerful FX than one can accomplish on the FV-1.  Do you think there's any possibility that your board would be compatible with that approach?

See: https://enzienaudio.com/

Regards,

DL

Hi.... I just took a look at this webpage. Did I get it right? Is this framework some kind of "compiler" for PD?

So I can theoretically use some PD projects and convert them to code? interesting.... I have a Raspberry PD project unfinished because the CPU load was too much for the Pi.

Mat

markseel

#7
The initial code base for building custom audio processing/effects applications is now on GitHub.  It's not been tested much but you can take a look to see how it works :-)  There's not much that you need to do - just add your own DSP code to the audio processing threads.  You'll need to download and install xTIMEcomposer - XMOS tools and source code are free!

Here's some notes:

1) Obtain the framework for building your own apps ...
git clone https://github.com/markseel/SimpleDSP.git


2) Set environment path to point to XMOS build tools ...
OS X:    /Applications/XMOS_xTIMEcomposer_Community_14.1.1/SetEnv.command
Windows: "c:\Program Files (x86)\XMOS\xTIMEcomposer\Community_14.2.0\SetEnv.bat"


3) Add your own source code to the starter application ...

The main application source code is in "main.c".
See "dsp.h" for DSP support functions.


// ================================================================================================
// "main.c"
// ================================================================================================

#include "dsp.h"

// A custom effects processing signal chain is constructed by adding DSP functionality to any of
// the seven audio processing threads below.  Each processing thread is allocated 100 MIPS of the
// total 700 MIPs available and executes once per audio cycle (48 kHz).

// Audio sample data flow is managed by the provided audio processing foundation object code that
// implements USB audio, USB MIDI and serial UART MIDI, the USB/ADC/DAC mixing functions,
// 15-band EQ, and firmware upgrades. Audio flows from through threads 1 through 7 in sequential
// order - all threads are executed once per audio cycle.

// The 'init' functions are each called once at program startup and should be used to initialize
// data used for audio processing.  The 'exec' functions are each called once per audio cycle. Note
// that audio samples are in Q1.31 fixed-point format.

// ------------------------------------------------------------------------------------------------

static void copy_property( int* dst, int idx, const int* src )
{
    int off = 5 * idx;
    dst[0] = src[0+off]; dst[1] = src[1+off]; dst[2] = src[2+off];
    dst[3] = src[3+off]; dst[4] = src[4+off];
}

#define EXAMPLE_PROPERTY_ID1 0x1500
#define EXAMPLE_PROPERTY_ID2 0x1501
#define EXAMPLE_PROPERTY_ID3 0x1502
#define EXAMPLE_PROPERTY_ID4 0x1503

static int example_property_data1[5];
static int example_property_data234[3*5];

void audio_thread_1_init( void )
{
    for( int ii = 0; ii <  5; ++ii ) example_property_data1[ii]   = 0;
    for( int ii = 0; ii < 15; ++ii ) example_property_data234[ii] = 0;
}

void audio_thread_1_exec( int audio_samples[6], int prop_id, const int prop_data[5] )
{
    // Example: Update property with single set of 5 parameters
    if( prop_id == EXAMPLE_PROPERTY_ID1 ) copy_property( example_property_data1, 0, prop_data );

    // Example: Update property with multiple (3) sets of 5 parameters
    if( prop_id >= EXAMPLE_PROPERTY_ID2 && prop_id <= EXAMPLE_PROPERTY_ID4 ) {
        int prop_index = prop_id - EXAMPLE_PROPERTY_ID2;
        copy_property( example_property_data234, prop_index, prop_data );
    }

    // audio_samples[0] contains sample for the left audio channel
    // audio_samples[1] contains sample for the right audio channel
    // other audio samples can be used to pass data to the next audio processing thread

    // Decrease volume by 50% for left and right channels
    audio_samples[0] /= 2; // Left
    audio_samples[1] /= 2; // Right
}

// ------------------------------------------------------------------------------------------------

void audio_thread_2_init( void )
{
}

void audio_thread_2_exec( int audio_samples[6], int prop_id, const int prop_data[5] )
{
    // Do nothing - audio samples are not modified
}

// ------------------------------------------------------------------------------------------------

void audio_thread_3_init( void )
{
}

void audio_thread_3_exec( int audio_samples[6], int prop_id, const int prop_data[5] )
{
    // Do nothing - audio samples are not modified
}

// ------------------------------------------------------------------------------------------------

void audio_thread_4_init( void )
{
}

void audio_thread_4_exec( int audio_samples[6], int prop_id, const int prop_data[5] )
{
    // Do nothing - audio samples are not modified
}

// ------------------------------------------------------------------------------------------------

void audio_thread_5_init( void )
{
}

void audio_thread_5_exec( int audio_samples[6], int prop_id, const int prop_data[5] )
{
    // Do nothing - audio samples are not modified
}

// ------------------------------------------------------------------------------------------------

void audio_thread_6_init( void )
{
}

void audio_thread_6_exec( int audio_samples[6], int prop_id, const int prop_data[5] )
{
    // Do nothing - audio samples are not modified
}

// ------------------------------------------------------------------------------------------------

void audio_thread_7_init( void )
{
}

void audio_thread_7_exec( int audio_samples[6], int prop_id, const int prop_data[5] )
{
}

// ===============================================================================================
// ===============================================================================================


4) Build the application ...
OS X:    ./build.sh main
Windows: build.bat main


5) Run the application in RAM using JTAG/XTAG ...

This requires an XTAG-2 or XTAG-3 XMOS JTAG adapter.

xrun --xscope main.xe


6) Convert the firmware image to a binary for burning FLASH via UART or USB ...
xflash --noinq --boot-partition-size 524288 main.xe -o main.bin


7) Burn firmware binary to FLASH ...
Using XTAG:   xflash main.xe
Using USB:    TODO
Using serial: TODO

markseel

Github repository renamed and added README file:
https://github.com/markseel/FlexFX/blob/master/README.md

First mini FlexFX board should be here this week.
DSP and effects coding examples coming soon!

Open to requests :-)

Digital Larry

#9
Hi Mark,

This may sound ridiculous, but I'm so cozy with the FV-1 that I want everything else to be just like it.  However some of its features are really handy, e.g.

1) Simple all pass filter for reverbs
2) Inter-sample interpolation on short delay line for flange/chorus
3) Sin/Cos LFO blocks
4) Something like a tanh waveshaper that can be put into a resonant state variable filter block so you can push the resonance into distortion territory while keeping aliasing under control.  FV-1 doesn't actually have a tanh, although there is something similar, I haven't tried it like this (yet).
5) LFSR noise generator

For some reason I am more into building blocks than finished effects!
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

markseel

Thanks for the suggestions - not crazy at all - makes sense to have building blocks.  I'll add those to the list for additions to the DSP library.  I'll also add up-sampling and down-sampling (interpolating and decimating FIR's) to support non-linear effects (overdrive, distortion) to help prevent aliasing of the added noise.  The TANH and other similar tables were on my list already - mostly for modeling fuzz/tube/diode transfer functions for distortion effects.  I'll add slew-rate limiting too.  The LFO's should be easy to do.  I already have Lagrange interpolation in the library to support flange/chorus sample interpolation - but I'd suggest that implementors also up/down sampling before/after chorus for the highest quality (assuming that there's enough memory for the higher sample-rate delay line).

Digital Larry

Lagrange interpolation is really important when you are playing ZZ Top covers! (sorry). I had no idea what it was so I looked it up. 

http://mathworld.wolfram.com/LagrangeInterpolatingPolynomial.html

The FV-1 inter sample interpolation looks like Lagrange interpolation with order=1. Slightly better than nothing.

Can you briefly explain the rationale for up/down sampling for chorus?  Being that I live so much of my life inside the FV-1 which doesn't normally lend itself to oversampling (though I guess it could), my only perception for "why" of over sampling is a) easier filtering out of aliasing components b) better stability of filters when f > fs/4.  But I rarely use filters for guitar effects that go that high. The sweet spot for wah type things is way less than that even at 32 kHz sampling.
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

markseel

#12
Good question :)  Largely to reduce the effect of frequency response loss at the higher frequencies as a result of non-ideal interpolation.  Also by 4x up-sampling you gain fractional sub-sampling (at 1x) before applying linear, polynomial, or some other form of interpolation between two samples as determined by an LFO.

Look at the graphs in the literature below and consider the effect of up-sampling, applying polynomial/Lagrange and how it moves the area of significant non-ideal effects of interpolation beyond audible ranges. 

Here's a lot of info on fractional delay lines via interpolation:
https://ccrma.stanford.edu/~jos/Interpolation/Welcome.html
https://ccrma.stanford.edu/~jos/Interpolation/Interpolation.pdf
https://ccrma.stanford.edu/~dattorro/EffectDesignPart2.pdf

I think it's a good idea to try a number of approaches including up/down sampling, polynomial interpolation, and a combo of both and listen to see how they sound.  The polynomial interpolation is low MIPS and low memory.  Up/down sampling requires longish FIR's and increases delay line memory requirements.

Also probably good to consider what the effect is intended to sound like.  If one is trying to recreate a BBD-based effect then perhaps linear interpolation and high-frequency signal loss is a good thing?

Sounds like there's more effects building-block ideas here :D

Digital Larry

Guy I used to work with was apparently in a band for awhile with Julius Smith (prog, of course).
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

markseel

#14
Here's an example of an API for effects components.  You'd put the *_init functions in a thread_X_init function and put the *_run functions in a thread_X_exec function.  The effect parameter updates would happen automatically - you just need to string together these *_run functions in the order that makes sense for your effects.  I'll work up an example next ...


// --------------------------------------------------------------------------------
// Cabinet Simulation
// --------------------------------------------------------------------------------

// Properties for cabsim control (note that there are five 32-bit words per property)

#define PROPERTY_CABSIM_FLAGS       0x1500 // First word is Enable(1)/bypass(0)
#define PROPERTY_CABSIM_DATA_A_NEXT 0x1501 // Used to load block A with IR data
#define PROPERTY_CABSIM_DATA_A_LAST 0x1502 // Used to load block A with IR data
#define PROPERTY_CABSIM_DATA_B_NEXT 0x1503 // Used to load block B with IR data
#define PROPERTY_CABSIM_DATA_B_LAST 0x1504 // Used to load block B with IR data
#define PROPERTY_CABSIM_DATA_C_NEXT 0x1505 // Used to load block C with IR data
#define PROPERTY_CABSIM_DATA_C_LAST 0x1506 // Used to load block C with IR data
#define PROPERTY_CABSIM_DATA_D_NEXT 0x1507 // Used to load block D with IR data
#define PROPERTY_CABSIM_DATA_D_LAST 0x1508 // Used to load block D with IR data
#define PROPERTY_CABSIM_DATA_E_NEXT 0x1509 // Used to load block E with IR data
#define PROPERTY_CABSIM_DATA_E_LAST 0x150A // Used to load block E with IR data

// Initialize cabinet simulator state variables.

void efx_cabsim_lowres1_init( void ); // 1200 sample IR, 25.0 msec
void efx_cabsim_lowres2_init( void ); // 1200 sample IR, 25.0 msec
void efx_cabsim_highres_init( void ); // 3000 sample IR, 62.5 msec

// Execute one pass (one audio cycle) of a tone control.  Note that since the complete
// IR convolution is split accross multiple filters that the 64-bit accumlator must
// be pass between each IR convolution section.
//
// SAMPLE is the input sample.
// ACCUMH is the upper 32-bit word of the 64-bit accumulator.
// ACCUML is the lower 32-bit word of the 64-bit accumulator.
//
// Returns the resulting cabsim sample for the current audio cycle as well as the
// intermediate 64-bit accumulator in order to chain multiple cabsim sub-sections
// (blocks) over some number of threads.

int  efx_cabsim_lowres1_runA( int sample_q28, int* accumH, int* accumL );
int  efx_cabsim_lowres1_runB( int sample_q28, int* accumH, int* accumL );
int  efx_cabsim_lowres2_runC( int sample_q28, int* accumH, int* accumL );
int  efx_cabsim_lowres2_runD( int sample_q28, int* accumH, int* accumL );
int  efx_cabsim_highres_runA( int sample_q28, int* accumH, int* accumL );
int  efx_cabsim_highres_runB( int sample_q28, int* accumH, int* accumL );
int  efx_cabsim_highres_runC( int sample_q28, int* accumH, int* accumL );
int  efx_cabsim_highres_runD( int sample_q28, int* accumH, int* accumL );
int  efx_cabsim_highres_runE( int sample_q28, int* accumH, int* accumL );

// Update cabinet simulator parameters

int  efx_cabsimA_update( int property_data[6] );
int  efx_cabsimB_update( int property_data[6] );
int  efx_cabsimC_update( int property_data[6] );
int  efx_cabsimD_update( int property_data[6] );
int  efx_cabsimE_update( int property_data[6] );

// --------------------------------------------------------------------------------
// Tone Control
// --------------------------------------------------------------------------------

// Property for tone controls (note that there are five 32-bit words per property)
//
// Word0 is Enable(1)/bypass(0)
// Word1 is bass level, 0 to 24 (0 = -12dB, 12 = 0dB, 24 = +12dB)
// Word2 is midrange level, 0 to 24 (0 = -12dB, 12 = 0dB, 24 = +12dB)
// Word3 is treble level, 0 to 24 (0 = -12dB, 12 = 0dB, 24 = +12dB)

#define PROPERTY_TONE_1_CONTROL 0x1511
#define PROPERTY_TONE_2_CONTROL 0x1512
#define PROPERTY_TONE_3_CONTROL 0x1513

// Intialize a tone-control object consisting of a 2nd order low-shelving filter
// for bass control, 2nd order peaking for mid, and 2nd order high-shelf for treble.
// The biquad coefficients are pre-computed and placed in a look-up table supporting
// +/- 12dB gain (in 1 dB steps, 25 total steps) for each tone-control band.
//
// FREQ_BASS is the frequency of the low-shelf corner frequency.
// FREQ_MID is the frequency of peaking filter midpoint frequency.
// FREQ_TREB is the frequency of the high-shelf corner frequency.
//
// Frequencies are represented as a fractional value normalized to Fs / 2 (24 kHz)
// 0.0 (0 Hz) > frequency < 1.0 (24 kHz)

void efx_tone1_init( float freq_bass, float freq_mid, float freq_treb  );
void efx_tone2_init( float freq_bass, float freq_mid, float freq_treb  );
void efx_tone3_init( float freq_bass, float freq_mid, float freq_treb  );

// Execute one pass (one audio cycle) of a tone control.
//
// SAMPLE is the input sample.
// Returns the resulting chorus sample for the current audio cycle.

int  efx_tone1_run( int sample_q28 );
int  efx_tone2_run( int sample_q28 );
int  efx_tone3_run( int sample_q28 );

// Update tone control parameters

int  efx_tone1_update( int property_data[6] );
int  efx_tone2_update( int property_data[6] );
int  efx_tone3_update( int property_data[6] );

// --------------------------------------------------------------------------------
// LFO
// --------------------------------------------------------------------------------

// Property for LFO control (note that there are five 32-bit words per property)
//
// Word0 is is the oscillator frequency - a fractional value normalized to Fs / 2 (24 kHz)

#define PROPERTY_LFO_1_CONTROL 0x1521
#define PROPERTY_LFO_1_CONTROL 0x1522
#define PROPERTY_LFO_1_CONTROL 0x1523

// Intialize an LFO object.

void efx_lfo1_init( void );
void efx_lfo2_init( void );
void efx_lfo3_init( void );

// Execute one pass (one audio cycle) of an LFO.  Must be called once per audio cycle.
// Function returns the LFO magnitude; Q28(0.0 <= MAGNITUDE < Q28(1.0)

int  efx_lfo1_run( void );
int  efx_lfo2_run( void );
int  efx_lfo3_run( void );

// Update LFO parameters

int  efx_lfo1_update( int property_data[6] );
int  efx_lfo2_update( int property_data[6] );
int  efx_lfo3_update( int property_data[6] );

// --------------------------------------------------------------------------------
// Chorus
// --------------------------------------------------------------------------------

// Property for chorus control (note that there are five 32-bit words per property)
//
// Word0 is the type; 0 for sine wave, 1 for triangle
// Word1 is is the oscillator period in number-of-samples (at 48 kHz)

#define PROPERTY_CHORUS_CONTROL 0x1530

// Word0 is chorus 1 enable(=1) or bypass (=0)
// Word1 is chorus 2 enable(=1) or bypass (=0)
// Word2 is chorus 3 enable(=1) or bypass (=0)

#define PROPERTY_CHORUS_1_PARAMS 0x1531
#define PROPERTY_CHORUS_2_PARAMS 0x1532
#define PROPERTY_CHORUS_3_PARAMS 0x1533

// Word0 is chorus sub-sampling interpolation type
// ---> 0 for none, 1 for linear, 2 for 2nd order Lagrange, 3 for 3rd order Lagrange
// Word1 is base delay in number of samples
// Word2 is the modulation depth in number of samples
// Word3 (Q28 format) is the wet/dry mix; dry=Q28(0.0), 50%=Q28(0.5), wet=Q28(0.999...).

// Initialize a chorus object.
//
// LFO number refers to LFO object (1, 2 or 3).
// INTERP_TYPE = 0 for linear, 1 for 2nd order Lagrange, 2 for 3rd order Lagrange.
// DELAY_LINE points to sample delay line of lenth 2^N.  Make sure it's long enough!

void efx_chorus1_init( int* delay_line );
void efx_chorus2_init( int* delay_line );
void efx_chorus3_init( int* delay_line );

// Execute one pass (one audio cycle) of a chorus.
//
// SAMPLE is the input sample,
// Returns the resulting chorus sample for the current audio cycle.

int  efx_chorus1_run( int sample_q28 )
int  efx_chorus2_run( int sample_q28 )
int  efx_chorus3_run( int sample_q28 )

// Update chorus parameters

int  efx_chorus1_update( int property_data[6] );
int  efx_chorus2_update( int property_data[6] );
int  efx_chorus3_update( int property_data[6] );

// --------------------------------------------------------------------------------
// --------------------------------------------------------------------------------

free electron

Hi Mark,
do you plan to release the schematic for those who'd prefer to design their own PCB? I wanted to build a good quality stereo speaker simulator for my mini looping pedalboard. Your project looks perfect for that.
Do you know what range of current consumption is to be expected?

markseel

I'll post the schematic so you can build your own PCB or make modifications.  I haven't measured power consumption yet but if the XMOS part is working hard I'd expect this board to draw a lot of power - perhaps 200mA to 250mA at 5V.

markseel

#17
Here's an example for using some effects components showing an LFO and a chorus followed by a tone control for channel 0 (Left channel).  They're all put in thread2 just for illustration but you can be put effects in any thread of course as long as the signal flow makes sense (audio flows through threads 1 through 7.

This example would use just a fraction of the 100 MIPs available to the thread and there's a total of seven threads (each at 100 MIPs) - so there's a lot of processing power available.  It's not out of the question to implement a complex effects chain (compression, overdrive(s), flanging/chrous, tone/eq, cab sim) on one board.

The 'audio_thread_N_init' gets called once at board power-up or after a reset.  The 'audio_thread_N_exec' gets called once every audio cycle with up to four audio channels and property data being supplied every cycle.  By default 'audio_samples[0]' and 'audio_samples[1]' represent left and right audio channels respectively.  By having up to four audio channels passed from thread to thread one could represent a single audio channel (left or right) at up to 4x up-sampling - good for non-linear effects like distortion (more on that later).

The property data is supplied automatically and sourced via USB MIDI or serial/UART MIDI.  'property_data[0]' is the property ID which is zero when no property is being updated and non-zero when a property is to be updated.  The effects components will use the data automatically (by calling the effect property update function) if the property ID applies to a particular effect (see code below).  The remaining five words in the property_data[6] array represent the five 32-bit words that comprise a property parameter set.


void audio_thread_2_init( void )
{
    // Initialize any effects used in this thread (thread #2)

    efx_lfo1_init();
    efx_chorus1_init();

    // Bass frequency = 0.00208333 * Fs/2  ---> 100.0  Hz for Fs = 48 kHz
    // Mid frequency = 0.0208333 * Fs/2    ---> 1.000 kHz for Fs = 48 kHz
    // Treble frequency = 0.0208333 * Fs/2 ---> 10.00 kHz for Fs = 48 kHz
    efx_tone1_init( 0.00208333, 0.0208333, 0.208333 );
}

void audio_thread_2_exec( int audio_samples[4], const int property_data[6] )
{
    // Process the left channel audio sample for the current audio cycle.
    // The right channel (audio_samples[1]) and the other two audio channels are not modified.

    efx_lfo1_run(); // Update LFO value
    sample[0] = efx_chorus1_run( sample[0] ); // Apply tone shaping to left audio channel
    sample[0] = efx_tone1_run  ( sample[0] ); // Apply chorus effect to left audio channel

    // Give effects objects an opportunity to update parameters if property data applies

    efx_lfo1_update   ( property_data );
    efx_chorus1_update( property_data );
    efx_tone1_update  ( property_data );
}

potul

So, where do you provide the LFO and chorus parameters (lfo freq, chorus depth, etc...? in the property_data array?

markseel

#19
Quote from: potul on June 24, 2016, 05:30:04 AM
So, where do you provide the LFO and chorus parameters (lfo freq, chorus depth, etc...? in the property_data array?

Yes - in the property data.  The property data is filled in and passed to the audio processing threads automatically (as they arrive from a USB or serial MIDI source) but you can force an update yourself this way:

void audio_thread_2_exec( int audio_samples[4], const int property_data[6] )
{
    // Process the left channel audio sample for the current audio cycle.
    // The right channel (audio_samples[1]) and the other two audio channels are not modified.

    efx_lfo1_run(); // Update LFO value
    sample[0] = efx_chorus1_run( sample[0] ); // Apply tone shaping to left audio channel
    sample[0] = efx_tone1_run( sample[0] ); // Apply chorus effect to left audio channel

    // Manually update LFO parameters ...
    property_data[0] = PROPERTY_LFO_1_CONTROL;
    property_data[1] = Q28( 0.00016667 ); // 4 Hz --> (4 / (Fs/2)) 
    efx_lfo1_update( property_data );

    // Manually update chorus parameters ...
    property_data[0] = PROPERTY_CHORUS_1_CONTROL;
    property_data[1] = 960; // Base delay in number of samples --> 20 msec
    property_data[2] = 192; // Modulation depth in number of samples --> 4 msec
    property_data[3] = Q28(+0.5); // wet/dry mix --> 50%
    efx_chorus1_update( property_data );

    // Manually update tone control parameters ...
    property_data[0] = PROPERTY_TONE_1_CONTROL;
    property_data[1] = Q28(0.00416667); // Bass (low-shelf corner frequency) is 100 Hz --> 100 / (Fs/2)
    property_data[2] = Q28(0.04166667); // Mid (peaking center frequency) is 1000 Hz --> 1000 / (Fs/2)
    property_data[3] = Q28(0.41666667); // Treble (hi-shelf corner frequency) is 10000 Hz --> 10000 / (Fs/2)
    efx_tone1_update( property_data );
}