#include <FastLED.h>
#include <AsyncTimer.h>
#include <DmxSimple.h>
#define NUM_LEDS 60
#define TOTAL_LEDS 120
#define SWITCH_LEFT 4
#define SWITCH_RIGHT 6
#define LED_PIN_L 9
#define LED_PIN_R 10
#define DMXPIN 12
#define BRIGHTNESS 200
#define POTENTIOMETER_PIN_WIND A0
#define POTENTIOMETER_PIN_BRIGHTNESS A2
#define POTENTIOMETER_PIN_FADE A3
#define POTENTIOMETER_PIN_BOTTOM A5
#define POTENTIOMETER_PIN_TOP A4
CRGB ledsL[NUM_LEDS];
CRGB ledsR[NUM_LEDS];
uint8_t starIntensity[TOTAL_LEDS]; // Array to hold the intensity of the stars
uint8_t starIncreasing[TOTAL_LEDS];
uint8_t windNoise = 120;
uint16_t windX = 0;
uint16_t x = 0;
uint16_t y = 0; // Position along the noise function
uint8_t bloodHue = 96; // Blood color [hue from 0-255]
uint8_t bloodSat = 255; // Blood staturation [0-255]
int flowDirection = -1; // Use either 1 or -1 to set flow direction
uint16_t cycleLength = 3000; // Lover values = continuous flow, higher values = distinct pulses.
uint16_t pulseLength = 500; // How long the pulse takes to fade out. Higher value is longer.
uint16_t pulseOffset = 750; // Delay before second pulse. Higher value is more delay.
uint8_t baseBrightness = 10; // Brightness of LEDs when not pulsing. Set to 0 for off.
// Case variables
enum State { STARS,
NOISE,
HEARTBEAT
};
State currentState = NOISE;
State targetState = NOISE;
int caseEffectsDuration = 60000; // Duration of case effects in milliseconds
// Transition Variables
uint8_t maxBrightness = BRIGHTNESS;
uint8_t currentBrightness = maxBrightness;
bool effectStarted = false;
bool transitionInProgress = false;
/* int transitionDuration = 4000; // Duration of the fade in/out milliseconds */
AsyncTimer t;
uint16_t windVal = 150;
/* unsigned long effectStartTime = millis(); */
/* unsigned long transitionStartTime; */
// Define a palette.
DEFINE_GRADIENT_PALETTE(NorthernLightsPalette){
0, 0, 207, 82, // Colder colors
62, 3, 46, 62,
143, 25, 100, 106,
192, 0, 198, 144,
255, 0, 223, 150
};
DEFINE_GRADIENT_PALETTE(WarmNorthernLightsPalette){
0, 255, 40, 5, // Warmer reds and oranges
62, 255, 60, 5,
143, 255, 80, 0,
180, 255, 100, 0,
200, 255, 0, 0,
255, 255, 0, 0
};
CRGBPalette16 currentPalette = CRGBPalette16(NorthernLightsPalette);
CRGBPalette16 warmPalette = CRGBPalette16(WarmNorthernLightsPalette);
void setup() {
FastLED.addLeds<WS2811, LED_PIN_L, BRG>(ledsL, NUM_LEDS);
FastLED.addLeds<WS2811, LED_PIN_R, BRG>(ledsR, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
Serial.begin(57600);
DmxSimple.usePin(12);
DmxSimple.maxChannel(6);
pinMode(POTENTIOMETER_PIN_WIND, INPUT);
pinMode(POTENTIOMETER_PIN_FADE, INPUT);
pinMode(POTENTIOMETER_PIN_BOTTOM, INPUT);
pinMode(POTENTIOMETER_PIN_TOP, INPUT);
pinMode(SWITCH_LEFT, INPUT_PULLUP);
pinMode(SWITCH_RIGHT, INPUT_PULLUP);
memset(starIntensity, 0, TOTAL_LEDS); // Initialize the starIntensity array
memset(starIncreasing, false, TOTAL_LEDS);
}
void loop() {
// map(analogRead(POTENTIOMETER_PIN_TOP), 0, 1023, 0, NUM_LEDS - 1)
// Read the potentiometer values
int potValueFade = map(analogRead(POTENTIOMETER_PIN_FADE), 0, 1023, 0, 50);
int potValueBottom = map(analogRead(POTENTIOMETER_PIN_BOTTOM), 0, 1023, 0, NUM_LEDS - 1);
int potValueTop = NUM_LEDS - 1;
int potValueWind = map(analogRead(POTENTIOMETER_PIN_WIND), 0, 1023, 120, 250);
if (currentState == NOISE && transitionInProgress == false) {
maxBrightness = map(analogRead(POTENTIOMETER_PIN_BRIGHTNESS), 0, 1023, 0, BRIGHTNESS);
currentBrightness = maxBrightness;
FastLED.setBrightness(currentBrightness);
}
int switchLeft = digitalRead(SWITCH_LEFT) == 0 ? true : false;
int switchRight = digitalRead(SWITCH_RIGHT) == 0 ? true : false;
t.handle();
if (switchRight) {
windVal = map(analogRead(POTENTIOMETER_PIN_TOP), 0, 1023, 110, 350);
windNoise = map(min(analogRead(POTENTIOMETER_PIN_WIND), windVal), 80, windVal, 120, 255);
windX++;
} else {
windNoise = potValueWind;
}
// Ensure top is always above bottom
if (potValueTop < potValueBottom) {
potValueTop = potValueBottom;
}
if (currentState != targetState && transitionInProgress) {
EVERY_N_MILLISECONDS(40) {
FastLED.setBrightness(currentBrightness);
currentBrightness--;
if (FastLED.getBrightness() <= 0) {
currentState = targetState;
currentBrightness = 0;
}
}
}
if (currentState == targetState && transitionInProgress) {
EVERY_N_MILLISECONDS(40) {
FastLED.setBrightness(currentBrightness);
currentBrightness++;
if (FastLED.getBrightness() >= maxBrightness) {
currentBrightness = maxBrightness;
transitionInProgress = false;
}
}
}
// State management
switch (currentState) {
case HEARTBEAT:
// Heartbeat/pulse effect code
heartBeat(switchLeft);
if (transitionInProgress == false && effectStarted == true) {
effectStarted = false;
t.setTimeout(resetTransition, 60000);
}
break;
case STARS:
// Stars effect code
EVERY_N_MILLISECONDS(20) {
int windStars;
if (windNoise <= 120) {
windStars = 250;
} else if (windNoise > 120 && windNoise <= 200) {
windStars = map(windNoise, 121, 201, 250, 300);
} else {
windStars = 300;
}
addRandomStars(windStars);
}
if (transitionInProgress == false && effectStarted == true) {
effectStarted = false;
t.setTimeout(resetTransition, 60000);
}
break;
case NOISE:
// Noise effect code
EVERY_N_SECONDS(120) {
if (!transitionInProgress) {
uint8_t rNumber = random8();
if (rNumber <= 4) { // Adjust this value as needed
Serial.println("STARS");
currentState = STARS;
effectStarted = true;
} else if (rNumber > 4 && rNumber <= 8) { // Adjust this value as needed
Serial.println("HEARTBEAT");
targetState = HEARTBEAT;
transitionInProgress = true;
effectStarted = true;
}
}
}
EVERY_N_MILLISECONDS(10) {
int windSpeed;
int hueValue;
if (windNoise <= 120) {
hueValue = 0;
windSpeed = 3;
} else if (windNoise > 120 && windNoise <= 175) {
hueValue = map(windNoise, 121, 176, 0, 255);
windSpeed = map(windNoise, 121, 176, 3, 10);
} else {
windSpeed = 10;
hueValue = 255;
}
fill_noise(potValueBottom, potValueTop, hueValue, potValueFade);
x += windSpeed;
}
break;
}
int numSegments = 5;
int segmentSize = NUM_LEDS / numSegments;
for (int segment = 0; segment < numSegments; ++segment) {
CRGB avgColor = calculateAverageColor(ledsL, segment * segmentSize, (segment + 1) * segmentSize - 1);
DmxSimple.write(segment * 3 + 1, avgColor.r);
DmxSimple.write(segment * 3 + 2, avgColor.g);
DmxSimple.write(segment * 3 + 3, avgColor.b);
}
FastLED.show();
}
// Function for generating a nothern light effect
void fill_noise(int startLED, int endLED, int valueHue, int fade) {
uint8_t blendFactor = valueHue;
int fadeLength = fade; // Number of LEDs to fade in and out
for (int i = 0; i < NUM_LEDS; i++) {
if (i >= startLED && i <= endLED) {
uint8_t noise = inoise8(i * 15 + x, 0, i * 15 + x);
CRGB color1 = ColorFromPalette(currentPalette, noise);
CRGB color2 = ColorFromPalette(warmPalette, noise);
CRGB blendedColor = blend(color1, color2, blendFactor);
// Linear fade logic
int linearFadeFactor = 255;
if (i - startLED < fadeLength) { // Fade in logic
linearFadeFactor = map(i - startLED, 0, fadeLength - 1, 0, 255);
uint8_t noiseFade = inoise8(i * 125 + x + x, 0, i * 125 + x + x);
int noiseFadeFactor = map(noiseFade, 0, 255, 0, 255);
linearFadeFactor = (noiseFadeFactor * linearFadeFactor) / 255;
}
blendedColor.nscale8(linearFadeFactor);
ledsL[i] = blendedColor;
ledsR[i] = blendedColor;
} else {
ledsL[i] = CRGB::Black;
ledsR[i] = CRGB::Black;
}
}
}
// Function for generating a shimmering star effect
void addRandomStars(int speed) {
for (int i = 0; i < TOTAL_LEDS; i++) {
// Start a new star with a low probability and only if it's not already a star
if (random16() < speed && starIntensity[i] == 0) {
starIntensity[i] = 1;
starIncreasing[i] = true;
}
// Increase or decrease the intensity of the star
if (starIntensity[i] > 0) {
if (starIncreasing[i]) {
// Increase intensity (fade in)
if (starIntensity[i] < 245) {
starIntensity[i] += 10;
} else {
starIntensity[i] = 255;
starIncreasing[i] = false; // Peak reached, start decreasing
}
} else {
// Decrease intensity (fade out)
if (starIntensity[i] > 10) {
starIntensity[i] -= 10;
} else {
starIntensity[i] = 0; // Star has faded out
if (i < NUM_LEDS) {
ledsL[i] = CRGB::Black; // Ensure LED is turned off
} else {
ledsR[i % NUM_LEDS] = CRGB::Black;
}
}
// Update the LED color
if (starIntensity[i] > 0) {
if (i < NUM_LEDS) {
ledsL[i] = CHSV(0, 0, starIntensity[i]);
} else {
ledsR[i % NUM_LEDS] = CHSV(0, 0, starIntensity[i]);
}
}
}
}
}
}
// Function for generating a heartbeat/pulse effect
void heartBeat(int changeSwitch) {
if (!changeSwitch) {
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t bloodVal = sumPulse((5 / NUM_LEDS / 2) + (NUM_LEDS / 2) * i * flowDirection);
ledsL[i] = CHSV(bloodHue, bloodSat, bloodVal);
ledsR[i] = CHSV(bloodHue, bloodSat, bloodVal);
}
} else {
for (int i = 0; i < TOTAL_LEDS; i++) {
if (i < NUM_LEDS) {
uint8_t bloodVal = sumPulse((5 / TOTAL_LEDS / 2) + (TOTAL_LEDS / 2) * i * flowDirection);
ledsL[i] = CHSV(bloodHue, bloodSat, bloodVal);
} else {
uint8_t bloodVal = sumPulse((5 / TOTAL_LEDS / 2) + (TOTAL_LEDS / 2) * i * -flowDirection);
ledsR[i % NUM_LEDS] = CHSV(bloodHue, bloodSat, bloodVal);
}
}
}
}
int sumPulse(int time_shift) {
//time_shift = 0; //Uncomment to heart beat/pulse all LEDs together
int pulse1 = pulseWave8(millis() + time_shift, cycleLength, pulseLength);
int pulse2 = pulseWave8(millis() + time_shift + pulseOffset, cycleLength, pulseLength);
return qadd8(pulse1, pulse2); // Add pulses together without overflow
}
uint8_t pulseWave8(uint32_t ms, uint16_t cycleLength, uint16_t pulseLength) {
uint16_t T = ms % cycleLength;
if (T > pulseLength) return baseBrightness;
uint16_t halfPulse = pulseLength / 2;
if (T <= halfPulse) {
return (T * 255) / halfPulse; //first half = going up
} else {
return ((pulseLength - T) * 255) / halfPulse; //second half = going down
}
}
void resetTransition() {
transitionInProgress = true;
targetState = NOISE;
}
CRGB calculateAverageColor(CRGB *ledArray, int startIndex, int endIndex) {
long totalR = 0, totalG = 0, totalB = 0;
int count = 0;
for (int i = startIndex; i <= endIndex; i++) {
totalR += ledArray[i].r;
totalG += ledArray[i].g;
totalB += ledArray[i].b;
count++;
}
if (count == 0) return CRGB::Black; // Avoid division by zero
return CRGB(totalR / count, totalG / count, totalB / count);
}