diff --git a/RGBShadesAudio.ino b/RGBShadesAudio.ino new file mode 100644 index 0000000..fb8e872 --- /dev/null +++ b/RGBShadesAudio.ino @@ -0,0 +1,153 @@ +// RGB Shades Audio Demo Code - REQUIRES MSGEQ7 AUDIO SENSOR +// Copyright (c) 2015 macetech LLC +// This software is provided under the MIT License (see license.txt) +// Special credit to Mark Kriegsman for XY mapping code +// +// Use Version 3.0 or later https://github.com/FastLED/FastLED +// ZIP file https://github.com/FastLED/FastLED/archive/master.zip +// +// Use Arduino IDE 1.0 or later +// +// If your RGB Shades were purchased before July 2015: +// This version has the standard Arduino bootloader. R9 and R10 near the control buttons will be present. +// Select the “Arduino Pro or Pro Mini” option. +// Then, go back into the Tools menu and find the Processor option and select “ATmega328 (5V, 16MHz)”. +// +// If your RGB Shades were purchased after July 2015: +// This version has the Optiboot bootloader. R9 and 10 near the control buttons will be missing. +// Select the “Arduino Mini” option. +// Then, go back into the Tools menu and find the Processor option and select “ATmega328”. +// +// [Press] the SW1 button to cycle through available effects +// Effects will also automatically cycle at startup +// [Press and hold] the SW1 button (one second) to switch between auto and manual mode +// * Auto Mode (one blue blink): Effects automatically cycle over time +// * Manual Mode (two red blinks): Effects must be selected manually with SW1 button +// +// [Press] the SW2 button to cycle through available brightness levels +// [Press and hold] the SW2 button (one second) to reset brightness to startup value +// +// Brightness, selected effect, and auto-cycle are saved in EEPROM after a delay +// The RGB Shades will automatically start up with the last-selected settings + + +// RGB Shades data output to LEDs is on pin 5 +#define LED_PIN 5 + +// RGB Shades color order (Green/Red/Blue) +#define COLOR_ORDER GRB +#define CHIPSET WS2811 + +// Global maximum brightness value, maximum 255 +#define MAXBRIGHTNESS 72 +#define STARTBRIGHTNESS 102 + +// Cycle time (milliseconds between pattern changes) +#define cycleTime 15000 + +// Hue time (milliseconds between hue increments) +#define hueTime 30 + +// Time after changing settings before settings are saved to EEPROM +#define EEPROMDELAY 2000 + +// Include FastLED library and other useful files +#include +#include +#include "messages.h" +#include "font.h" +#include "XYmap.h" +#include "utils.h" +#include "audio.h" +#include "effects.h" +#include "buttons.h" + + +// Runs one time at the start of the program (power up or reset) +void setup() { + + // write FastLED configuration data + FastLED.addLeds(leds, LAST_VISIBLE_LED + 1);//.setCorrection(TypicalSMD5050); + + // set global brightness value + FastLED.setBrightness( scale8(STARTBRIGHTNESS, MAXBRIGHTNESS) ); + + // configure input buttons + pinMode(MODEBUTTON, INPUT_PULLUP); + pinMode(BRIGHTNESSBUTTON, INPUT_PULLUP); + pinMode(STROBEPIN, OUTPUT); + pinMode(RESETPIN, OUTPUT); + + digitalWrite(RESETPIN, LOW); + digitalWrite(STROBEPIN, HIGH); + + random16_add_entropy(analogRead(ANALOGPIN)); + +} + +// list of functions that will be displayed +functionList effectList[] = {drawVU, + RGBpulse, + drawAnalyzer }; + +/* functionList effectList[] = {threeSine, + threeDee, + scrollTextZero, + plasma, + confetti, + rider, + scrollTextOne, + glitter, + slantBars, + scrollTextTwo, + colorFill, + sideRain }; +*/ + +const byte numEffects = (sizeof(effectList)/sizeof(effectList[0])); + +// Runs over and over until power off or reset +void loop() +{ + currentMillis = millis(); // save the current timer value + updateButtons(); // read, debounce, and process the buttons + doButtons(); // perform actions based on button state + checkEEPROM(); // update the EEPROM if necessary + + // analyze the audio input + if (currentMillis - audioMillis > AUDIODELAY) { + audioMillis = currentMillis; + doAnalogs(); + } + + // switch to a new effect every cycleTime milliseconds + if (currentMillis - cycleMillis > cycleTime && autoCycle == true) { + cycleMillis = currentMillis; + if (++currentEffect >= numEffects) currentEffect = 0; // loop to start of effect list + effectInit = false; // trigger effect initialization when new effect is selected + } + + // increment the global hue value every hueTime milliseconds + if (currentMillis - hueMillis > hueTime) { + hueMillis = currentMillis; + hueCycle(1); // increment the global hue value + } + + // run the currently selected effect every effectDelay milliseconds + if (currentMillis - effectMillis > effectDelay) { + effectMillis = currentMillis; + effectList[currentEffect](); // run the selected effect function + random16_add_entropy(1); // make the random values a bit more random-ish + } + + // run a fade effect too if the confetti effect is running + if (effectList[currentEffect] == confetti) fadeAll(1); + + FastLED.show(); // send the contents of the led memory to the LEDs + +} + + + + + diff --git a/XYmap.h b/XYmap.h new file mode 100644 index 0000000..673ed4c --- /dev/null +++ b/XYmap.h @@ -0,0 +1,71 @@ +// Helper functions for a two-dimensional XY matrix of pixels. +// Special credit to Mark Kriegsman +// +// 2014-10-18 - Special version for RGB Shades Kickstarter +// https://www.kickstarter.com/projects/macetech/rgb-led-shades +// 2014-10-18 - code version 2c (local table, holes are r/w), +// by Mark Kriegsman +// +// This special 'XY' code lets you program the RGB Shades +// as a plain 16x5 matrix. +// +// Writing to and reading from the 'holes' in the layout is +// also allowed; holes retain their data, it's just not displayed. +// +// You can also test to see if you're on or off the layout +// like this +// if( XY(x,y) > LAST_VISIBLE_LED ) { ...off the layout...} +// +// X and Y bounds checking is also included, so it is safe +// to just do this without checking x or y in your code: +// leds[ XY(x,y) ] == CRGB::Red; +// All out of bounds coordinates map to the first hidden pixel. +// +// XY(x,y) takes x and y coordinates and returns an LED index number, +// for use like this: leds[ XY(x,y) ] == CRGB::Red; + + +// Params for width and height +const uint8_t kMatrixWidth = 16; +const uint8_t kMatrixHeight = 5; + +// Pixel layout +// +// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +// +------------------------------------------------ +// 0 | . 0 1 2 3 4 5 6 7 8 9 10 11 12 13 . +// 1 | 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 +// 2 | 30 31 32 33 34 35 36 . . 37 38 39 40 41 42 43 +// 3 | 57 56 55 54 53 52 51 . . 50 49 48 47 46 45 44 +// 4 | . 58 59 60 61 62 . . . . 63 64 65 66 67 . + +#define NUM_LEDS (kMatrixWidth * kMatrixHeight) +CRGB leds[ NUM_LEDS ]; + + +// This function will return the right 'led index number' for +// a given set of X and Y coordinates on your RGB Shades. +// This code, plus the supporting 80-byte table is much smaller +// and much faster than trying to calculate the pixel ID with code. +#define LAST_VISIBLE_LED 67 +uint8_t XY( uint8_t x, uint8_t y) +{ + // any out of bounds address maps to the first hidden pixel + if( (x >= kMatrixWidth) || (y >= kMatrixHeight) ) { + return (LAST_VISIBLE_LED + 1); + } + + const uint8_t ShadesTable[] = { + 68, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 69, + 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 30, 31, 32, 33, 34, 35, 36, 70, 71, 37, 38, 39, 40, 41, 42, 43, + 57, 56, 55, 54, 53, 52, 51, 72, 73, 50, 49, 48, 47, 46, 45, 44, + 74, 58, 59, 60, 61, 62, 75, 76, 77, 78, 63, 64, 65, 66, 67, 79 + }; + + uint8_t i = (y * kMatrixWidth) + x; + uint8_t j = ShadesTable[i]; + return j; +} + + diff --git a/audio.h b/audio.h new file mode 100644 index 0000000..40f6f65 --- /dev/null +++ b/audio.h @@ -0,0 +1,116 @@ +// Interface with MSGEQ7 chip for audio analysis + +#define AUDIODELAY 4 + +// Pin definitions +#define ANALOGPIN 3 +#define STROBEPIN 8 +#define RESETPIN 7 + +// Smooth/average settings +#define SPECTRUMSMOOTH 0.07 +#define PEAKDECAY 0.01 +#define NOISEFLOOR 65 + +// AGC settings +#define AGCSMOOTH 0.004 +#define GAINUPPERLIMIT 8.0 +#define GAINLOWERLIMIT 0.1 + +// Global variables +unsigned int spectrumValue[7]; // holds raw adc values +float spectrumDecay[7] = {0}; // holds time-averaged values +float spectrumPeaks[7] = {0}; // holds peak values +float audioAvg = 0.0; +float gainAGC = 0.0; + +void doAnalogs() { + + static PROGMEM const byte spectrumFactors[7] = {10, 10, 11, 12, 13, 14, 16}; + + // reset MSGEQ7 to first frequency bin + digitalWrite(RESETPIN, HIGH); + delayMicroseconds(5); + digitalWrite(RESETPIN, LOW); + + // store sum of values for AGC + int analogsum = 0; + + // cycle through each MSGEQ7 bin and read the analog values + for (int i = 0; i < 7; i++) { + + // set up the MSGEQ7 + digitalWrite(STROBEPIN, LOW); + delayMicroseconds(50); // to allow the output to settle + + // read the analog value + spectrumValue[i] = analogRead(ANALOGPIN); + digitalWrite(STROBEPIN, HIGH); + + if (i == 3) Serial.println(spectrumValue[i]); + + // noise floor filter + if (spectrumValue[i] < NOISEFLOOR) { + spectrumValue[i] = 0; + } else { + spectrumValue[i] -= NOISEFLOOR; + } + + // apply correction factor per frequency bin + spectrumValue[i] = (spectrumValue[i]*pgm_read_byte_near(spectrumFactors + i))/10; + + // prepare average for AGC + analogsum += spectrumValue[i]; + + // apply current gain value + spectrumValue[i] *= gainAGC; + + // process time-averaged values + spectrumDecay[i] = (1.0 - SPECTRUMSMOOTH) * spectrumDecay[i] + SPECTRUMSMOOTH * spectrumValue[i]; + + // process peak values + if (spectrumPeaks[i] < spectrumDecay[i]) spectrumPeaks[i] = spectrumDecay[i]; + spectrumPeaks[i] = spectrumPeaks[i] * (1.0 - PEAKDECAY); + } + + // Calculate audio levels for automatic gain + audioAvg = (1.0 - AGCSMOOTH) * audioAvg + AGCSMOOTH * (analogsum / 7.0); + + // Calculate gain adjustment factor + gainAGC = 250.0 / audioAvg; + if (gainAGC > GAINUPPERLIMIT) gainAGC = GAINUPPERLIMIT; + if (gainAGC < GAINLOWERLIMIT) gainAGC = GAINLOWERLIMIT; + + + + + +} + +// Attempt at beat detection +byte beatTriggered = 0; +#define beatLevel 20.0 +#define beatDeadzone 30.0 +#define beatDelay 50 +float lastBeatVal = 0; +byte beatDetect() { + static float beatAvg = 0; + static unsigned long lastBeatMillis; + float specCombo = (spectrumDecay[0] + spectrumDecay[1]) / 2.0; + beatAvg = (1.0 - AGCSMOOTH) * beatAvg + AGCSMOOTH * specCombo; + + if (lastBeatVal < beatAvg) lastBeatVal = beatAvg; + if ((specCombo - beatAvg) > beatLevel && beatTriggered == 0 && currentMillis - lastBeatMillis > beatDelay) { + beatTriggered = 1; + lastBeatVal = specCombo; + lastBeatMillis = currentMillis; + return 1; + } else if ((lastBeatVal - specCombo) > beatDeadzone) { + beatTriggered = 0; + return 0; + } else { + return 0; + } + +} + diff --git a/buttons.h b/buttons.h new file mode 100644 index 0000000..5b372a9 --- /dev/null +++ b/buttons.h @@ -0,0 +1,116 @@ +// Process button inputs and return button activity + +#define NUMBUTTONS 2 +#define MODEBUTTON 4 +#define BRIGHTNESSBUTTON 3 + +#define BTNIDLE 0 +#define BTNDEBOUNCING 1 +#define BTNPRESSED 2 +#define BTNRELEASED 3 +#define BTNLONGPRESS 4 +#define BTNLONGPRESSREAD 5 + +#define BTNDEBOUNCETIME 20 +#define BTNLONGPRESSTIME 1000 + +unsigned long buttonEvents[NUMBUTTONS]; +byte buttonStatuses[NUMBUTTONS]; +byte buttonmap[NUMBUTTONS] = {BRIGHTNESSBUTTON, MODEBUTTON}; + +void updateButtons() { + for (byte i = 0; i < NUMBUTTONS; i++) { + switch (buttonStatuses[i]) { + case BTNIDLE: + if (digitalRead(buttonmap[i]) == LOW) { + buttonEvents[i] = currentMillis; + buttonStatuses[i] = BTNDEBOUNCING; + } + break; + + case BTNDEBOUNCING: + if (currentMillis - buttonEvents[i] > BTNDEBOUNCETIME) { + if (digitalRead(buttonmap[i]) == LOW) { + buttonStatuses[i] = BTNPRESSED; + } + } + break; + + case BTNPRESSED: + if (digitalRead(buttonmap[i]) == HIGH) { + buttonStatuses[i] = BTNRELEASED; + } else if (currentMillis - buttonEvents[i] > BTNLONGPRESSTIME) { + buttonStatuses[i] = BTNLONGPRESS; + } + break; + + case BTNRELEASED: + break; + + case BTNLONGPRESS: + break; + + case BTNLONGPRESSREAD: + if (digitalRead(buttonmap[i]) == HIGH) { + buttonStatuses[i] = BTNIDLE; + } + break; + } + } +} + +byte buttonStatus(byte buttonNum) { + + byte tempStatus = buttonStatuses[buttonNum]; + if (tempStatus == BTNRELEASED) { + buttonStatuses[buttonNum] = BTNIDLE; + } else if (tempStatus == BTNLONGPRESS) { + buttonStatuses[buttonNum] = BTNLONGPRESSREAD; + } + + return tempStatus; + +} + +void doButtons() { + + // Check the mode button (for switching between effects) + switch (buttonStatus(0)) { + + case BTNRELEASED: // button was pressed and released quickly + cycleMillis = currentMillis; + if (++currentEffect >= numEffects) currentEffect = 0; // loop to start of effect list + effectInit = false; // trigger effect initialization when new effect is selected + eepromMillis = currentMillis; + eepromOutdated = true; + break; + + case BTNLONGPRESS: // button was held down for a while + autoCycle = !autoCycle; // toggle auto cycle mode + confirmBlink(); // one blue blink: auto mode. two red blinks: manual mode. + eepromMillis = currentMillis; + eepromOutdated = true; + break; + + } + + // Check the brightness adjust button + switch (buttonStatus(1)) { + + case BTNRELEASED: // button was pressed and released quickly + currentBrightness += 51; // increase the brightness (wraps to lowest) + FastLED.setBrightness(scale8(currentBrightness, MAXBRIGHTNESS)); + eepromMillis = currentMillis; + eepromOutdated = true; + break; + + case BTNLONGPRESS: // button was held down for a while + currentBrightness = STARTBRIGHTNESS; // reset brightness to startup value + FastLED.setBrightness(scale8(currentBrightness, MAXBRIGHTNESS)); + eepromMillis = currentMillis; + eepromOutdated = true; + break; + + } + +} diff --git a/effects.h b/effects.h new file mode 100644 index 0000000..0dc8380 --- /dev/null +++ b/effects.h @@ -0,0 +1,446 @@ +// Graphical effects to run on the RGB Shades LED array +// Each function should have the following components: +// * Must be declared void with no parameters or will break function pointer array +// * Check effectInit, if false then init any required settings and set effectInit true +// * Set effectDelay (the time in milliseconds until the next run of this effect) +// * All animation should be controlled with counters and effectDelay, no delay() or loops +// * Pixel data should be written using leds[XY(x,y)] to map coordinates to the RGB Shades layout + +// Triple Sine Waves +void threeSine() { + + static byte sineOffset = 0; // counter for current position of sine waves + + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 20; + } + + // Draw one frame of the animation into the LED array + for (byte x = 0; x < kMatrixWidth; x++) { + for (int y = 0; y < kMatrixHeight; y++) { + + // Calculate "sine" waves with varying periods + // sin8 is used for speed; cos8, quadwave8, or triwave8 would also work here + byte sinDistanceR = qmul8(abs(y * (255 / kMatrixHeight) - sin8(sineOffset * 9 + x * 16)), 2); + byte sinDistanceG = qmul8(abs(y * (255 / kMatrixHeight) - sin8(sineOffset * 10 + x * 16)), 2); + byte sinDistanceB = qmul8(abs(y * (255 / kMatrixHeight) - sin8(sineOffset * 11 + x * 16)), 2); + + leds[XY(x, y)] = CRGB(255 - sinDistanceR, 255 - sinDistanceG, 255 - sinDistanceB); + } + } + + sineOffset++; // byte will wrap from 255 to 0, matching sin8 0-255 cycle + +} + + +// RGB Plasma +void plasma() { + + static byte offset = 0; // counter for radial color wave motion + static int plasVector = 0; // counter for orbiting plasma center + + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 10; + } + + // Calculate current center of plasma pattern (can be offscreen) + int xOffset = cos8(plasVector / 256); + int yOffset = sin8(plasVector / 256); + + // Draw one frame of the animation into the LED array + for (int x = 0; x < kMatrixWidth; x++) { + for (int y = 0; y < kMatrixHeight; y++) { + byte color = sin8(sqrt(sq(((float)x - 7.5) * 10 + xOffset - 127) + sq(((float)y - 2) * 10 + yOffset - 127)) + offset); + leds[XY(x, y)] = CHSV(color, 255, 255); + } + } + + offset++; // wraps at 255 for sin8 + plasVector += 16; // using an int for slower orbit (wraps at 65536) + +} + + +// Scanning pattern left/right, uses global hue cycle +void rider() { + + static byte riderPos = 0; + + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 5; + riderPos = 0; + } + + // Draw one frame of the animation into the LED array + for (byte x = 0; x < kMatrixWidth; x++) { + int brightness = abs(x * (256 / kMatrixWidth) - triwave8(riderPos) * 2 + 127) * 3; + if (brightness > 255) brightness = 255; + brightness = 255 - brightness; + CRGB riderColor = CHSV(cycleHue, 255, brightness); + for (byte y = 0; y < kMatrixHeight; y++) { + leds[XY(x, y)] = riderColor; + } + } + + riderPos++; // byte wraps to 0 at 255, triwave8 is also 0-255 periodic + +} + + +// Shimmering noise, uses global hue cycle +void glitter() { + + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 15; + } + + // Draw one frame of the animation into the LED array + for (int x = 0; x < kMatrixWidth; x++) { + for (int y = 0; y < kMatrixHeight; y++) { + leds[XY(x, y)] = CHSV(cycleHue, 255, random8(5) * 63); + } + } + +} + + +// Fills saturated colors into the array from alternating directions +void colorFill() { + + static byte currentColor = 0; + static byte currentRow = 0; + static byte currentDirection = 0; + + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 45; + currentColor = 0; + currentRow = 0; + currentDirection = 0; + currentPalette = RainbowColors_p; + } + + // test a bitmask to fill up or down when currentDirection is 0 or 2 (0b00 or 0b10) + if (!(currentDirection & 1)) { + effectDelay = 45; // slower since vertical has fewer pixels + for (byte x = 0; x < kMatrixWidth; x++) { + byte y = currentRow; + if (currentDirection == 2) y = kMatrixHeight - 1 - currentRow; + leds[XY(x, y)] = currentPalette[currentColor]; + } + } + + // test a bitmask to fill left or right when currentDirection is 1 or 3 (0b01 or 0b11) + if (currentDirection & 1) { + effectDelay = 20; // faster since horizontal has more pixels + for (byte y = 0; y < kMatrixHeight; y++) { + byte x = currentRow; + if (currentDirection == 3) x = kMatrixWidth - 1 - currentRow; + leds[XY(x, y)] = currentPalette[currentColor]; + } + } + + currentRow++; + + // detect when a fill is complete, change color and direction + if ((!(currentDirection & 1) && currentRow >= kMatrixHeight) || ((currentDirection & 1) && currentRow >= kMatrixWidth)) { + currentRow = 0; + currentColor += random8(3, 6); + if (currentColor > 15) currentColor -= 16; + currentDirection++; + if (currentDirection > 3) currentDirection = 0; + effectDelay = 300; // wait a little bit longer after completing a fill + } + + +} + +// Emulate 3D anaglyph glasses +void threeDee() { + + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 50; + } + + for (byte x = 0; x < kMatrixWidth; x++) { + for (byte y = 0; y < kMatrixHeight; y++) { + if (x < 7) { + leds[XY(x, y)] = CRGB::Blue; + } else if (x > 8) { + leds[XY(x, y)] = CRGB::Red; + } else { + leds[XY(x, y)] = CRGB::Black; + } + } + } + + leds[XY(6, 0)] = CRGB::Black; + leds[XY(9, 0)] = CRGB::Black; + +} + +// Random pixels scroll sideways, uses current hue +#define rainDir 0 +void sideRain() { + + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 30; + } + + scrollArray(rainDir); + byte randPixel = random8(kMatrixHeight); + for (byte y = 0; y < kMatrixHeight; y++) leds[XY((kMatrixWidth - 1) * rainDir, y)] = CRGB::Black; + leds[XY((kMatrixWidth - 1)*rainDir, randPixel)] = CHSV(cycleHue, 255, 255); + +} + +// Pixels with random locations and random colors selected from a palette +// Use with the fadeAll function to allow old pixels to decay +void confetti() { + + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 10; + selectRandomPalette(); + } + + // scatter random colored pixels at several random coordinates + for (byte i = 0; i < 4; i++) { + leds[XY(random16(kMatrixWidth), random16(kMatrixHeight))] = ColorFromPalette(currentPalette, random16(255), 255); //CHSV(random16(255), 255, 255); + random16_add_entropy(1); + } + +} + + +// Draw slanting bars scrolling across the array, uses current hue +void slantBars() { + + static byte slantPos = 0; + + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 5; + } + + for (byte x = 0; x < kMatrixWidth; x++) { + for (byte y = 0; y < kMatrixHeight; y++) { + leds[XY(x, y)] = CHSV(cycleHue, 255, quadwave8(x * 32 + y * 32 + slantPos)); + } + } + + slantPos -= 4; + +} + + +#define NORMAL 0 +#define RAINBOW 1 +#define charSpacing 2 +// Scroll a text string +void scrollText(byte message, byte style, CRGB fgColor, CRGB bgColor) { + static byte currentMessageChar = 0; + static byte currentCharColumn = 0; + static byte paletteCycle = 0; + static CRGB currentColor; + static byte bitBuffer[16] = {0}; + static byte bitBufferPointer = 0; + + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 35; + currentMessageChar = 0; + currentCharColumn = 0; + selectFlashString(message); + loadCharBuffer(loadStringChar(message, currentMessageChar)); + currentPalette = RainbowColors_p; + for (byte i = 0; i < kMatrixWidth; i++) bitBuffer[i] = 0; + } + + paletteCycle += 15; + + if (currentCharColumn < 5) { // characters are 5 pixels wide + bitBuffer[(bitBufferPointer + kMatrixWidth - 1) % kMatrixWidth] = charBuffer[currentCharColumn]; // character + } else { + bitBuffer[(bitBufferPointer + kMatrixWidth - 1) % kMatrixWidth] = 0; // space + } + + CRGB pixelColor; + for (byte x = 0; x < kMatrixWidth; x++) { + for (byte y = 0; y < 5; y++) { // characters are 5 pixels tall + if (bitRead(bitBuffer[(bitBufferPointer + x) % kMatrixWidth], y) == 1) { + if (style == RAINBOW) { + pixelColor = ColorFromPalette(currentPalette, paletteCycle+y*16, 255); + } else { + pixelColor = fgColor; + } + } else { + pixelColor = bgColor; + } + leds[XY(x, y)] = pixelColor; + } + } + + currentCharColumn++; + if (currentCharColumn > (4 + charSpacing)) { + currentCharColumn = 0; + currentMessageChar++; + char nextChar = loadStringChar(message, currentMessageChar); + if (nextChar == 0) { // null character at end of strong + currentMessageChar = 0; + nextChar = loadStringChar(message, currentMessageChar); + } + loadCharBuffer(nextChar); + } + + bitBufferPointer++; + if (bitBufferPointer > 15) bitBufferPointer = 0; + +} + + +void scrollTextZero() { + scrollText(0, NORMAL, CRGB::Red, CRGB::Black); +} + +void scrollTextOne() { + scrollText(1, RAINBOW, 0, CRGB::Black); +} + +void scrollTextTwo() { + scrollText(2, NORMAL, CRGB::Green, CRGB(0,0,8)); +} + + + +#define analyzerFadeFactor 5 +#define analyzerScaleFactor 2 +#define analyzerPaletteFactor 2 +void drawAnalyzer() { + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 5; + //currentPalette = CRGBPalette16(CRGB::Red, CRGB::Orange, CRGB::Gray); + //currentPalette = RainbowColors_p; + //currentPalette = HeatColors_p; + selectRandomAudioPalette(); + } + + CRGB pixelColor; + + const float yScale = 255.0 / kMatrixHeight; + + for (byte x = 0; x < kMatrixWidth / 2; x++) { + byte newX = x; + if (x < 2) newX = 0; else newX = x - 1; + for (byte y = 0; y < kMatrixHeight; y++) { + if (x > 6) { + pixelColor = ColorFromPalette(currentPalette, 0, 0); + } else { + int senseValue = spectrumDecay[newX] / analyzerScaleFactor - yScale * (kMatrixHeight - 1 - y); + int pixelBrightness = senseValue * analyzerFadeFactor; + if (pixelBrightness > 255) pixelBrightness = 255; + if (pixelBrightness < 0) pixelBrightness = 0; + + int pixelPaletteIndex = senseValue / analyzerPaletteFactor - 15; + if (pixelPaletteIndex > 240) pixelPaletteIndex = 240; + if (pixelPaletteIndex < 0) pixelPaletteIndex = 0; + + pixelColor = ColorFromPalette(currentPalette, pixelPaletteIndex, pixelBrightness); + } + leds[XY(x, y)] = pixelColor; + leds[XY(kMatrixWidth - x - 1, y)] = pixelColor; + } + } + + +} + +#define VUFadeFactor 5 +#define VUScaleFactor 2.5 +#define VUPaletteFactor 1.5 +void drawVU() { + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 5; + selectRandomAudioPalette(); + } + + CRGB pixelColor; + + const float xScale = 255.0 / (kMatrixWidth / 2); + float specCombo = (spectrumDecay[0] + spectrumDecay[1]) / 2.0; + + for (byte x = 0; x < kMatrixWidth / 2; x++) { + int senseValue = specCombo / VUScaleFactor - xScale * x; + int pixelBrightness = senseValue * VUFadeFactor; + if (pixelBrightness > 255) pixelBrightness = 255; + if (pixelBrightness < 0) pixelBrightness = 0; + + int pixelPaletteIndex = senseValue / VUPaletteFactor - 15; + if (pixelPaletteIndex > 240) pixelPaletteIndex = 240; + if (pixelPaletteIndex < 0) pixelPaletteIndex = 0; + + pixelColor = ColorFromPalette(currentPalette, pixelPaletteIndex, pixelBrightness); + + for (byte y = 0; y < kMatrixHeight; y++) { + leds[XY(x, y)] = pixelColor; + leds[XY(kMatrixWidth - x - 1, y)] = pixelColor; + } + } + + +} + + +void RGBpulse() { + + // startup tasks + if (effectInit == false) { + effectInit = true; + effectDelay = 1; + } + + static byte RGBcycle = 0; + + fadeAll(1); + + if (beatDetect()) { + + switch (RGBcycle) { + case 0: + fillAll(CRGB::Red); + break; + case 1: + fillAll(CRGB::Lime); + break; + case 2: + fillAll(CRGB::Blue); + break; + } + + RGBcycle++; + if (RGBcycle > 2) RGBcycle = 0; + } + +} + diff --git a/font.h b/font.h new file mode 100644 index 0000000..f359146 --- /dev/null +++ b/font.h @@ -0,0 +1,74 @@ +// 5 x 5 pixel font (no lowercase) + +const char Font[][5] PROGMEM = { +{0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000}, // 32 +{0b00000000, 0b00000000, 0b00010111, 0b00000000, 0b00000000}, // 33 ! +{0b00000000, 0b00000011, 0b00000000, 0b00000011, 0b00000000}, // 34 " +{0b00001010, 0b00011111, 0b00001010, 0b00011111, 0b00001010}, // 35 # +{0b00010010, 0b00010101, 0b00011111, 0b00010101, 0b00001001}, // 36 $ +{0b00010001, 0b00001000, 0b00000100, 0b00000010, 0b00010001}, // 37 % +{0b00001010, 0b00010101, 0b00001010, 0b00010000, 0b00000000}, // 38 & +{0b00000000, 0b00000000, 0b00000011, 0b00000000, 0b00000000}, // 39 ' +{0b00000000, 0b00000000, 0b00001110, 0b00010001, 0b00000000}, // 40 ( +{0b00000000, 0b00010001, 0b00001110, 0b00000000, 0b00000000}, // 41 ) +{0b00010001, 0b00001010, 0b00011111, 0b00001010, 0b00010001}, // 42 * +{0b00000100, 0b00000100, 0b00011111, 0b00000100, 0b00000100}, // 43 + +{0b00000000, 0b00010000, 0b00001000, 0b00000000, 0b00000000}, // 44 , +{0b00000100, 0b00000100, 0b00000100, 0b00000100, 0b00000100}, // 45 - +{0b00000000, 0b00011000, 0b00011000, 0b00000000, 0b00000000}, // 46 . +{0b00010000, 0b00001000, 0b00000100, 0b00000010, 0b00000001}, // 47 / +{0b00001110, 0b00010001, 0b00010101, 0b00010001, 0b00001110}, // 48 0 +{0b00000000, 0b00010010, 0b00011111, 0b00010000, 0b00000000}, // 49 1 +{0b00010010, 0b00011001, 0b00010101, 0b00010101, 0b00010010}, // 50 2 +{0b00010101, 0b00010101, 0b00010101, 0b00010101, 0b00001010}, // 51 3 +{0b00000111, 0b00000100, 0b00000100, 0b00011111, 0b00000100}, // 52 4 +{0b00010111, 0b00010101, 0b00010101, 0b00010101, 0b00001001}, // 53 5 +{0b00001110, 0b00010101, 0b00010101, 0b00010101, 0b00001000}, // 54 6 +{0b00000001, 0b00000001, 0b00011001, 0b00000101, 0b00000011}, // 55 7 +{0b00001010, 0b00010101, 0b00010101, 0b00010101, 0b00001010}, // 56 8 +{0b00000010, 0b00010101, 0b00010101, 0b00010101, 0b00001110}, // 57 9 +{0b00000000, 0b00000000, 0b00001010, 0b00000000, 0b00000000}, // 58 : +{0b00000000, 0b00010000, 0b00001010, 0b00000000, 0b00000000}, // 59 ; +{0b00000000, 0b00000100, 0b00001010, 0b00010001, 0b00000000}, // 60 < +{0b00001010, 0b00001010, 0b00001010, 0b00001010, 0b00001010}, // 61 = +{0b00000000, 0b00010001, 0b00001010, 0b00000100, 0b00000000}, // 62 > +{0b00000010, 0b00000001, 0b00010101, 0b00000101, 0b00000010}, // 63 ? +{0b00011111, 0b00010001, 0b00010101, 0b00010101, 0b00010111}, // 64 @ +{0b00011110, 0b00000101, 0b00000101, 0b00000101, 0b00011110}, // 65 A +{0b00011111, 0b00010101, 0b00010101, 0b00010101, 0b00001010}, // 66 B +{0b00001110, 0b00010001, 0b00010001, 0b00010001, 0b00010001}, // 67 C +{0b00011111, 0b00010001, 0b00010001, 0b00010001, 0b00001110}, // 68 D +{0b00011111, 0b00010101, 0b00010101, 0b00010001, 0b00010001}, // 69 E +{0b00011111, 0b00000101, 0b00000101, 0b00000001, 0b00000001}, // 70 F +{0b00001110, 0b00010001, 0b00010101, 0b00010101, 0b00001101}, // 71 G +{0b00011111, 0b00000100, 0b00000100, 0b00000100, 0b00011111}, // 72 H +{0b00010001, 0b00010001, 0b00011111, 0b00010001, 0b00010001}, // 73 I +{0b00001000, 0b00010000, 0b00010000, 0b00010001, 0b00001111}, // 74 J +{0b00011111, 0b00000100, 0b00001010, 0b00010001, 0b00010001}, // 75 K +{0b00011111, 0b00010000, 0b00010000, 0b00010000, 0b00010000}, // 76 L +{0b00011111, 0b00000010, 0b00000100, 0b00000010, 0b00011111}, // 77 M +{0b00011111, 0b00000010, 0b00000100, 0b00001000, 0b00011111}, // 78 N +{0b00001110, 0b00010001, 0b00010001, 0b00010001, 0b00001110}, // 79 O +{0b00011111, 0b00000101, 0b00000101, 0b00000101, 0b00000010}, // 80 P +{0b00001110, 0b00010001, 0b00010001, 0b00001001, 0b00010110}, // 81 Q +{0b00011111, 0b00000101, 0b00000101, 0b00001101, 0b00010010}, // 82 R +{0b00010010, 0b00010101, 0b00010101, 0b00010101, 0b00001001}, // 83 S +{0b00000001, 0b00000001, 0b00011111, 0b00000001, 0b00000001}, // 84 T +{0b00001111, 0b00010000, 0b00010000, 0b00010000, 0b00001111}, // 85 U +{0b00000011, 0b00001100, 0b00010000, 0b00001100, 0b00000011}, // 86 V +{0b00000111, 0b00011000, 0b00000110, 0b00011000, 0b00000111}, // 87 W +{0b00010001, 0b00001010, 0b00000100, 0b00001010, 0b00010001}, // 88 X +{0b00000001, 0b00000010, 0b00011100, 0b00000010, 0b00000001}, // 89 Y +{0b00010001, 0b00011001, 0b00010101, 0b00010011, 0b00010001}, // 90 Z +{0b00000000, 0b00000000, 0b00011111, 0b00010001, 0b00010001}, // 91 [ +{0b00000001, 0b00000010, 0b00000100, 0b00001000, 0b00010000}, // 92 \ +{0b00010001, 0b00010001, 0b00011111, 0b00000000, 0b00000000}, // 93 ] +{0b00000100, 0b00000010, 0b00000001, 0b00000010, 0b00000100}, // 94 ^ +{0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000}, // 95 _ +{0b00011111, 0b00010001, 0b00010001, 0b00010001, 0b00011111}}; // 96 + + + + + + diff --git a/messages.h b/messages.h new file mode 100644 index 0000000..6b5e995 --- /dev/null +++ b/messages.h @@ -0,0 +1,11 @@ +// Scrolling messages + +const char string0[] PROGMEM = "WORLD MAKER FAIRE NYC 2015 "; +const char string1[] PROGMEM = "RGB SHADES! "; +const char string2[] PROGMEM = "macetech.com "; + +const char * const stringArray[] PROGMEM = { + string0, + string1, + string2 +}; diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..690b69c --- /dev/null +++ b/utils.h @@ -0,0 +1,199 @@ +// Assorted useful functions and variables + +// Global variables +boolean effectInit = false; // indicates if a pattern has been recently switched +uint16_t effectDelay = 0; // time between automatic effect changes +unsigned long effectMillis = 0; // store the time of last effect function run +unsigned long cycleMillis = 0; // store the time of last effect change +unsigned long currentMillis; // store current loop's millis value +unsigned long hueMillis; // store time of last hue change +unsigned long eepromMillis; // store time of last setting change +unsigned long audioMillis; // store time of last audio update +byte currentEffect = 0; // index to the currently running effect +boolean autoCycle = true; // flag for automatic effect changes +boolean eepromOutdated = false; // flag for when EEPROM may need to be updated +byte currentBrightness = STARTBRIGHTNESS; // 0-255 will be scaled to 0-MAXBRIGHTNESS + +CRGBPalette16 currentPalette(RainbowColors_p); // global palette storage + +typedef void (*functionList)(); // definition for list of effect function pointers +extern const byte numEffects; + + +// Increment the global hue value for functions that use it +byte cycleHue = 0; +byte cycleHueCount = 0; +void hueCycle(byte incr) { + cycleHueCount = 0; + cycleHue+=incr; +} + +// Set every LED in the array to a specified color +void fillAll(CRGB fillColor) { + for (byte i = 0; i < NUM_LEDS; i++) { + leds[i] = fillColor; + } +} + +// Fade every LED in the array by a specified amount +void fadeAll(byte fadeIncr) { + for (byte i = 0; i < NUM_LEDS; i++) { + leds[i] = leds[i].fadeToBlackBy(fadeIncr); + } +} + +// Shift all pixels by one, right or left (0 or 1) +void scrollArray(byte scrollDir) { + + byte scrollX = 0; + for (byte x = 1; x < kMatrixWidth; x++) { + if (scrollDir == 0) { + scrollX = kMatrixWidth - x; + } else if (scrollDir == 1) { + scrollX = x - 1; + } + + for (byte y = 0; y < kMatrixHeight; y++) { + leds[XY(scrollX,y)] = leds[XY(scrollX + scrollDir*2 - 1,y)]; + } + } + +} + + +// Pick a random palette from a list +void selectRandomPalette() { + + switch(random8(8)) { + case 0: + currentPalette = CloudColors_p; + break; + + case 1: + currentPalette = LavaColors_p; + break; + + case 2: + currentPalette = OceanColors_p; + break; + + case 4: + currentPalette = ForestColors_p; + break; + + case 5: + currentPalette = RainbowColors_p; + break; + + case 6: + currentPalette = PartyColors_p; + break; + + case 7: + currentPalette = HeatColors_p; + break; + } + +} + +// Pick a random palette from a list +void selectRandomAudioPalette() { + + switch(random8(8)) { + case 0: + currentPalette = CRGBPalette16(CRGB::Red, CRGB::Orange, CRGB::Gray); + break; + + case 1: + currentPalette = CRGBPalette16(CRGB::Blue, CRGB::Red, CRGB::Red); + break; + + case 2: + currentPalette = CRGBPalette16(CRGB::LightGrey, CRGB::MidnightBlue, CRGB::Black); + break; + + case 4: + currentPalette = CRGBPalette16(CRGB::DarkGreen, CRGB::PaleGreen); + break; + + case 5: + currentPalette = RainbowColors_p; + break; + + case 6: + currentPalette = PartyColors_p; + break; + + case 7: + currentPalette = HeatColors_p; + break; + } + +} + + + +// Interrupt normal operation to indicate that auto cycle mode has changed +void confirmBlink() { + + if (autoCycle) { // one blue blink, auto mode active + fillAll(CRGB::DarkBlue); + FastLED.show(); + FastLED.delay(200); + fillAll(CRGB::Black); + FastLED.delay(200); + } else { // two red blinks, manual mode active + fillAll(CRGB::DarkRed); + FastLED.show(); + FastLED.delay(200); + fillAll(CRGB::Black); + FastLED.delay(200); + fillAll(CRGB::DarkRed); + FastLED.show(); + FastLED.delay(200); + fillAll(CRGB::Black); + FastLED.delay(200); + } + +} + +// Determine flash address of text string +unsigned int currentStringAddress = 0; +void selectFlashString(byte string) { + currentStringAddress = pgm_read_word(&stringArray[string]); +} + +// Fetch font character bitmap from flash +byte charBuffer[5] = {0}; +void loadCharBuffer(byte character) { + byte mappedCharacter = character; + if (mappedCharacter >= 32 && mappedCharacter <= 95) { + mappedCharacter -= 32; // subtract font array offset + } else if (mappedCharacter >= 97 && mappedCharacter <= 122) { + mappedCharacter -= 64; // subtract font array offset and convert lowercase to uppercase + } else { + mappedCharacter = 96; // unknown character block + } + + for (byte i = 0; i < 5; i++) { + charBuffer[i] = pgm_read_byte(Font[mappedCharacter]+i); + } + +} + +// Fetch a character value from a text string in flash +char loadStringChar(byte string, byte character) { + return (char) pgm_read_byte(currentStringAddress + character); +} + +void checkEEPROM() { + if (eepromOutdated) { + if (currentMillis - eepromMillis > EEPROMDELAY) { + EEPROM.update(0, 99); + EEPROM.update(1, currentEffect); + EEPROM.update(2, autoCycle); + EEPROM.update(3, currentBrightness); + eepromOutdated = false; + } + } +}