Making a simple MIDI Controller. One Man's Journey

I am interested in making MIDI controllers to communicate with virtual instruments or a DAW.
I had to spend a fair amount of time figuring out what works for my setup.
I would like to offer the little bit of knowledge I have gathered thus far that may save you some troubles.

I have Arduino Uno, latest revision, a laptop running Windows 7 Professional and some old version of Ableton Live. I am just using the laptop's standard sound card. I am not using any extra hardware or specialized cables, only the USB cable connected from the Arduino to the laptop.
(This is a good time to mention that you can only use the USB connection from the Arduino to the PC for one thing at a time. Meaning you can upload a sketch, or send serial data.It is doing one or the other, but not both at the same time.)

I set out to create a very simple controller that I can expand upon as I learn how to code.

Before we venture into different ways to trigger MIDI, there is a simple sketch below that does not require any sort of extra input such as buttons, knobs, or force sensing resistors. It is a great way to test your setup.

I found that I needed 2 pieces of software for my Arduino to communicate with the audio software:

  1. Hairless MIDI The Hairless MIDI<->Serial Bridge
  2. LoopBe1 LoopBe1 - A Free Virtual MIDI Driver

DO NOT RUN HAIRLESS MIDI UNTIL AFTER YOU UPLOAD THE SKETCH TO THE ARDUINO AND HAVE STARTED THE AUDIO SOFTWARE!!!!!!!!

In case that's not clear:

  1. Upload sketch
  2. Start your audio software of choice (I will assume you know how to setup audio and MIDI Ins/Outs for your DAW/VST of choice.)
  3. Open Hairless

LoopBe1 kinda runs in the background so you don't have to do anything with it.

Here is a sketch from http://www.instructables.com/id/Arduino-Sensors-and-MIDI/?ALLSTEPS
that can serve as a good test to confirm you have the proper setup/configuration from Arduino to PC to Serial Bridge:

Step 3: Generating MIDI with Arduino

(Upload the following code onto the Arduino, it turns MIDI note 60 (middle C) on, waits for 300ms, then turns it off and waits for another 200ms:

byte noteON = 144;//note on command

void setup() {
  Serial.begin(9600);
}

void loop() {
  MIDImessage(noteON, 60, 100);//turn note on
  delay(300);//hold note for 300ms
  MIDImessage(noteON, 60, 0);//turn note off (note on with velocity 0)
  delay(200);//wait 200ms until triggering next note
}

//send MIDI message
void MIDImessage(byte command, byte data1, byte data2) {
  Serial.write(command);
  Serial.write(data1);
  Serial.write(data2);
}

You may have to mess around with the Baud rate settings in Hairless as well as the sketch for things to start communicating properly. However, for this sketch, the prescribed setting should do just fine.You should see some blinky lights on the Hairless GUI indicating that Arduino is indeed sending bytes to the laptop and that MIDI in is seeing them. Then you should hear some sounds coming from your laptop, pc, sound card,IO device, etc.
Don't forget to close Hairless MIDI before you try to upload any changes to the Arduino.
If you hear middle C turning on and off, Congratulations, everything is communicating.

My next mission is to figure out how to setup a couple of buttons to turn on MIDI notes so that I will have the ability to play a melody or whatever on a virtual instrument.

If anyone finds this thread helpful or can offer further guidance on the subject, please post a comment.

I will try to add to this post as I learn more and have more to share.

Thanks

Here's some quick Ideas I played with last year for a "stomp box" style of foot control. However I used the Fluxamasynth as my output synth (everything in the one spot and compact) and also used a Uno to to the hard work.

#include "Fluxamasynth.h"

# define bass 36              // Define midi note numbers for several GM drum sounds
# define snare 38
# define hihatC 42
# define hihatP 44
# define hihatO 46

Fluxamasynth synth;		// create a synth object

// * Some basic settings */
int channel = 9;              // MIDI channel number
int tempo = 127;              // Start tempo

const int pad0 = A0;  // Analog input pin that the potentiometer is attached to
const int pad1 = A1;  // Analog input pin that the potentiometer is attached to
const int pad2 = A2;  // Analog input pin that the potentiometer is attached to
const int ledPin = 13;       // LED connected to digital pin 13

int sensor0 = 0;         // value read from the sensor
int perc_0 = 45;         //Center
int sensor1 = 0;         // value read from the sensor
int perc_1 = 49;         //LHS
int sensor2 = 0;         // value read from the sensor
int perc_2 = 38;          //RHS
int trapADC = 0;            //used to trap the reading
int trapSensor = 0;
int sensorHi = 0;
int sensorLo = 2000;
int trigger = 0;
int hiHys = 500;
int loHys = 100;
int vel = 120;

void setup() {
  //Use the LED as a monitor
  pinMode(ledPin, OUTPUT);              // sets the digital pin as output
  // initialize serial communications to Midi 31,250 bps
  Serial.begin(31250);		        //  Set MIDI baud rate:
  //Set up the Midi
  
  synth.midiReset();                    // Do a complete MIDI reset
  //synth.setReverb(channel,5,255,100);   // A Plate Reverb with maximum effect level
  synth.setChannelVolume(channel, 127); // max. channel volume
  synth.setMasterVolume(255);	        // max. master volume
  //synth.noteOn(channel, hihatP, vel);	// play a note
  
  //Serial.begin(9600);		        // just for debug only

}

void loop() {
  // read the analogs into values:
  sensor0 = analogRead(pad0);
  sensor1 = analogRead(pad1);
  sensor2 = analogRead(pad2);
  // determine alarm status
  if (sensor0 > hiHys)
  {
    digitalWrite(ledPin, HIGH);   // sets the LED on
  }
  else
  {
    digitalWrite(ledPin, LOW);    // sets the LED off
  }

  if (sensor0 > hiHys){
    synth.noteOn(channel, perc_0, vel);	// play a note
    synth.noteOff(channel, perc_0);    
    trapADC = A0;
    trapSensor = sensor0;
  }
  if (sensor1 > hiHys){
    synth.noteOn(channel, perc_1, vel);	// play a note
    synth.noteOff(channel, perc_1);    
    trapADC = A1;
    trapSensor = sensor1;
  }
  if (sensor2 > hiHys){
    synth.noteOn(channel, perc_2, vel);	// play a note
    synth.noteOff(channel, perc_2);    
    trapADC = A2;
    trapSensor = sensor2;
  }
  while (trapSensor > hiHys ){
    delay(10);
    trapSensor = analogRead(trapADC);

  }
  while (trapSensor < loHys){
    delay(10);
    trapSensor = analogRead(trapADC);

  }
  // wait 10 milliseconds before the next loop
  // for the analog-to-digital converter to settle
  // after the last reading:
}

void update(){
  //delay(1000);
  Serial.print("Sensor A0 = " );
  Serial.print(sensor0,DEC);
  Serial.print(" Sensor A1 = " );
  Serial.print(sensor1,DEC);
  Serial.print(" Sensor A2 = " );
  Serial.print(sensor2,DEC);
  Serial.print(" Sensor lo = " );
  Serial.print(hiHys,DEC);
  Serial.print(" Sensor hi = " );
  Serial.println(loHys,DEC);
  Serial.println();
}
/*
Bass  	KeyNum	Sound
A_	33	Metronome Click
B_b	34	Metronome Bell
B_	35	Acoustic Bass Drum
C	36	Bass Drum 1
C#	37	Side Stick
D	38	Acoustic Snare
Eb	39	Hand Clap
E	40	Electric Snare
F	41	Low Floor Tom
F#	42	Closed Hi-Hat
G	43	High Floor Tom
G#	44	Pedal Hi-Hat
A	45	Low Tom
Bb	46	Open Hi-Hat
Bn	47	Low-Mid Tom
c	48	Hi-Mid Tom
c#	49	Crash Cymbal 1
d	50	High Tom
eb	51	Ride Cymbal 1
e	52	Chinese Cymbal
f	53	Ride Bell
f#	54	Tambourine
g	55	Splash Cymbal
g#	56	Cowbell
a	57	Crash Cymbal 2
bb	58	Vibraslap
bn	59	Ride Cymbal 2
C	60	Hi Bongo
C#	61	Low Bongo
D	62	Mute Hi Conga
D#	63	Open Hi Conga
E	64	Low Conga
F	65	High Timbale
F#	66	Low Timbale
G	67	High Agogo
G#	68	Low Agogo
A	69	Cabasa
Bb	70	Maracas
Bn	71	Short Whistle
c	72	Long Whistle
c#	73	Short Guiro
d	74	Long Guiro
d#	75	Claves
e	76	Hi Wood Block
f	77	Low Wood Block
f#	78	Mute Cuica
g	79	Open Cuica
g#	80	Mute Triangle
a	81	Open Triangle
	82	
	83	
	84	
*/

It uses three pressure sensitive sensors with a dropper resistor and reading the voltage with the analogue channels to set thresholds etc All "notes" are in the Percussion channel and it does not seem to matter when the off signal is sent, the beat plays the same.

Cheers, Rob

I hope this helps.

Fluxamasynth looks really nice. I might have to buy my wife something nice so she will let me order one.

Hi Matt,

Fluxamasynth is pretty cool. Well worth a look. Wealth of features and easy form factor.

I have photos below of the stomp box below. It is the Uno+Fluxa and shows the assembled with it all plugged in and also separated to show the Uno below and how the Fluxa plugs in as a shield above. The sensors are hidden under the carpet where the 3 black Velcro squares are placed. there two others for an approximate heel position. The little black square on the Fluxa board is it!!!!

I have also included a photo of my "much more worked on" Midi project with a Fluxa as well. It is built up on a Mega 2560 and has 512k SRAM added to it. Sounds impressive but it is actually 8 banks of 64k and I only use one of them, slight over kill, but I needed much more than the 8k in the Mega.

It uses a Fluxamasynth and it is connected to the second serial port on the Mega and drives it with classic Serial2.write() commands at the midi baud rate but also allows all the debugging on the standard serial line (Mega has 4 UART style serial ports!!) as well. Much easier and faster. I just jumper the Fluxa to the second serial with a jumper wire. (BTW On the stomp box I use the bit-bash library that Fluxamasyth has developed for the Uno style boards because the Uno has only one UART for Serial.) On the Mega set up I also have an Adafruit OLED display that sports a Joystick and and SD interface. This has allowed me to develop a menu system and be able to select tunes and change tempos, start and stop points etc It keeps settings between start ups in EEPROM so I don't need to re-set up stuff I am currently working on.

I have gone through two major phases with this project. I first got it to play standard midi files reasonably well. But as a music student I wanted to be able to select start and stop bars and have the midi only play that bit. I am learning flute and need to concentrate on getting some sections right much more than others. This I could not do with my version of standard midi format player, so I worked out my own .json style format, wrote a state engine to process it and have had it working that way ever since. I now realise the big answer is a probably a combination of the two approaches. Finding "Bars" and coincidental notes in midi files, is not as easy it might seem.

I also wrote a GUI Python program to enter the tunes in my own .json format. Why enter them myself? Well most of my tutorial work is centered around the Baroque age and there just don't seem to be much already entered (haha what a surprise!), so I have to enter it myself anyway.

However I can recommend the Fluxamasyth as it is a very powerful chip and easy to use. The serial interface is at TTL levels and it just talks directly to the Aduino systems. I only use a fraction of its power as a fully blown synth chip.

Cheers, Rob

UnoFluxa.png

Maybe upload images to http://imgur.com/ and link to them here. You don't need to join just upload and link to share 8)

Managed to attach two images to my original post.
Sorry about the language, but I expect more from IT expert hosted sites that just report "security problems" and give no lead as to what has caused it.

I hope that is enough to get my discussion across.

Cheers, Rob

PS Trying to add the third image to this post.

Does the fluxamasynth fit the Uno? All the pictures I see have it on the Mega.

Apparently the answer is "Yes"

Yes it is was first designed for the ultimate experimental machine, the Uno, but the Mega opens up a couple of very important opportunities. For example, and most importantly, when using the Uno you have to use the Soft Serial if you want to be able to have serial debugging going on, as well as still sending midi data to the fluxamasynth. So things have to be "bit bashed" to get the second Serial port to work. If you have any serious processing going on while the midi data is being queued then any bit bashing is a costly overhead you can do without.

This is a serious problem, as it is very handy when developing a program to get feedback and when using a Mega and its multiple fully buffered serial ports, it makes this an easy job, ie to both use the standard Serial.print and Serial2.write(instrument); for any midi communication. It also reduces the time/CPU overhead taken to send the various data to both destinations. If you are a person who writes perfect code every time and needs no debugging then attach it to a Uno, but if you are like me and just a humble mere mortal, then the debugging is so absolutely essential!!! So go the Mega 2560 and use the full buffering etc that the Mega gives you.

Cheers, Rob

Rob, you just went way over my head. LOL

I got my Fluxamasynth in the mail yesterday. So far I can not get any sound out of it.
I wonder if it has something to do with the "pin select jumper"
I can see that the UNO is transmitting. And I can Serial Monitor a Potentiometer connected through the
Fluxamasynth.

The getting started instructions @ http://moderndevice.com/documentation/fluxamasynth-quickstart-guide/
kinda suck for me, because it assumes I know something about jumpers.

Any advice on getting this thing to make some sound with this sketch?

#include <Fluxamasynth.h>

Fluxamasynth synth;     // create a synth object

int note=60;            // C4 on keyboard
int bend=512;           // midpoint of pitch bend range

void setup() {
  synth.setMasterVolume(255);            // max volume
  synth.setChorus(0, 5, 127, 127, 127);  // turn on Chorus effect
  synth.programChange(0, 0, 58);         // set instrument to Trombone
  synth.pitchBendRange(0, 127);          // set pitch bend to maximum range
  synth.noteOn(0, note, 64);             // play C4
  Serial.begin(9600);
}

void loop() {
  int sensorValue = analogRead(A0);
  Serial.println(sensorValue);
  delay(1); 
  bend=analogRead(0);
  synth.pitchBend(0, bend);  
}

You seem to know a hell of a lot about this shield.

a ha. Jumper.

Hi Matt,
Sorry if I confused you, always possible at a distance.

First of all if you are using a Uno and want to use the normal serial port for debugging then they suggest using Pin4 on the Uno as a Softserial ouput. This effectively uses a normal output port and bit-bangs it to make it appear as a serial port. Pretty handy coding coding. So the Fluxamasynth jumper would be on [center + Pin4]. If you want to send Midi with less software overhead then you should use a dedicated serial port, and the Fluxamasynth jumper [center+ Pin1] would connect it to the Serial out and share it with the Serial to the USB/screen terminal. This does work as I have used it but it has one major draw back that the Serial to the terminal can be interpreted by the Fluxamasynth as notes and produce some rather startling results, or sometimes nothing at all.

Below is my example of a simple "drum machine" EDIT: Sorry I have sent this before, my apologies :frowning: however comparing it to the ADC one you are trying may give you some more ideas.

// These constants won't change.


#include "Fluxamasynth.h"

# define bass 36              // Define midi note numbers for several GM drum sounds
# define snare 38
# define hihatC 42
# define hihatP 44
# define hihatO 46

Fluxamasynth synth;		// create a synth object

// * Some basic settings */
int channel = 9;              // MIDI channel number
int tempo = 127;              // Start tempo

const int pad0 = A0;  // Analog input pin that the potentiometer is attached to
const int pad1 = A1;  // Analog input pin that the potentiometer is attached to
const int pad2 = A2;  // Analog input pin that the potentiometer is attached to
const int ledPin = 13;       // LED connected to digital pin 13

int sensor0 = 0;         // value read from the sensor
int perc_0 = 45;         //Center
int sensor1 = 0;         // value read from the sensor
int perc_1 = 49;         //LHS
int sensor2 = 0;         // value read from the sensor
int perc_2 = 38;          //RHS
int trapADC = 0;            //used to trap the reading
int trapSensor = 0;
int sensorHi = 0;
int sensorLo = 2000;
int trigger = 0;
int hiHys = 500;
int loHys = 100;
int vel = 120;

void setup() {
  //Use the LED as a monitor
  pinMode(ledPin, OUTPUT);              // sets the digital pin as output
  // initialize serial communications to Midi 31,250 bps
  // Serial.begin(31250);		        //  Set MIDI baud rate:
  //Set up the Midi
  
  synth.midiReset();                    // Do a complete MIDI reset
  synth.setChannelVolume(channel, 127); // max. channel volume
  synth.setMasterVolume(255);	        // max. master volume
  //synth.noteOn(channel, hihatP, vel);	// play a note
  
  Serial.begin(9600);		        // just for debug only

}

void loop() {
  // read the analogs into values:
  sensor0 = analogRead(pad0);
  sensor1 = analogRead(pad1);
  sensor2 = analogRead(pad2);
  // determine alarm status
  if (sensor0 > hiHys)
  {
    digitalWrite(ledPin, HIGH);   // sets the LED on
  }
  else
  {
    digitalWrite(ledPin, LOW);    // sets the LED off
  }

  if (sensor0 > hiHys){
    synth.noteOn(channel, perc_0, vel);	// play a note
    synth.noteOff(channel, perc_0);    
    trapADC = A0;
    trapSensor = sensor0;
  }
  if (sensor1 > hiHys){
    synth.noteOn(channel, perc_1, vel);	// play a note
    synth.noteOff(channel, perc_1);    
    trapADC = A1;
    trapSensor = sensor1;
  }
  if (sensor2 > hiHys){
    synth.noteOn(channel, perc_2, vel);	// play a note
    synth.noteOff(channel, perc_2);    
    trapADC = A2;
    trapSensor = sensor2;
  }
  while (trapSensor > hiHys ){
    delay(10);
    trapSensor = analogRead(trapADC);

  }
  while (trapSensor < loHys){
    delay(10);
    trapSensor = analogRead(trapADC);

  }
  // wait 10 milliseconds before the next loop
  // for the analog-to-digital converter to settle
  // after the last reading:
}

void update(){
  //delay(1000);
  Serial.print("Sensor A0 = " );
  Serial.print(sensor0,DEC);
  Serial.print(" Sensor A1 = " );
  Serial.print(sensor1,DEC);
  Serial.print(" Sensor A2 = " );
  Serial.print(sensor2,DEC);
  Serial.print(" Sensor lo = " );
  Serial.print(hiHys,DEC);
  Serial.print(" Sensor hi = " );
  Serial.println(loHys,DEC);
  Serial.println();
}
/*
Bass  	KeyNum	Sound
A_	33	Metronome Click
B_b	34	Metronome Bell
B_	35	Acoustic Bass Drum
C	36	Bass Drum 1
C#	37	Side Stick
D	38	Acoustic Snare
Eb	39	Hand Clap
E	40	Electric Snare
F	41	Low Floor Tom
F#	42	Closed Hi-Hat
G	43	High Floor Tom
G#	44	Pedal Hi-Hat
A	45	Low Tom
Bb	46	Open Hi-Hat
Bn	47	Low-Mid Tom
c	48	Hi-Mid Tom
c#	49	Crash Cymbal 1
d	50	High Tom
eb	51	Ride Cymbal 1
e	52	Chinese Cymbal
f	53	Ride Bell
f#	54	Tambourine
g	55	Splash Cymbal
g#	56	Cowbell
a	57	Crash Cymbal 2
bb	58	Vibraslap
bn	59	Ride Cymbal 2
C	60	Hi Bongo
C#	61	Low Bongo
D	62	Mute Hi Conga
D#	63	Open Hi Conga
E	64	Low Conga
F	65	High Timbale
F#	66	Low Timbale
G	67	High Agogo
G#	68	Low Agogo
A	69	Cabasa
Bb	70	Maracas
Bn	71	Short Whistle
c	72	Long Whistle
c#	73	Short Guiro
d	74	Long Guiro
d#	75	Claves
e	76	Hi Wood Block
f	77	Low Wood Block
f#	78	Mute Cuica
g	79	Open Cuica
g#	80	Mute Triangle
a	81	Open Triangle
	82	
	83	
	84	
*/

However if you are using a Mega2560 the whole thing gets simpler as you can use the Serial Out 2 and just connect a flying lead to the middle jumper pin on the Fluxamasyth. The header clip is therefore not required. No special bit banging libraries are required and the serial port runs at top speed with no extra CPU overheads. Here are some setup code:

  Serial.begin(115200); //Serial interface for Debugging
  Serial.println("MIDI Begins..");
  Serial2.begin(31250); //set up Serial Interface 2 for Midi (Mega Pin16 Tx2)

And here is an example of what is required to drive it

void midi_note_on(byte channel, byte pitch, byte velocity) {
  Serial2.write(0x90 | (channel & 0x0F));
  Serial2.write(pitch);
  Serial2.write(velocity);
}
void midi_note_off(byte channel, byte pitch, byte velocity) {
  Serial2.write(0x80 | (channel & 0x0F));
  Serial2.write(pitch);
  Serial2.write(velocity);
}

I hope this helps....

Rob