#include <Arduino.h>
#include <FastLED.h>
// Configuration
#define NUM_LEDS 16
#define LED_PIN 2
// Analog input pins
#define SATURATION_PIN A0
#define SPEED_PIN A1
#define DUTY_CYCLE_PIN A2
#define SPATIAL_FREQ_PIN A3
#define COLOR_SPATIAL_PIN A4
#define COLOR_TIME_PIN A5
CRGB leds[NUM_LEDS];
uint32_t lastUpdate = 0;
float brightnessTime = 0.0f;
float colorTime = 0.0f;
// Animation parameters
float saturation;
float speed;
float dutyCycle;
float spatialFrequency;
float colorSpatialScale;
float colorTimeScale;
// Constants
const float MIN_COLOR_TIME = 0.0f; // No color change over time
const float MAX_COLOR_TIME = 5.0f; // Increased maximum speed (was 2.0)
const int MAX_WAVES = 4; // Maximum number of brightness waves
const int DUTY_CYCLE_STEPS = 8; // Increased number of steps
const float DUTY_CYCLE_LEVELS[] = {
0.01f, // Start lower
0.30f, // More steps in
0.40f, // the lower range
0.50f, // for better
0.60f, // control
0.70f,
0.80f,
1.00f
};
// Helper function to map analog input to float range
float mapFloat(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
float smoothStep(float x) {
return x * x * (3 - 2 * x);
}
CRGB calculateLedColor(float position) {
// Make position wrap smoothly around the circle
float circularPos = fmod(position + 1.0f, 1.0f);
// Calculate brightness with overlapping waves
float brightness = 0.0f;
float waveWidth = 1.0f / spatialFrequency; // Width of each wave
// Sum up contributions from multiple waves
for (int i = 0; i < spatialFrequency; i++) {
float wavePhase = fmod(brightnessTime - circularPos + (i * waveWidth), 1.0f);
float wave = sin(wavePhase * 2.0f * PI);
wave = (wave + 1.0f) * 0.5f;
// Apply sharpness
float sharpness = pow(20.0f, (1.0f - dutyCycle));
wave = pow(wave, sharpness);
// Add this wave's contribution
brightness = max(brightness, wave);
}
// Apply duty cycle blend
brightness = brightness + (1.0f - brightness) * dutyCycle;
if (brightness <= 0.01f) {
return CRGB::Black;
}
float spread = colorSpatialScale;
float hueOffset;
if (spread < 0.01f) {
hueOffset = 0;
} else {
// Calculate rotating center point
float center = fmod(colorTime * 0.1f, 1.0f); // Slow rotation of the center
// Calculate position relative to moving center
float relativePos = circularPos - center;
if (relativePos < -0.5f) relativePos += 1.0f;
if (relativePos > 0.5f) relativePos -= 1.0f;
// Scale the offset based on spread
hueOffset = relativePos * spread;
}
float hue = fmod(colorTime + hueOffset, 1.0f);
return CHSV(
hue * 255,
saturation * 255,
brightness * 255
);
}
void setup() {
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(255);
}
void loop() {
// Read control values from potentiometers
saturation = analogRead(SATURATION_PIN) / 1023.0f;
float rawSpeed = analogRead(SPEED_PIN) / 1023.0f;
speed = pow(rawSpeed, 2.5f); // More gradual curve, still reaches 1.0 at maximum
// Calculate duty cycle with exponential mapping for more sensitivity in lower range
float rawPosition = analogRead(DUTY_CYCLE_PIN) / 1023.0f;
rawPosition = pow(rawPosition, 1.5f); // Make more sensitive at low values
float scaledPosition = rawPosition * (DUTY_CYCLE_STEPS - 1);
int lowerIndex = floor(scaledPosition);
int upperIndex = min(lowerIndex + 1, DUTY_CYCLE_STEPS - 1);
float blend = smoothStep(scaledPosition - lowerIndex);
dutyCycle = DUTY_CYCLE_LEVELS[lowerIndex] * (1.0f - blend) +
DUTY_CYCLE_LEVELS[upperIndex] * blend;
// Calculate wave count
int waveCount = 1 + (analogRead(SPATIAL_FREQ_PIN) * MAX_WAVES) / 1024;
spatialFrequency = float(waveCount);
// Read color controls with exponential mapping for A5
colorSpatialScale = analogRead(COLOR_SPATIAL_PIN) / 1023.0f;
float rawTimeScale = analogRead(COLOR_TIME_PIN) / 1023.0f;
colorTimeScale = pow(rawTimeScale, 2.0f) * MAX_COLOR_TIME; // Exponential response
// Update time
uint32_t currentTime = millis();
float deltaTime = (currentTime - lastUpdate) / 1000.0f;
lastUpdate = currentTime;
// Brightness timing affected by A1 - start from 0 speed
float baseSpeed = speed * 3.0f; // Increased multiplier to maintain max speed
brightnessTime += deltaTime * baseSpeed;
// Color timing independent of A1
colorTime += deltaTime * colorTimeScale; // Remove baseSpeed dependency
// Update LEDs
for(int i = 0; i < NUM_LEDS; i++) {
float position = (float)i / NUM_LEDS;
leds[i] = calculateLedColor(position);
}
FastLED.show();
}