The firmware for the synth was written from scratch, and is probably the most complex embedded firmware I've written by myself to date. In this section I've tried to document the inner workings to some degree, and to cite sources I found useful.
I decided at the beginning of the project not to use an RTOS and instead to see how far I could get with just C and the functionality provided by the hardware. As such, nearly all functionality is handled by interrupts and timers, and it took extra focus to avoid tying the achitecture to the specific hardware too closely. In retrospect, I'm not sure whether this made it more difficult to develop or kept me from making it too complicated. Perhaps a bit of both.
Rotary encoder input and MIDI data are handled by the respective interrupts for each, the data decoded as it arrives and placed into circular buffers/input queues. The rotary encoders are handled by a state transition lookup table, and MIDI by simple hand-written code that pieces together MIDI commands one byte per call and places the commands in the input queue once decoded.
Audio is mixed a block at a time, with each block being 64 samples. The DAC output buffer is 128 samples long, so that once one half the buffer has been played, the "mixer" interrupt can be triggered to refill the depleted 64 sample portion with new data. Because the microcontroller doens't have the hardware for 16-bit SPI DMA, an interrupt runs at the DAC sample rate to feed it data.
The "mixer" interrupt is where all of the audio processing happens. See below for features.
If there's any time left over between interrupts, the "main" non-interrupt code reads from the rotary encoder input queue and presents the user with a menu interface to control sound parameters. This way, audio mixing performance is guaranteed, and in high CPU usage situations, UI responsiveness is automatically sacrificed to avoid audio issues.
- Monosynth and polysynth modes:
- Four note polyphony in polysynth mode
- Monosynth mode with selectable note priority
- Most recent ("stack") note
- Highest note
- Lowest note
- Pitch glide in mono- and poly- mode
- New note envelope trigger settings:
- Reset to zero (quick fade prev. voice on channel)
- Keep current channel amplitude
- Legato mode
- Three synth engines:
- "Virtual analog" mode
- Two oscillators per voice; adjustable mix
- Various waveforms:
- Pulse (naive and polyblep antialiased)
- Sawtooth (naive and polyblep antialiased)
- Sine (linear interpolated)
- Triangle (naive)
- White noise (Linear congruential generator-based)
- Rustle noise (Linear feedback shift register-based)
- 2nd order state variable filter
- Filter cutoff pitch tracking (adjustable amount)
- Filter cutoff envelope (adjustable amount)
- FM synthesis mode
- Simple two operator FM synthesis
- Envelope modulated modulation index
- Selectable waveform as modulation source
- Modulator/carrier ratios adjustable independently
- Separate integer/decimal ratio adjustment
- "MultiSaw" mode
- Five "naive" sawtooth oscillators per voice
- Adjustable detune spread
- Adjustable center/detuned oscillator mix
- Same filter as "virtual analog" mode
- Amplitude envelope (ADSR) w/linear or exp. attack
- Adjustable pitch bend range
- Adjustable velocity sensitivity