Summary:

Turn any surface into a MIDI drum. Great for you, not so great for anyone who has to listen.

Materials:

materials-5

  1. Arduino with I2C support and USB
  2. USB-Micro Cable
  3. MPU-6050 Accelerometer & Gyro
  4. Arduino IDE
  5. Digital Audio Workstation (or other program that supports USB-MIDI instruments)

Process:

Prerequisites:

In the Arduino IDE install the MIDIUSB Library and the MPU6050_tockn Library.

Connections:

Plug the USB-Micro cable into the Arduino, and plug the other end into the computer. Connect the VCC and GND pins of the MPU-6050 to the 5V and GND pins of the Arduino, respectively. Connect the SDA pin of the MPU-6050 to the Arduino's SDA pin, and the SCL pin of the MPU-6050 to the Arduino's SCL pin. You might need to reference a board-specific pinout diagram to determine which built-in pins are assigned to I2C.

connections-1

Use double-sided foam tape to affix the MPU-6050 to a surface that has some yield, so that tapping or hitting it will act a bit like a drum.

install

Feel the Beat:

The goal is to use the MPU-6050 to do some basic acceleration averaging, and use one axis' average acceleration as measurement of note velocity.

First, we'll import the libraries for the MPU-6050, and define some threshold constants and local variables. We'll also add the appropriate initialization functions.

#include <MPU6050_tockn.h>
#include <Wire.h>

// ms to Sample
#define SAMPLE_PERIOD 5

// Sensitivity Threshholds:
// Minumum accelleration needed to trigger
#define ACC_MIN 1
// Max acceleration we want to output
#define ACC_MAX 10

MPU6050 mpu6050(Wire);
long timer = 0;
float accY, oldAccY, velocity;
bool isOn = false;

void init() {
    Serial.begin(9600);
    Wire.begin();
    mpu6050.begin();
    mpu6050.calcGyroOffsets(true);
}

To do basic pulse detection we'll take continuous readings from one axis of the accelerometer. These readings will be averaged over a predefined window of time to provide some debounce against detection spikes. Every time that window finishes we'll compare the value averaged to the last value averaged, to determine if there's been a significant change in acceleration. If this change is over our defined threshold we will want to trigger some sort of MIDI event. Finally we reset our timer for the sampling window.

void loop() {
    mpu6050.update();

    // Take weighted average of samples on the Y axis
    float sample = mpu6050.getAccY();
    accY = ( min(sample, accY)*0.30 + max(sample, accY)*0.70 );

    // If sample period is up
    if(millis() - timer > SAMPLE_PERIOD){

        // Measure change in Y since last sample period
        float diffY = abs(accY - oldAccY)*10;
        oldAccY = accY;

        int thresh = (diffY > ACC_MIN) ? 1 : 0;
        Serial.print("Thresh : ");Serial.print(thresh);
        Serial.print("\tDiffY : ");Serial.println(diffY);
        
        if (thresh) {
            // MIDI
        }
        
        timer = millis();
    }
}

If we run this as-is we can use the Arduino IDE Serial Plotter (Tools > Serial Plotter) to view relative intensity and triggers. For example, I've hit the table a few times, with varying velocity. The red plot below shows the intensity registered, while the blue plot shows when that hit would trigger a MIDI note.

velocity_vs_trigger

Making Noise:

First we need a way to send MIDI commands to our DAW. Since MIDI is just a serial protocol we could bit-bash some information onto a serial connection. But the Arduino MIDIUSB library provides a more simple abstraction.

After importing the library we'll define a constant for the note we want to trigger. You could also use random values, read values from serial, or from a sensor such as a potentiometer, but for now a static value works fine. We'll also reuse some helper functions from the MIDIUSB example code to send MIDI note on and MIDI note off messages.

#include "MIDIUSB.h"

// 38 = D1 - By default this is the snare on drum kits
#define MIDI_NOTE 38

// Minumum velocity we want the note to have
#define VELOCITY_BIAS 5

// First parameter is the event type (0x09 = note on, 0x08 = note off).
// Second parameter is note-on/note-off, combined with the channel.
// Channel can be anything between 0-15. Typically reported to the user as 1-16.
// Third parameter is the note number (48 = middle C).
// Fourth parameter is the velocity (64 = normal, 127 = fastest).
void noteOn(byte channel, byte pitch, byte velocity) {
    midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
    MidiUSB.sendMIDI(noteOn);
}

void noteOff(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
}

Now we can add logic to send velocity-scaled commands to our DAW. We'll modify the if(thresh) block to also check a boolean called isOn. This will prevent us from sending multiple MIDI note on commands in a row. Note that we don't want to evaluate if(thresh && !isOn), as that would force us to send a noteOff every sample window after a note is triggered, which would defeat the purpose of the debouncing we did.

If the threshold has been met and we aren't currently mid-note, we'll want to send a velocity value. For sanity we'll bound our velocity value to be within the trigger range we set before. Then we'll map the value from that range into the the range [VELOCITY_BIAS:127], the legal range of MIDI velocity values. Then we can call our noteOn function to trigger our MIDI_NOTE at the velocity calculated, and toggle the isOn boolean.

If we haven't met the threshold after our sampling period then we can send a noteOff with 0 velocity, and reset our isOn boolean.

if (thresh) {
    if (!isOn) {
    
        // Trim to ACC_MIN <= velocity <= ACC_MAX
        velocity = max(min(ACC_MAX, diffY), ACC_MIN);

        // Map to int range [VELOCITY_BIAS:127]
        velocity = int(map(velocity, ACC_MIN, ACC_MAX, VELOCITY_BIAS, 127));

        noteOn(1, MIDI_NOTE, velocity);
        isOn = true;
    }
} else {
    noteOff(1, MIDI_NOTE, 0);
    isOn = false;
}

Testing:

Open your DAW of choice. I've chosen GarageBand because it's free. Create an instrument, and set the MIDI input to the usbmodemMIDI source. Then, pound away and amaze yourself at the siqq beats you create.

Code:

Code examples for today can be found here.

Analysis & Future Work:

For what it's worth an accelerometer/gyro might not be the best sensor for this application. Something pressure sensitive like a piezoelectric transducer might yield better results. But I had a gyro on hand, so that's what I used.

Possible uses for code like this would be an acceleration-based MIDI controller, altering pitch, vibrato, or synthesizer parameters based on acceleration or orientation. For actual percussion there exist a multitude of much better suited acoustic-electric triggers.