Drummer

Opinionated drum sequencer module.

Drummer

Shmøergh Drummer is a drum sequencer which generates drum loops that "make sense". What "makes sense" is of course very subjective, so it's an opinionated drum sequencer: it has built in patterns that produce more or less classic hiphop/funk/electronic/dance rhythms but with the intention to keep it interesting.

The module is heavily inspired by Mutable Instrument's Grids but the inner logic [of my module] is much more rudimental.

Features

  • Opinionated drum sequencer
  • 4x trigger outputs
  • Hardcoded pattern pool for each channel with free mix-and-match
  • Shuffle
  • Pattern start reset button
  • Ability to add semi-random extra notes to patterns
  • Re-randomize extra notes
  • Expects 4PPQ external clock
  • Nice blinking LEDs
  • Works with +12V

Resources

Schematics
Code

Concept

The core idea of this module is to make a sequencer that mimics a real drummer. Being a funk drummer half of my life I decided to take my own playing style as the foundation, which consists of two basic things:

  1. Sticking to a basic pattern to support the music and keep the dancers move
  2. Temporarily changing some parts of the patterns to adjust the overall intensity of the groove.

This is the basic concept of lots of popular music like funk or techno: the same beat over and over with a flow of interesting interrupts. To transform it into a device I needed a machine that makes drum patterns and allows intensity change.

Drum patterns

The module has four channels, just like most drummers have four limbs. Each channel has a specific role (kick, snare, hihat and effects like a cowbell or a crash). And because each channel has a specific role, the patters they can play also have to be different. For example a very basic typical drum groove plays the kick on 1 and 3, the snare on 2 and 4 and the hihat on each 8th:

16ths: 0123456789ABCDEF
KICK:  X-------X-------
SNARE: ----X------X----
HH:    X-X-X-X-X-X-X-X-

Usually it doesn't make sense to have a hit on the snare on every 1/8 note, so a shared set of patterns for all channels doesn't make sense either. We need a pattern pool for each channel separately. You get the point.

Now if we mix and match the patterns from various channels, it already makes up a very high number of interesting, yet useful grooves.

Intensity

Drum groove intensity has a lot to do with changing the patterns and adding some drum fills. Its primary role is to temporarily introduce some flavor while keeping the flow. Good drummers do it in a way that people don't even recognize it unless they deliberately listen to it.

To apply this concept to my device, I thought of a semi-random funciton which adds extra notes to the basic groove. The more extra notes, the more intense the groove becomes. Being "semi-random" means that the extra notes have fixed position in the 16th grid per channel at each intensity level, but it is randomized which notes are actually added to the original pattern. Again, the higher the intensity the higher chance for extra notes.

This means that same intensity levels result in different final patterns.

How does it work?

Shmøergh Drummer is a sequencer that generates Eurorack standard 5V trigger signals. It needs a 4PPQ external clock which can be an oscillator, an LFO or the output of an external controller.

UI

The interface is pretty straightforward:

  • 4 trigger output: kick, snare, hihat, FX
  • 4 knobs which allow selecting the pattern for each channel
  • A global "Intensity" knob to adjust the number of semi-random extra notes
  • A "Shuffle" knob which does exactly that
  • A button that resets the start of the sequence on short press, and re-randomizes the Intensity on long press
  • LEDs for incoming clock and triggers
As DIY as it gets

Technical details

Core

Shmøergh Drummer uses an ATMEGA328P uCU and the code is Arduino based.

Hardware

The circuit is a classic "Arduino on a PCB" which I already detailed in the FM synth post. It's a rudimentary hardware, the most "interesting" part is the clock input, which is a transistor switch that inverts the incoming clock so the interrupt in the code should be set to be triggered to the FALLING edge of the clock input.

Each output is buffered with a 4050 IC and there's a LED for each channel and clock.

Parts of the code, mostly for myself to remember

Patterns

I store the patterns in 16bit binary (in include/patterns.h) with the most significant bit being the first 16th note in the measure – so read the pattern from left to right, like you would on any step sequencer. This is a fairly visual format, for example this are the first 3 kick patterns:

0b1000000000000000,
0b1000000010000000,
0b1000100000000000,
...

Setup

For performance reasons I write the output directly to PORT D instead of using digitalWrite. In setup I init all knobs and buttons then reset all outputs and attach an interrupt to the clock in pin. As written before the interrupt has to be triggered to the FALLING edge of the clock input as the switch circuit inverts the original clock.

// Set PORTD 7, 6, 5, 4, 3 as outputs and leaves 0, 1 and 2 untouched (clock on pin 2 is already set to input)
//             76543210
DDRD = DDRD | B11111000;

// Reset all outputs
PORTD |= B00000000;

// Listen to clock in on INT0 (digital pin 2)
attachInterrupt(digitalPinToInterrupt(CLOCK_IN), onClockIn, FALLING);

Then I count the number of patterns for each channel. This is needed later to calculate the pattern depending on the knobs.

Loop

On each interrupt I set the clockState to true which is when I play notes (if (clockState && !pulseState) {...) in the loop function.

The output is dynamically built up during the loop into the portDOut variable. It's initialized at the beginning of the loop:

int portDOut = B00000000;

I use binary operations to compare the actually selected pattern plus the calculated extra note with the current step (ie. "should I play a note for the current 16th note?"). In the example below, seqBD[] is the array that contains all the kick patterns, patternBD is the index that is set by the position of the corresponding kick knob and the extraNotesBD is the calculated intensity extra notes. If any of the mapping match then the kick is triggered.


For shuffle I simply delay the output with a certain time, based on the value of the shuffle knob:

if (shuffleValue != 0 && currentSixteenth % 2) {
    delay((drummer.shuffleDelay(float(pulseLength), float(SHUFFLE_RESOLUTION), float(shuffleValue))));
}

For performance reasons I used software multiplexing to read the values of the knobs:

switch (analogMux) {
    case 0:
        patternBD = drummer.mapKnob(noOfPatternsBD, analogRead(PATTERN_SELECTOR_BD));
        analogMux++;
        break;
...
}

When the output is completely built up (ie. all the channels triggers are set) then I send the output to PORT D and shift down the current step with 1/16th note. If it's at 0 then I reset the current to 16 so that it restarts the sequence:

// Update outputs
PORTD |= portDOut;
triggerState = true;

// Reset steps
currentStep >>= 1;
if (currentStep == 0) {
    currentStep = 0b1000000000000000;
}

I wait for a predefined length for each trigger signal before I reset the output (note that the first 2 bits in the operation are 1 because of best practice: those pins are unused ATM and using 1s in PORTD &= B0000011 keep their values unchanged.)

if (triggerState && (millis() - pulseKeeper) > TRIGGER_PULSE_LENGTH) {
  PORTD &= B00000011;
  triggerState = false;
}

The lib/Drummer/Drummer.cpp is a small helper class for calculating the intensity extra notes and the shuffle value. The extra notes calculation is more interesting:

uint16_t Drummer::extraNotes(uint16_t map, int intensity) {
    // Loop through the bits in the map and if it's 1 then randomize it
    uint16_t currentMap = 1;
    uint16_t extranotes = 0;

    // Calculate how much chance the new notes have. Depends on intensity
    float chance = CHANCE_LOWER_LIMIT + (((CHANCE_UPPER_LIMIT - CHANCE_LOWER_LIMIT)/(_maxIntensity - 2)) * (intensity - 1));

    for (int i = 0; i < 16; i++) {
        if (uint16_t(currentMap) & map) {
            if (random(0, _maxIntensity + 1) < (_maxIntensity * chance)) {
                extranotes |= currentMap;
            }
        }
        currentMap <<= 1;
    }
    return extranotes;
}

The extraNotes method is called for each channel/drum in the main loop. The map input parameter contains the map of potential extra notes. It is a similar 16bit binary as the normal patterns. The intensity map contains typically offbeat notes so that they really become "extra" notes besides. For example below is a set of potential extra notes. When it's added to the regular notes from the pattern they might become something like this:

// Extra notes (intensity map)
0b0100001001000001

// Regualar pattern
0b1000000010000000

// The two combined
int resultPattern = 0b0100001001000001 | 0b1000000010000000 // -> becomes 0b1100001011000001

The intensity is just the intensity value of the knob. This is needed because the chance of extra notes increase with intensity.

The currentMap variable helps looping through the 16ths in the intensity map and the extraNotes contain the final extra notes to be added to the normal pattern.

The chance of adding extra notes increases with intensity, so if the intensity is higher there should be more and more extra notes. For this I defined a minimum and a maximum chance (CHANCE_LOWER_LIMIT and CHANCE_UPPER_LIMIT, 0.5 and 0.8 respecitvely).

The I loop through the potential extra notes (intensity map) and if there's a potential extra note for the given 16th then I randomize whether it should actually be played or not.

The rest is kinda obvious.