ESP32-S3 Polysynth Details
ESP32-S3 Polysynth
Video below so you can hear what sort of sounds it makes!
I have used some little 8-bit processors in the past to make simple noise boxes and drone synths, but I came to realise that now in 2026 a cheap ESP32-S3 microcontroller and DAC with the right programming could pretty well generate the same sounds as virtually any synth produced in the 80's and 90's, so I set about seeing what I could come up with..
I like synths to look like a synth, lots of knobs and buttons - rather than a large swathe of black metal with a solitary LCD and encoder popped in the middle. I was toying with the idea of seeing if I could make a modern version of the old Maplin 5600 Synthesizer as it is well documented, and hasn't got too many features that would be difficult to emulate. It also ticks the box for knobs, switches and buttons.
I started looking further into the design of the 5600 - 4 oscillators, mixer, envelope shaper etc and decided that I didn't want to 'clone' it, rather just use it as inspiration. I don't know who owns the rights to the 5600, and the last thing I want is to get into any copyright / IP stealing type bother so I'll just create something new from scratch. The 5600 has a nice EMS style patch panel, so I will try and add a panel or matrix of some sort into my creation, and make sure that there's lots of knobs and buttons, and no menu diving! Everything you can adjust on the synth will have a button or knob sat there on the front panel. That was my basic brief, and starting point.
This makes the actual electronics hardware for the Synth part pretty simple, I used an off the shelf ESP32-S3 Dev module, and then created a small PCB that contained an PCM5102 Digital to Analogue converter Chip that takes the digital I2S Audio data from the ESP32-S3 and gives you a Stereo L/R output. It also has a simple Midi In using an opto-Isolator, a MCP2526 CAN transceiver Chip and then an SD Card reader so I can save patches and load wavetables into the synth Engine.
I like synths to look like a synth, lots of knobs and buttons - rather than a large swathe of black metal with a solitary LCD and encoder popped in the middle. I was toying with the idea of seeing if I could make a modern version of the old Maplin 5600 Synthesizer as it is well documented, and hasn't got too many features that would be difficult to emulate. It also ticks the box for knobs, switches and buttons.
I started looking further into the design of the 5600 - 4 oscillators, mixer, envelope shaper etc and decided that I didn't want to 'clone' it, rather just use it as inspiration. I don't know who owns the rights to the 5600, and the last thing I want is to get into any copyright / IP stealing type bother so I'll just create something new from scratch. The 5600 has a nice EMS style patch panel, so I will try and add a panel or matrix of some sort into my creation, and make sure that there's lots of knobs and buttons, and no menu diving! Everything you can adjust on the synth will have a button or knob sat there on the front panel. That was my basic brief, and starting point.
Practicalities
Knobs, switches and buttons are great from a users point of view - tweak something and then hear an instant change in the sound produced. The more controls the better, and they look cool..
The ESP32-S3 processor though is quite limited on it's IO count, if I considered what I would I use before any controls ( Audio I2S port, Midi, SD Card ) it doesn't really leave a lot of IO available for the knobs, switched and buttons.
In my day job, I design sensors for the aviation industry and they use CAN bus to communicate with each other, and the host. It's mainly used in the automotive world, but it's a great format for sending a lot of control information across a network ( CAN = Controller Area Network )
The ESP32-S3 has a built in CAN controller, although Espressif who make the ESP32-S3 call it TWAI which relates to ( Two Wire Automotive Interface ) but it is CAN, you just need to add an external transceiver IC to drive and read from the CAN bus. If I use the CAN interface, I don't need the ESP32-S3 to read or poll any physical controls, I can just send it a CAN frame with a message inside that would say something like " Set Oscillator 3 to Sawtooth " or " Lower Oscillator 1 by an octave "
The ESP32-S3 processor though is quite limited on it's IO count, if I considered what I would I use before any controls ( Audio I2S port, Midi, SD Card ) it doesn't really leave a lot of IO available for the knobs, switched and buttons.
In my day job, I design sensors for the aviation industry and they use CAN bus to communicate with each other, and the host. It's mainly used in the automotive world, but it's a great format for sending a lot of control information across a network ( CAN = Controller Area Network )
The ESP32-S3 has a built in CAN controller, although Espressif who make the ESP32-S3 call it TWAI which relates to ( Two Wire Automotive Interface ) but it is CAN, you just need to add an external transceiver IC to drive and read from the CAN bus. If I use the CAN interface, I don't need the ESP32-S3 to read or poll any physical controls, I can just send it a CAN frame with a message inside that would say something like " Set Oscillator 3 to Sawtooth " or " Lower Oscillator 1 by an octave "
Hardware
The AT238p is being well utilised, and with all this extra IO the schematic for the Oscillator control board does look a little busy:
But in terms of the actual PCB, everything fits into place quite nicely, all the switches / buttons and leds on the front:
On the rear you can see the two IO expanders, Analogue Mux in the middle and then the processor and CAN IC's on the right and finally the two connectors for the CAN and power - In and out to daisy chain to the next panel.
The board supports the controls for 2 oscillators, hence the two rows of identical controls - the final finished PCB with the front panel fitted ended up looking like this:
As there are 3 identical Oscillator panels ( gives 6 controllable oscillators ) I needed a way to differentiate one panel from the other, so the ESP32-S3 knows which oscillator the message is intended for. Using a couple of spare IO on the AT238p, I had 2 Ident lines which can be tied either to VCC or VSS with some zero ohm links, when the board powers up, it reads its ident and then knows which oscillator board it is. When the ESP32-S3 boots up, it then sends a request out on the CAN bus and asks what panels are connected, and configures number of voices and functions accordingly.
That basically sums up what control you have over the oscillators, the Oscillator control PCB although it looks complicated is really nothing more than a distributed IO node. It keeps track locally of all the knobs, switches and buttons and at startup, the ESP32-S3 sends a CAN 'Parameter Request' message - the Oscillator board then sends all the values for the knobs and controls, it also strobes all the led's on the board, so when you power up and see all the led's sequence you know it's all working and sending data to the ESP32-S3. The majority of the time the CAN bus sits idle, as the control boards know what the local values are, it only ever sends a change of value to the ESP32-S3 when the user alters a knob, switch or button. The Led's are controlled by the ESP32-S3 though, if you turn on a modifier - the control board sends a 'Modifier Enabled' message, and the ESP32-S3 replies with an acknowledgement it has been turned on, and the control board then illuminates the Modifier enabled LED. Same for Clone / Solo / FM mode / Hard Sync - all these leds are controlled by the ESP32-S3.
On the rear it has the same IO expanders, processor and CAN chips, and connections for the CAN bus and power:
The final finished Commons Controls with it's front panel fitted then looks like this:
Oscillator Functions
The Oscillator panel has the following controls and functions per oscillator channel.
Octave control: Sets the overall oscillator pitch at normal, or +/- 1 octave. The original Maplin 5600 had a 7 octave range for each oscillator!
Detune / Pan control: There's a little selector button above the control that determines if the control is for detune, or Pan. Pressing the selector button illuminates the relevant led to let you know what mode it is in. Another little feature is that the LED will then flash the more the knob is turned, if you detune or pan by a lot, the led flashes faster. The reason being that it is very difficult to set the exact midpoint with a knob, so I've created a 'flat' zone where the knob is at mid and the LED then reverts to solid on, so you can easily go back to zero detune or centre pan. The Detune / Pan control also has a different function in FM mode, I'll cover that at the end of this section.
Waveform: This is a rotary selector switch and it sets the waveform that the oscillator will produce
Octave control: Sets the overall oscillator pitch at normal, or +/- 1 octave. The original Maplin 5600 had a 7 octave range for each oscillator!
Detune / Pan control: There's a little selector button above the control that determines if the control is for detune, or Pan. Pressing the selector button illuminates the relevant led to let you know what mode it is in. Another little feature is that the LED will then flash the more the knob is turned, if you detune or pan by a lot, the led flashes faster. The reason being that it is very difficult to set the exact midpoint with a knob, so I've created a 'flat' zone where the knob is at mid and the LED then reverts to solid on, so you can easily go back to zero detune or centre pan. The Detune / Pan control also has a different function in FM mode, I'll cover that at the end of this section.
Waveform: This is a rotary selector switch and it sets the waveform that the oscillator will produce
- Wav - This relates to a wavetable .wav file loaded from the SD Card
- Tri - computed Triangle waveform
- Noise - random white noise
- Chaos - computed chaos oscillator with an additional seed control
- Pulse - computed PWM with an additional pulse width control
- Saw - computed sawtooth waveform
Pulse Width: This controls the ratio on the PWM waveform if selected. Also available as a destination on the routing panel
Chaos-R: This is the seed value for the Chaos waveform if selected. Also available as a destination on the routing panel
There are then 2 modifiers, a Wavefolder and a Comb resonator. Originally I had a Sample and Hold modifier, but soon realised I didn't need 6 of them so changed the S&H to the Comb Resonator. S&H is now implemented with the LFO.
There are then 2 modifiers, a Wavefolder and a Comb resonator. Originally I had a Sample and Hold modifier, but soon realised I didn't need 6 of them so changed the S&H to the Comb Resonator. S&H is now implemented with the LFO.
Wavefolder: A little selector button at the top enables the wave folder, and subsequent presses determine if the control knob alters the Bias amount, or the number of folds applied. Pressing and holding the button then switches the wavefolder off.
Comb Resonator: Same as the wavefolder, the selector buttons enables the comb resonator, and subsequent presses determine if the control knob alters the pitch or feedback values. Pressing the holding the button then switches the wavefolder off.
The Modifiers do take quite a lot of processing power to implement, if you enabled both on all 6 voices, then that's 12 lots of extra DSP work the ESP32 has to complete, and sometimes it doesn't finishing doing the math before the audio I2S buffer has emptied, which then results in either glitches or a crash! So the modifiers are gated, if you select one you can't select the other. The only real fix is to upgrade the processor to something like a Teensy 4.1 or RaspPI - I effectively reached the limit of what the ESP32-S3 can do!
Level: This is an individual output level control for the oscillator.
There are then some extra features which I added in to make workflow easier, and to give more options for different sounds.
Comb Resonator: Same as the wavefolder, the selector buttons enables the comb resonator, and subsequent presses determine if the control knob alters the pitch or feedback values. Pressing the holding the button then switches the wavefolder off.
The Modifiers do take quite a lot of processing power to implement, if you enabled both on all 6 voices, then that's 12 lots of extra DSP work the ESP32 has to complete, and sometimes it doesn't finishing doing the math before the audio I2S buffer has emptied, which then results in either glitches or a crash! So the modifiers are gated, if you select one you can't select the other. The only real fix is to upgrade the processor to something like a Teensy 4.1 or RaspPI - I effectively reached the limit of what the ESP32-S3 can do!
Level: This is an individual output level control for the oscillator.
There are then some extra features which I added in to make workflow easier, and to give more options for different sounds.
Solo: Pressing this will mute all the other oscillators, effectively giving you a solitary mono synth. It allows you to work on 1 oscillator at a time to select waveforms, adjust modifiers etc to generate the desired sound.
Clone: This follows on from the Solo button, once you have used solo to set the oscillator up in mono, pressing clone then copies across all the oscillator parameters to the other oscillators, so you can then play in poly with the same tone generated in solo mode. You can then also make slight adjustments to all the oscillators, so each oscillator will have a slightly different timbre or tone to the others and then when you play in poly, you get a much more richer sound with nuances as you play because all the oscillators are slightly different.
Clone: This follows on from the Solo button, once you have used solo to set the oscillator up in mono, pressing clone then copies across all the oscillator parameters to the other oscillators, so you can then play in poly with the same tone generated in solo mode. You can then also make slight adjustments to all the oscillators, so each oscillator will have a slightly different timbre or tone to the others and then when you play in poly, you get a much more richer sound with nuances as you play because all the oscillators are slightly different.
FM Mode: This works per oscillator board pair, pressing it effectively FM modulates oscillator 1 with oscillator 2. In this mode, the detune / pan control function changes to FM Modulation amount for the top oscillator and FM Ratio amount for the bottom oscillator, both 'detune' and 'pan' leds light to indicate this. When you then clone from FM mode, you effectively clone the remaining oscillator pairs so on a 6 oscillator synth you end up with 3 FM voices, as each FM voice needs 2 oscillators to generate.
Sync to 1: This does a hard sync on oscillator 2 to oscillator 1, I stole the function off the Moog Prodidgy! but it only works well when the synth is played in mono or unison mode.
That basically sums up what control you have over the oscillators, the Oscillator control PCB although it looks complicated is really nothing more than a distributed IO node. It keeps track locally of all the knobs, switches and buttons and at startup, the ESP32-S3 sends a CAN 'Parameter Request' message - the Oscillator board then sends all the values for the knobs and controls, it also strobes all the led's on the board, so when you power up and see all the led's sequence you know it's all working and sending data to the ESP32-S3. The majority of the time the CAN bus sits idle, as the control boards know what the local values are, it only ever sends a change of value to the ESP32-S3 when the user alters a knob, switch or button. The Led's are controlled by the ESP32-S3 though, if you turn on a modifier - the control board sends a 'Modifier Enabled' message, and the ESP32-S3 replies with an acknowledgement it has been turned on, and the control board then illuminates the Modifier enabled LED. Same for Clone / Solo / FM mode / Hard Sync - all these leds are controlled by the ESP32-S3.
Common Controls
After the oscillators, the next control panel I called the 'Common Controls' mainly because it controlled things that were common to all the oscillators. It has the Envelope controls, filter controls, play mode and a couple of LFO's. I originally included a ring modulator in there, but as I developed the ESP32-S3 firmware a little more, it made more sense to turn it into something I called a multi-modulator, effectively a small effects section - don't get to excited!
I used the same schematic as the Oscillators and trimmed out a couple of the switches and leds:
I used the same schematic as the Oscillators and trimmed out a couple of the switches and leds:
You can see it looks very similar, the same with the PCB design:
On the rear it has the same IO expanders, processor and CAN chips, and connections for the CAN bus and power:
The ESP32-S3 is also able to detect if there is a Commons panel installed when it powers up and sends out the request message. If there isn't one fitted, the ESP32-S3 uses a set of default values. Same as the oscillator board, during power up the Commons panel sends all it's parameter values to the ESP32-S3 and strobes through all its led's to confirm everything is okay.
LFO-1 and LFO-2: These are both identical in function and have the following controls:
Filter Frequency: Sets the filter cut-off point, range approx 20hz to 18Khz
Filter Resonance: Emphasises the frequencies around the cut-off point
There's then a little selector button to select between Envelope control or Tracking control
Commons Panel Functions
Looking at what the controls are on the Commons panel in a little more detail.LFO-1 and LFO-2: These are both identical in function and have the following controls:
- Retrigger - Triggers the LFO on every keypress, rather than running continuously
- Rate - The frequency the LFO Oscillator runs at.
- Delay - Period that occurs before the LFO starts running
- Waveform - ( Sine / Triangle / Ramp / S&H )
Normally you also have a 'Depth' control on an LFO, but in this synth the depth is set on the Routing panel, you can assign an LFO to a target destination - pitch for example - and then use the modulation control on the routing panel to set how much the LFO will affect the pitch, essentially the same as depth.
Attack: Envelope Attack time
Decay: Envelope Decay time
Sustain: Envelope Sustain level
Release: Envelope Release time, goes up to several seconds
Play Mode button: Pressing this cycles through the various play modes / oscillator arrangements
Attack: Envelope Attack time
Decay: Envelope Decay time
Sustain: Envelope Sustain level
Release: Envelope Release time, goes up to several seconds
Play Mode button: Pressing this cycles through the various play modes / oscillator arrangements
- Poly - As you play, an oscillator is assigned to each note pressed up to a maximum of 6 notes. If more than 6 notes are pressed, it will steal the oscillator from the first of the 6 notes pressed and use it for the 7th and so on.
- Duo - This pairs up Oscillators (1+2), (3+4), (5+6) so you get 3 note poly. Allows for 2 voices per note for a fuller sound, or when the Oscillators are set to FM mode.
- Unison - This plays all 6 oscillators on every key press but is in effect now a mono synth. Ideal for rich textured sounds where you can set the oscillators to different waveform types etc. The portamento control on the Patch panel also becomes active in Unison mode.
Unison Detune: When unison play mode is set, this knob will simultaneously detune all 6 oscillators, and it also spreads out the stereo field by adjusting the pan setting for each oscillator.
Filter Resonance: Emphasises the frequencies around the cut-off point
There's then a little selector button to select between Envelope control or Tracking control
Envelope: Determines how much the Frequency control follows the ADSR Envelope shape as a note places. Set at zero, the filter stays the same throughout.
Tracking: Sets how much the filter frequency follows the pitch of the notes played, higher notes increase the filter frequency to help the timbre scale with pitch
Tracking: Sets how much the filter frequency follows the pitch of the notes played, higher notes increase the filter frequency to help the timbre scale with pitch
Another selector button, and that selects between filter mode:
The Routing panel needs a lot more IO than the commons or Oscillator panel as there are a lot of Leds to manage, and also a couple of 7 segment displays to show the route number and also how much modulation is applied on the route. There are no analogue values, I used an encoder on this one for the modulation amount so a precise value can be set. The hardware uses the same AT238p processor, MCP23017 IO expanders for the buttons and some leds, but I then added in a couple of MAX7219 Led drivers, one for the 7-segment displays and another one for the LED matrix:
- Lowpass Filter - Only lets the lower frequencies pass through
- Bandpass Filter - Only lets the mid range frequencies pass through
- Highpass Filter - Only lets the high range frequencies pass through
The next section on the Commons panel originally started off as a Ring Modulator where you could modulate various oscillator pairs against each other. Great in unison as all voices were used, but in poly not all oscillators come into play so the effect would drop in and out. I then changed it to work as a simple effects unit with 4 modes:
- Tremolo - Old School tremolo type effect where it alters the level of each oscillator, the rate of the tremolo effect is controlled by LFO2
- Pan - This is a stereo panning effect, a very poor attempt at a pseudo Leslie type thing. It pans each oscillator voice with more range for every oscillator in play, sounds good through headphones when you play a chord in poly mode, as each oscillator moves in different positions and at different speeds to all the others. The pan speed is controlled via LFO2
- Ring - Ring modulator, modulates oscillator 1 against the next oscillator in use. Makes for some interesting metallic sounds, especially if you're in FM mode and using Duo to play
- Dist - Distortion type effect, just adds a little bit of clipping to the overall audio. Very subtle in operation.
The multi modulator is enabled with the imaginatively labelled 'Enable' button, and then the amount of effect applied is controlled with the MODULATION control. There is also another button that is labelled up as 'User 2'. Originally when it was just a Ring Modulator this button would 'freeze' the output and generate weird noises. Now it is assignable and can be used for various things as and when I write the code! Currently it works as an odd 'Invert' button - but it inverts the key-bed. Imagine the key-bed pivoted at middle 'C' - pressing this button then flips the key-bed over, so the lower notes now appear at the RHS of the key bed and the higher notes at the LHS. Why? well it seemed like a good idea at the time, I thought it may help with musical composition - play a phrase, and then flip the key -bed over and hear what it sounds like when you play it again.. The Jury is still out on this one!
A little insight into the software at this point. I set an arbitrary limit on the number of voices the synth should have. It's a trade-off between processing power available and the complexity of each voice. The original Maplin 5600 had 4 voices, so I wanted a little more and set the limit at 8, however in practice I use 6 as the software detects how many oscillator boards are on the CAN bus and assigns the right number of voices. The ESP32-S3 has to model the entire audio path, before it gets transferred over I2S into the DAC and converted into audio. There is essentially an audio buffer in code where the computed audio is placed, and in the main loop on every cycle, the buffer is emptied or 'consumed' by the DAC, so the software has to 'refill' the buffer ready for the next loop, to keep a constant stream of data that the DAC consumes and turns into audio. If the processor / software can't keep up - because it's modelling lots of voices and modifiers then the buffer runs dry and you end up with audio glitches and dropouts as the DAC hasn't got anything to consume. From experimentation the processor has a maximum of 1300uS processing time each cycle to fill up the buffer.
The ESP-32 has to essentially model an entire synth audio path for each voice, so for 6 voices it models 6 oscillators, 6 Envelopes, 6 Filters before passing it over to the DAC. In this build, I have put physical controls for all 6 oscillators, but the envelope and filter share a set of common controls. If you really wanted to, you could add a set of common controls for each oscillator and then be able to alter the envelope and filter for each voice independently, but I took the easier option and just had the one set of common controls, even though the software is modelling 6 envelopes and 6 filters.
With the basic synth audio path modelled and running, the next stage is Routing. The Maplin 5600 had a pin matrix panel that would let you connect an oscillator to a filter or LFO. I wanted this synth to do the same, but matrix panels are hard to find and very expensive so I came up with a digital version. Instead of putting a pin in an X-Y position, I would have buttons on the X-Y axis with a matrix of Leds. Pressing the relevant X-Y buttons would light the Led where the virtual 'pin' would have gone. On the 5600 you can put in as many pins as you want, so I referred to the 'pin' as a route and allowed you to store multiple routes.
A little insight into the software at this point. I set an arbitrary limit on the number of voices the synth should have. It's a trade-off between processing power available and the complexity of each voice. The original Maplin 5600 had 4 voices, so I wanted a little more and set the limit at 8, however in practice I use 6 as the software detects how many oscillator boards are on the CAN bus and assigns the right number of voices. The ESP32-S3 has to model the entire audio path, before it gets transferred over I2S into the DAC and converted into audio. There is essentially an audio buffer in code where the computed audio is placed, and in the main loop on every cycle, the buffer is emptied or 'consumed' by the DAC, so the software has to 'refill' the buffer ready for the next loop, to keep a constant stream of data that the DAC consumes and turns into audio. If the processor / software can't keep up - because it's modelling lots of voices and modifiers then the buffer runs dry and you end up with audio glitches and dropouts as the DAC hasn't got anything to consume. From experimentation the processor has a maximum of 1300uS processing time each cycle to fill up the buffer.
The ESP-32 has to essentially model an entire synth audio path for each voice, so for 6 voices it models 6 oscillators, 6 Envelopes, 6 Filters before passing it over to the DAC. In this build, I have put physical controls for all 6 oscillators, but the envelope and filter share a set of common controls. If you really wanted to, you could add a set of common controls for each oscillator and then be able to alter the envelope and filter for each voice independently, but I took the easier option and just had the one set of common controls, even though the software is modelling 6 envelopes and 6 filters.
With the basic synth audio path modelled and running, the next stage is Routing. The Maplin 5600 had a pin matrix panel that would let you connect an oscillator to a filter or LFO. I wanted this synth to do the same, but matrix panels are hard to find and very expensive so I came up with a digital version. Instead of putting a pin in an X-Y position, I would have buttons on the X-Y axis with a matrix of Leds. Pressing the relevant X-Y buttons would light the Led where the virtual 'pin' would have gone. On the 5600 you can put in as many pins as you want, so I referred to the 'pin' as a route and allowed you to store multiple routes.
Routing Hardware:
The Routing panel needs a lot more IO than the commons or Oscillator panel as there are a lot of Leds to manage, and also a couple of 7 segment displays to show the route number and also how much modulation is applied on the route. There are no analogue values, I used an encoder on this one for the modulation amount so a precise value can be set. The hardware uses the same AT238p processor, MCP23017 IO expanders for the buttons and some leds, but I then added in a couple of MAX7219 Led drivers, one for the 7-segment displays and another one for the LED matrix:
You can see it's very similar to the previous schematics, but has more IO expansion. The board layout was probably the trickiest of the lot, but came out like this:
On the top are the two MAX7219 devices for driving the 7 segment displays, and the 7 X 6 Led matrix. With the front panel on, it looks rather neat:
However I won't lie - getting all those LED's to line up with the holes in the metalwork took the patience of a saint, just when you think they're all lined up you tighten the screws and one folds back down underneath!
Source Select
This button cycles through all the available sources that can control a destination or target within the synthesizers source code:
Unsurprisingly the hardware uses the same AT238p / CAN combo and IO expander. The only difference is that there is a 10 character display which is used to show the patch or filenames. In reality it just needs a couple of digital IO lines from the AT238p to drive it.
I only used an 8 way analogue multiplexer and just 4 inputs, so there is some spare capacity if ever needed. The PCB layout was relatively easy, just keeping the numeric buttons in a neat order:
Routing Panel Functions
The Routing panel doesn't cause the ESP32-S3 to produce any sounds, all it does is allow you to effectively get variables in the code to interfere with other variables in the code! I refer to them as a source and a destination. It looks complicated to use but the operation is very simple in reality. You have to select your source of modulation, then select the destination which can be a global value that affects all voices, or it can be any combination of individual oscillator specific destinations. You then have a modulation amount control that determines how much the source modulates the destination, it is can apply positive or negative modulation so the value ranges from -99 to +99 and shown on the little 7 segment display. Once you have set your source and destination, and modulation amount - that is effectively a 'Route' You can then save that route, and then create a new one that will work in parallel with other routes - it would be the same analogy as adding another patch cable in a modular system.Source Select
This button cycles through all the available sources that can control a destination or target within the synthesizers source code:
- LFO1 - this is a regular LFO and its controls are set on the Common Controls Panel
- LFO2 - this is a regular LFO and its controls are also set on the Common Controls Panel
- ADSR - This is the sounds envelope shape and set on the Commons Controls Panel
- VELOCITY - This relates to how hard the key is pressed when playing
- NOTE - This relates to the pitch of the note being played
- MOD WHEEL - The Modulation wheel on the midi controller connected to the Synth
You can only select 1 source at a time, but you can create separate routes and each route can have a different source.
Global Destination
Global Destination
This button cycles through all the available global destinations - global meaning it effects all the voices at once:
- CUTOFF - Filter Cutoff Frequency
- RESONANCE - Filter Resonance
- MAST LEV - Master Level - Ideal for tremolo / wah type effects
- MORPH - Morph control between 2 wavetable waveforms if used
- LFO2 MOD - The Rate input for LFO2, so you can effectively use LFO1 to modulate LFO2
If you choose not to use a Global destination, then you can use the matrix and pick an oscillator specific destination:
- PITCH - Individual Oscillator Pitch
- PULSE W - Pulse width setting for the Pulse waveform if selected
- FOLDS - Number of folds applied by the wavefolder modifier
- BIAS - Wavefolder bias value
- CH SEED - Chaos Seed value for the Chaos waveform if selected
- COMB R - Resonance control for the Comb Resonator modifier
- LEVEL - Overall Oscillator level
If the Oscillators are in FM mode, then two of the oscillator specific destinations change:
- BIAS - Becomes FM Ratio
- COMB R - Becomes FM Modulation or Level
Once you have selected your source and destination, you can control how much the source modulates the destination with the MODULATION control, this can be a positive or negative value in the range from -99 to +99 and it is shown on the 7 segment display. The Synth will sound in real time while you use the Modulation control so you can find the right value. This combination of Source + Destination + Modulation amount is called a Route, and you have the following Route controls:
- SAVE ROUTE: Pressing this button saves the route into memory.
- INC: Pressing this Increments to the next route. The display shows you what route is selected.
- DEC: Pressing this goes back to the previous route.
- CLEAR ROUTE: This button clears the route information
When you skip through the routes with the INC and DEC buttons, the associated Leds / Matrix display how the route has been defined.
Patch Panel
The remaining panel is the Patch Panel, this allows you to store and recall a patch from memory and also load up a .wav file into the two wavetable slots A and B. This didn't take up a great deal of space on the PCB size I had adopted, so I also included an Arpeggiator and a couple of analogue controls that I had forgotten to include earlier on in the design, such as portamento and master volume!
Unsurprisingly the hardware uses the same AT238p / CAN combo and IO expander. The only difference is that there is a 10 character display which is used to show the patch or filenames. In reality it just needs a couple of digital IO lines from the AT238p to drive it.
I only used an 8 way analogue multiplexer and just 4 inputs, so there is some spare capacity if ever needed. The PCB layout was relatively easy, just keeping the numeric buttons in a neat order:
I didn't have a 3D model for the display, hence just the header in the board render in KiCad. With the metal front panel fitted, this is how it turned out:
The most important control is STORAGE, as it decides if the display and numeric buttons are dealing with a Patch or Wavetable A or Wavetable B. In the Patch position the display will show you the name of the patch in use. Pressing the INC or DEC buttons will scroll through all the patches saved on the SD Card and the name will appear on the display, or you can key in a 3 digit number on the numeric buttons to get the Patch directly. Once you have selected the patch you want, you press the LOAD button and the Patch Led flashes once to acknowledge and then the synth loads up the patch.
With the STORAGE control on in the A position you use the INC and DEC buttons to scroll through all the .wav oscillator waveforms - or use the numeric buttons to enter the 3 digit number directly to get to the .wav file you want to use. Pressing LOAD then causes the Table A Led to flash once to acknowledge, and the .wav file is loaded into the Wavetable A slot. The process is then identical if you have the STORAGE control in the B position and it loads a .wav file into the Wavetable B slot
With the STORAGE control on in the A position you use the INC and DEC buttons to scroll through all the .wav oscillator waveforms - or use the numeric buttons to enter the 3 digit number directly to get to the .wav file you want to use. Pressing LOAD then causes the Table A Led to flash once to acknowledge, and the .wav file is loaded into the Wavetable A slot. The process is then identical if you have the STORAGE control in the B position and it loads a .wav file into the Wavetable B slot
The remaining file handling button is SAVE, this only works when the STORAGE control is in Patch mode. Pressing SAVE will take a snapshot of all the synth parameters and routes defined and save them to the next free slot on the SD Card.
The Patch Panel also houses the Arpeggiator:
Basic layout of the casing, all the trays are the same size - apart from the green ones that are deeper as one holds the ESP32-S3 board, and the other holds the mains power supply.
Assembly then is just a matter of screwing the front plate to the PCB with some 12mm hex spacers in between, and then some 8mm spacers on the rear of the PCB that are used to screw the assembly into the case from the rear. From the picture above you can see the extent of the wiring involved, black and red are the 5v power and the white / blue twisted pair is the CAN bus. I made up some simple cables with connectors either end, and they just plug in from one board to the next.
Building one yourself
The Patch Panel also houses the Arpeggiator:
- ENABLE - Turns the Arpeggiator on or off
- RATE - Controls the speed the Arpeggiator runs at - the Display shows BPM
- LATCH - Holds the current pattern so you can take hands of the keys
- LENGTH - Controls the note length played by the Arpeggiator
- PATTERN - Sets the ARP pattern Up / Down / Up and Down / Triad
- RANGE - Sets the number of Octaves the Arpeggiator plays through
The remaining controls for the synth are also located on the Patch Panel as there was no where else to put them!
MORPH: This is like a balance control between the .wav files stored in Wavetable A and Wavetable B. Set it full left and Wavetable A is played, full right and Wavetable B is played. As a reminder, the wavetable sound is only heard if the Waveform selector on the oscillator is set to wave. The Morph control is also a global destination on the Routing panel, so Morph control can be automated if required.
MORPH: This is like a balance control between the .wav files stored in Wavetable A and Wavetable B. Set it full left and Wavetable A is played, full right and Wavetable B is played. As a reminder, the wavetable sound is only heard if the Waveform selector on the oscillator is set to wave. The Morph control is also a global destination on the Routing panel, so Morph control can be automated if required.
MASTER VOLUME: This is the overall volume level output from the synth.
PORTAMENTO: This sets the glide rate from one note to the next - currently only implemented for operation when in Unison mode.
USER 1: Effectively a spare control - I will find a use for it at some point!
There are also some hidden functions that are activated by pressing the numeric 0 button and then one of the 1 - 9 buttons:
0+1: Sets the Midi Receive channel, use the INC and DEC buttons to select
0+1: Sets the Midi Receive channel, use the INC and DEC buttons to select
0+2: Sets the Global Tuning +/- 50 cents from 440Hz for middle A
0+3: Sets if the ARP clock follows the midi clock or runs independantly
0+4: Sets the portamento range / speed
0+5: Sets how the ARP finishes when hand removed from keys
0+9: Panic - stops all sound output instantly
0+9: Panic - stops all sound output instantly
There's a couple of combinations left ( 0+6, 0+7, 0+8) that I might set to define a use for the spare User 1 knob on the Patch Panel, and the User 2 button on the Commons Panel - work in progress!
Firmware
There are 5 pieces of code running on 7 individual processors that create the ESP32-Polysynth, all communicating with each other on a common CAN bus. To this end, there is a universal include file used for the AT238p code and the ESP32-S3 code so all the data passed over the CAN bus is understood by each piece of application code.
This is called synth_params.h and it defines all the CAN message ID's and contents for every single action that can be passed between processors.
I won't go into to much detail how the code works, it will all be on GitHub by the time you read this. The code is broken down into separate modules to make it easier to work on and understand, rather than a single block of code with thousands of lines in there!
The code was written using the Arduino IDE as it supports both AT238p and ESP32-S3 without any difficulty, and it's what I'm used to!
A quick overview of the code modules used in the main ESP32-S3 code:
Synth_can.cpp This is the main CAN handler, it sets up the ESP32's TWAI interface operating parameters, it manages the system start up handshake where it polls the CAN bus to see what other remote boards are fitted (Oscillator / Commons etc) and then sets default values for any boards that are missing. It also manages all the remote boards LED's as they are all set as acknowledgements from the ESP32-S3. The rest of the code effectively decodes the incoming CAN messages against the declarations in synth_params.h and populates the appropriate variables that run in the synth engine. It also looks after sending the filename text to the display on the Patch Panel and the BPM value when the Arpeggiator is in use.
I won't go into to much detail how the code works, it will all be on GitHub by the time you read this. The code is broken down into separate modules to make it easier to work on and understand, rather than a single block of code with thousands of lines in there!
The code was written using the Arduino IDE as it supports both AT238p and ESP32-S3 without any difficulty, and it's what I'm used to!
A quick overview of the code modules used in the main ESP32-S3 code:
Synth_can.cpp This is the main CAN handler, it sets up the ESP32's TWAI interface operating parameters, it manages the system start up handshake where it polls the CAN bus to see what other remote boards are fitted (Oscillator / Commons etc) and then sets default values for any boards that are missing. It also manages all the remote boards LED's as they are all set as acknowledgements from the ESP32-S3. The rest of the code effectively decodes the incoming CAN messages against the declarations in synth_params.h and populates the appropriate variables that run in the synth engine. It also looks after sending the filename text to the display on the Patch Panel and the BPM value when the Arpeggiator is in use.
Synth_can.h Just holds the function prototypes used in Synth_can.cpp
Synth_midi.cpp This handles the Midi byte processing / decoding, voice allocation for Poly / Duo / Unison modes.
Synth_midi.h Just holds the function prototypes used in Synth_midi.cpp
Synth_midi.h Just holds the function prototypes used in Synth_midi.cpp
Synth_engine.cpp The Main synth engine that produces the sounds. It sets up the I2S interface, and defines and initialises all the variables and structures used withing the code block. It computes the Arpeggiator notes, manages the voice cloning and soloing functions and voice allocation. Its main function though is the Audio_Task() where it models the waveforms, modifiers, routes, multi modulator, ADSR, Filter and then copies into the I2S Audio buffer.
Synth_engine.h Contains a lot of the system variable #defines, Enumerations, Structures, extern variables used by other code blocks and all the function prototypes used in Synth_engine.cpp
Synth_card.cpp This manages the SD card and file system. Initializes the SD card and mounts it. It checks the validity of any stored .wav files for the wavetable before loading them. It generates a JSON file structure used for saving and loading patches. Once a patch is loaded it copies all the parameters and routes into the Synth engine.
Synth_card.h Just contains the function prototypes used in Synth_card.cpp
Synth_v14.ino Effectively the main file, calls the setup functions for the I2S / CAN / Midi / SD Card code modules. Starts the Audio_Task() on Core 1 and processes each note as it comes in.
The AT328p code is done in a similar fashion, there are separate modules that look after the CAN port, scan the IO, Parse CAN messages etc.. All of these will be on GitHub.
The PCB's are all the same size, and use readily available components - nothing obscure or hard to find. I had the blank PCB's made at JLCPCB and each one took about 2hrs to solder the components on. All the switches and knobs I sourced from Ali-Express and are common to all panels, helps here to buy in bulk as you get a good price, and some spares left over.
The AT238p boards also have a serial port fitted, so you can test each board once it's made and check all the knobs / switches / buttons are working.
All the front panel artwork I drew up in Visio - I used KiCad to lay the PCB's out and all the controls are fitted in a 1mm x 1mm grid size, so I set the same origin point in Visio as KiCad, and it was just a matter of getting the component X-Y position from KiCad and copying the value into the object position in Visio. This ensured that the front panels lined up with the PCB's. I converted the artwork into an .stl and then 3D printed a test panel to check, before getting the design made up in aluminium panels.
The PCB and front panel size was determined by my 3D printer - I've previously made things out of wood and metal, and the casing ends up costing way more that the electronics inside! So this time round I kept it all within the confines of something I could make at home, and slot together to make the finished casing. I came up with the basic design of a tray that the PCB and front panel dropped into, screwed into place from the rear. The sides of the tray have some dovetails attached, and then there's a series of locking blocks that push over the top of the dovetails to hold the parts together.
The AT328p code is done in a similar fashion, there are separate modules that look after the CAN port, scan the IO, Parse CAN messages etc.. All of these will be on GitHub.
Assembly
The PCB's are all the same size, and use readily available components - nothing obscure or hard to find. I had the blank PCB's made at JLCPCB and each one took about 2hrs to solder the components on. All the switches and knobs I sourced from Ali-Express and are common to all panels, helps here to buy in bulk as you get a good price, and some spares left over.
The AT238p boards also have a serial port fitted, so you can test each board once it's made and check all the knobs / switches / buttons are working.
All the front panel artwork I drew up in Visio - I used KiCad to lay the PCB's out and all the controls are fitted in a 1mm x 1mm grid size, so I set the same origin point in Visio as KiCad, and it was just a matter of getting the component X-Y position from KiCad and copying the value into the object position in Visio. This ensured that the front panels lined up with the PCB's. I converted the artwork into an .stl and then 3D printed a test panel to check, before getting the design made up in aluminium panels.
The PCB and front panel size was determined by my 3D printer - I've previously made things out of wood and metal, and the casing ends up costing way more that the electronics inside! So this time round I kept it all within the confines of something I could make at home, and slot together to make the finished casing. I came up with the basic design of a tray that the PCB and front panel dropped into, screwed into place from the rear. The sides of the tray have some dovetails attached, and then there's a series of locking blocks that push over the top of the dovetails to hold the parts together.
As well as the dovetail locking blocks, there is a hole through the middle of the joint where the power and CAN cables can be passed through from one section to the other.
Assembly then is just a matter of screwing the front plate to the PCB with some 12mm hex spacers in between, and then some 8mm spacers on the rear of the PCB that are used to screw the assembly into the case from the rear. From the picture above you can see the extent of the wiring involved, black and red are the 5v power and the white / blue twisted pair is the CAN bus. I made up some simple cables with connectors either end, and they just plug in from one board to the next.
Building one yourself
I've designed the Synth to be relatively easy to assemble from a DIY point of view, a single board per module, easy wiring and readily available parts. Ideally I would like to be able to offer them as a kit with everything included, but as I'm working 5 days a week I don't have the spare time to start kitting them up, and also dealing with support or any issues that may then result if a component is put in the wrong place etc.
I will have all the files - source code / PCB Gerbers on GitHub, but if anyone is interested in helping me produce or sell this commercially then please get in touch!
I will have all the files - source code / PCB Gerbers on GitHub, but if anyone is interested in helping me produce or sell this commercially then please get in touch!



















Comments
Post a Comment