Hi! Hope you're enjoying this blog. I have a new home at www.goldsborough.me. Be sure to also check by there for new posts <3

Monday, February 3, 2014

The Mini Midi

Finished, at last. I finally completed my project of building a mini midi controller, a Teensy based controller for interfacing with DJ/Music production software via MIDI signals. I had finished the electronics last December, and also the programming was not a big problem. What did prove difficult, though was building the case, as this project was DIY all the way. Anyway, here some pictures:



Once upon a time on a breadboard...


The secret to getting ahead is getting started - Mark Twain



The electronics: front


From the side. Note: I removed one row of LEDs in the end.


And there was light!


Screw buying buttons



The finished Mini MIDI






Ta.Da.

A video in which I describe the electronic components:





Dry/Wet control of the effects means controlling how much of a certain effect “reaches” the audio signal. Dry means no effect, Wet means the audio signal is affected 100% percent by the effect. 

A video in which I talk about the hardware I used (have a look at how I made the buttons):



A video in which I show the Mini Midi in action:



Note: The first song is not off, it's just not loud enough to really hear in the video. 

So, there it is. Some comments:

What electronic components did I use?

The Teensy++ as a micro controller. Why not an Arduino? The main reason is that Arduinos cannot act as native MIDI/HID devices, meaning that, without flashing the MIDI firmware onto the Arduino, Arduinos cannot send MIDI signals. Teensys, on the other hand, can do so very easily and have a nice built in library. There's no extra software required really so the programming can still happen over the Arduino IDE. 

8 Rotary potentiometers. Two for the Dry/Wet control of the effects and 6 for the high, mid and low sound levels of the two decks. 

1 Slide potentiometer, for crossfading between the two decks. 

1 Rotary encoder for the browser.

6 Buttons, 2 for Play/Pause, 2 for loading songs from the browser and 2 for turning on effects. 

All the potentiometers are controlled over a multiplexer. The two LED bars on each side are controlled by two shift registers.  

What hardware did I use?

I went the DIY path all the way, meaning, whereas most people would buy cases or get them CNCd online, I built the case from scratch. It was very, very hard and the result is not the best it could be, especially in comparison to something machine-made, but it does feel nice to have done it all alone.

The method of pressing the buttons via the plastic sheets works as follows:

The top line in this picture is the plastic sheet with, say, Play/Pause on it. It is glued to the ground by that vertical rod on the left, which is nothing else than a clipped-off pin of an LED. Additionally, underneath the plastic sheet you can see that big light-grey box which lies right on the pushbutton (the dark grey/black boxes) and which is glued to the plastic sheet. So now, if you press down on the plastic sheet, the big grey box also goes down and pushes the button. Because the plastic sheet is glued to the ground, though, the whole system will spring back into it's original position. Worked great. 

That being said, I'm not sure I'll never again build a case on my own again, it's just to inefficient. 

How long did it take me? 


What did I enjoy the most? Doing the electronics and programming it.
What did I enjoy the least? Building the case/hardware.

Lastly, I'll talk about my source code. Since releasing something under no license means releasing it copyrighted, it is released under the MIT license:
/*

The MIT License (MIT)

Copyright (c) <year> <copyright holders>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/


#include 
#include 

//SHIFT REGISTERS A

const int latchPin_a = 9; //Latch pin of first shift register
const int clockPin_a = 8; //Clock pin of first shift register
const int dataPin_a = 7; //Data pin of first shift register

//SHIFT REGISTERS B

const int latchPin_b = 13; //Latch pin of second shift register
const int clockPin_b = 12; //Clock pin of second shift register
const int dataPin_b = 11; //Data pin of second shift register

// SHIFT REGISTER BUTTON

const int shift_reg_button = 21; // Button to control whether the LED bars are on or off
boolean shift_reg_state = false;

//BUTTONS A

const int fx_a = 1; //fx button left side
const int load_a = 4; //load song on left side from browser
const int play_a = 5; //play left side

// LEDS A

const int fx_a_led = 27; //it's led
const int play_a_led = 0; //it's led

//BUTTONS B

const int fx_b = 24; //fx button right side
const int load_b = 23; //load song on right side from browser
const int play_b = 22; //play right side

// LEDS B

const int fx_b_led = 26; //it's led
const int play_b_led = 25; //it's led

//ENCODER

const int encoderPin1 = 18; //Pin one of the encoder
const int encoderPin2 = 19; //Pin two of the encoder

// volatile means it can change within a loop, special type for ISR (interupt service routine)
volatile int lastEncoded = 0; //last value
volatile long encoderValue = 0; //sum of values

long lastencoderValue = 0;
int last_val = 0;

int lastMSB = 0; //Most Significant Bit -> 100000, the first 1 is meant
int lastLSB = 0; //Least Significant Bit -> the right most 0 is meant

// MUX

const int mux_0 = 17; //s0
const int mux_1 = 16; //s1
const int mux_2 = 15; //s2

int s0 = 0; // value of s0
int s1 = 0; // value of s1
int s2 = 0; // value of s2

const int mux_pin = 7; // multiplexer analog output pin
 
int  mux_bin [] = {0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111}; //list of binary values

// OTHER POTENTIOMETERS

const int slide_pot = 6; //Slide pot analog pin

/* The below pot_vals are stored at one point in the loop and later on compared with a 
// second reading, if the values in that very vey short period of time has changed (basically)
// if a potentiometer has turned, a signal is sent and these values are reset to those, thus
// readings are a lot more efficient because signals are only sent when a change has actually
// occured, opposed to constant sending of signals! 
// Check out my blog at http://goo.gl/0yaVBo for more info on that. 
*/ 

int pot_vals[9] = {0}; //9th value is for the slide pot
int pot_notes[9] = {60,61,62,63,64,65,66,67,68};

int bs[6] = {fx_a,play_a,fx_b,play_b,load_a,load_b}; //all the buttons
int notes[6] = {50,51,52,53,54,55};

int leds[4] = {fx_a_led,play_a_led,fx_b_led,play_b_led};

// SHIFT REGISTER PATTERNS + VARIABLES FOR FUNCTION

byte stages [] = {
                    0b00000000,0b11111111,                                                                                 // on off
                    0b00000000,0b10000000,0b01000000,0b00100000,0b00010000,0b00001000,0b00000100,0b00000010,0b00000001,    // left to right 1 by 1
                    0b00000000,0b00000001,0b00000010,0b00000100,0b00001000,0b00010000,0b00100000,0b01000000,0b10000000,    // right to left 1 by 1
                    0b00000000,0b00011000,0b00100100,0b01000010,0b10000001,                                                // streaming out from middle
                    0b00000000,0b10000001,0b01000010,0b00100100,0b00011000,                                                // streaming in from outside
                    0b00000000,0b00011000,0b00111100,0b01111110,0b11111111,                                                // streaming out from middle, leaving previous ones on
                    0b00000000,0b11111111,0b01111110,0b00111100,0b00011000,                                                // streaming in from outside, leaving previous ones on
                    0b00000000,0b11000000,0b01100000,0b00110000,0b00011000,0b00001100,0b00000110,0b00000011,               // left to right 2 by 2
                    0b00000000,0b00000011,0b00000110,0b00001100,0b00011000,0b00110000,0b01100000,0b11000000,               // right to left 2 by 2
                    0b00000000,0b11110000,0b00001111,                                                                      // left to right 4 by 4
                    0b00000000,0b00001111,0b11110000,                                                                      // right to left 4 by 4
                    0b00000000,0b00011000,0b00100100,0b01011010,0b10100101,                                                // streaming out from middle, leaving previous ones blinking
                    0b00000000,0b10000001,0b01000010,0b10100101,0b01011010,                                                // streaming in from outside, leaving previous ones blinking
                    0b00000000,0b10000000,0b01000010,0b00100100,0b00010000,0b00000001,0b00000010,0b00000100,0b00001000,    // right to middle, left to middle
                  };

int patterns [14] = {2,9,9,5,5,5,5,8,8,3,3,5,5,9}; //how many stages each pattern has, I keep the number in the brackets just to know how many are in there
                      
int intervs [7] = {10,25,50,75,100,150,250};
int reps [5] = {2,5,10,15,20};

int r_p; //random pattern
int r_i; //random interval
int r_r; //random repitition number

int index; //index from which the pattern starts
int interv; //it's interval
int rep; //how many times do you want the pattern´?
int num_stages; //how many stages it is, so we know how much to subtract if repitions are left over, if you don't care for clarity, this is basically tot_ind - index

int tot_ind; //the last index of the pattern

// Debouncing

long t; //for debouncing the buttons without delay or Bounce library
long last_t[9] = {millis()}; //initialize a list of 8 last_ts each at current millis() // 4 toggle buttons, 2 normal buttons, 1 shift_reg_button, 1 for pattern

// Channel 1:  Pushbuttons
// Channel 2: Potentiometers

// SET TRUE IF YOU WANT SERIAL OUTPUT

boolean SER = true;

// ----------------------- SETUP ------------------------------- \\

void setup() {
  
  // Shift Registers
  
  pinMode(latchPin_a, OUTPUT);
  pinMode(dataPin_a, OUTPUT);  
  pinMode(clockPin_a, OUTPUT);
  
  pinMode(latchPin_b, OUTPUT);
  pinMode(dataPin_b, OUTPUT);  
  pinMode(clockPin_b, OUTPUT);
  
  pinMode(shift_reg_button,INPUT);
    
  //Encoder
  
  pinMode(encoderPin1, INPUT); 
  pinMode(encoderPin2, INPUT);

  digitalWrite(encoderPin1, HIGH); //turn pullup resistor on
  digitalWrite(encoderPin2, HIGH); //turn pullup resistor on

  //call updateEncoder() when any high/low changed seen
  //on interrupt 0 (pin 2), or interrupt 1 (pin 3) 
  attachInterrupt(6, updateEncoder, CHANGE); 
  attachInterrupt(7, updateEncoder, CHANGE);
  
  //Mux
  
  pinMode(mux_0, OUTPUT);    // s0
  pinMode(mux_1, OUTPUT);    // s1
  pinMode(mux_2, OUTPUT);    // s2
  
  //Buttons
  
  for (int i = 0; i < 7; i++) pinMode(bs[i],INPUT); //deck buttons

  //Leds
  
  for (int i = 0; i < 4; i++) pinMode(leds[i],OUTPUT); 
  
  if (SER) Serial.begin(57600); //set serial if wanted
  
  randomSeed(analogRead(0)); //seed random
  
  shift(0b00000000);
  get_patt();
  
}

void loop() {
  
  t = millis(); // get elapsed time
  
  check_pots();
  check_buttons();
  
  if (shift_reg_state) shift_reg();
   
}

void check_buttons() //check buttons for HIGH signal
{
  for (int i = 0; i < 6; i++) //for every fx and play button
  {
    if (digitalRead(bs[i]) == HIGH && t >= last_t[i] + 250) 
    {
      usbMIDI.sendNoteOn(notes[i],100,1);
      
      if (i < 4)
      {
        if (digitalRead(leds[i]) == HIGH) digitalWrite(leds[i],LOW);
        else digitalWrite(leds[i],HIGH);
      }
      
      last_t[i] = t;

    }
    
    
  }
 
 if (digitalRead(shift_reg_button) && t >= last_t[6] + 200) //if shift_reg_button is HIGH
 {
   
   if (shift_reg_state == false) shift_reg_state = true; //if previous state is false, switch to false, this then triggers the if clause in the led_bars() function
   else 
   {
     shift_reg_state = false; //if previous state was on, so true, switch to false
     shift(0b00000000); //make all shift_reg pins go low here because I don't want this to happen everytime during the loop
   }

   last_t[6] = t;   
 }  

}

void shift(byte b)
{
  digitalWrite(latchPin_a, LOW);
  shiftOut(dataPin_a,clockPin_a,MSBFIRST,b);
  digitalWrite(latchPin_a, HIGH);
  
  digitalWrite(latchPin_b, LOW);
  shiftOut(dataPin_b,clockPin_b,MSBFIRST,b);
  digitalWrite(latchPin_b, HIGH);
}

void get_patt()
{
  r_p = random() % 14; //pattern
  r_i = random() % 7;  //interval
  r_r = random() % 5;  //repetitions
  
  interv = intervs[r_i];
  rep = reps[r_r];
  num_stages = patterns [r_p];
  
  tot_ind = 0;
  index = 0;
 
  for (int i = 0; i < r_p; i++)  tot_ind += patterns[i];//add all indexes until the last index of the pattern before
  
  index = tot_ind; //since tot_ind stores the index + 1, this is exactly the first index of the pattern
  
  tot_ind += num_stages; //then add the total stages of this pattern to get the last pattern, now we have a beginning and an end
  
  if (SER)
  {
    Serial.print("NEW: ");
    Serial.println(r_p);
    Serial.print("Interval: ");
    Serial.println(interv);
    Serial.print("Repetitions: ");
    Serial.println(rep);
    Serial.print("Number of stages: ");
    Serial.println(num_stages);
    Serial.print("First index: ");
    Serial.println(index);
    Serial.print("Last index: ");
    Serial.println(tot_ind-1);
  } 
  
}

void shift_reg()
{ 
  shift(stages[index]);
  
  if (t >= (last_t[7] + interv))
  {
     Serial.println("pushed");
     
  digitalWrite(latchPin_a, LOW);
  shiftOut(dataPin_a,clockPin_a,MSBFIRST,stages[index]);
  digitalWrite(latchPin_a, HIGH);
  
  digitalWrite(latchPin_b, LOW);
  shiftOut(dataPin_b,clockPin_b,MSBFIRST,stages[index]);
  digitalWrite(latchPin_b, HIGH);
  
  if (index == tot_ind-1) //if the pattern is over
  {
    if (rep == 0) get_patt(); //if no more repetitions, get new pattern
    
    else//if repetitions left, go back to first stage
    {
      index -= num_stages; 
      rep--;
    }
    
  }
  
  index++;
  
   last_t[7] = t; //next time the last millis() is the current millis()
  }
  
}


void updateEncoder(){
  int MSB = digitalRead(encoderPin1); //MSB = most significant bit
  int LSB = digitalRead(encoderPin2); //LSB = least significant bit

  int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
  int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value

  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --;
  
  
  if(t >= (last_t[8] + 20))
  {
    Serial.print("encoder: ");
    Serial.println(encoderValue/4);
    
    if (encoderValue > last_val) usbMIDI.sendControlChange(75,70,1);
    
    else usbMIDI.sendControlChange(75,60,1);
    
    last_val = encoderValue;
    
    last_t[8] = t;
  }
  

  lastEncoded = encoded; //store this value for next time
}

void check_pots()
{
  int val; //analogRead value
  
  //MULTIPLEXER
  
  for (int i = 0; i < 8; i++) { //loop through each channel, checking for a signal

   int row = mux_bin[i]; //channel 5 = 5th element in the bin list -> 101 etc. 
    
   s0 = bitRead(row,0); //bitRead() -> parameter 1 = binary sequence, parameter 2 = which bit to read, starting from the right most bit
   s1 = bitRead(row,1); //channel 7 = 111, 1 = 2nd bit (starting at 0, 0 = 1st element)
   s2 = bitRead(row,2); // third bit
 
   digitalWrite(mux_0, s0); // send the bits to the digital inputs 
   digitalWrite(mux_1, s1);
   digitalWrite(mux_2, s2);
   
   val = analogRead(mux_pin)/8;
   
   if (i == 0 || i == 7) val = 127 - val;
   
   if (val != pot_vals[i] && val != pot_vals[i] + 1 && val != pot_vals[i] -1)
  {
       Serial.print("Channel: ");
       Serial.print(i+1);
       Serial.print(" ==== ");
       Serial.println(val); // after sending the binary sequence, the mux determines which channel to read from and sends it to this analog input
       
       usbMIDI.sendControlChange(pot_notes[i],val,1);
       
       pot_vals[i] = val;
  } 
}
  
  //SLIDE POT
  
  val = analogRead(slide_pot)/8;
  
  if (val != pot_vals[8])
  {
       Serial.print("Slide pot ");
       Serial.print(" ==== ");
       Serial.println(val); // after sending the binary sequence, the mux determines which channel to read from and sends it to this analog input
       
       usbMIDI.sendControlChange(pot_notes[8],val,1);
       
       pot_vals[8] = val;
  } 
  
}

Lots of this code are just functions for handling the checking of the potentiometers/buttons and sending the MIDI signals. I wrote everything myself except for the encoder function, which, despite me knowing how to write the code for an encoder, is just a really nice and efficient way that I wanted to use. All credit goes to the author. 

Something I tinkered with for a little longer was how I would devise a system for displaying many patterns on my LED bars. The led bars are controlled via shift registers, microchips that pulse a bit through 8 channels, making the current for that individual LED either go high or low, on or off. This happens so fast, though, that you can even make it look like all LEDS were on at the same time. 

Here a video that shows some of the patterns:





The function that sends a byte (one bit for each of the 8 channels) to the shift register is called shiftOut(), which takes, besides the pin numbers of the different pins of the shift register, the byte that you want to send to it as a parameter. That’s why there’s that huge array of bytes up there. Each pattern has a certain amount of bytes. E.g. turning the LEDs all off and on would be:

0b00000000, 0b11111111

0b is the prefix for a binary number. As you can see, when the shiftOut function sends the first byte to the shift register, it will go through each channel and set it to the corresponding value of the byte, in this case 0, or LOW, for very LED. Then, it will send out the next byte, with all 1s, so all HIGH, and turn each LED on. 

Instead of using a multidimensional array for the patterns, which is impossible since not all patterns have the same amount of bytes/states, I store the indexes in a separate array. 

There are to more variables that are relevant, for one the interval between each individual state of a pattern, which basically determines how fast an entire pattern goes by. Also, the number of repetitions of each pattern. It is noteworthy to say at this point that the entire program does not use the delay() function that stops the whole processor, as this could interrupt a MIDI signal. Everything is done via interval debouncing and the millis() function, meaning the code rather checks whether a certain period of time has passed. 

The array index, the interval and the repetitions variable are all generated randomly, so the following situation is an example of how the whole code works for one pattern:

The index i is randomly generated. At position i, the pattern:

0b00000000, 0b11000000, 0b00110000, 0b00001111, 0b00000011

is found. This would show two leds “streaming” through the bar. 

Next, the interval interv and the repetitions rep are randomly generated. 

Whenever interv has passed, the index is incremented by one, so the next state of the pattern is shown. If the index has reached the maximum index for this pattern, stored in the second array I mentioned above, it will check whether rep > 0. If so, the index is decreased by the maximum index. Thus, the whole pattern is “reset” and the pattern will repeat again. This will continue so long until rep = 0. If rep = 0, the index is increased to the start of the next  pattern. 



This was the only non-standard part in that code. Feel free to use/modify it and comment if you have any questions/issues regarding the code or anything else. 

No comments :

Post a Comment