The BIG Arduino MIDI controller thread

I won’t hijack this thread, but I just wanted to say that I am in the process of designing my final prototype controller (having already built and tested 3 different versions of it – pic below). I have found it to be a very very fun and educational project. I’ll likely end up posting my own build log since mine is substantially larger (full 4 channel, i’m only done the basics and i’m up to 64 buttons, 16 pots and 5 faders [4x60mm, 1x100mm]), and I hate hijacking threads, nice job on yours so far though, if you need any assistance let me know :slight_smile:

L-R: Behringer DX-500-M (-M for midi :wink:), quick transport test controller i whipped up last night, code and electronic prototype (used to test code/schematics, and has obviously gone through changes).

Here is the current state of my final prototype.. lots of room for transport/fx/global controls

that’s not hijacking - that’s exactly the kind of project that should be posted in this thread!!

would you be willing to share the code you used? can you give any more details about how its all wired up? looks like you’ve wired up some midi ports? did you create your own mux shield to get more inputs out of the arduino? how about some close up shots of the wiring? :slight_smile:

welcome to the forum btw - great first post :smiley:

Well okay then!

Open Source for the win my friend… the (current) code is in my next post (for the breadboard prototype, the other controllers use code that is pretty much identical). Be aware that while it is working and fairly fast (it checks all inputs and sends midi out in an average of 1.2 milliseconds, or about 50,000 times per second), it is not what I consider good looking code, and will be re-factored and cleaned up quite a bit…

I’ll do ya one better, below is an old (but it should work) schematic for a bunch of pots and buttons, it doesnt include the midi-in circuit or the rotary encoder circuit (the touch pad gets wired to +5v, GND and two digital pins).

It’s not what is on the breadboard right now, but it is similar.

Yeppers! Midi out is dead simple (once you’ve figured out that most schematics for midi out shows the connector FROM THE BACK, not the front as most assume).

Midi-In was another matter… there are a number of bad schematics out there, I ended up using a 4N35 optoisolator, with a 270r input resistor (for the input led) and a 1k pull-up for the transistor side. It works great… chained two three controllers together and there was ZERO latency… I’ll put up a schematic for it later today.

Not really, but I suppose the idea is the same… I am (currently) using 4 IC’s to handle the buttons, pots and faders, 2x CD4021 allow 8 DIGITAL inputs each (daisy chain-able, so two gives me 16 inputs using 3 digital pins on the atmega), 2x CD4051 give me 8 ANALOG inputs each to handle the pots and faders… so that’s a total of 32 inputs using a total of 8 pins (3 for the 4021’s, 3 for both 4051’s and 1 analog input for each 4051). I can easily extend that to many many more (adding two more of each would give me 32 buttons and 32 pots/faders), with the only limit being how fast you can deal with the many inputs…

Ask and yee shall receive! (see bottom of post)

Thanks, glad to be here!

If you have any other questions, please just ask away!

Schematic:

Better pic of the prototype:

Wiring:


Midi Out:

Midi In:

Guts of the prototype:

Stock on the front:

Almost stock on the back:

Not so stock on the inside:

Behringer would never do this:

Closeup of DX-500-M guts:

One more closeup:

My custom breadboard arduino:

Here’s the code:

/*
TODO
Refactor input code turn everthing into functions for easier maintanance and fewer global variables
Add an initial calibration routine to prevent initial flood of midi data on startup
Rethink encoder code, it seems to be a little slow
Possible issue with encoder buttons
Implement messages queue'ing system to buffer messages before sending (possible resolution to encoder issue)
*/

//#include <ps2.h>
//#include <Trackpad.h>
#include <WProgram.h>
#include <Midi.h>

#define NUM_POTS 13
#define NUM_BUTTONS 16
#define BTN_DEBOUNCE 75
#define NUM_ENCODERS 2
#define SMOOTHING 3
#define POT_SELECT_PINS {2,3,4}; //A, B, C on 4051
#define POT_READ_PINS {0,1}
#define MIDI_CHANNEL 1
#define XY_SENSITIVITY 25
#define XY_CC_X 71 // xy pad X CC#
#define XY_CC_Y 70 // xy pad Y CC#
#define XY_NOTE 69 // A-4
#define XY_MIN_X 1320
#define XY_MAX_X 5480
#define XY_MIN_Y 1245
#define XY_MAX_Y 4490
#define XY_CLOCK 18 // Orange Wire
#define XY_LATCH 19 // Blue Wire
#define ENC1_A 8
#define ENC1_B 9
#define ENC2_A 8
#define ENC2_B 9
#define ENC_PORT PINB
#define ENC_MODE_REL 1
#define ENC_MODE_ABS 2
#define ENC_MODE ENC_MODE_REL
#define CW 0b01
#define CCW 0b11
#define CLOCK_4021 7
#define LATCH_4021 6
#define DATA_4021 5
#define ON 0
#define OFF 1

/* Debug Defines */
#define DEBUG_POT 1
#define DEBUG_BTN 2
#define DEBUG_PAD 4
#define DEBUG_ENC 8
#define DEBUG_ALL 15
#define DEBUG false
#define BENCH false
#define DEBUG_SEL DEBUG_POT
/* Debug Defines */

class MyMidi : public Midi {
public:
MyMidi(HardwareSerial &s) : Midi(s) {}
void handleNoteOn(unsigned int channel, unsigned int note, unsigned int velocity) {
sendNoteOn(channel, note, velocity);
}
void handleNoteOff(unsigned int channel, unsigned int note, unsigned int velocity) {
sendNoteOff(channel, note, velocity);
}
void handleControlChange(unsigned int channel, unsigned int controller, unsigned int value) {
sendControlChange(channel, controller, value);
}
};

/* returns byte: bits 1,2 are encoder 1... bits 2,3 are encoder 2... 0b11 is CCW 0b01 is CW */
byte read_two_encoders() {
int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
static uint8_t old_AB1, old_AB2 = 0;
byte rtn = 0;

old_AB1 <<= 2; //remember previous state
old_AB1 |= (ENC_PORT & 0b11); //add current state
old_AB2 <<= 2; //remember previous state
old_AB2 |= (ENC_PORT & 0b1100) >> 2; //add current state
rtn |= (enc_states[(old_AB1 & 0b1111)]) & 0b11;
return rtn | ((enc_states[(old_AB2 & 0b1111)]) & 0b11) << 2;
}

//Trackpad xyPad(XY_CLOCK, XY_LATCH);
MyMidi midi(Serial);
byte potReadPins[] = POT_READ_PINS;
byte potSelectPins[] = POT_SELECT_PINS;
byte buttons1, lastButtons = B11111111; // Default state of buttons
byte buttons2 = B11111111; // Default state of buttons
byte encValues[NUM_ENCODERS] = {1,1};
byte prevEncValues[NUM_ENCODERS] = {1,1};
byte counter = 0; // for benchmarking
boolean buttonState[NUM_BUTTONS] = {};
boolean lastButtonState[NUM_BUTTONS] = {};
int potValues[NUM_POTS] = {};
int prevPotValues[NUM_POTS] = {};
unsigned int allButtons = 0;
unsigned long lastBtn = 0; // for debouncing buttons
unsigned long current = 0; // for benchmarking
unsigned long running = 0; // for benchmarking
unsigned long benchmarks[4] = {0,0,0,0};

void setup() {
if (DEBUG||BENCH) Serial.begin(38400);
else midi.begin(0);
pinMode(LATCH_4021, OUTPUT);
pinMode(CLOCK_4021, OUTPUT);
pinMode(DATA_4021, INPUT);
pinMode(ENC1_A, INPUT);
pinMode(ENC1_B, INPUT);
pinMode(ENC2_A,INPUT);
pinMode(ENC2_B,INPUT);
for (byte i=0;i < 3;i++) pinMode(potSelectPins[i], OUTPUT);
for (byte i=0;i < NUM_POTS;i++) potValues[i] = prevPotValues[i] = 0;
for (byte i=0;i < NUM_BUTTONS;i++) buttonState[i] = lastButtonState[i] = 1;
if (DEBUG||BENCH) Serial.println("Begin");
}

void loop() {
if (BENCH) current = micros();
midi.poll();
/*--------------|  BUTTONS  |--------------*/
digitalWrite(LATCH_4021,1);delayMicroseconds(20);digitalWrite(LATCH_4021,0);
buttons1 = shiftIn(DATA_4021, CLOCK_4021, LSBFIRST);
buttons2 = shiftIn(DATA_4021, CLOCK_4021, LSBFIRST);
allButtons = word(buttons1, buttons2);
if ((allButtons != lastButtons) && (micros() - lastBtn > BTN_DEBOUNCE)) {
lastBtn = micros();lastButtons = allButtons;
for (byte i=0;i<NUM_BUTTONS;i++){
if (!bitRead(allButtons, i)) buttonState[i] = ON;
else buttonState[i] = OFF;
if (buttonState[i] != lastButtonState[i]) {
lastButtonState[i] = buttonState[i];
if (buttonState[i] == OFF) {
if (DEBUG && (DEBUG_SEL & DEBUG_BTN)) {Serial.print("Button ");Serial.print(i+1,DEC);Serial.println(": OFF");}
else midi.sendNoteOff(MIDI_CHANNEL, i, 0); // Stop the note
} else {
if (DEBUG && (DEBUG_SEL & DEBUG_BTN)) {Serial.print("Button ");Serial.print(i+1,DEC);Serial.println(": ON");}
else midi.sendNoteOn(MIDI_CHANNEL, i, 127); // Send the note
}
}
}
}
if (BENCH) {
benchmarks[0] += micros()-current;
current = micros();
}
/*--------------|  BUTTONS  |--------------*/
/*--------------|  POTS  |--------------*/
byte chipSelect = 0;
for(int i = 0; i < NUM_POTS; i++) {
PORTD = (NUM_POTS > 8 && i >= 8) ? (i % 8) << 2 : i << 2; // set pins 2, 3 and 4 to value between 0 (00000) and 7 (11100) to select the pot
if (i > 0 && (i % 8 == 0)) chipSelect++; // increase chip select index every 8 pots
potValues[i] = analogRead(potReadPins[chipSelect]);
if (abs(potValues[i] - prevPotValues[i]) > SMOOTHING) {
prevPotValues[i] = potValues[i];
if (!DEBUG) midi.sendControlChange(MIDI_CHANNEL, i, map(potValues[i],5,1020,0,127));
}
if (DEBUG && (DEBUG_SEL & DEBUG_POT)) {Serial.print(" P");Serial.print(i+1,DEC);Serial.print(":");Serial.print(potValues[i],DEC);}
}
if (DEBUG && (DEBUG_SEL & DEBUG_POT)) Serial.println();
if (BENCH) {
benchmarks[1] += micros()-current;
current = micros();
}
/*--------------|  POTS  |--------------*/
/*--------------|  Encoders  |--------------*/
static byte encoderCnt = 0;
byte enc1,enc2,encValue = 0;
encValue = read_two_encoders();
if(encValue && (++encoderCnt % 4) == 0) {
enc1 |= encValue & 0b11;
if (enc1 == CW) {
if (ENC_MODE & ENC_MODE_REL) {
if (DEBUG && (DEBUG_SEL & DEBUG_ENC)) Serial.println("ENC1: ->");
else midi.sendControlChange(MIDI_CHANNEL,30,1);
}
if (ENC_MODE & ENC_MODE_ABS) {
encValues[0]++;
if(encValues[0] >= 127) encValues[0] -= 127;
midi.sendControlChange(MIDI_CHANNEL,40,encValues[0]);
}
} else if (enc1 == CCW) {
if (ENC_MODE & ENC_MODE_REL) {
if (DEBUG && (DEBUG_SEL & DEBUG_ENC)) Serial.println("ENC1: <-");
else midi.sendControlChange(MIDI_CHANNEL,30,127);
}
if (ENC_MODE & ENC_MODE_ABS) {
encValues[0]--;
if(encValues[0] <= 0) encValues[0] += 127;
midi.sendControlChange(MIDI_CHANNEL,40,encValues[0]);
}
}

enc2 |= (encValue >> 2) & 0b11;
if (enc2 == CW) {
if (ENC_MODE & ENC_MODE_REL) {
if (DEBUG && (DEBUG_SEL & DEBUG_ENC)) Serial.println("ENC2: ->");
else midi.sendControlChange(MIDI_CHANNEL,31,1);
}
if (ENC_MODE & ENC_MODE_ABS) {
encValues[1]++;
if(encValues[1] >= 127) encValues[1] -= 127;
midi.sendControlChange(MIDI_CHANNEL,41,encValues[1]);
}
} else if (enc2 == CCW) {
if (ENC_MODE & ENC_MODE_REL) {
if (DEBUG && (DEBUG_SEL & DEBUG_ENC)) Serial.println("ENC2: <-");
else midi.sendControlChange(MIDI_CHANNEL,31,127);
}
if (ENC_MODE & ENC_MODE_ABS) {
encValues[1]--;
if(encValues[1] <= 0) encValues[1] += 127;
midi.sendControlChange(MIDI_CHANNEL,41,encValues[1]);
}
}
}
if (BENCH) {
benchmarks[3] += micros()-current;
current = micros();
}
/*--------------|  Encoders  |--------------*/
///*--------------|  XY Pad  |--------------*/
//  packet_t * packet;
//  packet = xyPad.getNewPacket();
//  static boolean xyOn = false;
//  if (packet->z > XY_SENSITIVITY) {
//    if (!xyOn) {
//      xyOn = true;
//      if (DEBUG && (DEBUG_SEL & DEBUG_PAD)) Serial.println("XY Pad ON");
//      else midi.sendNoteOn(MIDI_CHANNEL, XY_NOTE, 127);
//    }
//    if (DEBUG && (DEBUG_SEL & DEBUG_PAD)) {
//      Serial.print("X: ");Serial.print(packet->x,DEC);Serial.print(" Y: ");Serial.println(packet->y,DEC);
//    } else {
//      midi.sendControlChange(MIDI_CHANNEL, XY_CC_X, constrain(map(packet->x, XY_MIN_X, XY_MAX_X, 0, 127), 0, 127));
//      midi.sendControlChange(MIDI_CHANNEL, XY_CC_Y, constrain(map(packet->y, XY_MIN_Y, XY_MAX_Y, 0, 127), 0, 127));
//    }
//  } else {
//    if (xyOn) {
//      xyOn = false;
//      if (DEBUG && (DEBUG_SEL & DEBUG_PAD)) Serial.println("XY Pad OFF");
//      else midi.sendNoteOff(MIDI_CHANNEL, XY_NOTE, 0);
//    }
//  }
//  if (BENCH) {
//    benchmarks[2] += micros()-current;
//    current = micros();
//  }
///*--------------|  X/Y Pad  |--------------*/
if (BENCH) {
counter++;
if (counter >= 10) {
Serial.print("(ms) Btn: ");Serial.print(benchmarks[0]/counter,DEC);
Serial.print(" Pot: ");Serial.print(benchmarks[1]/counter,DEC);
//      Serial.print(" Pad: ");Serial.print(benchmarks[2]/counter,DEC);
Serial.print(" Enc: ");Serial.println(benchmarks[3]/counter,DEC);
counter = benchmarks[0] = benchmarks[1] = benchmarks[2] = benchmarks[3] = 0;
}
}
}

Erm… could someone pick my jaw up from the floor please? Lovely work.

How long has it taken you to this point DJNecro?

Yay, Eagle :smiley:

Looks like you’re making good progress mate. Nice to see someone take some stuff out of their head and down on paper.

I’m about hit the hay, so I’ll leave the majority of my questions till the morning.

One thing I’d like to know though is, which pots and faders are you using?
I’ve been shopping around for good quality analog inputs. The only models I can find which looks relatively well made are Bourns, and those I can’t buy in quantities under 120+.

I’m very excited about your project. I’ll have nothing short of a thousand questions ready for you tomorrow, regarding 40XXN chips, boards and that touch screen.

Keep up the good work :slight_smile:

The midi stuff is about 8-12 weeks of time so far (sporadic since it’s currently a hobby), overall electronics/arduino stuff? just over a year… all self taught.

Nothing special for the prototype, that is a question I have myself… I only know that I’ll be using ALPS pots/faders/encoders, and likely buttons too, but I find it harder to buy buttons online, as its not as much the look but the feel of the button that is most important… it’s tough to push a button over the intertubes these days… :wink: I’d love to use arcade buttons, but they are way too pricey in the qty i’d be buying, and their size!!! oh so big… lol

I am not too sure how much ‘quality’ is required when we’re dealing with digital electronics… as long as it has a smooth taper, the rest is up to the software… that being said, physical construction (metal, not plastic crappyness) is always important. If you wanted to use the pots to build a true analog mixer, then yeah… quality all the way since sound is going through them… for us, we’re sending 1’s and 0’s, the arduino reads a pot and gives back a value from 0-1023, nothing more, nothing less… if anything I would say the adc (analog-digital converter) of the atmega chip (or whatever processor is being used) is more important, the higher precision, the better (atmega’s use 10bit, others use 12bit or more)..

Gotta… Stop… Rambling… :wink:

Oh, and it’s a touchpad (ripped out of a notebook), NOT a touch screen… though that would be sick :slight_smile:

I know what you mean about the quality of the analog parts.
What I meant was more that, with manufacturers like ALPS, most of their datasheets are so groggy and incomplete that I have a hard time believing the quality of their mechanical parts.

RS-Components sell ALPS parts and a few Bourns as well. They sell in mostly reasonable quantities (1-10+), and ship at reasonable rates. Oh well.

your 40XXN chips, are those just generic ADCs or? What are your experiences with these?

As well, since you’re drawing up things in eagle, I assume you’ve put some work into designing the PCBs as well?

Your workflow looks very similar to mine, too. I’m quite well versed in drawing up schematics and plans for controllers, cases and parts, if you’d like some help getting things laid out nice and tidy, give me a shout :slight_smile:

Keep up the good work

I havent found the datasheets lacking (at least for their buttons and pots), the mechanical drawings are very precise and complete, and as for electrically… ground, power, signal… 99% of pots are wired the same, outside contacts are for ground/power and the centre is signal…

Unless you are referring to more detailed information such as force required to push/turn, etc…

If I can find buttons and pots for just under a dollar a piece in quantity (for my final prototype I am expecting 70+ pots and nearly 200 buttons), that would be ideal… I always wondered how companies justify $500+ for a ‘simple’ controller… Then I researched the component prices, and unless I am building thousands of controllers (10’s of thousands of buttons), I doubt I could be competitive…

The 4021’s are digital (de)multiplexers, think of them like a network switch. you connect 8 (or more) buttons to the inputs of the 4021, and then ask it for the current state. It reads all the inputs in parallel (all 8 at once), then spits it back out serially (one at a time).

The 4051’s are their analog counterparts. Hook 8 (or more) analog sources to the inputs, and then one by one you go through the inputs and read them. Internally, the chip will switch each input to a common output (which is connected to an analog input on the arduino). Then its just a simple analogRead() to grab the value.

It sounds a little complicated, but once you’ve played with them (they are cheaper than dirt, pick some up!) and seen how the code works it becomes second nature…

The fun part comes when you daisy chain them together :smiley: My final controller will likely use an even bigger IC that has 32 inputs, so 4 for pots and faders, 6-8 for buttons and i’ll be set :slight_smile: let’s just say that using 10 (5 each) of the larger ic’s you could have 160 buttons and 160 pots/faders using only 9 digital inputs and 5 analog inputs: 5 digital, 5 analog for the pots and 4 digital for the buttons (iirc).

The problem then moves into the software, in that the more time spent reading inputs, the less time available to send midi, so you will encounter latency… I have no clue when that will become a factor (as in how many inputs), but with my current prototype it reads all the inputs (about 27) 50,000 times per second… I have a feeling I have a little headroom before I have to worry :wink:

I would like to find some sort of information as to how many messages per second is required for “zero latency”…

Yeah, I’ve done a few :wink:

Here’s a quick writeup I did a while back: http://arduino.cc/forum/index.php/topic,5650.0.html

Well here is the current state of the prototype. The channels, fx, and transport sections are complete, still have to take care of the loops and globals, then fit as many more controls as space allows…

I invite criticism and suggestions to the layout, it’s a fluid work in process so any input is always appreciated :slight_smile:

btw, it is currently 18" x 13" (and I have already decided to move the xfader down a half inch or so)

very nice design!

have you thought about using encoders for the gain controls? (like the S4)

I haven’t, no… I am now though… :slight_smile:

My goal is to have at least 2-4 encoders per channel plus 4 or 8 for personal choice… Are endless encoders really that useful for gain? I find my self barely touching it, and when I do, it’s rarely more than a few degrees of motion…

Anything you would change? Placement, distances, sizes (5mm small/12mm large buttons, 10mm knobs), etc?

Also, what would one pay for such a device? assuming I end up completing it :slight_smile:

are you thinking of selling it once its completed? Depending on the finish/quality, anything between $500-800? I’d probably want a bunch of arcade buttons on there first!

the reason I would consider using encoders as gain controls is because Traktor autogains each track when it loads. This means that you would need to use soft takeover on any potentiometer as the knob would always be in the wrong position when loading new tracks. An encoder solves this problem perfectly.

Ahhh… good point sir! Although I tend to adjust the auto-gain myself since I don’t find it works all that well, I can understand that there would be many people who don’t… I’ll make the 4 channel gains endless encoders…

What’s the deal with arcade buttons? It seems as if everyone is all gaga over them, but is it just the size/feel? or are they somehow more accurate than normal buttons?

My issue is that they’re HUUUGE… I would sacrifice quite a bit of real estate to fit them… however after I get the required stuff (global/fx/loop/channel/etc) nailed down, I’ll see how much space I have left over..

Re: selling it… I probably wouldn’t sell the first one I build, but I am toying with a custom controller building service… tell me what you want, and i will build it for you… It would be more expensive than just a controller, but the end result is 100% custom for your needs.

What would be your 100% MUST HAVE feature that is not always included on a controller? (i.e., such-and-such controller would be PERFECT, if only it had a…)

check any of Ean’s videos to see what you can do with arcade buttons, performance-wise.

my perfect controller would be the concept I’m designing at the moment (check my sig - the cdj2000 link)

Ean’s stuff always impresses me, however I don’t really see what the arcade style buttons did for his performance… Anyone with sufficient skill (sooooo not me…) should be able to perform pretty much the same with similar (but cheaper) buttons… as long as the button is reliable..

Unless you can find me a place to buy arcade buttons for about a buck each that is… :slight_smile:

Arcade buttons are all about the feel and ease of pressing the button.

I think Ean stated in an interview some while back that he actually starting having wrist problems due to playing bad buttons.
When you’re heavily hitting the buttons, or quickly, whatever, it’s nice to have some big, soft fluffy buttons to press, rather than some small rubbery ones.

I was thinking of using the short travel clicky style that is used on the ddm4000… If there is room for them, I will certainly add as many as I can though…

Arduino arrived today… got it hooked up and finally working (pain in the arse trying to get the drivers working in Win7 x64)

I loaded my first bit of code on to it just now and it’s all working nicely :smiley:

Just spent the last 20 minutes ordering a whole bunch of extra components so I can do some basic stuff with it, including:

NEW HELPING HAND TOOL SOLDER MODELING KIT MAGNIFYING
KINGBRIGHT 0.56" 7 SEGMENT LED DISPLAY HE RED (CA)
10 WAY SINGLE ROW PCB PIN HEADER CONNECTOR (10 PACK)
6 WAY DOUBLE ROW PCB PIN HEADER CONNECTOR (15 PACK)
(Pack of 25) Small Tactile PCB Switch / Button 6x6x4mm
840 Pin Breadboard 66mm x 174mm & 140 Jumper Wire Kit
Solderless Breadboard Jumper Cable Wire Kit 2 bundles

plus some encoders, more potentiometers and some ribbon cable/wiring… hopefully should have something semi-usable in the next week or so!

32 analogue pots via midi using multiplexers:

[ame=“Arduino based 32 pot midi controller | Four 4051 multiplexer… | Flickr”]Arduino based 32 pot midi controller | Flickr - Photo Sharing!@@AMEPARAM@@http://farm4.static.flickr.com/3418/3359088943_c4b24d6a7d_m.jpg@@AMEPARAM@@3359088943@@AMEPARAM@@c4b24d6a7d[/ame]

Just book marked this:slight_smile:

Hope i get time during the week to go through it.

Thanks MiLO for starting this.