Arduino MIDI Switch

Recently I started testing the Arduino microcontroller platform. I ordered some Arduino Nano clones for about 7,- EUR. They work great – USB connectivity is onboard, so there is no need for programmers like with the PIC controllers; you can directly upload sketches from your PC to the Arduino. The Arduino IDE (Integrated Development Environment) is available for free (arduino.cc), so it is really easy to get your first programs running. It soon turned out that this platform is a great starting point for developing MIDI applications/hardware. Especially for DIY!

My first MIDI devices were based on the PIC microcontroller. I started programming the PICs with assembler, which is quite abstract and not really effective, especially if you’re not a pro… At least the PIC-based Mitch (MIDI Switch) is outdated (hard- and software). Time to move on – here comes the Arduino MIDI Switch!

Note: This MIDI device is a prototype. It has been tested with different other MIDI devices and works great as it is, but it has not been used in a live setup so far. Also this project is only the core of a complete setup, you have to add at least a power supply and the wiring for the switch outputs (or audio loops).

Arduino MIDI Switch: Schematic

The Arduino MIDI switch for guitar amp/effect switching is my first complete Arduino project. It receives MIDI program change messages from any MIDI pedal/device and can store switch settings for eight outputs with each program. You can set the MIDI receive channel (1-16); this setting will be saved, so it is persistent when switching on/off the device. The switch can be operated from only two push buttons. These buttons have different functions depending on a long/short press. There is a small 128×64 OLED display showing all relevant info, like current MIDI program number, MIDI channel, operating mode, etc.

Software for the Arduino platform is written in C++, which is a high-level language, so reading and writing code is more intuitive (there are also tools to program PICs in high-level languages – but that’s a different topic). Many Arduino projects deal with input/output processing (sensors, interfaces, displays, switches), there are many libraries with useful functions already available. The Arduino IDE comes with (basic) examples.

My software makes use of several Arduino libraries; if you decide to program your Arduino with this software, you have to download/install those libraries first, otherwise you will not be able to compile and upload my code:

  • Software serial library (included in Arduino IDE by default)
  • Text only Arduino library for SSD1306 OLED displays (https://github.com/greiman/SSD1306Ascii)
  • Arduino OneButton library (https://github.com/mathertel/OneButton)

How to use the MIDI switch:

  • Connect a MIDI device (any device sending MIDI program changes)
  • Set the MIDI channel: Button 2 *long press* (repeated *long press* increases channel)
  • Send program change on correct channel; MIDI in LED lights up shortly, display shows current program number
  • Set outputs for current MIDI program number: Button 1 *long press* starts edit mode (edit mode LED lights up, display shows message)
  • Select outputs: Button 1 *short press* cycles through switch index (1-8, shown on display)
  • Toggle outputs (on/off): Button 2 *short press* changes output state of selected output (on/off shown on display)
  • Exit edit mode/save settings: Button 1 *long press* (edit mode LED turns off)

This is the source of the current software version (download ino-file here):

// midi_switch.ino
// version 2018-05-28
// Arduino MIDI switcher
// by jimkim.de
// https://www.jimkim.de

// libraries

// serial comm
#include <SoftwareSerial.h>
// use SoftwareSerial lib instead of the serial lib; this lets us control which pins the MIDI interface is connected to
// only RX is needed; set RX = TX pin to save one I/O pin
SoftwareSerial mySerial(12, 12); // RX, TX

#include <EEPROM.h>

// libs for ssd1306 OLED
#include <Wire.h>
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"
// OLED address: 0X3C+SA0 - 0x3C or 0x3D
#define I2C_ADDRESS 0x3C
// define proper RST_PIN if required
#define RST_PIN -1
SSD1306AsciiWire oled;

// lib for one button control
#include "OneButton.h"
// setup a new OneButton on pin A1.  
OneButton button1(A1, true);
// setup a new OneButton on pin A2.  
OneButton button2(A2, true);

// constants
#define MIDI_LED 10
#define EDIT_LED 11
#define MIDI_PGM_CHANGE 192

const int outputCount = 8;    // the number of output pins

byte statusByte;
byte dataByte;
// set MIDI channel (which channel to listen to)
byte setMidiChannel;
// current MIDI channel (from MIDI receive)
byte midiChannel;
char midiChannelChar[2];
// init pgmNumber for arduino startup
byte pgmNumber = 0;
char pgmNumberChar[3];
byte midiCommand;

char title[16] = "MIDI Switch 1.0";

byte currentSwitchIndex = 0;
byte currentSwitchIndexBefore = 0;
byte currentSwitchState = 0;
byte switchChange = 0;
byte editMode = 0;

// define output pins
int outputPins[] = {
  2, 3, 4, 5, 6, 7, 8, 9
};
// var containing output setting for each output pin
byte outputPinsState;   

void setup() {
    
    // setup HW serial for debug communication
    Serial.begin(9600);    
    
    // setup SoftSerial for MIDI control
    mySerial.begin(31250);
    delay(100);

    // onboard LED
    pinMode(MIDI_LED, OUTPUT);
    pinMode(EDIT_LED, OUTPUT);

    // read output switch setting from eeprom
    outputPinsState = EEPROM.read(pgmNumber);

    // loop over the pin array and set them all to output:
    for (int thisOutput = 0; thisOutput < outputCount; thisOutput++) {
      pinMode(outputPins[thisOutput], OUTPUT);
      // if outputState for this pin is active, set HIGH
      if ( bitRead(outputPinsState, thisOutput) == 1 ) {
        digitalWrite(outputPins[thisOutput], HIGH);  
      }
    }

    // read MIDI channel setting from eeprom
    setMidiChannel = EEPROM.read(500);
    // at very first startup after sketch upload (before any custom MIDI channel has been actively set by user)
    // the default eeprom value may be > 15; if so, set value = 0 (which is MIDI channel 1)
    if ( setMidiChannel > 15 ) {
      setMidiChannel = 0;  
    }

    // init OLED display
    Wire.begin();
    Wire.setClock(400000L);
    #if RST_PIN >= 0
    oled.begin(&Adafruit128x64, I2C_ADDRESS, RST_PIN);
    #else // RST_PIN >= 0
    oled.begin(&Adafruit128x64, I2C_ADDRESS);
    #endif // RST_PIN >= 0

    oled.setFont(ZevvPeep8x16);
    
    refreshDisplay(11);

    // link the button 1 functions.
    button1.attachClick(click1);
    button1.attachLongPressStart(longPressStart1);
    
    // link the button 2 functions.
    button2.attachClick(click2);
    button2.attachLongPressStart(longPressStart2);
    
}

void loop () {

    // Is there any MIDI waiting to be read?
    if ( mySerial.available() > 1 && editMode == 0 ) {

        // read MIDI byte
        statusByte = mySerial.read();
        dataByte = mySerial.read();
    
        // remove program info from status byte (only channel value left)       
        midiChannel = statusByte & B00001111;
        // remove channel info from status byte (only program value left) 
        midiCommand = statusByte & B11110000;

        // Serial.println("MIDI channel:");
        // Serial.println(midiChannel);
        // Serial.println("MIDI command:");
        // Serial.println(midiCommand);        
        
        // check if midiCommand = MIDI PROGRAM CHANGE on selected MIDI channel   
        if ( midiCommand == MIDI_PGM_CHANGE && midiChannel == setMidiChannel ) {
            
            // save current program number
            pgmNumber = dataByte + 1;
            // read output switch setting from eeprom for this program number
            outputPinsState = EEPROM.read(pgmNumber);
            // set outputs
            for ( int thisOutput = 0; thisOutput < outputCount; thisOutput++ ) {
              // if outputState for this pin is active, set HIGH
              if ( bitRead(outputPinsState, thisOutput ) == 1 ) {
                digitalWrite( outputPins[thisOutput], HIGH );  
              } else {
                digitalWrite( outputPins[thisOutput], LOW );
              }
            }

            // Serial.println("Output pins after MIDI receive:"); 
            // Serial.println(outputPinsState); 
            
            refreshDisplay(21);
            
            // flash MIDI led
            digitalWrite(MIDI_LED, HIGH);
            delay(100);
            digitalWrite(MIDI_LED, LOW);
                        
        } else {
          // flush other MIDI messages
          while( mySerial.available() ) {
            mySerial.read();
          }  
        }
    
    }

    // edit mode
    if ( editMode == 1 ) {
      
      // check if another switch output has been selected
      if ( currentSwitchIndex != currentSwitchIndexBefore ) {
        
        // display switch index
        currentSwitchIndexBefore = currentSwitchIndex;
        
        refreshDisplay(41);
                    
      }

      // check if output state has been changed
      if ( switchChange == 1 ) {
        
        // toggle switch state
        if ( bitRead(outputPinsState, currentSwitchIndex) == 1 ) {
          digitalWrite(outputPins[currentSwitchIndex], LOW);
          bitClear(outputPinsState, currentSwitchIndex);
          refreshDisplay(51);            
        } else {
          digitalWrite(outputPins[currentSwitchIndex], HIGH);
          bitSet(outputPinsState, currentSwitchIndex);
          refreshDisplay(52);   
        }

        switchChange = 0;
                         
      }
    }

    // keep watching the push buttons:
    button1.tick();
    button2.tick();
    
} // loop


//
// functions
//


// button 1 short press: select switch outputs in edit mode
void click1() {
  
  if ( editMode == 1 ) {
    
    if ( currentSwitchIndex < 7 ) {
      currentSwitchIndex++;
    } else {
      currentSwitchIndex = 0;
    }
  
  }  

}

// button 1 long press: start edit mode
void longPressStart1() {

  // toggle edit mode
  if ( editMode == 0) {
    
    // start edit mode
    editMode = 1;

    outputPinsState = EEPROM.read(pgmNumber);

    // reset switch index
    currentSwitchIndex = 0;
    currentSwitchIndexBefore = 0;
    
    // refresh display
    refreshDisplay(31);

    // switch on edit led
    digitalWrite(EDIT_LED, HIGH);
  
  } else {
    
    // exit edit mode
    editMode = 0;
    EEPROM.update(pgmNumber, outputPinsState);
    refreshDisplay(32);

    // switch off edit led
    digitalWrite(EDIT_LED, LOW);
    
    // flush MIDI messages that may have been received during edit mode
    while( mySerial.available() ) {
      mySerial.read();
    }      
  
  }
  
}

// button 2 short press: toggle switch output
void click2() {
  
  if ( editMode == 1 ) {
  
    if ( switchChange == 0 ) {
      switchChange = 1;
    }
    
  }
  
}

// button 2 long press: edit MIDI channel
void longPressStart2() {
  
  if ( editMode == 0 ) {
    
    // increase MIDI channel and save setting in eeprom
    setMidiChannel++;
    if ( setMidiChannel > 15 ) {
      setMidiChannel = 0;
    }
    EEPROM.update(500, setMidiChannel);
    refreshDisplay(81);
       
  }
  
}

// display actions
void refreshDisplay(byte mode) {
  
  // startup
  if ( mode == 11 ) {
    oled.clear();
    oled.println(title);
    sprintf(midiChannelChar, "%02d", setMidiChannel+1);
    oled.println("Channel ");
    oled.setCursor(64,2);
    oled.println(midiChannelChar);
    oled.set2X();
    oled.print("PGM");
    sprintf(pgmNumberChar, "%03d", pgmNumber);
    oled.setCursor(64,4);
    oled.println(pgmNumberChar);
  }
  
  // update program number
  if ( mode == 21 ) {
    sprintf(pgmNumberChar, "%03d", pgmNumber);
    oled.set2X();
    oled.setCursor(64,4);
    oled.print(pgmNumberChar);
  }
  
  // enter edit mode
  if ( mode == 31 ) {
    oled.clear();
    oled.set1X();
    oled.println(title);
    oled.println("Edit Mode:");    
    oled.set2X();
    oled.print("SW : ");
    oled.setCursor(32,4);
    oled.print(currentSwitchIndex+1);
    // display output status of current switch
    oled.setCursor(80,4);       
    if ( bitRead(outputPinsState, currentSwitchIndex) == 1 ) {
      oled.print("On ");  
    } else {
      oled.print("Off");   
    }
  }

  // exit edit mode
  if ( mode == 32 ) {
    oled.clear();
    oled.set1X();
    oled.println(title);
    sprintf(midiChannelChar, "%02d", setMidiChannel+1);
    oled.println("Channel ");
    oled.setCursor(64,2);
    oled.println(midiChannelChar);
    oled.set2X();
    oled.print("PGM");
    sprintf(pgmNumberChar, "%03d", pgmNumber);
    oled.setCursor(64,4);
    oled.println(pgmNumberChar);
  }

  // update switch index and state
  if ( mode == 41 ) {
    oled.setCursor(32,4);
    oled.print(currentSwitchIndex+1);
    // display output status of current switch
    oled.setCursor(80,4);       
    if ( bitRead(outputPinsState, currentSwitchIndex) == 1 ) {
      oled.print("On ");  
    } else {
      oled.print("Off");   
    }
  }

  // switch state off
  if ( mode == 51 ) {
    oled.setCursor(80,4);
    oled.print("Off");
  }

  // switch state on
  if ( mode == 52 ) {
    oled.setCursor(80,4);
    oled.print("On ");
  }

  // update MIDI channel
  if ( mode == 81 ) {
    sprintf(midiChannelChar, "%02d", setMidiChannel+1);
    oled.set1X();
    oled.setCursor(64,2);
    oled.println(midiChannelChar);
  }  
  
}
Top