You could easily create your own MIDI out function, MIDI in is a bit trickier, but still doable, especially if you only need 'normal' events, without sysex etc.
This is the code I used for a simple MIDI controller with 6 potentiometers, for example. If you don't understand what is happening, just ask. (It will probably be much more readable if you copy and paste it in the IDE.)
/* These are constants (actually preprocessor macros): whenever we put one of these 3 words (the ones in capitals) in our code, the precompiler will replace it by the 0xsomething after the word.
This makes our code more readable, instead of typing the meaningless 0x90, we can now just type NOTE_ON, if we want a note to be on. */
#define NOTE_OFF 0x80
#define NOTE_ON 0x90
#define CC 0xB0
/* This is also a constant value (preprocessor macro). If you want to change the number of analog inputs, you can simply do it once on this line, instead of changing it everywhere in your code.*/
#define NUMBER_OF_ANALOG_INPUTS 6 // The Uno has 6 analog inputs, we'll use all of them in this example. If you only need 4, change this to 4, and you'll be able to use A4 & A5 as normal I/O pins.
// NOTE: if you change this value, also change the controllers array, to match the number of inputs and the number of controllers.
#define CHANNEL 1 // Send all messages on channel 1.
/* The list with the corresponding controller numbers: for example, the values of the potentiometer on A0 will be sent as the first controller number in this list, A1 as the second, etc...
Here's the list with all controller numbers: http://midi.org/techspecs/midimessages.php#3 You can change them if you want.*/
int controllers[NUMBER_OF_ANALOG_INPUTS] = {
0x10, 0x11, 0x12, 0x13, 0x14, 0x15
};
int analogVal[NUMBER_OF_ANALOG_INPUTS]; // Declare an array for the values from the analog inputs
int analogOld[NUMBER_OF_ANALOG_INPUTS]; // Declare an array for the previous analog values. (To know if the knob has been turned since the last measurement)
/* The format of the message to send via serial. We create a new data structure, that can store 3 bytes at once. This will be easier to send as MIDI. */
typedef struct {
uint8_t status; // first byte : status message (NOTE_ON, NOTE_OFF or CC (controlchange) and midi channel (0-15)
uint8_t data1; // second byte : first value (0-127), controller number or note number
uint8_t data2; // third byte : second value (0-127), controller value or velocity
}
t_midiMsg; // We call this structure 't_midiMsg'
void setup() // The setup runs only once, at startup.
{
pinMode(13, OUTPUT); // Set pin 13 (the one with the LED) to output
digitalWrite(13, LOW); // Turn off the LED
for(int i = 0; i < NUMBER_OF_ANALOG_INPUTS; i++){ // We make all values of analogOld -1, so it will always be different from any possible analog reading.
analogOld[i]=-1;
}
Serial.begin(31250); // Start a serial connection @31250 baud or pulses per second on digital pin 0 and 1, 31250 baud is the original MIDI speed. The MIDI output is on pin 1 (TX)
digitalWrite(13, HIGH);// Turn on the LED, when the loop is about to start.
}
void loop() // The loop keeps on repeating forever.
{
t_midiMsg msg; // create a variable 'msg' of data type 't_midiMsg' we just created
for(int i = 0; i < NUMBER_OF_ANALOG_INPUTS; i++){ // Repeat this procedure for every analog input.
analogVal[i] = analogRead(i+A0)/8; // The resolution of the Arduino's ADC is 10 bit, and the MIDI message has only 7 bits, 10 - 7 = 3, so we divide by 2^3, or 8.
if(analogVal[i] != analogOld[i]){ // Only send the value, if it is a different value than last time.
msg.status = CC; // Controll Change
msg.status = msg.status | CHANNEL-1; // Channels are zero based (0 = ch1, and F = ch16). Bitwise or to add the status message (s) and channel (c) together:
/* status = 0bssss0000
* channel = 0b0000cccc
* | --------------------- (bitwise or)
* msg.status = 0bsssscccc
*/
msg.data1 = controllers[i]; // Get the controller number from the array above.
msg.data2 = analogVal[i]; // Get the value of the analog input from the analogVal array.
Serial.write((uint8_t *)&msg, sizeof(msg)); // Send the MIDI message. (send three bytes, starting at the address of msg)
analogOld[i] = analogVal[i]; // Put the analog values in the array for old analog values, so we can compare the new values with the previous ones.
delay(10); // Wait for 10ms, so it doesn't flood the computer/synth with MIDI-messages
}
}
}
or using bit fields:
/* The format of the message to send via serial. We create a new data structure, that can store 3 bytes at once. This will be easier to send as MIDI. */
typedef struct {
unsigned int channel : 4; // second nibble : midi channel (0-15) (channel and status are swapped, because Arduino is Little Endian)
unsigned int status : 4; // first nibble : status message (NOTE_ON, NOTE_OFF or CC (controlchange)
uint8_t data1; // second byte : first value (0-127), controller number or note number
uint8_t data2; // third byte : second value (0-127), controller value or velocity
}
t_midiMsg; // We call this structure 't_midiMsg'
void MIDISend(uint8_t status, uint8_t channel, uint8_t data1, uint8_t data2) {
t_midiMsg msg;
msg.status = status & 0xF;
msg.channel = (channel-1) & 0xF; // channels are 0-based
msg.data1 = data1 & 0x7F;
msg.data2 = data2 & 0x7F;
Serial.write((uint8_t *)&msg,3);
}