Custom xmegaE5 FM synth
  • Hi all,

    I recently uploaded my custom FM synth hardware project to GitHub. My aim was to to create Electric Piano type sounds as much as I can. It is nowhere finished and rock-solid but I wanted to share.

    Details and sound sample can be found from the link: https://github.com/kehribar/xmega_fm-synth

  • Looks interesting @kehribar
    what are the editable parameters ?

  • Hi,

    All of the editable parameters can be seen inside the midi_controlMessageHandler() function which are: output filter cutoff frequency, output filter resonance rate, attack rate for all envelopes, decay rate for all envelopes and fm modulation depth. You can check the following link: https://github.com/kehribar/xmega_fm-synth/blob/master/firmware/main.c#L43

    There are many other hardcoded parameters like, LFO frequency, LFO depth, FM modulation index, FM frequency ratio, etc. can be made remote controllable but at the moment I haven’t made them.

    Also to everyone,

    I am open for suggestions and comments about the coding style, algorithm efficiency of the synthesiser algorithm and my implementation in general. This is why I posted this project in the first place actually :)

    ihsan.

  • Let’s start with the basics:

    • Don’t do your computations one sample at a time!! Do your computations on small blocks of samples (0.25 to 2 ms long). This allows all the important variables (phase accumulators) to stay in registers without moving to/from memory.
    • Search for code examples explaining how to use the DMA peripherals to automatically transfer data from RAM to the DAC. Use a timer at the sample rate as a DMA trigger, and program your DMA to do the transfer in the background.

    At this point, you can either:

    • Get an interrupt whenever a block DMA transfer is 50% complete, so you can fill another buffer.
    • Or you can simply poll the DMA transfer byte counter and write new samples whenever a block of (eg) 16 samples has been flushed to the DAC (see this example).

    With this framework in place, the workflow is the following:

    • Every 16 sample, you get an interrupt or a condition is met in the polling loop.
    • First, you process all the MIDI events from the FIFO and you can thus update the synth parameters.
    • Then, you recompute all envelopes, modulations, etc – all the stuff that doesn’t need to be computed for every sample.
    • Then you compute a block of 16 samples.

    Everything is happening “in precise modern lovers order” in this chunk of computation. The only concurrent resources left are the two FIFOs (one for the sample out buffer, one for MIDI), so you no longer need these ATOMIC_BLOCK. You no longer have a pesky interrupt and costly register push/pops at 48kHz or whatever.

    All these changes are going to free up 30 to 40% of your CPU.

  • Random tidbits:

    • To detect overflow, use if (value < increment) (increment is your “attackRate” variable). Thus, you don’t have to store the previous value.
    • It might be a matter of taste, but I write my envelope generation code so that the attack and decay phases are two particular cases of the same code (that only deals with positive increments and overflows). Details are here. Check the posts, this is a very “zen” implementation of envelopes that can easily be extended for ADSR, multistage, looping envelopes etc.
    • Your LFO modulates output amplitude, which is probably the least interesting parameter to modulate (unless you’re after an electric piano vibrato?). You’ll need an LFO on frequency, FM amount, cutoff…
    • Your variable g_cutoff should respond exponentially to the MIDI parameter.
    • Maybe I’m wrong about it, but your MIDI decoding code does not seem to handle running status!
    • It’s a good start to put all the hardware dependent routines in their own file, but then “DACA.CH1DATA = totalOutput + 1024” (or whatever will replace it!) should belong there too!
  • Hi pichenettes,

    Thanks a lot for your in depth analysis and comments.

    I actually worked with DMA and event system of the Xmega series before. (I made 20 FPS camera with the same board: https://github.com/kehribar/xmega_ov7670) I didn’t consider using DMA for the audio as well but it all makes sense to fill a buffer now and dequeue it with DMA later. Compiler won’t load most of the ram variables to the registers each time I do a sample computation and that will save time. Not an easy optimisation to see. Clever!

    About the MIDI, you are right. I’m only handling regular 3 byte length note on/off and control messages. I’ve just learned about the existence of “running status” now. :)

    For LFO, I’m really after to create a ‘Rhodes’ type of sound. Therefore I guess I’ll keep that part.

    Here one simple question:

    • Why should cutoff respond exponentially to the control variable? I’m guessing because logarithmic pots were used in the analog filter control knobs.

    Also again thanks for all other insights. They are very thoughtful.

    Best,
    ihsan.

  • > Why should cutoff respond exponentially to the control variable?

    All analog filters are designed like that.

    The cutoff control of an analog filter uses a linear pot, but the filter circuit itself is designed to have an exponential response to voltage (a part of the circuit, usually a transistor pair, will create an exponential response). So when you sweep a filter with a decaying, linear envelope that goes from 8V to 0V in 1s, it will go from 10kHz to 625 Hz in 0.5s, and 625 Hz to 40 Hz in the remaining 0.5s

    Why? Our perception of frequency is on a logarithmic scale. This applies to fundamental frequency (pitch), but also to “frequency distribution” (timbre), which we aim to control with the filter. More practically, this allows the use of the filter resonance as a source of playable pitched notes, and this allows keytracking (ensures that the feeling of “darkness” or “brightness” of a sound is not affected by how low or high you play it on the keyboard).