Introduction: 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.
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 (When I made this 2 years ago AngledCables was about the only source for this adapter cable. Looking online recently I've decided to purchase some from AdaFruit at a much better price.)
Step 1: Enclosure
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.
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.
Wiring used quick-disconnects that were both crimped & soldered
After programming, I had the enclosure powder coated for about $40 at a local shop and mounted everything. The final step was to apply a vinyl decal made by friends at Innovative Tint & Graphics.
I etched a custom PCB as a breakout for the Teensy that used tall female headers as a socket for the microcontroller. I've been using a standard 24-pin IC socket with the same measurements for the Teensies in more recent projects.
Step 2: Firmware
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. Be sure that Board and USB Type in the Arduino IDE Tools menu are set correctly as in the image.
Possible changes you may want to make in the code:
* The MIDI channel that the PacMod responds to is midi_ch if you want it to be something other than 3.
* The scaling of the analog pin value in relation to the desired MIDI cc value is analog_scale, set to a value of 8 in the code to give a range of 0 to 128. To change it change analog_scale to ( 1024 / yourDesiredMaxValue ). If you change analog_scale you may also need to change analog_threshold, which is the amount of change on a Teensy analog pin that constitutes sending a MIDI message.
* The MIDI note numbers that are triggered by the buttons are outlined in the array digital_note[] so changing these values will change which notes are mapped to which buttons.
* The MIDI continuous controller numbers mapped to to pots are in array analog_control[] so you can change these as well.
Attachments

Second Prize in the
DIY Audio

Participated in the
Instructables Design Competition

Participated in the
A/V Contest
52 Comments
5 years ago
Your work is great and your instruction's are very easy to follow ! Have you ever looked into any midifighters and thought about adding any led lighting and maybe a few rotarys ? Again such great work and i wish i had a dad that made me something like this , I never even had one at all so this would be a dream come true for some people . keep up the good work
Reply 5 years ago
Hey thanks for the kind words! My son was obsessed with the original MIDI Fighter at the time and so was the inspiration for this. I haven't looked at expanding this particular project any as my son no longer DJs. However I am working a little here and there when I can get time on a different audio effect project. It'll maybe use an encoder but not really in any cool way.
6 years ago
Wow that's a great how-to.
I would like to ask if it is possible to use rotary encoders instead of analog pots? If so, which parts in the sketch have to be changed to which commands?
Reply 6 years ago
Thank you for the kind words! It would certainly be possible to use encoders instead of pots. How that would be done is a little beyond the scope of a comment here, but the all of the variables containing "analog" and the routines that handle them would be the bits in question.
6 years ago
Hi, awesome project. Can you tell me how you wired the arcade buttons on the Teensy. Did you use a multiplexer ?
Thanks !
Reply 6 years ago
Thanks! No multiplexing on this. Each button is wired directly to a digital input.
Reply 6 years ago
Hey thanks ! Did you use a teensy 2.0 or a teensy ++ ? Also sorry to ask but I can see in the pictures there is a pac mod rev 1.0 little board, what's that ? Thanks a lot !
Reply 6 years ago
No sweat! It was a Teensy 2.0. The other board labeled "PacMod rev 1.0: is just a PCB I etched to solder a socket for the Teensy and terminals to land the other wiring.
Reply 6 years ago
Thanks a lot !
6 years ago
This is an awesome dad! Personal presents are the best, always. Nice message and instructable.
Reply 6 years ago
Thanks!
7 years ago on Introduction
Hi, thanks for your tutorial. My setup has 6 analog inputs - knobs & sliders & I've tried to tweak the code to fit it but it only seems to be detecting 1 knob & changing for (b = 7; b >= 2; b--) { to for (b = 2; b <= 7; b++) { makes the first row of buttons disappear and none of the knobs work. If someone could help me out I'd be very grateful! Thanks!
#include <Bounce.h>
/*
PacMod DJ Controller 002
*/
// pin definitions
const int digital_pin[] = { 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13 };
const int analog_pin[] = { A2, A3, A4, A5, A6, A7 };
// variables for the states of the controls
boolean digital_stored_state[12];
int analog_stored_state[6];
// amount of change that constitutes sending a midi message
const int analog_threshold = 2;
const int analog_scale = 8;
// Debounce
long debounceDelay = 20;
Bounce digital_debouncer[] = {
Bounce(digital_pin[0], debounceDelay),
Bounce(digital_pin[1], debounceDelay),
Bounce(digital_pin[2], debounceDelay),
Bounce(digital_pin[3], debounceDelay),
Bounce(digital_pin[4], debounceDelay),
Bounce(digital_pin[5], debounceDelay),
Bounce(digital_pin[6], debounceDelay),
Bounce(digital_pin[7], debounceDelay),
Bounce(digital_pin[8], debounceDelay),
Bounce(digital_pin[9], debounceDelay),
Bounce(digital_pin[10], debounceDelay),
Bounce(digital_pin[11], debounceDelay),
Bounce(digital_pin[12], debounceDelay),
Bounce(digital_pin[13], debounceDelay),
//Bounce(digital_pin[14], debounceDelay),
//Bounce(digital_pin[15], debounceDelay),
//Bounce(digital_pin[16], debounceDelay),
//Bounce(digital_pin[17], debounceDelay),
//Bounce(digital_pin[18], debounceDelay)
};
// MIDI settings
int midi_ch = 3;
int midi_vel = 127;
const int digital_note[] = { 48, 49, 50, 51, 44, 45, 46, 47, 40, 41, 42, 43 };
const int analog_control[] = { 0, 1, 2, 3, 4, 5 };
void setup() {
Serial.begin(38400);
// set the pin modes && zero saved states
int b = 0;
// digital pins
for (b = 11; b >= 0; b--) {
pinMode(digital_pin[b], INPUT_PULLUP);
digital_stored_state[b] = false;
}
// analog pins
for (b = 7; b >= 2; b--) {
analog_stored_state[b] = 0;
}
}
void loop() {
fcnProcessButtons();
}
//Function to process the buttons
void fcnProcessButtons() {
int b = 0;
// digital pins
for (b = 11; 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);
} else {
usbMIDI.sendNoteOff(digital_note[b], midi_vel, midi_ch);
}
digital_stored_state[b] = !digital_stored_state[b];
}
}
// analog pins
for (b = 7; b >= 2; b--) {
int analog_state = analogRead(analog_pin[b]);
if (analog_state - analog_stored_state[b] >= analog_scale || analog_stored_state[b] - analog_state >= analog_scale) {
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;
}
}
}
Reply 7 years ago on Introduction
Your for loop variables should be referencing the index of the array. By saying "for (b = 2; b <= 7; b++)" you are using analog_pin[2] to analog_pin[7], This skips the first 2 elements of your array:analog_pin[0] and [1]. AND it tries to access analog_pin[6] and [7] when you have only declared 6 elements! The highest element index will be [5]. Try this: "for (b = 0; b <= 5; b++)"
I have only had a cursory glance at your code. There may be more amiss.
Reply 7 years ago on Introduction
Thanks! It works perfectly now!
I just edited the code to use pin 3 as a shift button and it works but when I hold the shift button, the new midi notes seem to be all over the place, would you have any idea how to fix that?
it's currently mapping the shifted MIDI notes to
48, 49, 50 to 18, 17, 16
44, 45, 46, 47 to 14, 0, 1, 2
40, 41, 42, 43 to 3, 4, 5, 6
It's supposed to map the shift notes like this (+12 from original):
48, 49, 50 to 60, 61, 62
44, 45, 46, 47 to 56, 57, 58, 59
40, 41, 42, 43 to 52, 53, 54, 55
#include <Bounce.h>
/*
PacMod DJ Controller 002
*/
//SHIFT_______________________________________________
//shift buttons offer dual functionality to your pushbuttons and encoders
//if using a shift button enter the pin number here, else put 0
int shiftPin = 3;
int shiftChange;
// pin definitions
const int digital_pin[] = { 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13 };
const int analog_pin[] = { A3, A4, A5, A6, A7 };
// variables for the states of the controls
boolean digital_stored_state[24];
int analog_stored_state[6];
// amount of change that constitutes sending a midi message
const int analog_threshold = 2;
const int analog_scale = 8;
// Debounce
long debounceDelay = 20;
Bounce digital_debouncer[] = {
Bounce(digital_pin[0], debounceDelay),
Bounce(digital_pin[1], debounceDelay),
Bounce(digital_pin[2], debounceDelay),
Bounce(digital_pin[3], debounceDelay),
Bounce(digital_pin[4], debounceDelay),
Bounce(digital_pin[5], debounceDelay),
Bounce(digital_pin[6], debounceDelay),
Bounce(digital_pin[7], debounceDelay),
Bounce(digital_pin[8], debounceDelay),
Bounce(digital_pin[9], debounceDelay),
Bounce(digital_pin[10], debounceDelay),
Bounce(digital_pin[11], debounceDelay),
Bounce(digital_pin[12], debounceDelay),
Bounce(digital_pin[13], debounceDelay),
//Bounce(digital_pin[14], debounceDelay),
//Bounce(digital_pin[15], debounceDelay),
//Bounce(digital_pin[16], debounceDelay),
//Bounce(digital_pin[17], debounceDelay),
//Bounce(digital_pin[18], debounceDelay)
};
// MIDI settings
int midi_ch = 3;
int midi_vel = 127;
const int digital_note[] = { 48, 49, 50, 51, 44, 45, 46, 47, 40, 41, 42, 43};
const int analog_control[] = { 0, 1, 2, 3, 4, 5 };
void setup() {
Serial.begin(38400);
// set the pin modes && zero saved states
int b = 0;
//SHIFT - pin config _______________________________________________
//we need enable the shift pin as an INPUT as well as turn on the pullup resistor
if(shiftPin!=0){
pinMode(shiftPin,INPUT_PULLUP); //shift button
}
// digital pins
for (b = 11; b >= 0; b--) {
pinMode(digital_pin[b], INPUT_PULLUP);
digital_stored_state[b] = false;
}
// analog pins
for (b = 0; b < 5; b++) {
analog_stored_state[b] = 0;
}
}
void loop() {
fcnProcessButtons();
//SHIFT loop _______________________________________________
if(shiftPin!=0){
if(digitalRead(shiftPin)==LOW){ //check if shift button was engaged
shiftChange = 12; //if enganged, the offset is 12
}else{
shiftChange = 0;
}
}
}
//Function to process the buttons
void fcnProcessButtons() {
int b = 0;
// digital pins
for (b = 11; b >= 0; b--)
if(b!=shiftPin){ //ensure this is not the shift pin
int j = b + shiftChange; //add the shift change (+12)
digital_debouncer[b].update();
boolean state = digital_debouncer[b].read();
if (state != digital_stored_state[j]) {
if (state == false) {
usbMIDI.sendNoteOn(digital_note[j], midi_vel, midi_ch);
} else {
usbMIDI.sendNoteOff(digital_note[j], midi_vel, midi_ch);
}
digital_stored_state[j] = !digital_stored_state[j];
}
}
// analog pins
for (b = 0; b < 5; b++) {
int analog_state = analogRead(analog_pin[b]);
if (analog_state - analog_stored_state[b] >= analog_scale || analog_stored_state[b] - analog_state >= analog_scale) {
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;
}
}
}
Reply 7 years ago on Introduction
The shifting isn't my code, but it looks like you're trying to use the loop index variable "b" incorrectly again. Your line "int j = b + shiftChange; //add the shift change (+12)" is affecting the loop index (and thus the array index) and you definitely don't want that. You really don't need the line "if(b!=shiftPin){ //ensure this is not the shift pin" as the loop index is for the elements of the array, not for the pin numbers. Your shift pin shouldn't be in the array of note numbers. Take those 2 lines out and just change the note on and off commands ("usbMIDI.sendNoteOn(digital_note[g] + shiftChange, midi_vel, midi_ch);" and "usbMIDI.sendNoteOff(digital_note[g] + shiftChange, midi_vel, midi_ch);")
I haven't looked through the code thoroughly so I'm not sure how they/you are handling what happens if the shift button is pressed while a note is pressed and lifted while the note is still pressed. I'll leave that to you and the author of the shift code.
Reply 7 years ago on Introduction
Thank you so much for your help! It works great now!
I ended up leaving "if(b!=shiftPin){ //ensure this is not the shift pin" in because otherwise it wasn't sure if it was to send midi messages or just act as a shift.
Reply 7 years ago on Introduction
You don't need it. Earlier you set the shiftChange to 12 if shift is pressed, 0 if it's not. That's actually the only place you need to be watching for the shift button. Later in the code you're always adding shiftChange to the MIDI note number - which is handled in the earlier code: zero if not shifting, 12 if shifting.
Reply 7 years ago on Introduction
Thanks for getting back to me so quickly! I'm using Analog pins 2,3,4,5,6,7 & digital pins 0,1,2,3,4,5,8,9,10
Reply 7 years ago on Introduction
You're declaring the arrays and telling the Arduino what pins you are using in these 2 lines:
const int digital_pin[] = { 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13 };
const int analog_pin[] = { A2, A3, A4, A5, A6, A7 };
So you can see that the analog_pin array has 6 members:
analog_pin[4] is pin A6
analog_pin[5] is pin A7
It appears that you are getting confused about what is happening in the for loop and want to call out the actual pin number, but that is not the case. The variable "b" is being used to walk through the array members by index - starting at analog_pin[0] and ending at analog_pin[5]. This will then refer to the physical pin that is stored in that array member.
Truthfully, I should have used a const for the array counts such as "const int ANALOG_COUNT", which in your case would be 6, and then the loops would be in the form of: "for (b = 0; b < ANALOG_COUNT; b++)". Notice that the qualifier to end the loop isn't "<=" anymore, but just "<". This is so the loop will exit after the index of the last member of the array which is always the size of the array minus 1. This is because the FIRST index is zero and not 1. Hope this clears things up some!
9 years ago on Introduction
Hello, I am Extremely interested on your design, but Coming from a Novation Launchpad Back ground could you give me another CPU maybe that could withstand more than 25 Arcade Buttons?
Thanks