MIDI Drum Kit on Python and Arduino

2,470

23

Introduction: MIDI Drum Kit on Python and Arduino

About: Hi, I have a hobby to build some DIY Audio stuff, simple gadgets, etc. Hope you will find my instructables useful! :)

I always wanted to buy a drum kit since I was a kid. Back then, all the musical equipment didn't have all the digital applications as we have a plenty of today, hence the prices along with expectations were too high. Recently I've decided to buy a cheapest drum kit from eBay, with the only priority: Ability to tear it down and attach my own hardware and software to device.

The purchase was not disappointing at all: Portable roll-up drum kit with 9 different sound pads, two foot switch pedals for kick drum and hi-hat and micro-USB power socket. What was really demotivating, it is the output sounds (Actual use for this kit is to connect external speaker and enjoy it). So, i decided to convert it to my own programmable via USB, MIDI drum kit based on Arduino and User Interface based on Python, for handy use and easy modifications like, volume, note and channel selections.

Features of the device:

  • Low price
  • Creating drum kit from any digital inputs - even array of push buttons
  • Communication support and power supply via USB interface only - Integration of USB to UART converter and Arduino device
  • Mininum parts for proper operation
  • Easy-to-use Python based UI
  • Complete MIDI support with adjustable velocity, note and Arduino pins
  • Save & Load custom drum configurations stored in device' memory

Let's proceed to the project...

Step 1: Theory of Operation

Block Diagram

First of all, let's focus on the project structure, and divide it into separate blocks:

Roll-Up Drum Kit

The main unit of the project. It consists of 9 separate drum pads, where each pad is an array of buttons that change their logic state while are hit. Because of its structure, there is a possibility to construct this particular drum kit from any push buttons. Each drum pad is connected to the pull-up resistor on the main electronic board, thus while the drum pad is being repeatedly hit, a specific switch is tied to circuit's ground and logical LOW is present on drum pad line. When there are no pressure applied, the drum pad switch is open and due to pull-up resistor to power line, logical HIGH is present on the drum pad line. Because the purpose of the project is to create a complete digital MIDI device, all the analog parts on the main PCB can be neglected. It is important to notice, that drum kit has two pedals for kick drum and hi-hat, which are also tied to the pull-up resistors and share the same operation logic as all the drum pads (We will discuss it a little bit later).

Arduino Pro-Micro

The brain of drum kit. Its purpose is to detect whether there is a signal coming out of a drum pad and provide appropriate MIDI output with all the necessary parameters: Note, velocity and duration of signal. Because of digital nature of drum pads, they can be simply tied to arduino digital inputs (10 pins total). In order to store all the desired settings and MIDI information, we are going to use its memory - EEPROM, hence every time we power up the device, MIDI information is being loaded from EEPROM, making it re-programmable and re-configurable. Also, Arduino Pro-Micro is available in a very small package and can be allocated easily in the drum kit inner case.

FTDI USB To Serial Converter

In order to program and define our device features with the help of PC application, there is need to convert USB interface to serial, because Arduino Pro-Micro doesn't have USB. Since the communication between devices is based upon UART, the FTDI device is used in this project, due to its simplicity of use regardless its additional properties.

PC Application - Python

When it comes to development of user interfaces and fast-to-build projects, Python is a superb solution. The purpose of UI application is to make it much more convenient to redefine MIDI properties for our drum kit, store information, program device and make communication between the systems without the need for compiling the code over and over again. Because we are using serial interface to communicate with drum kit, there are a plenty of free of charge modules all around the internet, that support any types of serial communication. In addition, as it will be discussed later, UART interface consists of total of three pins : RXD, TXD and DTR. DTR is used to perform reset on Arduino module, thus when we are interested in running MIDI app or connect UI to program device, there is absolutely no need to reattach USB cable or whatsoever.

Step 2: Parts and Instruments

Parts

Instruments

  • Soldering Iron/Station
  • Soldering Tin
  • Thin Diameter Single Core wire
  • Tweezers
  • Cutter
  • Plier
  • Knife
  • Screw Driver
  • 3D Printer (Optional - for customized pedal platforms)

Software

Step 3: Soldering and Assembly

Since there are three modules that have to be combined, the soldering and assembling process is short and simple:

  • Attach together Arduino Pro-Micro with FTDI device, make sure that the connections comply with I/O defined at each device:
    • VBUS-VBUS
    • GND-GND
    • DTR-DTR
    • RXD-TXD
    • TXD-RXD
  • Remove all the screws from the drum plastic enclosure, make sure you can focus on pad-to-board cable, and its pull-up resistors
  • Solder thin wires for Arduino-FTDI module that we've constructed previously:
    • Digital inputs: D[2:11]
    • VBUS
    • D+
    • D-
    • GND
  • Insert the module inside the battery case so the wires would be floating on the same side as pull-up resistors of pads
  • Solder all digital inputs to the drum pad terminals as it is shown in the last figure.
  • Solder micro-USB bus (VBUS,D+,D-,GND) to FTDI device, make sure that there are no mistakes tracing these wires.
  • Attach the Arduino-FTDI module with hot-glue to the battery case
  • Assembly the device with appropriate screws attachment

We've done it, device is assembled. Let's continue to the code...

Step 4: Programming A: Arduino

Lets Describe our sketch step-by-step:

  • First of all, there is need to include two necessary libraries for the proper operation. EEPROM is already pre-installed in the Arduino IDE, but the debouncer module for kick drum has to be installed separately.
#include <Debouncer.h><br>#include <EEPROM.h>
  • These switches are used mainly in debugging sequences. If you want to try out the Arduino terminals connection to the drum pads, and determine all the digital inputs, these switches should be defined.
/* Developer Switches: Uncomment desired mode for debugging or initializing */<br>//#define LOAD_DEFAULT_VALUES // Load constant values instead of EEPROM
//#define PRINT_PADS_PIN_NUMBERS // Print pin number that is connected to a pad that was hit via serial port
  • Constant fields represent all the default values, including the drum pad enumeration. In order to run device for the very first time, there is need to know exact connection of Hi-Hat and Kick pedals.
/* Drum type enumeration */
enum DRUM_POSITION { KICK = 0, SNARE, HIHAT, RIDE, CYMBAL1, CYMBAL2, TOM_HIGH, TOM_MID, TOM_LO, HIHAT_PEDAL };
/* Default values */
const uint8_t DRUM_NOTES[10] = { 36, 40, 42, 51, 49, 55, 47, 45, 43, 48};
const uint8_t DRUM_VELOCITIES[10] = { 110, 100, 100, 110, 110,110,110,110,110,110};
const uint8_t DRUM_PINS[10] = { 8, 6, 4, 3, 11, 9, 5, 10, 2, 7 };
/* Kick drum debounce duration */
const uint8_t KICK_DB_DURATION = 30;
  • EEPROM is used to store/load all the data coming from PC application. The addresses span described above, shows exact location for MIDI information for each drum pad
/* EEPROM Addresses mapping <br>
Notes:      |0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09|
Pins:       |0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13|
Velocities  |0x14,0x15,0x16,0x17,0x18,0x19,0x20,0x21,0x22,0x23| */
const uint8_t NOTES_ADDR = 0x00; 
const uint8_t VELOCITIES_ADDR = 0x14;
const uint8_t PINS_ADDR = 0x0A;
  • Global variables are used to determine each pad's state, and perform MIDI communication accordingly.
/* Global Variables */<br>
uint8_t drumNotes[10], drumVelocities[10], drumPins[10];  // MIDI Variables
uint8_t uartBuffer[64];                                   // UART Buffer for collecting and storing MIDI Data
Debouncer kick(DRUM_PINS[KICK], KICK_DB_DURATION);        // Debouncer object for kick drum
volatile bool previousState[9] = {0,0,0,0,0,0,0,0,0};     // Drum pad previous logic states
volatile bool currentState[9] = {0,0,0,0,0,0,0,0,0};      // Drum pad current logic states
  • EEPROM Functions.
/* Store settings in the EEPROM*/
void storeEEPROM() {
  memcpy(drumNotes, uartBuffer, 10);
  memcpy(drumPins, uartBuffer + 10, 10);
  memcpy(drumVelocities, uartBuffer + 20, 10);
  for (uint8_t i = 0; i < 10; i++) EEPROM.write(NOTES_ADDR + i, drumNotes[i]);
  for (uint8_t i = 0; i < 10; i++) EEPROM.write(PINS_ADDR + i, drumPins[i]);
  for (uint8_t i = 0; i < 10; i++) EEPROM.write(VELOCITIES_ADDR + i, drumVelocities[i]);
}
/* Load settings from the EEPROM*/
void loadEEPROM() {
  for (uint8_t i = 0; i < 10; i++) drumNotes[i] = EEPROM.read(NOTES_ADDR + i);
  for (uint8_t i = 0; i < 10; i++) drumPins[i] = EEPROM.read(PINS_ADDR + i);
  for (uint8_t i = 0; i < 10; i++) drumVelocities[i] = EEPROM.read(VELOCITIES_ADDR + i);
}
  • Initialization of variables and programming mode, in the case of pedals and Arduino boot are activated simultaneously.
void enterProgrammingMode() {
  bool confirmBreak = false;
  uint8_t lineCnt = 0;
  uint8_t charCnt = 0;
  char readChar = 0;
  while(!confirmBreak) {
      if (Serial.available()) {
        uartBuffer[charCnt] = Serial.read();
        if (charCnt >= 29) confirmBreak = true;
        else charCnt++;
      }
  }
  Serial.println("OK");
  storeEEPROM();
}
void initValues() {
  #ifdef LOAD_DEFAULT_VALUES
  memcpy(drumNotes, DRUM_NOTES, 10);
  memcpy(drumVelocities, DRUM_VELOCITIES, 10);
  memcpy(drumPins, DRUM_PINS, 10);
  #else
  loadEEPROM();
  #endif
}
  • MIDI Communication handlers with delay of 1ms note hold time
/* Play MIDI note function */
void midiOut(enum DRUM_POSITION drumIn) {
  if (drumIn == HIHAT) { // If HI-HAT was hit, there is need to perform a check whether pedal is pressed
    if (!digitalRead(drumPins[HIHAT_PEDAL])) {
      noteOn(0x90, drumNotes[HIHAT_PEDAL], drumVelocities[HIHAT_PEDAL]);
      delay(1);
      noteOn(0x90, drumNotes[HIHAT_PEDAL], 0);
    }
    else {
      noteOn(0x90, drumNotes[HIHAT], drumVelocities[HIHAT]);
      delay(1);
      noteOn(0x90, drumNotes[HIHAT], 0);         
    }
  }
  else { // Regular drum MIDI transmission
    noteOn(0x90, drumNotes[drumIn], drumVelocities[drumIn]);
    delay(1);
    noteOn(0x90, drumNotes[drumIn], 0);
  }
}
void noteOn(int cmd, int pitch, int velocity) {<br>  Serial.write(cmd);
  Serial.write(pitch);
  Serial.write(velocity);
}
  • setup() and loop() functions with infinite device operation loop:
void setup() {
  Serial.begin(115200);
  for (uint8_t i = 0; i < 10; i++) {
    pinMode(i + 2,INPUT);
  }
  #ifdef PRINT_PADS_PIN_NUMBERS
    while(true) { // Infinite debug loop
       for (uint8_t i = 0; i < 10; i++) {
        if (!digitalRead(i + 2)) {
          Serial.print("Pin No: D");
          Serial.print(i + '0'); // Convert number to ASCII character
        }
       }
    }
  #else
  initValues();
  /* Programming mode: If two pedals are pressed while booting - mode is activated */
  if (!digitalRead(drumPins[KICK]) && !digitalRead(drumPins[HIHAT_PEDAL])) enterProgrammingMode();
  #endif
}
void loop() {<br>    for (uint8_t i = 1; i < 9; i = i + 1) {
      currentState[i] = digitalRead(drumPins[i]);
      if (!currentState[i] && previousState[i]) midiOut(i); // Compare states and detect falling edge
      previousState[i] = currentState[i];
    }
    kick.update(); // Kick drum uses custom debounce algorithm
    if (kick.edge())  if (kick.falling()) midiOut(KICK);
    
}

Step 5: Programming B: Python & User Interface

Python User Interface is a little bit complicated to understand at first sight, hence we would try to explain its basics, how to use, what function does every button have and how to program Arduino device properly.

User Interface - Application

UI is a graphical representation for our drum kit programmer, making it really easy to use and convenient to program Arduino device anytime. UI consists of several graphical modules that are tied to their suggested operation. let's review them one by one:

  1. Drum Set Image: Python UI uses X-Y image coordinates to determine which drum type was selected. If valid drum region was selected, secondary IO message shows up, with fields of note, velocity and Arduino terminal for dedicated drum pad. After these parameters are verified by user and approved, these values can be transmitted directly to Arduino device.
  2. External Controller Image: In order to be able to use MIDI drum kit with VST/Music creating environment, there is a need to run Serial-To-MIDI interpreter. I've used Hairless, which is available for free and can be run directly from our UI, just by pressing its image.
  3. COM Port List: In order to communicate with Arduino, there is need to specify its attached COM port. The list is being refreshed by pressing Refresh button.
  4. Load/Save Configuration: There are default MIDI values defined in the code, which can be modified by user via interacting with UI. Configuration is defined in config.txt file in a specific format, which can be saved or loaded by user.
  5. Program Device Button: In order to store all the modified MIDI values in Arduino EEPROM, there is need to press two foot pedals (Kick drum and Hi-hat pedal) after that, wait for transmission of data to complete. If there were any communication issues, proper pop-up will be shown. If transmission succeeds, UI will show its successful message.
  6. Exit Button: Just exit application, with user's permission.

Python Code Highlights

There are a lot of things going on in the code, so we will expand on the written functions rather than on the whole code.

First of all, in order to use UI, there is need to download several modules, to make the code work:

import os<br>import threading
import tkinter as tk
from tkinter import messagebox
from tkinter import *
from PIL import ImageTk, Image
import numpy as np
import serial
import glob

Some of modules are included in default Python package. Several modules should be installed via PIP tool:

pip install Pillow
pip install numpy
pip install ScreenInfo

It is strongly recommended to run application via PyCharm. In the future releases, I am planning to export an executable for the project.

Brief Code Explanation

It will be much easier to understand the code if we would look at its lines from the perspective of functions and classes:

1. The main function - here the code starts

if __name__ == '__main__':<br>    drumkit_gui()

2. Drum Kit constants, coordinates and default MIDI information

class Drums:<br>    DRUM_TYPES = ["Kick", "Hihat", "Snare", "Crash 1", "Crash 2", "Tom High", "Tom Mid", "Tom Low", "Ride",
                  "Hihat Pedal", "Controller"]
    COORDINATES_X = [323, 117, 205, 173, 565, 271, 386, 488, 487, 135, 79]
    COORDINATES_Y = [268, 115, 192, 40, 29, 107, 104, 190, 71, 408, 208]
    DIMS_WIDTH = [60, 145, 130, 120, 120, 70, 70, 130, 120, 70, 145]
    DIMS_LENGTH = [60, 60, 80, 35, 35, 40, 40, 70, 35, 100, 50]
    DRUM_ENUM = ["Kick", "Snare", "Hihat", "Ride", "Crash 1", "Crash 2", "Tom High", "Tom Mid", "Tom Low", "Hihat Pedal"]
    DRUM_NOTES = [36, 40, 42, 51, 49, 55, 47, 45, 43, 48]
    DRUM_VELOCITIES = [110, 100, 100, 110, 110, 110, 110, 110, 110, 110]
    DRUM_PINS = [8, 6, 4, 3, 11, 9, 5, 10, 2, 7]

3. UI Functions - Handling of user interface and graphical objects

def set_active(ui)
def secondary_ui(drum_type)
class SelectionUi(tk.Frame)
class Application(tk.Frame)
def drumkit_gui()
def event_ui_clicked(event)
def getorigin(self, event)

4. Serial communication

def get_serial_ports()
def communicate_with_arduino(port)

5. Working with files: Store/Load settings from the txt file

def save_config()
def load_config()

6. Running external application hairless.exe from the code using Python Threading capabilities

class ExternalExecutableThread(threading.Thread)
def run_hairless_executable()

In order to run the code, there is a list of files that has to be attached to the project folder:

  • config.txt: Settings file
  • hairless.exe: Hairless MIDI converter
  • drumkit.png: Image that defines all the clickable drum pads on our UI (Has to be downloaded from this step' images set)
  • drumgui.py: The project code

That's everything we need to emphasize to make it work. It is very important to add files to the project: drum set image, hairless.exe executable and settings file config.txt.

And.. Here we've done! :)

Hope you'll find this instructable useful.

Thanks for reading! :)

Be the First to Share

    Recommendations

    • Maps Challenge

      Maps Challenge
    • DIY Summer Camp Contest

      DIY Summer Camp Contest
    • Summer Fun: Student Design Challenge

      Summer Fun: Student Design Challenge

    Comments