Arduino “Make Your Uno” Synth – 4 – Drum Machine

This post talks through the porting and operating of a commonly used sketch for an Arduino drum machine, showing how to get it up and running on the Arduino Make Your Uno Synth Shield..

The full index of projects and my personal build notes can be found here: Arduino “Make Your Uno” Synth.

If you spend much time looking at musical applications of an Arduino online you might have come across the “O2 Minipops” drum machine application.

This is a relatively simple circuit and associated sketch put together by Jan Ostman from his no-longer-available-blog.  But searching online will provide many examples of people building this drum machine.  One of the ones I like is this one on “Blog Hoskins”‘s site: http://bloghoskins.blogspot.com/2016/11/korg-mini-pops-diy-drum-machine.html which also mentions some of the updates people have done – e.g. to add MIDI or other functionality.

This won’t work on the Synth Shield as the audio output is on the wrong pin.  But the code is quite complex to understand and therefore not very amenable to modification.  So I’ve taken the core application as the “spec”, extracted the patterns and drum samples and reimplemented some of the core functionality myself.

Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

These are the key Arduino tutorials for the main concepts used in this project:

If you are new to Arduino, see the Getting Started pages.

Background and Context

Here is a comparison of the original with my version:

Original Synth Shield
Arduino Nano Uno
Drum Tracks 8 8
Drum Voices 8 8
Patterns 16 16
Tempo Abstract “slow to fast” 60 to 960 steps per minute
Pattern Control A4 A0
Tempo Control A5 A2
PWM Output D11 D9
Run/Stop D10 None
Mute Switches D2-D9 None
Clock Output D13 None
Reset Output D12 None
Analog Sequencer Output A0-A3 None
MIDI Added by others No

The new code is provided on GitHub (see below) but there is a more detailed explanation of what it is doing later in this post.

It should be possible to go back and add some of the extra functionality, but as an application for the Synth Shield, I didn’t think it was required right now.

The original code is highly optimised with very little use of the typical Arduino environment and direct PORT I/O throughout.  This means the overheads and impact on the signal processing code are kept to a minimum and will be largely predictable  However it also makes the code hard to understand, difficult to change, and I’ve not found any discussion of what the code is doing anywhere else online.

For this experiment, I’ve opted for standard Arduino functions where possible, more verbose code where it makes for more readability, and kept the low-level direct register access to elements of the code that absolutely need it (e.g. for configuring the timers).

I’ve also used more functions (which will also have an overhead) rather than inline code again erring on the side of readability.

Basically this is not as highly optimised as the original and skips much of the additional functionality.  But for an 8-bit microcontroller setup aimed at learning, driving a basic amplifier into a small speaker, I think it is good enough for a bit of fun!

By the way, to be explicit about the reason why I can’t use the existing code, the original uses Timer 1 for the sample interrupt and Timer 2 for output on OC2A for PWM on D11.  The Synth Shield needs PWM output on D9 which is associated with Timer 1.

Consequently I’ve have to swap the timer configurations which required getting into the existing code in detail and working out how to reconfigure everything.  Then having gone that far anyway, I decided in the interests of providing code more useful to beginner’s understanding I would re-write it all at a higher level (with more comments!) and sacrifice some of the optimal performance.  It also means I now have code I’m happy to answer questions about should anyone else try to use it.

Parts list

  • Arduino “Make Your Uno” Kit
  • USB-C programming lead

The Circuit

We’re going to initially make use of A0, A2 (circled below) and the volume (speaker).

Arduino Synth Shield - Drums

The Experiment!

This application shows how to perform direct digital synthesis on an Arduino, making full use of its hardware pulse-width-modulation (PWM) capability.

If you just want to get on and give it a go, the code can be downloaded from GitHub, then just fiddle about with A0, A2 and the volume and see what happens!

Download from GitHub Here

The eight drum voices are listed as: “GU BG2 BD CL CW MA CY QU” in the code.  The original analog Korg Minipops 7 manual lists the available sounds as:

  • Bass Drum (BD)
  • Conga
  • Large Bongo
  • Small Bongo (BG2?)
  • Claves (CL)
  • Cow Bell (CW?)
  • Snare Drum
  • Cymbal (CY)
  • Hi-hat
  • Quijada (QU)
  • Guiro (GU)
  • Tambourine

I’ve guessed some mappings, but I’ve no idea what “MA” is – I’d guess Maracas, but the original doesn’t list that…  You’ll have to have a listen and see what you think!

There are 16 different patterns in total described in the original code as follows (I don’t know if these mirror the original patterns or were developed just for the Arduino code):

16-step patterns:

  • Hard rock
  • Disco
  • Reggae
  • Rock
  • Samba
  • Rumba
  • Cha-Cha
  • Swing
  • Bossa Nova
  • Beguine
  • Synthpop

12-step patterns:

  • Boogie
  • Waltz
  • Jazz rock
  • Slow rock
  • Oxygen

The 16-step patterns tend to be four beats of four subdivisions.  The 12-step patterns are either four beats of three, or three beats of four.

The tempo “bpm” range is actually steps not beats, so the 60 to 960 range is actually from 15 to 240 beats per minute (assuming four subdivisions) or 20 to 320 (assuming three).

If that last pattern sounds a bit familiar, then you might spot why the original was call the “O2 Minipops” drum machine (here’s a clue).

How it Works

If you want to know a little more about how it all works, the basic idea is that there are several threads of activity going on:

  • A 20kHz trigger that will reliably output a sample value to the PWM hardware.
  • The PWM hardware peripheral that will continuously output a PWM signal on the required pin (in our case D9).
  • The audio generation that works out what sounds need to be played and which part of each sound has to be played right now.
  • The “control logic” monitoring the analog (and digital in the original) inputs and adjusting the behaviour accordingly.  Analog inputs control the pattern choice and tempo.
  • The step sequencer turns the playing of samples off and on as required according to the currently selected pattern, the current playing step, and the current tempo.

In this code this manifests itself as two key threads of software execution:

  • A 20kHz interrupt that takes the next audio sample to be played and outputs it to the hardware.
  • Everything else in the main Arudino loop().

A key tension is ensuring that the control loop() can prepare the list of samples associated with audio in time for the interrupt to keep coming along and sending them out.  A good trick is to take the slower activities (like reading analog inputs) and only doing them infrequently.  At the Arduino’s 16Mhz system clock speed, we only need to read the potentiometers once every 500 odd passes through the loop() and still seem responsive for a person using the device.  The timing of the step sequencer is governed by the currently selected tempo.

Recall that the software doesn’t need to do anything to actually generate the PWM signal – once it is all up and running, the Arduino hardware will do this for us.

This gives the following outline structure of the code:

setup():
  Set up a sample buffer
  Set up the timer for PWM audio output on D9
  Set up the timer for the sample interrupt
  Set up the IO to read A0 and A2 analog inputs

interrupt():
  Write out the next sample from the buffer to the PWM hardware

loop():
  Work out the next sample to be played and add to the buffer
  Periodically (i.e. not every time) read the IO (update tempo/pattern)
  IF tempo says it's time to play a step in the sequence:
    read the current pattern and step
    FOREACH drum voice:
      IF this step in this pattern requires this drum:
        Turn on this drum

Key features of the implementation:

  • The buffer is a “ring buffer” where samples are added (“pushed”) as part of the control loop, using a “write pointer” and taken off again (“popped”) as part of the sample-playing interrupt, using a “read pointer”.  The buffer is circular, so writes off the end start back at the beginning.
  • The analog inputs are only read once every 400 scans through the loop, with one after 200 scans and the other after another 200.
  • The tempo is implemented by monitoring the millis() number of milliseconds value.  If the time of the next step has not been reached, then no further processing will happen.
  • Samples are played by using a sample-specific counter and an index.  The counter indicates how much of the sample is still to be played and the index remembers what the next sample value will be.  To play the sample, the counter is set to the size of the sample and the index is set back to 0.

Polyphony is achieved by combining samples.  The basic idea is as follows:

sampleGet():
  use signed integer for the total and initialise to 0
  FOREACH drum voice:
    IF voice sample counter > 0:
      total = total + this voice's sample - sample bias
      increase this voice's sample index and decrease its sample count

  re-bias the total and clip to the range required by the PWM hardware
  return total as the next sample to be played

Note that the samples are stored as 8-bit numbers in the range 0 to 255, with 128 being the “zero midpoint” so to convert to a +/- range for addition, we need to subtract that bias value (128).  But once the calculation is complete the total needs re-aligning to the range required by the PWM hardware, which also happens to be 0 to 255, with 128 as the mid-point.

There are comments throughout the code, especially detailing the configuration of the registers for the two timers.

If anyone is interested in my analysis of the original code, I have quite a few scribbles about it.  Be warned though there are (I believe) some inaccuracies in some of the comments and (also I believe) some of the original code is redundant.  Key differences/comments I had:

  • The comments state a 40kHz sample rate.  I’m pretty sure it is configured for 20kHz.  And I think the OCR2A value should be 799 not 800.
  • Timer 0 is configured for 61Hz but with no signal output or interrupt, so as far as I can, I don’t think Timer 0 features in the functionality of this code?
  • There are extra register initialisations that I don’t think are required.  Specifically OSCCAL and ASSR.

As already mentioned there are lots of examples of “clever code” rather than using any of the Arduino’s API, particularly for both digital and analog IO.

I acknowledge though, that the Arduino’s IO functions are pretty slow in comparison so I can see the attraction of bypassing them, but in reality the IO handling is not time critical, as long as it doesn’t interrupt the sample generation.

Closing Thoughts

Understanding the original code in order to do a ground-up reimplementation took a bit longer than I expected, but that just validates the idea to me.  Whilst not as optimised as the original, I believe this is more than adequate for the Synth Shield and more importantly I feel I now have something I’m happy to publish and consequently answer queries about.

Future additions could include putting in the mute switches, possibly MIDI support, and the clocks if required.  I am wondering about using one of the other pots as a run/stop control…  It might also be fun to allow the drums to be played via a set of buttons (or touch pads).

But a beginner could just load up this code and use it “as is” quite well I think.

Kevin

Leave a comment