I am running a crowdfunding campaign for an open-source pocket digital groovebox called the PGB-1. It’s an all-in-one instrument, that means you can program and play songs only using this device and nothing else (see the demo here).
PGB-1 is based on the RaspberryPi RP2040 microcontroller, with software development kits available for C/C++, CircuitPython, and Ada (Rust is on the TODO list). The stock firmware I will describe in this post is fully implemented in Ada. Let me walk you through the design.
The Two Main Components
At the Firmware level, PGB-1 is made of two main components: the sequencer and the synthesizer. Early in development I decided to keep them as isolated as possible.
Inside the PGB-1 firmware, the sequencer and synthesizer only talk via MIDI messages.
Keeping those two components isolated and only communicating through a standard protocol has great benefits in terms of development and maintenance as they can be modified and tested independently. But there are other benefits to this approach.
First, it means easy integration with external gear. The PGB-1 can sequence up to 16 external instruments, or its internal instruments can be played with an external MIDI keyboard or another sequencer. This opens up a lot of different use cases.
Second, this architecture is a great match for the RP2040 dual core microcontroller. Digital synthesis is a CPU intensive and hard real-time task. By dedicating a CPU core solely to this task, we can achieve great performance and simplify the timing requirements.
One drawback of this approach is the limitation of the MIDI protocol, with its data values of only 7-bits. In my opinion, the benefits listed above clearly outweigh the limitations.
The RP2040 Core0 is not only running the sequencer, there is also the user interface (LEDs, keyboard and OLED screen). Still, at this point Core0 is not fully loaded, we have room for more…
The Audio Path
Still missing in this picture is the audio path, and this is where it gets complicated. There are 10 audio sources on the PGB-1, 8 internal digital synth voices, audio line input, and microphone. All these can run through a mixer with 4 global effects (Reverb, Overdrive, Filter, and Bitcrusher) and a bypass (no effect, clean signal).
On the synthesis side, the incoming MIDI messages are sent to one of the synthesis engines depending on the MIDI channel ID. Each engine will then generate a fixed sized mono audio buffer. The mono buffers are then passed through stereo spanning blocks that will place the sound more or less on the right or left side depending on track setting (also controlled from MIDI), resulting in 8 stereo buffers.
These 8 stereo buffers are then dispatched into one of the 5 FX send stereo buffers given the FX settings for each track.
At this point the job of core1 is over, the FX send buffers are sent to core0 for FX processing and final mix.
Back on core0, audio buffers coming from the microphone and line inputs are also dispatched into one of the of FX buffers. This means users can add reverb or another effect to the incoming signal.
Now it’s time for the actual FX processing that modifies the buffers in place, and finally the very last operation of mixing all the 5 stereo FX buffers together to produce the output stereo buffer.
This picture above is still a simplified view, I skipped the various queues for MIDI messages and buffers, interrupts, periodic tasks, and other synchronization mechanisms. But it should give you an idea of what it takes to push the rp2040 to its limits and deliver a very powerful instrument in a small and affordable package.
As always, if you have any questions on this topic or anything else, there are several ways to reach us. You can use the campaign question form, join the Wee Noise Makers Discord Server, or contact us on our social media channels.
Meanwhile, don’t hesitate to talk about the PGB-1 with people around you; it’s a great way to help the campaign. There’s still plenty of time to back the project!