Control Change

Instead of playing musical notes, you'll be sending more generic MIDI commands from the Circuit Playground and then configuring your DJ software to use them. The MIDI Control Change command can be used to send numbered controls and their values. 

For example, usbMIDI.sendControlChange(12, 127, 1) will send a MIDI command number 12 with a value of 127 over channel 1. You can configure your DJ software to listen for command number 12 on channel 1 and when the value sent is 127, it can raise the volume fader on deck B to full volume. You can stream a series of values from 0-to-127 over that command number 12 and depending on the capacative pad reading on your Circuit Playground that is triggering that MIDI command, you and raise and lower the volume.

PZ-1 Code

Here is the Circuit Playground PZ-1 Pizza Box DJ code. Copy it and paste it into a new Arduino sketch.

Download: file
//Circuit Playground PZ-1 Pizza Box DJ
// by John Park
// a.k.a. DJ Sternocleidomastoid
// for Adafruit Industries
// MIT License
///////////////////////////////////////////////////////////////////////////////
// MIDI controller for Traktor, Mixxx and other DJ software
// Uses Circuit Playground (Teensy Core) to send MIDI over
// USB
// 
// MIDI controller command mapping must be set up in your DJ software, check
// below for MIDI signal outputs
///////////////////////////////////////////////////////////////////////////////

#include <Adafruit_CircuitPlayground.h>
#include <Wire.h>
#include <SPI.h>

///////////////////////////////////////////////////////////////////////////////
//MIDI CC (change control) assignments
const int CHANNEL         = 14; //MIDI channel 
const int LEFT_BUTTON_CC  =  4; //Play/Pause Deck A
const int RIGHT_BUTTON_CC = 19; //MIDI CC for left button: FX 1 On
const int CAP1_CONTROL    = 1;  //Select/Set+Store Hotcue 2 (1 is load point)
const int CAP0_CONTROL    = 0;  //Left fader mixes Deck B volume down
const int CAP2_CONTROL    = 2;  //Delete hotcue 2 point       
const int CAP3_CONTROL    = 3;  //Remix Trigger 6-1 or Loop Size /2  
const int CAP12_CONTROL   = 12; //Right fader mixes Deck A volume down
const int CAP6_CONTROL    = 6;  //Scratch (jog turn) Deck B     
const int CAP9_CONTROL    = 9;  //Play/Pause Deck B
const int CAP10_CONTROL   = 10; //Remix Trigger 6-4 or Loop Set 4 bar

int leftButtonState = 0;
int rightButtonState = 0;
int leftButtonLastState = 0;
int rightButtonLastState = 0;
int cap0State = 0;
int cap0LastState = 0;
int cap1State = 0;
int cap1LastState = 0;
int cap2State = 0;
int cap2LastState = 0;
int cap3State = 0;
int cap3LastState = 0;
int cap12State = 0;
int cap12LastState = 0;
int cap6State = 0;
int cap6LastState = 0;
int cap9State = 0;
int cap9LastState = 0;
int cap10State = 0;
int cap10LastState = 0;
bool cap1ON = false; //prevents off command from being spewed
bool cap12ON = false; //prevents off command from being spewed
bool cap6ON = false; //prevents off command from being spewed

const int CAPMIN = 25; //lowest value that's considered an intentional touch to minimize crosstalk
const int CAPSLOP = 0; //value of capacitive pad variance that's considered noise


///////////////////////////////////////////////////////////////////////////////
//mic_meter code
// To keep the display 'lively,' an upper and lower range of volume
// levels are dynamically adjusted based on recent audio history, and
// the graph is fit into this range.
#define  FRAMES 8
uint16_t lvl[FRAMES],           // Audio level for the prior #FRAMES frames
         avgLo  = 6,            // Audio volume lower end of range
         avgHi  = 512,          // Audio volume upper end of range
         sum    = 256 * FRAMES; // Sum of lvl[] array
uint8_t  lvlIdx = 0;            // Counter into lvl[] array
int16_t  peak   = 0;            // Falling dot shows recent max
int8_t   peakV  = 0;            // Velocity of peak dot

///////////////////////////////////////////////////////////////////////////////
void setup() {
  CircuitPlayground.begin();
  CircuitPlayground.setBrightness(30); //make brighter for performance, up to 255
  CircuitPlayground.clearPixels();
  for(uint8_t i=0; i<FRAMES; i++) lvl[i] = 256;

}

///////////////////////////////////////////////////////////////////////////////
// COLOR TABLES for animation ----------------------------------------------

const uint8_t PROGMEM
  reds[]   = { 0x9A, 0x75, 0x00, 0x00, 0x00, 0x65, 0x84, 0x9A, 0xAD, 0xAD },
  greens[] = { 0x00, 0x00, 0x00, 0x87, 0xB1, 0x9E, 0x87, 0x66, 0x00, 0x00 },
  blues[]  = { 0x95, 0xD5, 0xFF, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
  gamma8[] = { // Gamma correction improves the appearance of midrange colors
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06,
    0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09,
    0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0E,
    0x0E, 0x0F, 0x0F, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14,
    0x14, 0x15, 0x15, 0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1B,
    0x1B, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F, 0x1F, 0x20, 0x21, 0x22, 0x22, 0x23,
    0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2A, 0x2B, 0x2C, 0x2D,
    0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
    0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x44, 0x45, 0x46,
    0x47, 0x48, 0x49, 0x4B, 0x4C, 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x54, 0x55,
    0x56, 0x58, 0x59, 0x5A, 0x5C, 0x5D, 0x5E, 0x60, 0x61, 0x63, 0x64, 0x66,
    0x67, 0x69, 0x6A, 0x6C, 0x6D, 0x6F, 0x70, 0x72, 0x73, 0x75, 0x77, 0x78,
    0x7A, 0x7C, 0x7D, 0x7F, 0x81, 0x82, 0x84, 0x86, 0x88, 0x89, 0x8B, 0x8D,
    0x8F, 0x91, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA4,
    0xA6, 0xA8, 0xAA, 0xAC, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC,
    0xBF, 0xC1, 0xC3, 0xC5, 0xC7, 0xCA, 0xCC, 0xCE, 0xD1, 0xD3, 0xD5, 0xD7,
    0xDA, 0xDC, 0xDF, 0xE1, 0xE3, 0xE6, 0xE8, 0xEB, 0xED, 0xF0, 0xF2, 0xF5,
    0xF7, 0xFA, 0xFC, 0xFF };
///////////////////////////////////////////////////////////////////////////////


///////////////////////////////////////////////////////////////////////////////
void loop() {

  //Sound activated lights
  uint8_t  i, r, g, b;
    uint16_t minLvl, maxLvl, a, scaled;

    a           = CircuitPlayground.mic.peak(10); // 10 ms of audio
    sum        -= lvl[lvlIdx];
    lvl[lvlIdx] = a;
    sum        += a;                              // Sum of lvl[] array
    minLvl = maxLvl = lvl[0];                     // Calc min, max of lvl[]...
    for(i=1; i<FRAMES; i++) {
      if(lvl[i] < minLvl)      minLvl = lvl[i];
      else if(lvl[i] > maxLvl) maxLvl = lvl[i];
    }

    // Keep some minimum distance between min & max levels,
    // else the display gets "jumpy."
    if((maxLvl - minLvl) < 40) {
      maxLvl = (minLvl < (512-40)) ? minLvl + 40 : 512;
    }
    avgLo = (avgLo * 7 + minLvl + 2) / 8; // Dampen min/max levels
    avgHi = (maxLvl >= avgHi) ?           // (fake rolling averages)
      (avgHi *  3 + maxLvl + 1) /  4 :    // Fast rise
      (avgHi * 31 + maxLvl + 8) / 32;     // Slow decay

    a = sum / FRAMES; // Average of lvl[] array
    if(a <= avgLo) {  // Below min?
      scaled = 0;     // Bargraph = zero
    } else {          // Else scale to fixed-point coordspace 0-2560
      scaled = 2560L * (a - avgLo) / (avgHi - avgLo);
      if(scaled > 2560) scaled = 2560;
    }
    if(scaled >= peak) {            // New peak
      peakV = (scaled - peak) / 4;  // Give it an upward nudge
      peak  = scaled;
    }

    uint8_t  whole  = scaled / 256,    // Full-brightness pixels (0-10)
             frac   = scaled & 255;    // Brightness of fractional pixel
    int      whole2 = peak / 256,      // Index of peak pixel
             frac2  = peak & 255;      // Between-pixels position of peak
    uint16_t a1, a2;                   // Scaling factors for color blending

    for(i=0; i<10; i++) {              // For each NeoPixel...
      if(i <= whole) {                 // In currently-lit area?
        r = pgm_read_byte(&reds[i]),   // Look up pixel color
        g = pgm_read_byte(&greens[i]),
        b = pgm_read_byte(&blues[i]);
        if(i == whole) {               // Fraction pixel at top of range?
          a1 = (uint16_t)frac + 1;     // Fade toward black
          r  = (r * a1) >> 8;
          g  = (g * a1) >> 8;
          b  = (b * a1) >> 8;
        }
      } else {
        r = g = b = 0;                 // In unlit area
      }
      // Composite the peak pixel atop whatever else is happening...
      if(i == whole2) {                // Peak pixel?
        a1 = 256 - frac2;              // Existing pixel blend factor 1-256
        a2 = frac2 + 1;                // Peak pixel blend factor 1-256
        r  = ((r * a1) + (0x84 * a2)) >> 8; // Will
        g  = ((g * a1) + (0x87 * a2)) >> 8; // it
        b  = ((b * a1) + (0xC3 * a2)) >> 8; // blend?
      } else if(i == (whole2-1)) {     // Just below peak pixel
        a1 = frac2 + 1;                // Opposite blend ratios to above,
        a2 = 256 - frac2;              // but same idea
        r  = ((r * a1) + (0x84 * a2)) >> 8;
        g  = ((g * a1) + (0x87 * a2)) >> 8;
        b  = ((b * a1) + (0xC3 * a2)) >> 8;
      }
      CircuitPlayground.strip.setPixelColor(i,
        pgm_read_byte(&gamma8[r]),
        pgm_read_byte(&gamma8[g]),
        pgm_read_byte(&gamma8[b]));
    }
    CircuitPlayground.strip.show();

    peak += peakV;
    if(peak <= 0) {
      peak  = 0;
      peakV = 0;
    } else if(peakV >= -126) {
      peakV -= 2;
    }

    if(++lvlIdx >= FRAMES) lvlIdx = 0;

///////////////////////////////////////////////////////////////////////////////
//BUTTONS
//
//read the buttons
  leftButtonState =  (CircuitPlayground.leftButton());  
  //not pressed = 0, pressed = 1
  rightButtonState = (CircuitPlayground.rightButton());
  
//Left Button
//compare current button states to previous button states
  if(leftButtonState != leftButtonLastState){           //the state has changed
    if(leftButtonState == 1){                           //went from off to on, it's pressed
      usbMIDI.sendControlChange(LEFT_BUTTON_CC, 1, CHANNEL); //send MIDI note ON command
      CircuitPlayground.redLED(HIGH); //turn on the red LED
    }
    else{                   //the button went from on to off, it isn't pressed
      usbMIDI.sendControlChange(LEFT_BUTTON_CC, 0, CHANNEL);  //send MIDI note OFF 
      CircuitPlayground.redLED(LOW);                    //turn off the red LED
    }
    leftButtonLastState = leftButtonState;       //toggle
  }


//Right Button
  if(rightButtonState != rightButtonLastState){           
    if(rightButtonState == 1){                           
      usbMIDI.sendControlChange(RIGHT_BUTTON_CC, 1, CHANNEL); 
      CircuitPlayground.redLED(HIGH); 
    }
    else{                                              
      usbMIDI.sendControlChange(RIGHT_BUTTON_CC, 0, CHANNEL);  
      CircuitPlayground.redLED(LOW);                    
    }
    rightButtonLastState = rightButtonState;              
  }
///////////////////////////////////////////////////////////////////////////////
//Cap sense pads
//
//read the capacative pads
  int cap1  = CircuitPlayground.readCap(1);
  int cap0  = CircuitPlayground.readCap(0);
  int cap2  = CircuitPlayground.readCap(2);
  int cap3  = CircuitPlayground.readCap(3);
  int cap12 = CircuitPlayground.readCap(12);
  int cap6  = CircuitPlayground.readCap(6);
  int cap9  = CircuitPlayground.readCap(9);
  int cap10 = CircuitPlayground.readCap(10);



  //cap1 Crossfader Deck B
  
  if(cap1 > CAPMIN){
       cap1State=1;
    }
    else{
       cap1State=0;
    }
    if(cap1State != cap1LastState){
      if(cap1State==1){
        usbMIDI.sendControlChange(CAP1_CONTROL, 127, CHANNEL);
        CircuitPlayground.redLED(HIGH); 
      }
      else {
        usbMIDI.sendControlChange(CAP1_CONTROL, 0, CHANNEL);
        CircuitPlayground.redLED(LOW);                    
      }
      cap1LastState = cap1State;
    }
    
  //cap0 
    if(cap0 > CAPMIN){
       cap0State=1;
    }
    else{
       cap0State=0;
    }
    if(cap0State != cap0LastState){
      if(cap0State==1){
        usbMIDI.sendControlChange(CAP0_CONTROL, 1, CHANNEL);
        CircuitPlayground.redLED(HIGH); 
      }
      else {
        usbMIDI.sendControlChange(CAP0_CONTROL, 0, CHANNEL);
        CircuitPlayground.redLED(LOW);                    
      }
      cap0LastState = cap0State;
    }

//cap2
      if(cap2 > CAPMIN){
         cap2State=1;
      }
      else{
         cap2State=0;
      }
      if(cap2State != cap2LastState){
        if(cap2State==1){
          usbMIDI.sendControlChange(CAP2_CONTROL, 1, CHANNEL);
          CircuitPlayground.redLED(HIGH); 
        }
        else {
          usbMIDI.sendControlChange(CAP2_CONTROL, 0, CHANNEL);
          CircuitPlayground.redLED(LOW);                    
        }
      cap2LastState = cap2State;
      }
   
//cap3
      if(cap3 > CAPMIN){
         cap3State=1;
      }
      else{
         cap3State=0;
      }
      if(cap3State != cap3LastState){
        if(cap3State==1){
          usbMIDI.sendControlChange(CAP3_CONTROL, 1, CHANNEL);
          CircuitPlayground.redLED(HIGH); 
        }
        else {
          usbMIDI.sendControlChange(CAP3_CONTROL, 0, CHANNEL);
          CircuitPlayground.redLED(LOW);                    
        }
      cap3LastState = cap3State;
      }
    
//cap12 Crossfader Deck B

  if(cap12 > CAPMIN){
       cap12State=1;
    }
    else{
       cap12State=0;
    }
    if(cap12State != cap12LastState){
      if(cap12State==1){
        usbMIDI.sendControlChange(CAP12_CONTROL, 127, CHANNEL);
        CircuitPlayground.redLED(HIGH); 
      }
      else {
        usbMIDI.sendControlChange(CAP12_CONTROL, 0, CHANNEL);
        CircuitPlayground.redLED(LOW);                    
      }
      cap12LastState = cap12State;
    }
    
//cap6
  if (cap6>CAPMIN){
      int cap6NewVal = map(CircuitPlayground.readCap(6),0,200,0,127);
      if (abs(cap6 - cap6NewVal>CAPSLOP)) {
          cap6 = cap6NewVal;
          usbMIDI.sendControlChange(CAP6_CONTROL, cap6, CHANNEL);
          CircuitPlayground.redLED(HIGH); 
          cap6ON = true;
      }
      else{
        if (cap6ON==true){
           usbMIDI.sendControlChange(CAP6_CONTROL, 0, CHANNEL); //send a 0
          CircuitPlayground.redLED(LOW); 
           cap6ON=false;
        }
      }
    }
//cap9
      if(cap9 > CAPMIN){
         cap9State=1;
      }
      else{
         cap9State=0;
      }
      if(cap9State != cap9LastState){
        if(cap9State==1){
          usbMIDI.sendControlChange(CAP9_CONTROL, 1, CHANNEL);
          CircuitPlayground.redLED(HIGH); 
        }
        else {
          usbMIDI.sendControlChange(CAP9_CONTROL, 0, CHANNEL);
          CircuitPlayground.redLED(LOW);                    
        }
      cap9LastState = cap9State;
      }
    
//cap10
      if(cap10 > CAPMIN){
         cap10State=1;
      }
      else{
         cap10State=0;
      }
      if(cap10State != cap10LastState){
        if(cap10State==1){
          usbMIDI.sendControlChange(CAP10_CONTROL, 1, CHANNEL);
          CircuitPlayground.redLED(HIGH); 
        }
        else {
          usbMIDI.sendControlChange(CAP10_CONTROL, 0, CHANNEL);
          CircuitPlayground.redLED(LOW);                    
        }
      cap10LastState = cap10State;
      }
    

  // MIDI Controllers should discard incoming MIDI messages.
    while (usbMIDI.read()) {
  }
}

You can download the code with the button below. Once downloaded, unzip it and place the directory in your Arduino sketches directory.

Customize the Code

You can see in the top of the sketch there are variables for the different MIDI control change commands. These numbers can be changed, but they correspond to the Circuit Playground pins for convenience. These are the MIDI control change numbers you'll need to know when mapping the PZ-1 to your DJ software. The comments show how I'm mapping them in Traktor.

The sketch also contains the Circuit Playground example sketch mic_meter to handle the sweet sound-reactive NeoPixel lighting effects.

Map It

Once you've uploaded the code, verify it is sending the commands by opening your MIDI monitoring software. When you choose the Circuit Playground as MIDI input and press the buttons and pads, you'll see the number, value, and channel data streaming in the utility.

Launch your DJ software and head to the MIDI controller configuration area (usually in the preferences). Here you can refer to the help for particular software you're using to assign each MIDI control change signal from the PZ-1 to the desired software command.

Here is a sample mapping that pairs the Circuit Playground PZ-1 with Traktor Pro 2 from Native Instruments.

This guide was first published on Sep 25, 2016. It was last updated on Sep 25, 2016.

This page (DJ Software MIDI Commands) was last updated on Oct 24, 2020.