32-knob midi controller

dayvonjersen 2015-05-15T23:28:24Z #awesome

Skip to section:

There are a veritable plethora of absolutely amazing free VSTs (free as in beer) available to musicians right now on the Internet.

One such VST is GSinth2, which is a simple 3-oscillator synthesizer. It is capable of producing some really unique sounds. Here is a track I made with GSinth (among other things) entitled “Ganjira”. It’s actually what got me thinking about getting a MIDI controller for controlling parameters.

You see, at the time I had just bought a Korg nanoKEY2 and used it to compose that track. Even though I cannot play the keyboard proper, I rather enjoyed the process of playing in time with a loop to construct a nice sequence, figuring out which notes “work” quickly and being able to record and quantize them in place where previously, I had exclusively relied upon the piano roll and penciling in sequences.

Getting a good sound from GSinth however involves tweaking 29 parameters. This is made easier by the included “Random” button which randomizes all of these parameters and can give one a good starting point, but ultimately one will have to tweak each parameter individually which, with a standard mouse and keyboard, can be disruptive to the new creative process I had discovered.

I had come across the Behringer BCR2000 Rotary being used by a DJ I knew at a venue I used to frequent in Alphabet City about two years earlier, so I looked up how much it would cost. At the time of writing it is $150, but I remember it being more in 2012. In any case, that was too much (me being spoiled by the $50 nanoKEY2), so I looked for alternative options.

There weren’t any. Unless you built one yourself. I found Builder appealing easy to someone with no prior experience with electronics, but ultimately the price tag would be too high.

Estimating the cost of Builder

In my searches somewhere, somehow I came across Arduino, looked at a couple YouTube videos, a couple blogs and discovered Hackaday, but most importantly found a couple projects which did exactly what I wanted: used potentiometers to send MIDI signals.

Estimating the cost with Arduino

Parts

1. Ready-made hardware to hack

2. Tools and stuff

3. Components

(The last 4 items on this list I bought in person on Canal Street)

4. A case for the finished controller

I didn’t think this far ahead and ended up putting this fire hazard:

Into a cardboard box sealed with electrical tape:

It only caught fire once :^)

Circuit Diagrams

So I didn’t and still don’t know how to read or write schematics; I just sorta looked at the aforementioned blogs and went from there.

Blogs are unlinked to because I couldn’t find them again (I never backed up my bookmarks…) I’m noticing a pattern of how much of a mess I was/am…

I just took some notes on what signals went to which pins:

Expanded the whole multiplexer thing and how each potentiometer was to be connected, and how all of that was going to connect to the Arduino and send MIDI Out to the MIDI-to-USB to connect to the computer

And what the MIDI In circuit looked like:

Build Process

I tried to photograph as much of the process as I could, but alas many pictures came out blurry and are not shown here and I failed to discern what was important during the later stages…

So I started by placing the key components on one board: I needed to make room for the Arduino, the DINs and the USB connector to line up nicely along the rear, and give the CD4051’s a place to live.

This board would ultimately become the “main board” where all the circuitry would be.


The other board would be used to mount all of the potentiometers (as I had bought 25¢ PCB-mounted ones rather than the panel-mount ones, more on that later).

This was probably the most time consuming process, and grew increasingly difficult as more pots were added to the board. There was less margin for error. Tangible margin, mind you.

But after doing more soldering than I had ever done in my life, I got there. Next, I had to wire up the pots for power, ground, and most importantly, the signal from each. These Velleman perfboards had a convenient set of pads seen on the left above: I would use these to connect to the multiplexers.

Wiring up the pots started out simple enough:

But quickly grew unruly

And ended up utterly fucking ridiculous

I appreciate now the value of PCBs more than any man on Earth ._.


Next, the multiplexers needed to be wired to be able to receive signal from the pots. Oh and I guess I put the MIDI-In circuit in place at some point before that.

Again, started out alright:

But quickly turned to chaos:


Lastly, I needed to connect the two boards together, making sure to make the connections be logically sequential (numbered left-to-right, top to bottom).

If you thought the spaghetti above was bad, well…

But it worked out alright

This was a triumph, I’m making a note here.

I’m glad it’s over. Time to write some code!

Code

If the hardware got messy and quickly turned into a huge pile of spaghetti, the purely virtual world of bits and bytes would surely provide respite, right? I mean it’s not like all my previous software projects were spaghetti as well, right? Eh…

So I started out nice and theoretical, used lots of comments, I wanted this to be good. Clever, efficient, no nonsense:

(don’t use this)

/**
 * 32-knob midi controller
 * arduino sketch
 * by dayvonjersen
 *
 * I used 32 potentiometers, 5 multiplexers, an arduino, and an inexpensive midi-usb device
 *
 * If my website is no longer up,
 * I hope that you can reverse-engineer
 * this
 * from my comments.
 */

int lastRead[32]; // holds last read pot value
int x=0;          // counter
int y=0;          // placeholder

/**
 * selectKnob(n)
 * Returns current potentiometer value at zero-indexed position n.
 *
 * ~ There are 5 CD4051 multiplexers in the circuit.
 *   - Four of them are connected, in left to right, top to bottom order, to 32 potentiometers.
 *   - Thus, each of these "slave" multiplexers have 8 potentiometers connected to them.
 *   - Each of these "slaves" send their analog outputs to a "master" multiplexer.
 *   - This "master" multiplexer passes along the selected "slave"'s output to ANALOG 0.
 *
 * ~ Each multiplexer has 3 selector pins, which can be set ON or OFF.
 *   - Together, this makes a binary number from 0-7.
 *   - The "master" pins are connected to DIGITAL 2, 3, and 4.
 *   - Each of the "slaves" pins are connected together and then to DIGITAL 5, 6, and 7.
 *
 * ~ Thus, to select a particular potentiometer, we need to set the 6 selector pins as follows:
 *
 *   - If it's a "master" pin, the value sent is for the integer value of "n" divided by 8.
 *     - Integers are always rounded down to the nearest whole number;
 *       this will always give us a value from 0 to 3.
 *
 *    - If it's a "slave" pin, the value sent is for the remainder of "n" divided by 8.
 *     - This will always give us a value from 0 to 7.
 */
int selectKnob(int n) {
    int a=2; // pin#
    int b=0; // evaluate this number
    int c=0; // at this bit position

    for(a=2;a<8;a++) {
        if(a<5) { b=n/8; c=a-2; } else { b=n%8; c=a-5; }
        if(bitRead(b,c)) { digitalWrite(a,HIGH); } else { digitalWrite(a,LOW); }
    }

    return map(analogRead(A0), 0, 1023, 0, 127);
    // return analogRead(A0)/8; // value is between 0 and 1023, midi control values are 0-127
}

void setup() {
    /*
     * CD4051 selector inputs are on DIGITAL 2-7
     */
    for(x=2;x<8;x++) {
        pinMode(x,OUTPUT);
    }

    pinMode(A0,INPUT); // "master" 4051 output is on ANALOG 0

    // get some default values first
    for(x=0;x<32;x++) {
        lastRead[x]=-1;//selectKnob(x);
    }

    // 31250 is default midi baud rate
    // 9600 for debugging
    Serial.begin(31250);
}

void loop() {
    // around the world
    // around the world
    for(x=0;x<32;x++) {
        y=selectKnob(x);
        //  Debug stuff

        /*    
        Serial.print(x);
        if(lastRead[x] >= y+2 || lastRead[x] <= y-2) {
            lastRead[x] = y;
            Serial.print(" is ");
        } else {
            Serial.print(" still equals ");
        }

        Serial.println(y);
        delay(500);
        */

        if(lastRead[x] >= y+2 || lastRead[x] <= y-2) { // only send if the knob has actually moved
            Serial.write(176); //
            Serial.write(x);   // Controller #
            Serial.write(y);   // Current Value
            lastRead[x]=y;
        }
    }
} // kthxbye

You can see my mind was still in “hardware mode”. The code serves as a virtual extension to the working circuit, but it merely focusing on making the hardware spit out values and does not sufficiently debounce the signal.

if(lastRead[x] >= y+2 || lastRead[x] <= y-2) {
// only send if the knob has actually moved

This line was not enough, and for some reason, I did not investigate further at the time. I guess I chalked it up to the potentiometers being cheap (25 cents!) or my wiring not being good enough, and let my self-loathing get the better of me.

I figured that there was no way to algorithmically filter the signal and considered it a failure.

I did try to use it a number of times, but knobs would jump unpredictably in wild amounts which would cause grief when trying to assign or use them. Also a few knobs were inexplicably tied together (29 and 30 would move together, if I remember correctly.)

Better code

Then last January I decided to get my head out of my ass and solve this issue.

Rather than try to find a one-size-fits-all, general-purpose debouncing solution, I analyzed the output of each knob by printing the values to the serial monitor and would try to correct each knob’s behavior on every iteration of the loop.

Hopefully the comments explain my approach clearly:

(use this instead plz)

// for inputs
int lastReadValue[32];
int x = 0;
int y = 0;
// for debouncing
unsigned long lastReadTime[32];
int diffValue=0;
int avgValue=0;
int modeValue[100];
int i = 0;
int j = 0;

/**
 * shamelessly taken from Arduino Average library 
 */
int mode(int data[], int count) {
    int pos;
    int inner;
    int most = 0;
    int mostcount = 0;
    int current;
    int currentcount;
  
    for(pos = 0; pos < count; pos++) {
        current = data[pos];
        currentcount = 0;
        for(inner = pos+1; inner < count; inner++) {
            if(data[inner] == current) {
                currentcount++;
            }
        }
        if(currentcount > mostcount) {
            most = current;
            mostcount = currentcount;
        }
        if(count-pos < mostcount) {
            break;
        }
    }
    return most;
}

void selectKnob(int n) {
    int a = 2; // pin#
    int b = 0; // evaluate this number
    int c = 0; // at this bit position
 
    for(a = 2; a < 8; a++) {
        if(a < 5) {
            b = n/8;
            c = a-2;
        } else {
            b = n%8;
            c = a-5;
        }
        digitalWrite(a, bitRead(b,c) ? HIGH : LOW);
    }
}

// regular mode
void sendMidi(int controller, int value) {
    if(lastReadValue[controller] != value) {
        Serial.write(176);
        Serial.write(controller);
        Serial.write(value);
    }
}

// debug mode
void __sendMidi(int controller, int value) {
    if(lastReadValue[controller] != value) {
        Serial.print("Controller #");
        Serial.print(controller);
        Serial.print(": ");
        Serial.println(value);
    }
}

void setup() {
  /*
   * CD4051 selector inputs are on DIGITAL 2-7
   */
    for(x = 2; x < 8; x++) {
        pinMode(x, OUTPUT);
    }
  
    pinMode(A0, INPUT); // "master" 4051 output is on ANALOG 0
  
    // set some default values first
    for(x = 0; x < 32; x++) {
        lastReadValue[x] = 0;
        lastReadTime[x]  = millis();
    }
    
    // 31250 is default midi baud rate
    // 9600 for debugging
    Serial.begin(31250);
}

void loop() {
    for(x = 0; x < 32; x++) {
        // dead soldiers
        if(x == 2 || x == 16 || x == 29 || x == 30) {
            continue;
        }
    
        selectKnob(x);
        y = round(analogRead(A0)/8);
    
        /**
        * Debouncing 2.0 
        */
    
        diffValue = abs(lastReadValue[x] - y);
    
        // no movement or jitter
        if( diffValue == 0 ||
            ((diffValue == 1 || diffValue > 40)
            && lastReadTime[x] - millis() < 50 )
        ) {
            continue;
        }
        
        // debouncing phase 1: mean average
        if(diffValue < 4) {
      
            if(lastReadTime[x] - millis() < 25) {
                continue;
            }
      
            avgValue = analogRead(A0);
            for(i = 0; i < 24; i++) {
                avgValue += analogRead(A0);
                delay(1);
            }
            y = round((avgValue / 25)/8);
      
        }
    
        // knob specific debouncing
        switch(x) {
        case 0:
        case 4: // fallthrough is a feature
        case 9:
        case 10:
        case 12:
        case 21:
        case 23:     
        
            // Get a starting point
            if(millis() < 1000) {
                lastReadValue[x] = round(analogRead(A0)/8);
            }
        
            // And make sure it's had time to calm down
            if(lastReadTime[x] - millis() < 25) {
                continue;
            }
        
            // take the mode        
            if(diffValue > 5) {
                avgValue = 0;
                for(i = 0; i < 100; i++) {
                    modeValue[i] = round(analogRead(A0)/8);
                }
                avgValue = mode(modeValue, 100);
                if((lastReadValue[x] - avgValue) == 1) {
                    continue;
                } else {
                    y = avgValue;
                }
            }
        
            // random jumps
            if(diffValue > 25
                && (lastReadTime[x] - millis()) < 500
            ) {
                continue;
            }
            break;
        }
    
        sendMidi(x, y);
        lastReadValue[x] = y;
        lastReadTime[x] = millis();
    }
}

This has worked out very well. It’s stable now, and I took the knobs off of the pots that are disabled. When life hands you lemons…

No MIDI In

I guess I should explain why there’s no MIDI In functionality despite the circuit being there.

At no point was I ever able to successfully send MIDI In to the Arduino from the MIDI-to-USB through this circuit. I’m not sure if it’s the circuit itself, or more likely The perils of cheap MIDI adapters.

What I had originally envisioned was “syncing” the controller to an envelope or something in the DAW by listening to MIDI In, storing the value, and simply not sending a value until it was within some range of the stored value.

Syncing is a fairly complicated task when you’re sending static values. The MIDI Specification includes “Data Increment” and “Data Decrement” messages which is what controllers with rotary encoders use to achieve “endless” rotations.

Rotary encoders are expensive and there weren’t a lot of examples at the time I made this (which was 2012). The aforementioned Behringer Rotary uses rotary encoders.

Post Mortem

Is this a product for le epic startup to manufacture for loadsa dosh in le internet of things and stuff? No.

Was it a learning experience? Surely.

Would I do it again or what would I do differently? Panel-mount pots would be less shit to deal with, and easier to wire unless I got the tools to make, print, and mill PCBs. Rotary encoders could prove more useful for doing temporary effects (like effecting an envelope and letting it snap back to where it “should” be). I would probably make another controller or something else with Arduino if I felt so inspired, but at the moment I’d really have no use for another.

Have I gotten a lot of use out of it? Well, maybe not as much as I should. I made this track straight after getting it working. I use it occasionally to mess around with parameters occasionally, but it and my nanoKEY2 and a Casio I found don’t really get enough use.

I’m more of a composer than a performer. I guess that’s why I enjoy DJing so much. I use these MIDI devices to get an idea of what I want to create, but pretty much everything I make is with the piano roll ._.

~dayvonjersen