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.
<p>Hi, thanks for your tutorial. My setup has 6 analog inputs - knobs &amp; sliders &amp; I've tried to tweak the code to fit it but it only seems to be detecting 1 knob &amp; changing for (b = 7; b &gt;= 2; b--) { to for (b = 2; b &lt;= 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!<br><br></p><p> #include &lt;Bounce.h&gt;</p><p>/*</p><p> PacMod DJ Controller 002</p><p>*/</p><p>// pin definitions</p><p>const int digital_pin[] = { 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13 };</p><p>const int analog_pin[] = { A2, A3, A4, A5, A6, A7 };</p><p>// variables for the states of the controls</p><p>boolean digital_stored_state[12];</p><p>int analog_stored_state[6];</p><p>// amount of change that constitutes sending a midi message</p><p>const int analog_threshold = 2;</p><p>const int analog_scale = 8;</p><p>// Debounce</p><p>long debounceDelay = 20;</p><p>Bounce digital_debouncer[] = {</p><p> Bounce(digital_pin[0], debounceDelay),</p><p> Bounce(digital_pin[1], debounceDelay),</p><p> Bounce(digital_pin[2], debounceDelay),</p><p> Bounce(digital_pin[3], debounceDelay),</p><p> Bounce(digital_pin[4], debounceDelay),</p><p> Bounce(digital_pin[5], debounceDelay),</p><p> Bounce(digital_pin[6], debounceDelay),</p><p> Bounce(digital_pin[7], debounceDelay),</p><p> Bounce(digital_pin[8], debounceDelay),</p><p> Bounce(digital_pin[9], debounceDelay),</p><p> Bounce(digital_pin[10], debounceDelay),</p><p> Bounce(digital_pin[11], debounceDelay),</p><p> Bounce(digital_pin[12], debounceDelay),</p><p> Bounce(digital_pin[13], debounceDelay),</p><p> //Bounce(digital_pin[14], debounceDelay),</p><p> //Bounce(digital_pin[15], debounceDelay),</p><p> //Bounce(digital_pin[16], debounceDelay),</p><p> //Bounce(digital_pin[17], debounceDelay),</p><p> //Bounce(digital_pin[18], debounceDelay)</p><p>};</p><p>// MIDI settings</p><p>int midi_ch = 3;</p><p>int midi_vel = 127;</p><p>const int digital_note[] = { 48, 49, 50, 51, 44, 45, 46, 47, 40, 41, 42, 43 };</p><p>const int analog_control[] = { 0, 1, 2, 3, 4, 5 };</p><p>void setup() {</p><p> Serial.begin(38400);</p><p> // set the pin modes &amp;&amp; zero saved states</p><p> int b = 0;</p><p> // digital pins</p><p> for (b = 11; b &gt;= 0; b--) {</p><p> pinMode(digital_pin[b], INPUT_PULLUP);</p><p> digital_stored_state[b] = false;</p><p> }</p><p> // analog pins</p><p> for (b = 7; b &gt;= 2; b--) {</p><p> analog_stored_state[b] = 0; </p><p> }</p><p>}</p><p>void loop() {</p><p> fcnProcessButtons();</p><p>}</p><p>//Function to process the buttons</p><p>void fcnProcessButtons() {</p><p> int b = 0;</p><p> // digital pins</p><p> for (b = 11; b &gt;= 0; b--) {</p><p> digital_debouncer[b].update();</p><p> boolean state = digital_debouncer[b].read();</p><p> if (state != digital_stored_state[b]) {</p><p> if (state == false) {</p><p> usbMIDI.sendNoteOn(digital_note[b], midi_vel, midi_ch);</p><p> } else {</p><p> usbMIDI.sendNoteOff(digital_note[b], midi_vel, midi_ch);</p><p> }</p><p> digital_stored_state[b] = !digital_stored_state[b];</p><p> }</p><p> }</p><p> // analog pins</p><p> for (b = 7; b &gt;= 2; b--) {</p><p> int analog_state = analogRead(analog_pin[b]);</p><p> if (analog_state - analog_stored_state[b] &gt;= analog_scale || analog_stored_state[b] - analog_state &gt;= analog_scale) {</p><p> int scaled_value = analog_state / analog_scale;</p><p> usbMIDI.sendControlChange(analog_control[b], scaled_value, midi_ch);</p><p> /*</p><p> Serial.print(&quot;analog value &quot;);</p><p> Serial.print(b);</p><p> Serial.print(&quot;: &quot;);</p><p> Serial.print(analog_state);</p><p> Serial.print(&quot; scaled: &quot;);</p><p> Serial.println(scaled_value);</p><p> */</p><p> analog_stored_state[b] = analog_state;</p><p> }</p><p> }</p><p>}</p>
<p>Your for loop variables should be referencing the index of the array. By saying &quot;for (b = 2; b &lt;= 7; b++)&quot; 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: &quot;for (b = 0; b &lt;= 5; b++)&quot;<br><br>I have only had a cursory glance at your code. There may be more amiss.</p>
<p>Thanks! It works perfectly now! </p><p><br>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? <br><br>it's currently mapping the shifted MIDI notes to<br>48, 49, 50 to 18, 17, 16<br>44, 45, 46, 47 to 14, 0, 1, 2<br>40, 41, 42, 43 to 3, 4, 5, 6</p><p>It's supposed to map the shift notes like this (+12 from original): </p><p>48, 49, 50 to 60, 61, 62<br>44, 45, 46, 47 to 56, 57, 58, 59<br>40, 41, 42, 43 to 52, 53, 54, 55<br><br><br></p><p>#include &lt;Bounce.h&gt;</p><p>/*</p><p>PacMod DJ Controller 002</p><p>*/</p><p>//SHIFT_______________________________________________</p><p>//shift buttons offer dual functionality to your pushbuttons and encoders</p><p>//if using a shift button enter the pin number here, else put 0</p><p>int shiftPin = 3;</p><p>int shiftChange;</p><p>// pin definitions</p><p>const int digital_pin[] = { 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13 };</p><p>const int analog_pin[] = { A3, A4, A5, A6, A7 };</p><p>// variables for the states of the controls</p><p>boolean digital_stored_state[24];</p><p>int analog_stored_state[6];</p><p>// amount of change that constitutes sending a midi message</p><p>const int analog_threshold = 2;</p><p>const int analog_scale = 8;</p><p>// Debounce</p><p>long debounceDelay = 20;</p><p>Bounce digital_debouncer[] = {</p><p>Bounce(digital_pin[0], debounceDelay),</p><p>Bounce(digital_pin[1], debounceDelay),</p><p>Bounce(digital_pin[2], debounceDelay),</p><p>Bounce(digital_pin[3], debounceDelay),</p><p>Bounce(digital_pin[4], debounceDelay),</p><p>Bounce(digital_pin[5], debounceDelay),</p><p>Bounce(digital_pin[6], debounceDelay),</p><p>Bounce(digital_pin[7], debounceDelay),</p><p>Bounce(digital_pin[8], debounceDelay),</p><p>Bounce(digital_pin[9], debounceDelay),</p><p>Bounce(digital_pin[10], debounceDelay),</p><p>Bounce(digital_pin[11], debounceDelay),</p><p>Bounce(digital_pin[12], debounceDelay),</p><p>Bounce(digital_pin[13], debounceDelay),</p><p>//Bounce(digital_pin[14], debounceDelay),</p><p>//Bounce(digital_pin[15], debounceDelay),</p><p>//Bounce(digital_pin[16], debounceDelay),</p><p>//Bounce(digital_pin[17], debounceDelay),</p><p>//Bounce(digital_pin[18], debounceDelay)</p><p>};</p><p>// MIDI settings</p><p>int midi_ch = 3;</p><p>int midi_vel = 127;</p><p>const int digital_note[] = { 48, 49, 50, 51, 44, 45, 46, 47, 40, 41, 42, 43};</p><p>const int analog_control[] = { 0, 1, 2, 3, 4, 5 };</p><p>void setup() {</p><p>Serial.begin(38400);</p><p>// set the pin modes &amp;&amp; zero saved states</p><p>int b = 0;</p><p>//SHIFT - pin config _______________________________________________</p><p>//we need enable the shift pin as an INPUT as well as turn on the pullup resistor</p><p>if(shiftPin!=0){</p><p>pinMode(shiftPin,INPUT_PULLUP); //shift button</p><p>}</p><p>// digital pins</p><p>for (b = 11; b &gt;= 0; b--) {</p><p>pinMode(digital_pin[b], INPUT_PULLUP);</p><p>digital_stored_state[b] = false;</p><p>}</p><p>// analog pins</p><p>for (b = 0; b &lt; 5; b++) {</p><p>analog_stored_state[b] = 0;</p><p>}</p><p>}</p><p>void loop() {</p><p>fcnProcessButtons();</p><p>//SHIFT loop _______________________________________________</p><p>if(shiftPin!=0){</p><p>if(digitalRead(shiftPin)==LOW){ //check if shift button was engaged</p><p>shiftChange = 12; //if enganged, the offset is 12</p><p>}else{</p><p>shiftChange = 0;</p><p>}</p><p>}</p><p>}</p><p>//Function to process the buttons</p><p>void fcnProcessButtons() {</p><p>int b = 0;</p><p>// digital pins</p><p>for (b = 11; b &gt;= 0; b--)</p><p>if(b!=shiftPin){ //ensure this is not the shift pin</p><p>int j = b + shiftChange; //add the shift change (+12)</p><p>digital_debouncer[b].update();</p><p>boolean state = digital_debouncer[b].read();</p><p>if (state != digital_stored_state[j]) {</p><p>if (state == false) {</p><p>usbMIDI.sendNoteOn(digital_note[j], midi_vel, midi_ch);</p><p>} else {</p><p>usbMIDI.sendNoteOff(digital_note[j], midi_vel, midi_ch);</p><p>}</p><p>digital_stored_state[j] = !digital_stored_state[j];</p><p>}</p><p>}</p><p>// analog pins</p><p>for (b = 0; b &lt; 5; b++) {</p><p>int analog_state = analogRead(analog_pin[b]);</p><p>if (analog_state - analog_stored_state[b] &gt;= analog_scale || analog_stored_state[b] - analog_state &gt;= analog_scale) {</p><p>int scaled_value = analog_state / analog_scale;</p><p>usbMIDI.sendControlChange(analog_control[b], scaled_value, midi_ch);</p><p>/* Serial.print(&quot;analog value &quot;);</p><p>Serial.print(b);</p><p>Serial.print(&quot;: &quot;);</p><p>Serial.print(analog_state);</p><p>Serial.print(&quot; scaled: &quot;);</p><p>Serial.println(scaled_value);*/</p><p>analog_stored_state[b] = analog_state;</p><p>}</p><p>}</p><p>} </p>
<p>The shifting isn't my code, but it looks like you're trying to use the loop index variable &quot;b&quot; incorrectly again. Your line &quot;int j = b + shiftChange; //add the shift change (+12)&quot; is affecting the loop index (and thus the array index) and you definitely don't want that. You really don't need the line &quot;if(b!=shiftPin){ //ensure this is not the shift pin&quot; 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 (&quot;usbMIDI.sendNoteOn(digital_note[g] + shiftChange, midi_vel, midi_ch);&quot; and &quot;usbMIDI.sendNoteOff(digital_note[g] + shiftChange, midi_vel, midi_ch);&quot;)</p><p>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.</p>
<p>Thank you so much for your help! It works great now! <br>I ended up leaving &quot;if(b!=shiftPin){ //ensure this is not the shift pin&quot; in because otherwise it wasn't sure if it was to send midi messages or just act as a shift. </p>
<p>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.</p>
<p>Thanks for getting back to me so quickly! I'm using Analog pins 2,3,4,5,6,7 &amp; digital pins 0,1,2,3,4,5,8,9,10</p>
<p>You're declaring the arrays and telling the Arduino what pins you are using in these 2 lines:<br><br>const int digital_pin[] = { 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13 };</p><p>const int analog_pin[] = { A2, A3, A4, A5, A6, A7 };<br><br>So you can see that the analog_pin array has 6 members:</p><div>analog_pin[0] is pin A2</div><div>analog_pin[1] is pin A3</div><div>analog_pin[2] is pin A4</div><div>analog_pin[3] is pin A5</div><p>analog_pin[4] is pin A6</p><p>analog_pin[5] is pin A7</p><p>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 &quot;b&quot; 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.<br><br></p><p>Truthfully, I should have used a const for the array counts such as &quot;const int ANALOG_COUNT&quot;, which in your case would be 6, and then the loops would be in the form of: &quot;for (b = 0; b &lt; ANALOG_COUNT; b++)&quot;. Notice that the qualifier to end the loop isn't &quot;&lt;=&quot; anymore, but just &quot;&lt;&quot;. 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!</p>
<p>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?</p><p>Thanks</p>
<p>Do you have any suggestions on one?</p>
<p>The Teensy DOES have 25 IO pins, though. it is actually the lowest IO count device that PJRC.com offers, so I would suggest going there first and looking for something you want.<br><br>If you need to more IO than the Teensy has to work with there are other ways to implement IO busses that use a few IO pins to control large IO fields. This way you can still have use of special pins. As an example, I have one design where I can control up to 256 outputs using 16 pins, also use the serial and USB ports, and still have unused pins too.</p>
wow... nice controller. what did you use to cut the enclosure?
Thanks! For the button holes I used a unibit (or stepped bit) and then a rotary file in a dremel to make the registration indentions. For the linear pots I drilled 2 tiny holes just outside the end of travel on each side and a cutoff wheel in a dremel to connect them, then a small flat file to empretty that.
In the parts list you link to a 4&quot; enclosure, but looking at it and the size of the components, is that the 7&quot; enclosure <a href="http://www.iprojectbox.com/sloped-keyboard-style-enclosure-p-50.html?manufacturers_id=30&osCsid=e40137380d90e161015317f25734578d" rel="nofollow">here</a>?
Yup! You are quite correct! I just went back and checked the invoice from iProjectBox and it is for the KB7-7<br><br>Thanks for pointing this out! I'll correct the text!
What was the total cost of this? Its super awesome!! Btw, happy birthday to Skrillex :-)
I'll have to estimate a bit, but somewhere between $150 to $200.
This really is awesome! Definitely going to have a shot at something similar...cheers for the guide!
Congratulations on being a finalist in the DIY Audio Contest!! Good luck to you!
does it work with ableton live 8?
I would venture to say yes, though I've never used Ableton Live. If you can specify the MIDI note numbers in the software then there absolutely should be no reason that it wouldn't. I know it worked well in Traktor (so my son said) with the exception noted in the Instructable about the scale of the sliders.
you should definitely market these, and maybe try making some different models. I showed this to my friend (also a DJ) and he said he would pay upwards of $250 for a controller like this if it had some knobs, and maybe a few piano keys.
I'd originally thought about an analog joystick. I don't recall now why I didn't end up using one. Those are encouraging words! I was talking to a guy I work with about it today and he was encouraging me to make a few to sell as well. I'm just not sure that they'd fly off the shelves though. Without investing quite a bit in some tools and hardware it'd cost quite a bit to make one-offs. I think I put about $150 or so in this one.
i would definitely encourage pursuing every option. vintage arcade sounds and songs are becoming increasingly prevalent in EDM (electric dance music) and being able to mix them from a vintage arcade controller would be awesome! they may not fly off the shelves here, but EDM and vintage video games are the life nectar of europe, along with muscle cars, if you were to sell them online you would be sending a lot of packages to germany.
also, a 360 degree joystick would be great for warping and resonance etc.
It has a really professional finish, and has a nice retro feel about it (I guess that was what you were trying to achieve :P) I hope you do well in the contests :)
Thanks much!
Looks neat. What does it do?
Thanks! It's a DJ controller. Basically it sends MIDI events over serial. DJ's use them to do things like trigger &amp; manipulate samples &amp; loops in real time.
Is there any video demonstration of this project? I really like the concept but I'd like to see it in action.
Unfortunately no. I built this about 1.5 to 2 years ago for my son's 18th birthday. Since then his apartment got broken into and this was one of the things stolen.
I have a neat pack of Instructables to talk about, today on my Blog, and your's one of them! :) <br>http://faz-voce-mesmo.blogspot.pt/2012/11/instructables-traceparts-e-noticias.html
That's awesome! Thanks!
Well, it's my pleasure to divulge this stuff, too! :)
Don't forget to vote in the top right!
They keep changing the look of this, but I've found it, now - Done!
Thanks so much!!
This is a great instructable. Thanks a lot! <br>While I'm not into controllerism or DJing myself, I'm an enthusiast of arcade gaming and it always excites me to see stuff done with arcade parts. The usefulness of this instructable, for me, is more because of the use of the Teensy. I just ordered one to use as a &quot;fix&quot; to a faulty joystick I bought, so I can get some tips from yours. <br>And the iProjectBox site seems really useful. Thanks for it! <br>As soon as it arrives and I put it to work, I'll finish the instructable I'm doing about it.
Thanks for the kind words! I know there are a couple of other development boards out there with similar features to the Teensy and I haven't used them, but I just can't overstate how much I like the Teensies! I even just used one at work as logic glue for an obsolete 84 zone heat controller.
Awesome! I usually play with Arduinos, but this time I chose to buy a Teensy due to its size. The joystick I'm gonna modify is a Competition Pro USB, that got not much space left inside (its original PCB is tiny), so the Teensy footprint is perfect for this project. And I know it can be recognized natively as a joystick device, so I'm going for it.

About This Instructable


265 favorites


Bio: The pre-neopostmodern electro-Amish man using sense and caution when voiding any warranty.
More by RedBinary: Zeta Reticuli: MIDI controlled 10-band EQ and multiple external effect interface PACMOD MIDI DJ Controller Cheap and simple USB car fast-charger mod
Add instructable to: