Building DMC60, a 12-bit drum machine where the grit is the point

DMC60 is the project I've spent more time on than anything else this year. It's a drum machine and sampler built with JUCE, and it's designed around one idea: the "flaws" of the classic 12-bit samplers are what make drums hit the way they do on so many records.

So DMC60 doesn't try to be clean. Everything runs through a 26.04 kHz internal rate, samples are quantized to 12 bits, and there's an SSM2044-style filter on the voice. The aliasing you get from that low rate is deliberate. In the README I put it this way: those are features, not bugs.

Why 12 bits and 26.04 kHz

Modern plugins give you 32-bit float at whatever rate your session runs at, which is great for transparency. But play a kick through a 12-bit converter at 26 kHz and you get something clean audio doesn't give you: a crunchy top end, folded-back harmonics, and a low end that feels denser than it measures.

The core of the effect is simple. Here's a sketch of the quantization step. The clamp matters: a signed 12-bit converter tops out at +2047, so a full-scale peak clips slightly instead of landing on a level the hardware never had.

// Simplified: snap a float sample in [-1, 1] to a signed 12-bit grid
inline float quantize12 (float x)
{
    constexpr float scale = 2048.0f; // 2^12 levels, split across both polarities
    const float code = std::clamp (std::round (x * scale), -2048.0f, 2047.0f);
    return code / scale;
}

The harder part is everything around it. The sample-rate conversion has to alias the way the old hardware did instead of filtering it away, and the filter has to respond the way the analog chip did. That's where most of my time went.

One codebase, four platforms

DMC60 builds from a single CMake project into:

FormatmacOSWindowsLinuxiPadOS
AU✅———
VST3✅✅✅—
Standalone✅✅✅✅

JUCE does most of the cross-platform work, but the iPad version took real design effort. A drum machine is something you want to touch, so the iPad app got its own layout, not a shrunk-down desktop window:

  • Native point sizes, with 44-point minimum button targets and 48×44-point fader handles
  • Pad controls and the session inspector start closed, so the 4×4 pad grid gets the screen
  • Background audio, and live sampling from the microphone
  • Projects and programs live in the app's Documents folder, so they show up in the Files app

I leaned on Apple's button guidance along with Fitts's Law and Hick's Law to size and simplify the controls. Bigger targets that are closer to your thumb, and fewer choices on screen at once.

I also added a desktop preview mode. Launching the standalone with DMC60_IPAD_LAYOUT=1 renders the iPad layout on the Mac, and a touch_layout_smoke test renders the real editor at iPad size to check pad and toolbar dimensions without touching audio hardware.

A pre-commit hook that builds everything

One habit that paid off: the repo's pre-commit hook runs a full Release build before every commit. It's slower than a lint check, but a C++ plugin that doesn't build is worthless, and this way the main branch always does.

npm run prepare        # point git at .githooks
./scripts/build-app.sh # the same build the hook runs

What's next

DMC60 has its own program format (.dmcpgm), and building kits for it by hand got old fast. That's what led to my next tool, DorDSP Kit Pro, which turns folders of samples into ready-to-play kits for DMC60, the MPC, and Ableton.