Final Project – Cody Colgren, Ivan Martinez

Pictures:

Videos:

 

Parts:

  • Adafruit Mpr121 12-key capacitive touch sensor breakout ~ 8$
    • https://www.adafruit.com/product/1982?gclid=Cj0KCQiAoJrfBRC0ARIsANqkS_6RuPNdRHs-B-2Smzg_qLApG69EhU9lueRRtaQl0zpRjjnzIk3-ktQaAjuoEALw_wc
  • Arduino UNO board ~6$
    • https://www.ebay.com/i/263810558921?chn=ps
  • Adafruit Protoboard ~5$
    • https://www.adafruit.com/product/1609?gclid=Cj0KCQiArqPgBRCRARIsAPwlHoU-PLaLyAv5IVq7pmErJvoLIOK5ihGXWtPOPIkdsv2ktI8Q3PUIlHYaAtsLEALw_wcB
  • Copper Tape ~$3
    • https://www.target.com/p/scotch-r-expressions-washi-tape-59-in-x-275-in-copper-foil/-/A-17088871?ref=tgt_adv_XS000000&AFID=google_pla_df&fndsrc=tgtao&CPNG=PLA_Seasonal%2BShopping_Local&adgroup=SC_Seasonal&LID=700000001170770pgs&network=g&device=c&location=9021717&gclsrc=aw.ds&ds_rl=1246978&ds_rl=1246978&ds_rl=1246978&ref=tgt_adv_XS000000&AFID=google_pla_df&CPNG=PLA_Seasonal+Shopping_Local&adgroup=SC_Seasonal&LID=700000001170770pgs&network=g&device=c&location=9021717&gclid=Cj0KCQiAw5_fBRCSARIsAGodhk8HahIcbg4V-
  • Guitar Hero Guitar ~ $20
    • https://www.unbeatablesale.com/dcgrl21360.html?gclid=Cj0KCQiAw5_fBRCSARIsAGodhk9PY8IEOBZROcuY6T5zPqCAJNLLdz12G5FKj9rODRG7ZJ-IlbYx-hwaAvoHEALw_wcB

Code:

#include <Wire.h>
#include <Adafruit_MPR121.h>
//#include <pitches.h>
//#include <LED_animations.h>
#include <CurieBLE.h>

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

/* BE SURE TO SET THE NUMBER OF NEOPIXELS
++++++++++++++++++++++++++++++++++++++++++++++++++ */
#define NUMPIXELS_STRIP 10 // Number of LEDs in neopixel strip
#define NUMPIXELS_RING 12
#define DATAPIN 6

#define BUZZER_PIN A0 // The output pin for the piezo buzzer
#define NUM_OF_SAMPLES 5 // Higher number whens more delay but more consistent readings

// You can have up to 4 on one i2c bus but one is enough for testing!
Adafruit_MPR121 cap = Adafruit_MPR121();

// Keeps track of the last pins touched
// so we know when buttons are ‘released’
uint16_t lasttouched = 0;
uint16_t currtouched = 0;
//int note;
//unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
//unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
uint16_t showType;
uint8_t i;

// Pattern types supported:
enum pattern { NONE, RAINBOW_CYCLE, RAINBOW, TWINKLERAND, SIMPLEWAVE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE };
// Pattern directions supported:
enum direction { FORWARD, REVERSE };

// NeoPattern Class – derived from the Adafruit_NeoPixel class
class NeoPatterns : public Adafruit_NeoPixel
{
public:

// Member Variables:
pattern ActivePattern; // which pattern is running
direction Direction; // direction to run the pattern

unsigned long Interval; // milliseconds between updates
unsigned long lastUpdate; // last update of position

uint32_t Color1, Color2; // What colors are in use
uint16_t TotalSteps; // total number of steps in the pattern
uint16_t Index; // current step within the pattern

void (*OnComplete)(); // Callback on completion of pattern

// Constructor – calls base-class constructor to initialize strip
NeoPatterns(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)())
: Adafruit_NeoPixel(pixels, pin, type)
{
OnComplete = callback;
}

// Update the pattern
void Update()
{
if ((millis() – lastUpdate) > Interval) // time to update
{
lastUpdate = millis();
switch (ActivePattern)
{
case RAINBOW_CYCLE:
RainbowCycleUpdate();
break;
case THEATER_CHASE:
TheaterChaseUpdate();
break;
case COLOR_WIPE:
ColorWipeUpdate();
break;
case SCANNER:
ScannerUpdate();
break;
case TWINKLERAND:
twinkleRandUpdate();
break;
// case SIMPLEWAVE:
// simpleWaveUpdate();
// break;
case FADE:
FadeUpdate();
break;
default:
break;
}
}
}

// Increment the Index and reset at the end
void Increment()
{
if (Direction == FORWARD)
{
Index++;
if (Index >= TotalSteps)
{
Index = 0;
if (OnComplete != NULL)
{
OnComplete(); // call the completion callback
}
}
}
else // Direction == REVERSE
{
–Index;
if (Index <= 0)
{
Index = TotalSteps – 1;
if (OnComplete != NULL)
{
OnComplete(); // call the comlpetion callback
}
}
}
}

// Reverse pattern direction
void Reverse()
{
if (Direction == FORWARD)
{
Direction = REVERSE;
Index = TotalSteps – 1;
}
else
{
Direction = FORWARD;
Index = 0;
}
}

// Initialize for a RainbowCycle
void RainbowCycle(uint8_t interval, direction dir = FORWARD)
{
ActivePattern = RAINBOW_CYCLE;
Interval = interval;
TotalSteps = 255;
Index = 0;
Direction = dir;
}

// Update the Rainbow Cycle Pattern
void RainbowCycleUpdate()
{
for (int i = 0; i < numPixels(); i++)
{
setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
}
show();
Increment();
}

// Initialize for a Theater Chase
void TheaterChase(uint32_t color1, uint32_t color2, uint8_t interval, direction dir = FORWARD)
{
ActivePattern = THEATER_CHASE;
Interval = interval;
TotalSteps = numPixels();
Color1 = color1;
Color2 = color2;
Index = 0;
Direction = dir;
}

// Update the Theater Chase Pattern
void TheaterChaseUpdate()
{
for (int i = 0; i < numPixels(); i++)
{
if ((i + Index) % 3 == 0)
{
setPixelColor(i, Color1);
}
else
{
setPixelColor(i, Color2);
}
}
show();
Increment();
}

// Initialize for a ColorWipe
void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD)
{
ActivePattern = COLOR_WIPE;
Interval = interval;
TotalSteps = numPixels();
Color1 = color;
Index = 0;
Direction = dir;
}

// Update the Color Wipe Pattern
void ColorWipeUpdate()
{
setPixelColor(Index, Color1);
show();
Increment();
}

// number, twinkle color, background color, delay
// twinkleRand(5,strip.Color(255,255,255),strip.Color(255, 0, 100),90);
// twinkle random number of pixels
void twinkleRand(int num, uint32_t color, uint32_t color1, uint8_t interval) {
ActivePattern = TWINKLERAND;
Interval = interval;
TotalSteps = numPixels();
Color1 = color;
Color2 = color1;
Index = 0;
}

// Update the Twinkle Random
void twinkleRandUpdate() {
int num;
//setPixelColor(Index, Color1);
// set background
ColorSet(Color(50, 50, 50)); // dimly lit
// for each num
for (int i = 0; i < num; i++) {
setPixelColor(random(numPixels()), Color1);
}
show();
Increment();
}

// // very simple wave: velocity, cycles,delay between frames
// // simpleWave(0.03,5,10);
// void simpleWave(uint32_t color, uint8_t interval, direction dir = FORWARD) {
// Interval = interval;
// Direction = dir;
// TotalSteps = numPixels();
// Color1 = color;
// Index = 0;
// }
//
// void simpleWaveUpdate() {
// float pos = 0.0;
// float rate;
// int cycles;
// // cycle through x times
// for (int x = 0; x < (numPixels()*cycles); x++) {
// pos = pos + rate;
// for (int i = 0; i < numPixels(); i++) {
// // sine wave, 3 offset waves make a rainbow!
// float level = sin(i + pos) * 127 + 128;
// // set color level
// setPixelColor(i, (int)level, 0, 0);
// }
// show();
// Increment();
// }
// }

// Initialize for a SCANNNER
void Scanner(uint32_t color1, uint8_t interval) {
ActivePattern = SCANNER;
Interval = interval;
TotalSteps = (numPixels() – 1) * 2;
Color1 = color1;
Index = 0;
}

// Update the Scanner Pattern
void ScannerUpdate() {
for (int i = 0; i < numPixels(); i++)
{
if (i == Index) // Scan Pixel to the right
{
setPixelColor(i, Color1);
}
else if (i == TotalSteps – Index) // Scan Pixel to the left
{
setPixelColor(i, Color1);
}
else // Fading tail
{
setPixelColor(i, DimColor(getPixelColor(i)));
}
}
show();
Increment();
}

// Initialize for a Fade
void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD)
{
ActivePattern = FADE;
Interval = interval;
TotalSteps = steps;
Color1 = color1;
Color2 = color2;
Index = 0;
Direction = dir;
}

// Update the Fade Pattern
void FadeUpdate()
{
// Calculate linear interpolation between Color1 and Color2
// Optimise order of operations to minimize truncation error
uint8_t red = ((Red(Color1) * (TotalSteps – Index)) + (Red(Color2) * Index)) / TotalSteps;
uint8_t green = ((Green(Color1) * (TotalSteps – Index)) + (Green(Color2) * Index)) / TotalSteps;
uint8_t blue = ((Blue(Color1) * (TotalSteps – Index)) + (Blue(Color2) * Index)) / TotalSteps;

ColorSet(Color(red, green, blue));
show();
Increment();
}

// Calculate 50% dimmed version of a color (used by ScannerUpdate)
uint32_t DimColor(uint32_t color)
{
// Shift R, G and B components one bit to the right
uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1);
return dimColor;
}

// Set all pixels to a color (synchronously)
void ColorSet(uint32_t color)
{
for (int i = 0; i < numPixels(); i++)
{
setPixelColor(i, color);
}
show();
}

// Returns the Red component of a 32-bit color
uint8_t Red(uint32_t color)
{
return (color >> 16) & 0xFF;
}

// Returns the Green component of a 32-bit color
uint8_t Green(uint32_t color)
{
return (color >> 8) & 0xFF;
}

// Returns the Blue component of a 32-bit color
uint8_t Blue(uint32_t color)
{
return color & 0xFF;
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r – g – b – back to r.
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 – WheelPos;
if (WheelPos < 85)
{
return Color(255 – WheelPos * 3, 0, WheelPos * 3);
}
else if (WheelPos < 170)
{
WheelPos -= 85;
return Color(0, WheelPos * 3, 255 – WheelPos * 3);
}
else
{
WheelPos -= 170;
return Color(WheelPos * 3, 255 – WheelPos * 3, 0);
}
}
};

// Each key corresponds to a note, which are defined here. Uncomment the scale that you want to use:
//int notes[] = {NOTE_A4, NOTE_AS4, NOTE_B4, NOTE_C4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_C5, NOTE_D5 }; // C-Major scale
//int notes[]={NOTE_A4,NOTE_B4,NOTE_C5,NOTE_D5,NOTE_E5,NOTE_F5,NOTE_G5,NOTE_A5}; // A-Minor scale
//int notes[]={NOTE_C4,NOTE_DS4,NOTE_F4,NOTE_FS4,NOTE_G4,NOTE_AS4,NOTE_C5,NOTE_DS5}; // C Blues scale

void Strip1Complete();
void Ring1Complete();
void StickComplete();

// // Define some NeoPatterns for a strip, a neopixel ring and a neopixel stick
// // as well as some completion routines
NeoPatterns Strip1(NUMPIXELS_STRIP, DATAPIN, NEO_GRB + NEO_KHZ800, &Strip1Complete);
NeoPatterns Ring1(NUMPIXELS_RING, 5, NEO_GRB + NEO_KHZ800, &Ring1Complete);
// NeoPatterns Ring2(16, 6, NEO_GRB + NEO_KHZ800, &Ring2Complete);
NeoPatterns Stick(16, 7, NEO_GRB + NEO_KHZ800, &StickComplete);

//Buffer to hold 5 bytes of MIDI data. Note the timestamp is forced
uint8_t midiData[] = {0x80, 0x80, 0x00, 0x00, 0x00};

int midiChannel = 0;

// https://www.midi.org/specifications/item/gm-level-1-sound-set
int instruments[] = {
36, // C3 Kick
38, // D3 Snare
42, // F#3 Closed hi-hat
46, // A#3 Open hi-hat
49, // C#4 crash symbol
71, //Short Whistle
73, ////Short Guiro
74, //Long Guiro
75, //Claves
76, //Hi Wood Block
77 //Low Wood Block
};

// https://www.midi.org/specifications/item/bluetooth-le-midi
BLEPeripheral blePeripheral;
BLEService midiService(“03B80E5A-EDE8-4B33-A751-6CE34EC4C700”);
BLECharacteristic midiCharacteristic(“7772E5DB-3868-4112-A1A9-F2669D106BF3”, BLEWrite | BLEWriteWithoutResponse | BLENotify | BLERead, 5);

void setupBluetooth()
{
blePeripheral.setLocalName(“Mac_N_Cheese”);
blePeripheral.setDeviceName(“MIDI_101”);

blePeripheral.setAdvertisedServiceUuid(midiService.uuid());

blePeripheral.addAttribute(midiService);
blePeripheral.addAttribute(midiCharacteristic);

blePeripheral.setEventHandler(BLEConnected, connectHandler);
blePeripheral.setEventHandler(BLEDisconnected, disconnectHandler);

blePeripheral.begin();
Serial.println((“Waiting for Bluetooth connections.”));
}

void playNote(uint8_t index) {
int note = instruments[index];

midiData[2] = 0x90 + midiChannel;
midiData[3] = note;
midiData[4] = 127; // velocity

midiCharacteristic.setValue(midiData, sizeof(midiData));
}

void releaseNote(uint8_t index) {
int note = instruments[index];

midiData[2] = 0x80 + midiChannel;
midiData[3] = note;
midiData[4] = 0; // velocity

midiCharacteristic.setValue(midiData, sizeof(midiData));
}

void connectHandler(BLECentral& central) {
Serial.print(“Central device: “);
Serial.print(central.address());
Serial.println(” connected.”);
}

void disconnectHandler(BLECentral& central) {
Serial.print(“Central device: “);
Serial.print(central.address());
Serial.println(” disconnected.”);
}

void setupTouchSensor() {
Serial.println(“Looking for MPR121 Capacitive Touch Sensor”);
// Default address is 0x5A, if tied to 3.3V its 0x5B
// If tied to SDA its 0x5C and if SCL then 0x5D
if (!cap.begin(0x5A)) {
Serial.println(“MPR121 not found, check wiring?”);
while (1);
}
Serial.println(“MPR121 found!”);
}

// Initialize everything and prepare to start
void setup() {
Serial.begin(9600);
// pinMode(8, INPUT_PULLUP);
// pinMode(9, INPUT_PULLUP);
// pinMode(BUZZER_PIN, OUTPUT);

setupTouchSensor();
setupBluetooth();

Serial.println(“MPR121 found!”);

// This is for Trinket 5V 16MHz, you can remove these three lines if you are not using a Trinket
#if defined (__AVR_ATtiny85__)
if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
#endif
// End of trinket special code

// Initialize all the pixelStrips
Strip1.begin();
Ring1.begin();
Stick.begin();

// Kick off a pattern
Strip1.TheaterChase(Strip1.Color(255, 255, 0), Strip1.Color(0, 0, 50), 100);
Ring1.RainbowCycle(3);
Ring1.Color1 = Ring1.Color1;
Stick.Scanner(Ring1.Color(255, 0, 0), 55);
}

// Main loop
void loop() {
// Update the rings.
Ring1.Update();
Strip1.Update();

currtouched = cap.touched();

// check if a trigger was TOUCHED
for (int i = 0; i < 12; i++) {
// if it *is* touched and *wasnt* touched before, alert!
if ((currtouched & _BV(i)) && !(lasttouched & _BV(i)) ) {
Serial.print(i); Serial.println(“touched”);
playNote(i);
// delay(20);
Ring1.Interval = 10;
Strip1.Interval = 100;
Strip1.TotalSteps = Strip1.numPixels();

// tone(BUZZER_PIN, notes[i]); // Plays the note corresponding to the key pressed
// delay (750); // add this back in if necessary
// noTone(BUZZER_PIN); // add this back in if necessary TO STOP THE BUZZER!!!!
showType++;
if (showType > 12) {
showType = 0;
}
startShow(currtouched);
Serial.println(currtouched/2);
}
// if it *was* touched and now *isnt*, alert!
if (!(currtouched & _BV(i)) && (lasttouched & _BV(i)) ) {
Serial.print(i); Serial.println(“released”);
releaseNote(i);
// noTone(BUZZER_PIN);
showType = 0;
// And update tbe stick
Stick.Update();
// /*______________________________________________________________________________________________
// // THEN RETURN TO – OR – DO THE AMBIENT PATTERN
//
// //**** comment out the 5 lines below if you don’t want it to return to the former pattern
// ______________________________________________________________________________________________ */
Ring1.ActivePattern = TWINKLERAND;
Ring1.Interval = 100;
Strip1.ActivePattern = THEATER_CHASE;
Strip1.TotalSteps = Strip1.numPixels();
Strip1.Interval = min(10, Strip1.Interval);
}
}
//reset
lasttouched = currtouched;
}
//————————————————————
//Completion Routines – get called on completion of a pattern
//————————————————————

// Ring1 Completion Callback
void Ring1Complete()
{
if (digitalRead(9) == LOW) // Button #2 pressed
{
// KEEP GOING AND CHANGE COLORS
Ring1.Interval = 40;
Ring1.Color1 = Ring1.Wheel(random(255));
Ring1.Interval = 20000;
}
else // Return to normal
{
// Ring1.simpleWave(0.03, 5, 10);
}
}

// Strip 1 Completion Callback
void Strip1Complete()
{
if (digitalRead(9) == LOW) // Button #2 pressed
{
// KEEP GOING AND CHANGE COLORS
Strip1.Interval = 20;
Strip1.Color1 = Strip1.Wheel(random(255));
Strip1.Interval = 20000;
}
else // Return to normal
{
Strip1.TheaterChase(Strip1.Color(127, 127, 127), Strip1.Color(63, 63, 63), 50); //WHITE
// Strip1.simpleWave(0.03, 5, 10);
}
}

// Stick Completion Callback
void StickComplete()
{
// Random color change for next scan
Stick.Color1 = Stick.Wheel(random(255));
}

//NONE, RAINBOW_CYCLE, RAINBOW, TWINKLERAND, SIMPLEWAVE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE
void startShow(int i) {
switch (i) {
case 1:
// BLUE
Strip1.ActivePattern = THEATER_CHASE;
Strip1.TheaterChase(Strip1.Color(0, 0, 127), Strip1.Color(0, 0, 127), 50); //blue
// theaterChase(Color(0, 0, 127), 50); // Blue
break;
case 2:
//RED
Strip1.ActivePattern = THEATER_CHASE;
Strip1.TheaterChase(Strip1.Color(127, 0, 0), Strip1.Color(127, 0, 0), 50); //red
// theaterChase(Color(127, 0, 0), 50); // Red
break;
case 4:
// Yellow
Strip1.ActivePattern = THEATER_CHASE;
Strip1.TheaterChase(Strip1.Color(127, 100, 0), Strip1.Color(64, 50, 0), 50); //yellow
break;
case 8:
// Orange
Strip1.ActivePattern = THEATER_CHASE;
Strip1.TheaterChase(Strip1.Color(180, 64, 0), Strip1.Color(180, 64, 0), 50); //orange
break;
case 16:
// Green
Strip1.ActivePattern = THEATER_CHASE;
Strip1.TheaterChase(Strip1.Color(0, 127, 0), Strip1.Color(0, 50, 0), 50); //green
break;
// green orange yellow red blue keys on guitar hero

// case 5:
// Ring1.ActivePattern = FADE;
// Strip1.ActivePattern = FADE;
// // rainbowCycle(20);
// break;
// case 6:
// Ring1.ActivePattern = RAINBOW_CYCLE;
// Strip1.ActivePattern = RAINBOW_CYCLE;
//
// break;
// case 7:
// Ring1.ActivePattern = TWINKLERAND;
// Strip1.ActivePattern = TWINKLERAND;
// // rainbow(20);
// break;
// case 8:
// Ring1.ActivePattern = COLOR_WIPE;
// Strip1.ActivePattern = COLOR_WIPE;
// // simpleWave(0.03, 5, 10);
// break;
// case 9:
// Ring1.ActivePattern = THEATER_CHASE;
// Strip1.ActivePattern = THEATER_CHASE;
// // theaterChase(Color(0, 0, 127), 50); // Blue
// break;
// case 10:
// Ring1.ActivePattern = SCANNER;
// Strip1.ActivePattern = SCANNER;
// // theaterChase(Color(0, 0, 127), 50); // Blue
// break;
// case 11:
// Ring1.ActivePattern = FADE;
// Strip1.ActivePattern = FADE;
// // theaterChase(Color(0, 0, 127), 50); // Blue
// break;
default:
noTone(BUZZER_PIN);
// show();
// if nothing else matches, do the default
// default is optional
break;
}
}