The Slab, my DIY groovebox

mrfry

Member
Hey everyone!

I asked for some help several months ago and now I'm ready to show what I've been working on. As said in the title it's an all in one sequencer / synth.

More info below the video, please wear headphones or use a half decent sound system 🙏


If you're curious it involves:
- An old Teensy 3.1 I've had lying around for years
- An old PicoADK (small batch of overclocked RP2040 boards with a lovely DAC and abysmal documentation)
- A 128x64 yellow OLED, 8 encoders and 5 tactile switches.

DISCLAIMER: I did get AI help. I'm not a big fan of genAI but my area of expertise is woodwork and this is my first real foray into C++ (more like C with classes) so I would've probably given up without it. I've got a month of Claude Code to refactor my initial UI spaghetti code. "We" discussed the general architecture and laid the foundations. I've learnt loads in the process and I'm now pretty independent. In fact the whole DSP side is completely AI-less. I'm not trying to brag (too much), mostly I don't want anyone to think this is just the result of an AI prompt. It's about 4 months of swearing, sweat and tears!

The Teensy handles the front panel inputs, the UI and the sequencing (including chord generation). Pretty much all of its pins are in use as I'm not multiplexing the encoders nor their switches (which I will regret not doing when I want to add pads to this thing). Out of 5, 4 of the tactile switches are multiplexed using a voltage divider (I only had analog pins left!) and the "shift" button has its own digital pin. The UI renders at 20fps but will throttle itself if anything else needs CPU time. It's never had to though, I'm amazed at how much the 3.1 can handle and it is a pleasure to program. Communication between classes is mostly based on an event queue system except the transport (timing) which uses callbacks for a tight clock. Every loop calls an update() method for all the components in which they check if an event that matters to them has been emitted (front panel input, cache updates, midi messages...). Apart from the odd temporary variable, very little is created at runtime to keep RAM usage as deterministic as possible. No interrupts either to keep control on when things happen. The 8 patterns can be edited in real time as they're playing. I'd like to add a proper pattern bank and oscillator presets but RAM is getting pretty scarce and there are a lot of parameters (48 per track accross all modes) and notes (64 per pattern per track) to store. I'm booting at 72% usage.

The PicoADK handles everything DSP at 44KHz. I would've loved to use a Teensy 4 instead but I had the ADK already. All 8 tracks oscillators, filter, envelopes and delay are generated onboard, the USB cables you see poking out are just for programming. Each track is monophonic (one note) and mono (one channel, but I'm hoping to add panning to the mixer in the future). The ADK handles way more than it should so polyphony is not an option but I do manage to sound chords using a trick inspired by an old Casio patent. In the patent they use half a cycle to generate a base waveform and the other half features a simulated resonant filter. The filter here is real but the samples of the 4 voices of the chord are interleaved in the same way. The illusion is suprisingly good but I do get a fair bit of artefact in the frequencies that overlap with my tenitis. I filter them with a low pass but the younger ones amongst you might still hear some :) The oscillators mix 4 wavetables using an X/Y coordinate system. I will add more exotic waveforms but for now the usual suspects are all here, including noise.

Communication between the two MCUs is via UART MIDI. I can save patterns and patches via Sysex to a computer using a little cli tool. Thinking of using the Pico as non volatile storage instead so the unit is fully standalone.

The Teensy code (UI and sequencer)
The PicoADK code (audio)
The CLI backup tool

Thanks for checking this out, looking forward to get some feedback. As you can tell I'm proud like a daddy but will do my best to handle (constructive) criticism 😁

All the best!
 
Thanks for you kind words Bill!

In my original post I was thinking about this patent: US4489391A.
A resonance characteristic control unit is provided with a first memory for storing first data corresponding to poles of a digital filter under a non-resonance condition, and a second memory for storing second data corresponding to poles of the filter under maximum resonance amplitude condition. The data stored in these memories are read out to calculate a filter coefficient corresponding to the resonance amplitude. The calculated resultant data is supplied to the digital filter to obtain a filtered output which is provided with a desired resonance characteristic.
Close but not quite about using half a cycle for each component.

I have read the patent you are referring to extensively. I've even got a printed out version that I've studied at coffee, on the train and even the loo :) I came across it because one of the filters in the Vult library is an implementation of it. Wikipedia has a really good explanation of it and I think it is a textbook example of what DSP is "made of". It's taught me a lot.

So... I can't actually find the patent I am referring to 😂 All I have found is this page about the CZ series of synths which features this illustration:

waveform-combo.jpg

Sadly they don't say which patent it is from. I must have stumbled on this page when I was binging on phase distortion literature. The CSOUND language also implements a pdhalf() function inspired by Casio's CZ series that allows to play two halves of a waveform at two different rates. I think my brain just combined all that data while I was sleeping and spat out a hallucination when I tried to make polyphony work on the Slab.

Here is the code snippet:

Code:
    mem poly_counter = 0;  // In Vult "mem" is used to declare a variable
                           // that persists across runs of a function exits.
 
    if (voice_count == 1) {
        synth_out = simple_osc(synth_note + env2Times256, synth_channel, 0);
    }
    else {
        if (poly_counter == 0) {
            synth_out = simple_osc(synth_note + 24.0 + env2Times256, synth_channel, poly_counter);
        }
        else if (poly_counter == 1) {
            synth_out = simple_osc(voice_2 + 24.0 + env2Times256, synth_channel, poly_counter);
        }
        else if (poly_counter == 2) {
            synth_out = simple_osc(voice_3 + 24.0 + env2Times256, synth_channel, poly_counter);
        }
        else if (poly_counter == 3) {
            synth_out = simple_osc(voice_4 + 24.0 + env2Times256, synth_channel, poly_counter);
        }
        poly_counter = (poly_counter + 1) % 4;  // Vult doesn't have "++"

        if (filter_freq > 0.67) {    // Filter out artefacts from dodgy poly
            filter_freq = 0.67;
        }
    }

I add 24 to my pitches because the counter makes the phase increment 4 times slower hence two octaves lower.
env2Times256 is just the pitch envelope scaled so it gives me nice big sweep.
simple_osc() pretty much just read from the array that contains the "morphed" waveform from the other selected 4 waveforms.

At transcompiling, Vult turns all floats (which it calls "real") into fixed16_t which the RP2040 handles more efficiently.

I did try % 32 as well, which seemed to makes sense as my waveforms are 128 samples long but it sounded like crap, I guess because each voice would be missing 3 contiguous quarters of its shape.

I'd love to know if this trick of splitting the DSP passes across the different voices of a chord is actually used / patented somewhere!
 
Last edited:
Hello.
The Casio patent number I mentioned is Casio's Phase Distortion patent.
The weblink you provided from perfectcircuit.com has illustrations taken directly from Casio CZ-1 User Manual. Figure numbers and page numbers align. Those same figures re-appear in Casio CZ-101 manual and in other CZ-series synth manuals (likely in other Casio synth families too). You can find the pdf of these manuals on the web (for free).

I've not run across those figures in Casio patents. But, can't say I've read all of them.
 
Back
Top