Introduction: Super Duper Microtonal MIDI Converter

I made this Arduino based microtonal MIDI converter as my first Arduino project:

I wanted to play microtonal scales with my MIDI keyboards, so I made this arduino based device that reads in MIDI notes and sends out corresponding microtonal pitches using MIDI notes and pitchbend values.

Here is the result of my journey to make a more complete and usable project of it. The device reads in MIDI notes from a keyboard and sends out n-TET microtonal scales. To the original project I added:

  • a couple encoders to change the number of steps per octave (from 0 to 99) and the base MIDI note number (also, from 0 to 99)
  • a seven segment display to show those two numbers
  • an LED illuminated push button to toggle between monophonic and polyphonic modes
  • MIDI in and out ports built from components instead of a shield so that they fit nicely on a protoshield
  • better code with a more complete MIDI implementation

You can see in the first picture, the 12 is the number of steps per octave, and the 60 is the base MIDI note number. The lit up button means that it is in monophonic mode, which basically means it will retrigger the last note when necessary. Pushing the button toggles the light off and sets it in 16-voice polyphonic mode and does not retrigger the last note. More details about retriggering are to come in the code section.

The second picture shows the inside a little better without the camera flash. I used some LEGO bricks to wedge the main boards in place.

You can see the MIDI out port in the third picture. The MIDI in port is on the left side. I turned the encoder knobs to set it to 10 steps per octave and 62 as the base MIDI note number.

And, here is a parts list:

Step 1: Learning More About MIDI and Arduino

Picture of Learning More About MIDI and Arduino

This instructable that covers Sending and Receiving on MIDI with an Arduino was a good place to start learning about MIDI, but it only scratches the surface. One of the problems I encountered was how to handle the MIDI System Real-Time messages that one of my MIDI keyboards sends. MIDI-OX was useful for monitoring the messages, except that I was not always sure about the messaging it shows. I later learned that it masks "running status"--more below.

So I wrote code that uses the Arduino to monitor the MIDI input and writes it out to the Serial monitor. I had to use a software serial port for MIDI since the MIDI 31250 baud rate is not supported by the Serial monitor. This way each serial port can have a different baud rate. The code below screens out any MIDI commands other than "note on" or "note off" by only printing out the commands and data below 160, but it is also instructive to build it to not screen out any commands. See this summary of MIDI messages for all the possible MIDI values.

#include <SoftwareSerial.h>
// receive on pin 7 and transmit on pin 8
SoftwareSerial softSer(7, 8);
int data = 0;
void setup() {
  Serial.begin(57600);
  softSer.begin(31250);
}
void loop() {
  if (softSer.available())
  {
    data = softSer.read();
    // screen out commands other than note on or note off
    if (data < 160) {
      Serial.println(data, DEC);
    }
  }
}

Another piece of the puzzle that I needed was the concept of "running status." This means that a successive duplicate command, like "note on," can be skipped and just the data can be sent. So, for example, when playing a 3 note chord, instead of sending 3 "note on" commands with 3 corresponding note and velocity pairs--9 bytes total, instead just 1 "note on" command is sent and then 3 pairs of note and velocity--7 bytes. Also, "note on" commands with zero velocity can be used instead of "note off." This means if you're only playing the keys, then a MIDI keyboard could send only 1 "note on" command and pair after pair of note and velocity bytes. This makes for a more efficient serial communication rate and reduced latency.

My final way to enhance the MIDI portion of my original Arduino device is to build the hardware from components instead of using a Linksprite MIDI shield. The advantage of doing this is that it can be added onto a proto-board with other functionality like encoders and a display, and panel mounted MIDI ports can be used. Above is a schematic of the circuit I used. For reference, here is the official electrical specification. Note that it calls for a resistor connected to MIDI out pin 5. I found that the resistor reduces the voltage level of the transmitted data enough that the receiving end could not read the it, so I omitted the resistor.

Step 2: Reading Quadrature Code Rotary Encoders

Picture of Reading Quadrature Code Rotary Encoders

I want to add some controls to be able to change values on the fly like the number of steps per octave and the base MIDI note. So I acquired these Bourns rotary encoders.

I found this Circuits@Home blog post about reading rotary encoders helpful, although I found it a little hard to follow as someone new to the world of Arduino. I will try to further explain some of the details below.

The post mentions that encoders generate quadrature code. I copied the quadrature code output pattern above from the data sheet for my encoders. It shows that when rotating clockwise the states of the AB signal output repeatedly cycle thru 00, 10, 11, and 01. (The order is reversed for counterclockwise.)

So then the code from the above referenced post (snippet below), broadly speaking, what it does is store 4 relevant bits in old_AB: the previous encoder reading (00, 10, 11, or 01) followed by the current encoder reading (also, 00, 10, 11, or 01). The array enc_states[] contains three possible responses to encoder readings: 0--no change, 1--increment for CW, and -1--decrement for CCW.

/* returns change in encoder state (-1,0,1) */
int8_t read_encoder()
{
  static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
  static uint8_t old_AB = 0;
  /**/
  old_AB <<= 2;                   //remember previous state
  old_AB |= ( ENC_PORT & 0x03 );  //add current state
  return ( enc_states[( old_AB & 0x0f )]);
}

So, for example, if the previous reading was 00 and the current reading is 10, then (binary) 0010 maps to enc_state[2] = 1, which means increment because the encoder moved clockwise. Another example, if the previous reading was 00 and the current reading is 11, then there is a read error or noise so that maps to enc_state[B0011] = 0--no change. The reader can work out the other 14 possible combinations of previous and current readings to see how they map to the enc_states[].

Step 3: Four Digit Seven Segment Display and Port Manipulation

Picture of Four Digit Seven Segment Display and Port Manipulation

Add a 4-digit seven segment display to show the values to be changed with encoders. I decided to use the first two digits to display the number of steps per octave and the last two digits the base MIDI note.

I acquired a four digit common anode seven segment display. According to the datasheet pins 6, 9, 10, and 12 are the digit pins. These pins are used for selecting each of the four digits. The other 8 pins are for the seven segments plus a decimal point.

I put current limiting resistors on each of the digit pins because there are only 4. (I would need 8 resistors if I put them on the segment pins.) The data sheet specifies a max steady current of 25 mA with a typical 2.2 volts forward. I used 220 ohm resistors. Then using Ohm's law the resistors will limit the current to (5 - 2.2) volts / 220 ohms = 12.7 mA, which is within the acceptable range.

I connected the seven segment pins to the 12 digital only Arduino pins 2-13. I simply connected them in an order that made it easy to wire up. Then using the pin mapping from the data sheet I specified the Arduino pin mapping in my code with a bunch of #define statements. I figured it is easier to adapt the code than to have a tangled mess of wires running between the display and Arduino.

// arduino pin numbers for the seven segments and decimal point
#define PIN_A  12
#define PIN_B  8
#define PIN_C  4
#define PIN_D  6
#define PIN_E  7
#define PIN_F  11
#define PIN_G  3
#define PIN_DP 5
// arduino pin numbers for the 4 digits
#define PIN_1 13
#define PIN_2 10
#define PIN_3 9
#define PIN_4 2

I originally started out using the SevSeg library from arduino.cc. It worked well for displaying numbers, but I found that it introduced a significant amount of latency with all the digitalWrite() calls--7 segments plus decimal point times 4 = 32 total. So I decided to write my own code to write to the pins more efficiently.

For that I needed to learn about port manipulation. The two data registers PORTD and PORTB map to Arduino pins 0-7 and 8-15, respectively. When the pin mode is set as OUTPUT, then writing a 0 or 1 to these registers results in setting the pin LOW or HIGH. So I wrote the following code to quickly set pins HIGH or LOW by writing directly to the corresponding PORT without any error checking.

pinHigh(int pin) {
  if (pin >= 2 && pin <= 7) {
    PORTD |= 1 << pin;
  }
  else if (pin >= 8 && pin <= 13) {
    PORTB |= 1 << (pin - 8);
  }
}
pinLow(int pin) {
  if (pin >= 2 && pin <= 7) {
    PORTD &= ~(1 << pin);
  }
  else if (pin >= 8 && pin <= 13) {
    PORTB &= ~(1 << (pin - 8));
  }
}

Step 4: Momentary Push Button Debouncing Circuit and LED

Picture of Momentary Push Button Debouncing Circuit and LED

Further testing revealed that for a monophonic synthesizer extra code is required to perform last note retriggering when more than one key is pressed and the keys pressed map to the same MIDI note number (but different pitchbend values). So I decided to use a momentary push button to toggle between monophonic and polyphonic modes along with an LED to indicate the two modes.

I acquired one of these buttons with a built-in LED. This page describes well why debouncing is necessary and provides a possible circuit about 2/3 of the way down. I implemented it according to the above schematic. The time for this debouncing circuit to discharge is directly proportional to the (external) resistance and the capacitance. So I chose the largest capacitor in my box of parts: 100 pF, and I chose an (external) resistor value less than the 20 K internal pull-up resistor but still large: 10 K. These values worked well, so I kept them.

I connected the LED using the above schematic with a 220 ohm current limiting resistor as recommended by adafruit.

Step 5: The Code

Here is the Arduino code. Notes about it are below.

Seven Segment library:

As outlined earlier I wrote a seven segment library with fast writing to digital pins. It needs to be fast, or else it slows the MIDI processing down noticeably.

Microtonal MIDI library:

I also wrote a microtonal MIDI library that handles the input of MIDI note events and the output of n-TET microtonal note events. To keep the code fast, this library filters out, i.e., does nothing with, MIDI commands or data that are not related to note events.

The code uses all 16 MIDI channels successively and cyclically to send pitchbend and notes. This is because pitchbend affects a whole channel, and each microtonal note often requires a different pitchbend value. It can be adjusted to use less channels, but the number of channels would be the number of available notes of polyphony.

I implemented a monophonic mode that retriggers the previous note when necessary. When dealing with microtonal scales with more than 12 notes, in some cases the same MIDI note is used for adjacent microtonal notes with different pitchbend values. If both notes are held on and then one released, when a note off command is sent for one of the microtonal notes on a monophonic synthesizer then both notes are turned off. When monophonic mode is active the code retriggers the remaining microtonal note and pitchbend value, which is what you would expect when playing a monosynth. The illuminated button toggles between retrigger being on for a monosynth and being off for a polyphonic synth.

I also implemented Pythagorean tuning in the code--mapped to the "00" steps per octave setting, but it is commented out because the required if statements caused noticeable latency. Perhaps there is more optimization to do, or perhaps a faster microcontroller like on a Teensy would allow my code to work well enough. Or, maybe stick with 53-TET for a good enough approximation of 5-limit just intonation. :)

Main code:

The main code includes interrupt handling for the rotary encoder and button. I found this page and this page helpful for writing the interrupt code.

The ISR handles all the rotary encoder and illuminated button functionality. When an encoder is turned, the motion is mapped to a change in the steps per octave or base MIDI note, and those values are updated for the seven segment display. When the button is pressed, monophonic mode is toggled on or off and the internal LED is toggled on or off to match.

The main loop consists of essentially a call to handle one byte of incoming MIDI data and then a single flash of the appropriate LEDs in the seven segment display.

Comments

About This Instructable

1,918views

26favorites

License:

More by havencking:Presenting the GameSquare!Teensy USB Wii Classic Controller Super Duper Microtonal MIDI Converter
Add instructable to: