PACMOD MIDI DJ Controller

PACMOD MIDI DJ Controller

PACMOD MIDI DJ Controller

For my son’s birthday I made him a DJ controller designed to resemble an old cabinet arcade machine. What follows is a brief overview of the process to make it, including code.

The Teensy was chosen as the brains because of the way it handles USB. Not only does it have native USB, rather than the FTDI USB-to-serial interface that turns making use of the port a series of work-arounds, but as you’ll see in the code the MIDI library makes writing the firmware a trivial pursuit.

Caveat: I found out later that my scaling of the sliders is off & doesn’t allow for full travel in Traktor. Since the controller is away at college with my son I haven’t had access to fix this. I’m certain that it’s just a matter of changing the scale factor, though.

UPDATE: A typo in the code was identified in the comments here that was causing the limited travel. The code posted below has been updated to reflect this.


The parts list:
Teensy 2.0 CPU from PJRC
7-inch sloped enclosure from iProjectBox
Sanwa Arcade Buttons from DJTechTools
Joystick from SparkFun
Slide potentiometers from SparkFun
Pot knobs from SparkFun
Panel adapter cable from AngledCables Update: When I made this in 2010 AngledCables was about the only source for this adapter cable. Looking online at the end of 2012 I’ve decided to purchase some from AdaFruit at a much better price.


Indents for arcade buttons

Indents for arcade buttons

Once all the parts were in hand the placement of the buttons was measured and marked out in pencil. Once the holes were cut I used a Dremel to put in the 4 indentions that index the buttons and keep them from rotating.


Pot slots

Pot slots

Drilling the mounting holes for the joystick was pretty straight-forward. The slots for the pots were marked out and small holes drilled at each end. A small cut-off wheel on the Dremel connects the dots.


Preparing wiring

Preparing wiring

Wiring crimped & soldered

Wiring crimped & soldered

Buttons being wired

Buttons being wired

Wiring used quick-disconnects that were both crimped & soldered


PCB ready to etch

PCB ready to etch

I etched a custom PCB as a breakout for the Teensy that used tall female headers as a socket for the microcontroller


Mounted PCB

Mounted PCB

After programming, I had the enclosure powder coated at a local shop and mounted everything. The final step was to apply a vinyl decal made by friends at Innovative Tint & Graphics.



Finished controllerFinished controllerFinished controllerFinished controller


Mapping

Mapping

The mapping tried to make use of as much of one for an existing controller as possible, just switched to a different channel.


You’ll want to go over to the Arduino playground and pick up the debounce library Bounce.h to include. It’s a very handy way to easily debounce mass inputs as demonstrated below.

I think the comments and naming make everything pretty clear as to what’s going on. I’ve even left in some junk that was really only part of the development and debugging. The line Serial.begin(38400) is one of these things. It doesn’t need to be there for the code to function. It was just used for the Arduino debugging statements that are now commented out.

If you’re not familiar with Teensy, there’s a good primer on the PJRC site. The most important thing if you’re going to be using the Arduino environment is the Teensy Loader.

  • http://www.RedBinary.com RedBinary

    “hello there. i want to build the same arcade controller for my self. i understand most of the code but i am not a programmer though. i wanted to ask you what does the part of const in digital_note does and similar for analog. how do you came up with these values??

    thank you very much”

    Good question! Those are the ‘MIDI note numbers’. The numbers that the controller sends to whatever it is controlling to tell it which note to play. The MIDI specification uses note numbers from 0 to 127 to sequentially lay out the piano scale like an enormous 10 octave keyboard. Here’s a (giant) image showing the note numbers: http://en.wikipedia.org/wiki/File:NoteNamesFrequenciesAndMidiNumbers.svg

    The note numbers I picked for the buttons were just to try to make use of the mapping in Traktor that my son was already using.

    Similarly, but possibly a little more confusing, analog_control[] are the numbers for which ‘continuous controller’ the DJ controller is using. In this context ‘continuous controller’ means an analog input like pitch bend or mod wheel (http://improv.sapp.org/doc/class/MidiOutput/controllers/controllers.html) I just arbitrarily picked 0 and 1 for my sliders. The sliders generate a range of 0-1023 in the Teensy, but since the MIDI specification says they should only be a range of 0-127 I used a scaling factor of 8. My son reports that my scaling factor doesn’t allow the sliders to go full range in Traktor, however.

    Hope this helps!

  • http://www.RedBinary.com RedBinary

    “THANK you very much for your reply..i am not sure i follow you 100% but i guess a little trial and error will do the trick….a final question (if you know it of course)….teensy 2 has 12 analog inputs…can i use only 6 lets say and the other 6 “declare” them in the code as digital?

    Thank you very much again…really appreciate your help”

    Happy to help! Using the Arduino environment you can use any of the analog pins as digital pins just by the way you reference them: http://arduino.cc/en/Tutorial/DigitalPins

    You’ll notice that in the code I’m using analog-capable digital_pin[] 11-18 as digital pins, but pins 20 and 21 as analog pins. I can do this by referencing them as A0 and A1 using analogRead().

  • Pingback: Red Binary | Designing a replacement for an obsolete Electro Cam control system in a Maac thermoformer

  • grivvr

    Great walkthrough. Thanks :)
    I was looking for a good solution to not sending the Control Change message each loop, and storing the states does that elegantly.

    I did notice that you’re comparing the difference in current and stored values against analog_scale, not analog_threshold, which I assume is a typo. This may account for the sliders not reaching their full range?

    You could also neaten that line up using absolute value;

    if (abs(analog_state - analog_stored_state[b] >= analog_threshold)) {
    ...
    }

    • http://www.RedBinary.com RedBinary

      Thanks for the kind words … and I think you’ve nailed it on the head about the typo! Thanks!

  • allmektig

    I’m working on a similar controller myself, but I’m making a foot controller.

    I have four expression pedals I’m going to use with it.
    The only problem is that the original pots are logarithic, and if I change those out for linear pots, I only get about 70% of the movement of the pot.
    Wich do you think would be easier, using the logarithmic pots and change the values to mach a linear movement in the software, or use the linear pots and map the values I actually get to midi values?

    • http://www.RedBinary.com RedBinary

      If you need linear off the top of my head I would think that it would be easier to use the linear pot. You’re going to have to remap the ADC values to MIDI values anyway, so just use the values at each end of travel as the ‘min’ & ‘max’ values.

      As an example, the pot at full travel will give you a range of 0 – 1023 which you would need to scale to 0 – 127 for (most) MIDI CCs. For full travel of the pot it would just be a matter of dividing the analog value by 8 to scale it. Just find out what your analog value is with the pedal at full stroke and divide that by the MIDI’s 127 to get your scale divisor. Using your estimated 70% travel that would mean the pot would top out sending an analog value of around 716. 716 / 127 = 5.6 so using a divisor of 6 would mean you get a MIDI range of 0 – 119. Since this isn’t full MIDI range, if you’d prefer the last few mm of travel to be maxed out at 127 use a divisor of 5 so your range is 0 – 143 and then just use if (MIDI_value > 127) MIDI_value = 127;

      Hope this helps! If I didn’t explain myself well don’t hesitate to let me know!

      :P atrick

  • hermankopinga

    HI,

     

    Great work! I used your code as a basis to learn about Teensy and Midi, worked wonders! Just curious and for further reference. What is the license for the code you posted above? There is none explicitly mentioned in the source.

    I’m currently learning to give my code the proper license and attribution and this feels like a nice experiment for that.

    Thanks,

    Herman

    • http://www.RedBinary.com RedBinary

      Herman:

      Thank you for the kind words & I’m really glad it helped you! I really hadn’t thought about a license for it, but you’ve probably got a very good point that it’s something I should include in this day and age. I will have to do some research and give it some thought.

      :P atrick

  • luke birch

    For some reason i cant get this to work, driving me crazy!

    Ive only got 1 pot (rotating pot) but for some reason, it will only read when im turning the pot left but not right.

    • http://RedBinary.com/ Red Binary

      I’m not understanding. Do you mean that the analog values get higher when turning the pot counter-clockwise & lower when turning clockwise? How is the pot wired?

      • luke birch

        Sorry mate, should have been clearer:
        When i turn the pot counter-clockwise – it shows values in the serial monitor UNTIL it reaches the end of the turn. Trying to turn it clockwise does not send any values. If it turn it back clockwise half way (without it sending values) and re-turn it counter-clockwise, it does not send any values. If i start up teensy with the pot turned all the way counter-clockwise and try to turn it clockwise, it sends no values.
        Im sure its something im doing wrong, just cant figure it out. Went to bed that night and had one of those sleeps where i couldnt stop thinking about it – haha. Ive pasted the code below – can you shed any light?
        Thanks mate.
        #include

        const int digital_pin[] = { 0, 1 };
        const int analog_pin[] = { 18 };

        boolean digital_stored_state[2];
        int analog_stored_state[1];

        const int analog_threshold = 2;
        const int analog_scale = 8;

        long debounceDelay = 20;

        Bounce digital_debouncer[] = {
        Bounce(digital_pin[0], debounceDelay),
        Bounce(digital_pin[1], debounceDelay)
        };

        int midi_ch = 3;
        int midi_vel = 127;

        const int digital_note[] = { 0, 1 };
        const int analog_control[]= { 18 };

        void setup() {
        Serial.begin(38400);

        int b = 0;

        for (b = 2; b >= 0; b–) {
        pinMode(digital_pin[b], INPUT_PULLUP);
        digital_stored_state[b] = false;
        Serial.print(“setup: pin “);
        Serial.println(b);
        }

        for (b = 1; b>= 0; b–) {
        analog_stored_state[b] = 0;
        }
        Serial.println(“—————-”);
        Serial.println(“MIDI DJ Controller Test – setup”);
        Serial.println(“—————-”);
        }

        void loop() {
        fcnProcessButtons();
        }

        void fcnProcessButtons() {
        int b = 0;

        for (b = 0; b >= 0; b–) {
        digital_debouncer[b].update();
        boolean state = digital_debouncer[b].read();
        if (state != digital_stored_state[b]) {
        if (state == false) {
        usbMIDI.sendNoteOn(digital_note[b], midi_vel, midi_ch);
        Serial.print(“MIDI note on: “);
        Serial.println(digital_note[b]);
        } else {
        usbMIDI.sendNoteOff(digital_note[b], midi_vel, midi_ch);
        Serial.print(“MIDI note off: “);
        Serial.println(digital_note[b]);
        }
        digital_stored_state[b] = !digital_stored_state[b];
        }
        }

        for (b = 1; b>= 0; b–) {
        int analog_state = analogRead(analog_pin[b]);
        if (abs(analog_state – analog_stored_state[b] >= analog_threshold)) {
        int scaled_value = analog_state / analog_scale;
        usbMIDI.sendControlChange(analog_control[b], scaled_value, midi_ch);
        Serial.print(“analog value “);
        Serial.print(b);
        Serial.print(“: “);
        Serial.print(analog_state);
        Serial.print(” scaled: “);
        Serial.println(scaled_value);
        analog_stored_state[b] = analog_state;
        }
        }
        }

        • luke birch

          Ignore the /bounce.h at the bottom, not sure why that added in there when i copied.

        • http://RedBinary.com/ Red Binary

          The first thing I would change is your third line above:

          “const int analog_pin[] = { 18 };”

          Pins that are being utilized as analog should be addressed as such:
          “const int analog_pin[] = { A3 };”

          The second thing I notice, and I’m a little surprised that it doesn’t cause bigger problems for you, is that in the loop where you’re handling the analog pin you’re looping from 1 to 0, though you only have 1 element in the analog handler arrays.

          Also, your declaring your digital_note[] and analog_control[] arrays with the same values as your pin arrays. These two arrays are the MIDI note numbers and continuous controller numbers that are to be controlled. This wouldn’t have anything to do with the issue you’re having trouble with right now, but just letting you know so it doesn’t cause you headaches down the road.

          Finally, just make sure your pot is wired one end to +5v, the other end to 0v, and the wiper to the analog pin.

          [edit]
          The loop I’m referring to in my second point above is:

          for (b = 1; b>= 0; b–) {
          int analog_state = analogRead(analog_pin[b]);
          if (abs(analog_state – analog_stored_state[b] >= analog_threshold)) {
          int scaled_value = analog_state / analog_scale;
          usbMIDI.sendControlChange(analog_control[b], scaled_value, midi_ch);
          Serial.print(“analog value “);
          Serial.print(b);
          Serial.print(“: “);
          Serial.print(analog_state);
          Serial.print(” scaled: “);
          Serial.println(scaled_value);
          analog_stored_state[b] = analog_state;
          }
          }

          You changed the digital handler loop which *does* have 2 elements in it’s arrays, so I think you may have just edited the wrong loop.

          I hope this helps! Let me know how it turns out!

          • luke birch

            Ohhhh ok – thanks for that. Im a bit new to all this – trying to learn as I go. What would you suggest I change that Loop to?
            Yep got the wiring nailed down – 1 x 5v, 1 x gnd, 1 x analog pin.
            Thanks for the repsonse!

          • http://RedBinary.com/ Red Binary

            Glad to help if I can! I think you just have the loop declarations reversed. The first loop (for the 2 digital inputs) should be “for (b = 1; b >= 0; b–) {…” and the second loop (for the single analog input) should be “for (b = 0; b >= 0; b–) {…”

            I’m just assuming that you’re just testing & prototyping right now to add more analog inputs later. If you’re only going to use 1 pot you could simplify the code a lot by not using unnecessary arrays and loops.

          • luke birch

            Thanks for your help so far mate. Seems im still stuck though (spent a few hours on it tonight). I still couldnt get it working after making your suggested changes above, so I added a second pot (these are turn pots, not sliders) to see if that was causing the issue, but still not working. Can get both of them to scale on the increase and output serial messages, but nothing when i try to turn clockwise.

            analog value 1: 1012 scaled: 126

            analog value 1: 1014 scaled: 126

            analog value 1: 1016 scaled: 127

            analog value 1: 1018 scaled: 127

            analog value 1: 1020 scaled: 127

            analog value 1: 1022 scaled: 127

            analog value 0: 626 scaled: 78

            analog value 0: 628 scaled: 78

            analog value 0: 630 scaled: 78

            analog value 0: 632 scaled: 79

            Updated code:

            #include

            const int digital_pin[] = { 0, 1 };

            const int analog_pin[] = { A3, A5 };

            boolean digital_stored_state[2];

            int analog_stored_state[2];

            const int analog_threshold = 2;

            const int analog_scale = 8;

            long debounceDelay = 20;

            Bounce digital_debouncer[] = {

            Bounce(digital_pin[0], debounceDelay),

            Bounce(digital_pin[1], debounceDelay)

            };

            int midi_ch = 3;

            int midi_vel = 127;

            const int digital_note[] = { 48, 49 };

            const int analog_control[]= { 0, 1 };

            void setup() {

            Serial.begin(38400);

            int b = 0;

            for (b = 1; b >= 0; b–) {

            pinMode(digital_pin[b], INPUT_PULLUP);

            digital_stored_state[b] = false;

            Serial.print(“setup: pin “);

            Serial.println(b);

            }

            for (b = 1; b>= 0; b–) {

            analog_stored_state[b] = 0;

            }

            Serial.println(“—————-”);

            Serial.println(“MIDI DJ Controller Test – setup”);

            Serial.println(“—————-”);

            }

            void loop() {

            fcnProcessButtons();

            }

            void fcnProcessButtons() {

            int b = 0;

            for (b = 1; b >= 0; b–) {

            digital_debouncer[b].update();

            boolean state = digital_debouncer[b].read();

            if (state != digital_stored_state[b]) {

            if (state == false) {

            usbMIDI.sendNoteOn(digital_note[b], midi_vel, midi_ch);

            Serial.print(“MIDI note on: “);

            Serial.println(digital_note[b]);

            } else {

            usbMIDI.sendNoteOff(digital_note[b], midi_vel, midi_ch);

            Serial.print(“MIDI note off: “);

            Serial.println(digital_note[b]);

            }

            digital_stored_state[b] = !digital_stored_state[b];

            }

            }

            for (b = 1; b>= 0; b–) {

            int analog_state = analogRead(analog_pin[b]);

            if (abs(analog_state – analog_stored_state[b] >= analog_threshold)) {

            int scaled_value = analog_state / analog_scale;

            usbMIDI.sendControlChange(analog_control[b], scaled_value, midi_ch);

            Serial.print(“analog value “);

            Serial.print(b);

            Serial.print(“: “);

            Serial.print(analog_state);

            Serial.print(” scaled: “);

            Serial.println(scaled_value);

            analog_stored_state[b] = analog_state;

            }

            }

            }

  • luke birch

    Figured it out!!!! Was messing around starting from the beginning with my own basic version of the code – realised it was a misplaced bracket in your code. Check it out:

    This:

    if (abs(analog_state – analog_stored_state[b] >= analog_threshold))

    Should be this:

    if (abs(analog_state – analog_stored_state[b]) >= analog_threshold)

    Working like a charm now!!

    • http://RedBinary.com/ Red Binary

      Ah ha! Yes I see how that can be a problem! Good catch! I’ll update the updated code to reflect this!