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; } }
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:
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