├── .gitignore ├── hardware └── img │ ├── pcb.jpg │ ├── rail.jpg │ ├── board.jpg │ ├── pcb-fix.jpg │ ├── audio_top.jpg │ ├── overview.jpg │ ├── audio_bottom.jpg │ ├── diffuser-top.jpg │ ├── assembled_side.jpg │ ├── disassembled_1.jpg │ ├── disassembled_2.jpg │ ├── assembled_bottom.jpg │ ├── assembled_front.jpg │ ├── diffuser-bottom.jpg │ └── infrared_receiver.jpg ├── src ├── main.cpp ├── fx │ ├── Blackout.cpp │ ├── ColorBar.cpp │ ├── Background.cpp │ ├── Rainbow.cpp │ ├── Blur.h │ ├── ColorBar.h │ ├── Blackout.h │ ├── Rainbow.h │ ├── Background.h │ ├── PeakMeter.h │ ├── Spectrum.cpp │ ├── Scan.h │ ├── FastPulse.cpp │ ├── Sunset.h │ ├── SubtleWave.cpp │ ├── Glitter.cpp │ ├── Glitter.h │ ├── RainbowMelt.h │ ├── Spectrum.h │ ├── SubtleWave.h │ ├── ColorTwinkles.h │ ├── MarchingRainbow.h │ ├── FastPulse.h │ ├── RippleReflections.h │ ├── VU2.h │ ├── MarchingRainbow.cpp │ ├── VU1.h │ ├── ColorTwinkles.cpp │ ├── SineMeter.cpp │ ├── Blur.cpp │ ├── Sunset.cpp │ ├── Beat.h │ ├── RainbowMelt.cpp │ ├── Strobe.h │ ├── Spiral.h │ ├── Sinelon.h │ ├── Juggle.h │ ├── RippleReflections.cpp │ ├── PeakMeter.cpp │ ├── Strobe.cpp │ ├── SineMeter.h │ ├── Fire.h │ ├── Volcane.h │ ├── Juggle.cpp │ ├── Scan.cpp │ ├── Bounce.h │ ├── Ants.h │ ├── Scroller.cpp │ ├── Traffic.h │ ├── Scroller.h │ ├── Matrix.h │ ├── Jelly.h │ ├── Elastic.h │ ├── Chaser.h │ ├── Photons.h │ ├── VU2.cpp │ ├── Sinelon.cpp │ ├── Vertigo.h │ ├── Drops.h │ ├── Motion.h │ ├── Ripple.h │ ├── Sparks.h │ ├── Photons.cpp │ ├── Spiral.cpp │ ├── Fireworks.h │ ├── Bounce.cpp │ ├── Vertigo.cpp │ ├── Jelly.cpp │ ├── Ants.cpp │ ├── Motion.cpp │ ├── Elastic.cpp │ ├── Fire.cpp │ ├── Chaser.cpp │ ├── VU1.cpp │ ├── DeepSpace.h │ ├── Matrix.cpp │ ├── Volcane.cpp │ ├── Traffic.cpp │ ├── Fireworks.cpp │ ├── Orbit.cpp │ ├── Drops.cpp │ ├── Orbit.h │ ├── Beat.cpp │ ├── Ripple.cpp │ ├── Sparks.cpp │ └── DeepSpace.cpp ├── sysfx │ ├── CycleSpeed.h │ ├── SpeedMeter.h │ ├── MicGainMeter.h │ ├── InputLevelMeter.h │ ├── SpeedMeter.cpp │ ├── CycleSpeed.cpp │ ├── MicGainMeter.cpp │ └── InputLevelMeter.cpp ├── Pixel.h ├── audio │ ├── AudioTrigger.h │ ├── AudioTrigger.cpp │ ├── PeakDetector.h │ ├── PeakDetector.cpp │ ├── AudioChannel.h │ ├── AudioSensor.h │ ├── AudioChannel.cpp │ └── AudioSensor.cpp ├── Timer.h ├── strip │ ├── PhysicalStrip.h │ ├── BufferedStrip.h │ ├── virtual │ │ ├── ReversedStrip.h │ │ ├── SubStrip.h │ │ ├── JoinedStrip.h │ │ ├── ReversedStrip.cpp │ │ └── SubStrip.cpp │ ├── StatefulStrip.h │ ├── StatefulStrip.cpp │ └── Strip.h ├── Easing.h ├── Brightness.h ├── Pixel.cpp ├── State.h ├── Interval.h ├── Timer.cpp ├── State.cpp ├── EllipticMotion.h ├── Brightness.cpp ├── Striptease.h ├── Point.h ├── remote │ ├── IRRemote.h │ ├── SoundbridgeRemote.h │ ├── SonyRemote_RMD420.h │ └── SerialRemote.h ├── Gradient.h ├── EllipticMotion.cpp ├── Multiplex.h ├── Fx.h ├── HarmonicMotion.h ├── Controller.h ├── Stage.h └── HarmonicMotion.cpp ├── test └── README ├── platformio.ini ├── library.json ├── lib └── README └── include └── README /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .pio 3 | .vscode 4 | **/.DS_Store -------------------------------------------------------------------------------- /hardware/img/pcb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/pcb.jpg -------------------------------------------------------------------------------- /hardware/img/rail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/rail.jpg -------------------------------------------------------------------------------- /hardware/img/board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/board.jpg -------------------------------------------------------------------------------- /hardware/img/pcb-fix.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/pcb-fix.jpg -------------------------------------------------------------------------------- /hardware/img/audio_top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/audio_top.jpg -------------------------------------------------------------------------------- /hardware/img/overview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/overview.jpg -------------------------------------------------------------------------------- /hardware/img/audio_bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/audio_bottom.jpg -------------------------------------------------------------------------------- /hardware/img/diffuser-top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/diffuser-top.jpg -------------------------------------------------------------------------------- /hardware/img/assembled_side.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/assembled_side.jpg -------------------------------------------------------------------------------- /hardware/img/disassembled_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/disassembled_1.jpg -------------------------------------------------------------------------------- /hardware/img/disassembled_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/disassembled_2.jpg -------------------------------------------------------------------------------- /hardware/img/assembled_bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/assembled_bottom.jpg -------------------------------------------------------------------------------- /hardware/img/assembled_front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/assembled_front.jpg -------------------------------------------------------------------------------- /hardware/img/diffuser-bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/diffuser-bottom.jpg -------------------------------------------------------------------------------- /hardware/img/infrared_receiver.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lpaolini/Striptease/HEAD/hardware/img/infrared_receiver.jpg -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // Dummy main.cpp 2 | 3 | #include 4 | 5 | void setup() { 6 | } 7 | 8 | void loop() { 9 | } 10 | -------------------------------------------------------------------------------- /src/fx/Blackout.cpp: -------------------------------------------------------------------------------- 1 | #include "Blackout.h" 2 | 3 | Blackout::Blackout(Strip *strip) : Fx(strip) {} 4 | 5 | void Blackout::reset() { 6 | clear(); 7 | } 8 | 9 | void Blackout::loop() { 10 | clear(); 11 | } 12 | -------------------------------------------------------------------------------- /src/fx/ColorBar.cpp: -------------------------------------------------------------------------------- 1 | #include "ColorBar.h" 2 | 3 | ColorBar::ColorBar(Strip *strip, State *state) : Fx(strip, state) {} 4 | 5 | void ColorBar::loop() { 6 | strip->paint(CHSV(255 * state->linearFxSpeed, 255, 128), false); 7 | } 8 | -------------------------------------------------------------------------------- /src/fx/Background.cpp: -------------------------------------------------------------------------------- 1 | #include "Background.h" 2 | 3 | Background::Background(Strip *strip, CRGB color) : Fx(strip) { 4 | this->color = color; 5 | } 6 | 7 | void Background::loop() { 8 | strip->paint(color, false); 9 | } 10 | -------------------------------------------------------------------------------- /src/fx/Rainbow.cpp: -------------------------------------------------------------------------------- 1 | #include "Rainbow.h" 2 | 3 | Rainbow::Rainbow(Strip *strip, State *state, uint8_t phase) : Fx(strip, state) { 4 | this->phase = phase; 5 | } 6 | 7 | void Rainbow::loop() { 8 | strip->rainbow(state->rotatingHue + phase); 9 | } 10 | -------------------------------------------------------------------------------- /src/fx/Blur.h: -------------------------------------------------------------------------------- 1 | #ifndef Blur_h 2 | #define Blur_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | 8 | class Blur : public Fx { 9 | public: 10 | Blur(Strip *strip); 11 | void loop(); 12 | }; 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/fx/ColorBar.h: -------------------------------------------------------------------------------- 1 | #ifndef ColorBar_h 2 | #define ColorBar_h 3 | 4 | #include 5 | #include "Fx.h" 6 | 7 | class ColorBar : public Fx { 8 | public: 9 | ColorBar(Strip *strip, State *state); 10 | void loop(); 11 | }; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/fx/Blackout.h: -------------------------------------------------------------------------------- 1 | #ifndef Blackout_h 2 | #define Blackout_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | 8 | class Blackout: public Fx { 9 | public: 10 | Blackout(Strip *strip); 11 | void loop(); 12 | void reset(); 13 | }; 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/fx/Rainbow.h: -------------------------------------------------------------------------------- 1 | #ifndef Rainbow_h 2 | #define Rainbow_h 3 | 4 | #include 5 | #include "Fx.h" 6 | 7 | class Rainbow : public Fx { 8 | private: 9 | uint8_t phase; 10 | 11 | public: 12 | Rainbow(Strip *strip, State *state, uint8_t phase = 0); 13 | void loop(); 14 | }; 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /src/fx/Background.h: -------------------------------------------------------------------------------- 1 | #ifndef Background_h 2 | #define Background_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | 8 | class Background: public Fx { 9 | private: 10 | CRGB color; 11 | 12 | public: 13 | Background(Strip *strip, CRGB color = CRGB::Black); 14 | void loop(); 15 | }; 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/sysfx/CycleSpeed.h: -------------------------------------------------------------------------------- 1 | #ifndef CycleSpeed_h 2 | #define CycleSpeed_h 3 | 4 | #include 5 | #include "Fx.h" 6 | #include "HarmonicMotion.h" 7 | 8 | class CycleSpeed : public Fx { 9 | private: 10 | HarmonicMotion slider; 11 | 12 | public: 13 | CycleSpeed(Strip *strip, State *state); 14 | void reset(); 15 | void loop(); 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/sysfx/SpeedMeter.h: -------------------------------------------------------------------------------- 1 | #ifndef SpeedMeter_h 2 | #define SpeedMeter_h 3 | 4 | #include 5 | #include "Fx.h" 6 | #include "HarmonicMotion.h" 7 | 8 | class SpeedMeter : public Fx { 9 | private: 10 | HarmonicMotion slider; 11 | 12 | public: 13 | SpeedMeter(Strip *strip, State *state); 14 | void reset(); 15 | void loop(); 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/fx/PeakMeter.h: -------------------------------------------------------------------------------- 1 | #ifndef PeakMeter_h 2 | #define PeakMeter_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "Timer.h" 8 | #include "Pixel.h" 9 | 10 | class PeakMeter : public Fx { 11 | private: 12 | Pixel pixel; 13 | elapsedMillis beat; 14 | 15 | public: 16 | PeakMeter(Strip *strip, AudioChannel *audioChannel); 17 | void loop(); 18 | void reset(); 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/Pixel.h: -------------------------------------------------------------------------------- 1 | #ifndef Pixel_h 2 | #define Pixel_h 3 | 4 | #include 5 | #include "strip/Strip.h" 6 | 7 | class Pixel { 8 | private: 9 | Strip *strip; 10 | bool first; 11 | int pos0; 12 | 13 | public: 14 | Pixel(); 15 | void setup(Strip *strip); 16 | void reset(); 17 | bool set(int pos, CRGB color, bool overlay = true); 18 | bool setNormalized(double pos, CRGB color); 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/fx/Spectrum.cpp: -------------------------------------------------------------------------------- 1 | #include "Spectrum.h" 2 | 3 | Spectrum::Spectrum(Strip *strip, AudioChannel *audioChannel) : Fx(strip, audioChannel) { 4 | segmentSize = floor(strip->size() / 16); 5 | } 6 | 7 | void Spectrum::loop() { 8 | strip->fade(20); 9 | for (int i = 0; i < 16; i++) { 10 | CRGB color = audioChannel->bands[i].peakDetected ? CRGB::Red : CRGB::Black; 11 | strip->paint(i * segmentSize + 1, (i + 1) * segmentSize - 2, color, true); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/fx/Scan.h: -------------------------------------------------------------------------------- 1 | #ifndef Scan_h 2 | #define Scan_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "HarmonicMotion.h" 8 | #include "Gradient.h" 9 | 10 | class Scan : public Fx { 11 | private: 12 | Gradient *GRADIENT = new Gradient(CRGB::Red, CRGB::Green, CRGB::Blue); 13 | HarmonicMotion item; 14 | 15 | public: 16 | Scan(Strip *strip, State *state); 17 | void loop(); 18 | void reset(); 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/audio/AudioTrigger.h: -------------------------------------------------------------------------------- 1 | #ifndef AudioTrigger_h 2 | #define AudioTrigger_h 3 | 4 | #include "AudioChannel.h" 5 | 6 | class AudioTrigger { 7 | private: 8 | AudioChannel *audioChannel; 9 | bool beatDetected; 10 | elapsedMicros timer; 11 | 12 | public: 13 | AudioTrigger(AudioChannel *audioChannel); 14 | void reset(); 15 | void loop(); 16 | bool triggered(double noSignalEventsPerSecond = 0, double signalEventsPerSecond = 0); 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/fx/FastPulse.cpp: -------------------------------------------------------------------------------- 1 | #include "FastPulse.h" 2 | 3 | FastPulse::FastPulse(Strip *strip, AudioChannel *audioChannel, State *state, CRGB pulseColor) : Fx(strip, audioChannel, state) { 4 | this->pulseColor = pulseColor; 5 | reset(); 6 | } 7 | 8 | void FastPulse::beforeRender() { 9 | t1 = int1.time(.1, state->linearFxSpeed); 10 | } 11 | 12 | CRGB FastPulse::render(int16_t index, double x) { 13 | double v = pow(triangle(frac(2 * wave(t1) + x)), 5); 14 | return v < .9 ? hsv(t1, 1, v) : pulseColor; 15 | } 16 | -------------------------------------------------------------------------------- /src/fx/Sunset.h: -------------------------------------------------------------------------------- 1 | #ifndef Sunset_h 2 | #define Sunset_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "HarmonicMotion.h" 8 | #include "Gradient.h" 9 | 10 | class Sunset : public Fx { 11 | private: 12 | Gradient *GRADIENT = new Gradient(CRGB::Blue, CRGB::Red, CRGB::Gold, CRGB::Red, CRGB::Blue); 13 | HarmonicMotion item; 14 | 15 | public: 16 | Sunset(Strip *strip, State *state); 17 | void loop(); 18 | void reset(); 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/fx/SubtleWave.cpp: -------------------------------------------------------------------------------- 1 | #include "SubtleWave.h" 2 | 3 | SubtleWave::SubtleWave(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | reset(); 5 | } 6 | 7 | void SubtleWave::beforeRender() { 8 | t1 = int1.time(.03, state->linearFxSpeed); 9 | t2 = int2.time(.04, state->linearFxSpeed); 10 | } 11 | 12 | CRGB SubtleWave::render(int16_t index, double x) { 13 | double v = pow((sin(TWO_PI * (t1 - 7 * x)) + sin(TWO_PI * (t1 - 10 * x)) + 4) / 6, 2); 14 | return hsv(.9, 1, v); 15 | } 16 | -------------------------------------------------------------------------------- /src/fx/Glitter.cpp: -------------------------------------------------------------------------------- 1 | #include "Glitter.h" 2 | 3 | Glitter::Glitter(Strip *strip, State *state) : Fx(strip, state) {} 4 | 5 | void Glitter::reset() { 6 | clear(); 7 | fadeTimer.reset(); 8 | timer.reset(); 9 | } 10 | 11 | void Glitter::loop() { 12 | if (fadeTimer.isElapsed()) { 13 | strip->fade(max(1, 20 * state->linearFxSpeed)); 14 | } 15 | if (timer.isElapsed(100 * (1 - state->linearFxSpeed))) { 16 | strip->paintRandomPos(SEGMENT_LENGTH, CHSV(state->rotatingHue + random8(64), 255, 255), true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/fx/Glitter.h: -------------------------------------------------------------------------------- 1 | #ifndef Glitter_h 2 | #define Glitter_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "Timer.h" 8 | 9 | class Glitter : public Fx { 10 | private: 11 | static const uint8_t SEGMENT_LENGTH = 10; 12 | static const uint8_t FADE_RATE = 30; 13 | Timer timer = Timer(0, true); 14 | Timer fadeTimer = Timer(10, true); 15 | 16 | public: 17 | Glitter(Strip *strip, State *state); 18 | void loop(); 19 | void reset(); 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/fx/RainbowMelt.h: -------------------------------------------------------------------------------- 1 | #ifndef RainbowMelt_h 2 | #define RainbowMelt_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "Fx.h" 8 | #include "Interval.h" 9 | #include "Pixel.h" 10 | 11 | class RainbowMelt : public Fx { 12 | private: 13 | Interval int1; 14 | double t1; 15 | 16 | public: 17 | RainbowMelt(Strip *strip, AudioChannel *audioChannel, State *state); 18 | void beforeRender(); 19 | CRGB render(int16_t index, double x); 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /src/fx/Spectrum.h: -------------------------------------------------------------------------------- 1 | #ifndef Spectrum_h 2 | #define Spectrum_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "Fx.h" 8 | #include "Timer.h" 9 | #include "HarmonicMotion.h" 10 | 11 | class Spectrum : public Fx { 12 | private: 13 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::Black, CRGB::Blue, CRGB::Red, CRGB::Yellow); 14 | uint8_t segmentSize; 15 | 16 | public: 17 | Spectrum(Strip *strip, AudioChannel *audioChannel); 18 | void loop(); 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/fx/SubtleWave.h: -------------------------------------------------------------------------------- 1 | #ifndef SubtleWave_h 2 | #define SubtleWave_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "Fx.h" 8 | #include "Interval.h" 9 | #include "Pixel.h" 10 | 11 | class SubtleWave : public Fx { 12 | private: 13 | Interval int1, int2; 14 | double t1, t2; 15 | 16 | public: 17 | SubtleWave(Strip *strip, AudioChannel *audioChannel, State *state); 18 | void beforeRender(); 19 | CRGB render(int16_t index, double x); 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/fx/ColorTwinkles.h: -------------------------------------------------------------------------------- 1 | #ifndef ColorTwinkles_h 2 | #define ColorTwinkles_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "Fx.h" 8 | #include "Interval.h" 9 | #include "Pixel.h" 10 | 11 | class ColorTwinkles : public Fx { 12 | private: 13 | Interval int1, int2; 14 | double t1, t2; 15 | 16 | public: 17 | ColorTwinkles(Strip *strip, AudioChannel *audioChannel, State *state); 18 | void beforeRender(); 19 | CRGB render(int16_t index, double x); 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/Timer.h: -------------------------------------------------------------------------------- 1 | #ifndef Timer_h 2 | #define Timer_h 3 | 4 | #include 5 | 6 | class Timer { 7 | private: 8 | unsigned long last; 9 | unsigned long duration; 10 | bool running; 11 | bool autoReset; 12 | 13 | public: 14 | explicit Timer(unsigned long duration, bool autoReset = true); 15 | void stop(); 16 | void reset(); 17 | void reset(unsigned long duration); 18 | bool isElapsed(); 19 | bool isElapsed(unsigned long duration); 20 | bool isRunning(); 21 | }; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/fx/MarchingRainbow.h: -------------------------------------------------------------------------------- 1 | #ifndef MarchingRainbow_h 2 | #define MarchingRainbow_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "Fx.h" 8 | #include "Interval.h" 9 | #include "Pixel.h" 10 | 11 | class MarchingRainbow : public Fx { 12 | private: 13 | Interval int1, int2; 14 | double t1, t2; 15 | 16 | public: 17 | MarchingRainbow(Strip *strip, AudioChannel *audioChannel, State *state); 18 | void beforeRender(); 19 | CRGB render(int16_t index, double x); 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/fx/FastPulse.h: -------------------------------------------------------------------------------- 1 | #ifndef FastPulse_h 2 | #define FastPulse_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "Fx.h" 8 | #include "Interval.h" 9 | #include "Pixel.h" 10 | 11 | class FastPulse : public Fx { 12 | private: 13 | Interval int1; 14 | double t1; 15 | CRGB pulseColor; 16 | 17 | public: 18 | FastPulse(Strip *strip, AudioChannel *audioChannel, State *state, CRGB pulseColor = CRGB::White); 19 | void beforeRender(); 20 | CRGB render(int16_t index, double x); 21 | }; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/fx/RippleReflections.h: -------------------------------------------------------------------------------- 1 | #ifndef RippleReflections_h 2 | #define RippleReflections_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "Fx.h" 8 | #include "Interval.h" 9 | #include "Pixel.h" 10 | 11 | class RippleReflections : public Fx { 12 | private: 13 | Interval int1, int2, int3; 14 | double t1, t2, t3; 15 | 16 | public: 17 | RippleReflections(Strip *strip, AudioChannel *audioChannel, State *state); 18 | void beforeRender(); 19 | CRGB render(int16_t index, double x); 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/sysfx/MicGainMeter.h: -------------------------------------------------------------------------------- 1 | #ifndef MicGainMeter_h 2 | #define MicGainMeter_h 3 | 4 | #include 5 | #include "Fx.h" 6 | #include "audio/AudioSensor.h" 7 | 8 | class MicGainMeter : public Fx { 9 | private: 10 | const static uint16_t CLIP_HOLD = 500; 11 | const static uint16_t BEAT_HOLD = 100; 12 | AudioSensor *audioSensor; 13 | elapsedMillis clipTimer; 14 | elapsedMillis beatTimer; 15 | 16 | public: 17 | MicGainMeter(Strip *strip, AudioChannel *audioChannel, AudioSensor *audioSensor); 18 | void loop(); 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/sysfx/InputLevelMeter.h: -------------------------------------------------------------------------------- 1 | #ifndef InputLevelMeter_h 2 | #define InputLevelMeter_h 3 | 4 | #include 5 | #include "Fx.h" 6 | #include "audio/AudioSensor.h" 7 | 8 | class InputLevelMeter : public Fx { 9 | private: 10 | const static uint16_t CLIP_HOLD = 500; 11 | const static uint16_t BEAT_HOLD = 100; 12 | AudioSensor *audioSensor; 13 | elapsedMillis clipTimer; 14 | elapsedMillis beatTimer; 15 | 16 | public: 17 | InputLevelMeter(Strip *strip, AudioChannel *audioChannel, AudioSensor *audioSensor); 18 | void loop(); 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/fx/VU2.h: -------------------------------------------------------------------------------- 1 | #ifndef VU2_h 2 | #define VU2_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "Timer.h" 8 | #include "HarmonicMotion.h" 9 | 10 | class VU2 : public Fx { 11 | private: 12 | uint16_t size; 13 | double elasticConstant; 14 | CRGB color; 15 | HarmonicMotion peak; 16 | void resetPeak(); 17 | 18 | public: 19 | VU2(Strip *strip, AudioChannel *audioChannel, uint16_t size = 10, double elasticConstant = 1000, CRGB color = CRGB::Red); 20 | void loop(); 21 | void reset(); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/fx/MarchingRainbow.cpp: -------------------------------------------------------------------------------- 1 | #include "MarchingRainbow.h" 2 | 3 | MarchingRainbow::MarchingRainbow(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | reset(); 5 | } 6 | 7 | void MarchingRainbow::beforeRender() { 8 | t1 = int1.time(.1, state->linearFxSpeed); 9 | t2 = int2.time(.05, state->linearFxSpeed); 10 | } 11 | 12 | CRGB MarchingRainbow::render(int16_t index, double x) { 13 | double w1 = wave(t1 + x); 14 | double w2 = wave(t2 - 10 * x + .2); 15 | double v = w1 - w2; 16 | double h = wave(wave(wave(t1 + x)) - x); 17 | return hsv(h, 1, v); 18 | } 19 | -------------------------------------------------------------------------------- /src/fx/VU1.h: -------------------------------------------------------------------------------- 1 | #ifndef VU1_h 2 | #define VU1_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "Timer.h" 8 | #include "HarmonicMotion.h" 9 | 10 | class VU1 : public Fx { 11 | private: 12 | HarmonicMotion vu; 13 | HarmonicMotion peak; 14 | HarmonicMotion peakHold; 15 | Timer fadeTimer = Timer(10); 16 | void resetVU(); 17 | void resetPeak(); 18 | void resetPeakHold(); 19 | 20 | public: 21 | VU1(Strip *strip, AudioChannel *audioChannel, State *state); 22 | void loop(); 23 | void reset(); 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/fx/ColorTwinkles.cpp: -------------------------------------------------------------------------------- 1 | #include "ColorTwinkles.h" 2 | 3 | ColorTwinkles::ColorTwinkles(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | reset(); 5 | } 6 | 7 | void ColorTwinkles::beforeRender() { 8 | t1 = int1.time(.5, state->linearFxSpeed) * TWO_PI; 9 | t2 = int2.time(.15, state->linearFxSpeed) * TWO_PI; 10 | } 11 | 12 | CRGB ColorTwinkles::render(int16_t index, double x) { 13 | double a = pow((1 + sin(index / 3 + TWO_PI * sin(index / 2 + t1))) / 2, 4) / 2; 14 | double b = sin(index / 3 + TWO_PI * sin(index / 2 + t2)); 15 | return hsv(b, 1, a > .05 ? a : 0); 16 | } 17 | -------------------------------------------------------------------------------- /src/fx/SineMeter.cpp: -------------------------------------------------------------------------------- 1 | #include "SineMeter.h" 2 | 3 | SineMeter::SineMeter(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | pixel.setup(strip); 5 | reset(); 6 | } 7 | 8 | void SineMeter::reset() { 9 | clear(); 10 | pixel.reset(); 11 | } 12 | 13 | void SineMeter::loop() { 14 | if (fadeTimer > 10) { 15 | fadeTimer -= 10; 16 | strip->fade(1 + 10 * state->parabolicFxSpeed); 17 | } 18 | int pos = beatsin16(10 + 100 * state->parabolicFxSpeed, 0, strip->last()); 19 | pixel.set(pos, ColorFromPalette(PALETTE, min(255, audioChannel->rms * 1000))); 20 | } 21 | -------------------------------------------------------------------------------- /src/fx/Blur.cpp: -------------------------------------------------------------------------------- 1 | #include "Blur.h" 2 | 3 | Blur::Blur(Strip *strip) : Fx(strip) {} 4 | 5 | void Blur::loop() { 6 | strip->blur(dim8_raw(beatsin8(3, 64, 192))); 7 | 8 | uint16_t i = beatsin16(9, 0, strip->last()); 9 | uint16_t j = beatsin16(7, 0, strip->last()); 10 | uint16_t k = beatsin16(5, 0, strip->last()); 11 | 12 | unsigned long ms = millis(); 13 | strip->paint((i + j) / 2, CHSV(ms / 29, 200, 255), true); 14 | strip->paint((j + k) / 2, CHSV(ms / 41, 200, 255), true); 15 | strip->paint((k + i) / 2, CHSV(ms / 73, 200, 255), true); 16 | strip->paint((k + i + j) / 3, CHSV(ms / 53, 200, 255), true); 17 | } 18 | -------------------------------------------------------------------------------- /src/fx/Sunset.cpp: -------------------------------------------------------------------------------- 1 | #include "Sunset.h" 2 | 3 | Sunset::Sunset(Strip *strip, State *state) : Fx(strip, state) { 4 | item.setup(strip); 5 | } 6 | 7 | void Sunset::reset() { 8 | item.reset() 9 | .setGradient(GRADIENT) 10 | .setElasticConstant(1) 11 | .setPosition(strip->center()) 12 | .setFixedPointPosition(strip->center()) 13 | .setVelocity(strip->size() * (.25 + .5 * state->linearFxSpeed)) 14 | .setRange(-strip->size() / 4, strip->size() / 4) 15 | .setMirror(false) 16 | .setShowWhenStable(true); 17 | } 18 | 19 | void Sunset::loop() { 20 | clear(); 21 | item.loop(); 22 | } 23 | -------------------------------------------------------------------------------- /src/fx/Beat.h: -------------------------------------------------------------------------------- 1 | #ifndef Beat_h 2 | #define Beat_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "audio/AudioTrigger.h" 8 | #include "Fx.h" 9 | #include "Timer.h" 10 | #include "HarmonicMotion.h" 11 | 12 | class Beat : public Fx { 13 | private: 14 | AudioTrigger *audioTrigger; 15 | HarmonicMotion peak; 16 | HarmonicMotion peakHold; 17 | HarmonicMotion peakHoldSlow; 18 | Timer timer = Timer(10); 19 | 20 | public: 21 | Beat(Strip *strip, AudioChannel *audioChannel); 22 | ~Beat(); 23 | void loop(); 24 | void reset(); 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/fx/RainbowMelt.cpp: -------------------------------------------------------------------------------- 1 | #include "RainbowMelt.h" 2 | 3 | RainbowMelt::RainbowMelt(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | reset(); 5 | } 6 | 7 | void RainbowMelt::beforeRender() { 8 | t1 = int1.time(.02, state->linearFxSpeed); // Time it takes for regions to move and melt 9 | } 10 | 11 | CRGB RainbowMelt::render(int16_t index, double x) { 12 | double c1 = 2 * abs(x - .5); // 0 at strip endpoints, 1 in the middle 13 | double c2 = wave(c1); 14 | double c3 = wave(c2 + t1); 15 | double v = pow(wave(c3 + t1), 2); // Separate the colors with dark regions 16 | return hsv(c1 + t1, 1, v); 17 | } 18 | -------------------------------------------------------------------------------- /src/strip/PhysicalStrip.h: -------------------------------------------------------------------------------- 1 | #ifndef PhysicalStrip_h 2 | #define PhysicalStrip_h 3 | 4 | #include 5 | #include "Strip.h" 6 | #include "BufferedStrip.h" 7 | #include "StatefulStrip.h" 8 | 9 | template 10 | class PhysicalStrip : public StatefulStrip { 11 | private: 12 | CRGBArray array; 13 | 14 | public: 15 | PhysicalStrip() : StatefulStrip(array, DENSITY) { 16 | FastLED.addLeds(array, SIZE); 17 | } 18 | 19 | Strip *overlay(double opacity) { 20 | return new BufferedStrip(this, opacity); 21 | } 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:teensy41] 12 | platform = teensy 13 | board = teensy41 14 | framework = arduino 15 | lib_deps = 16 | https://github.com/PaulStoffregen/Audio 17 | https://github.com/PaulStoffregen/WS2812Serial 18 | fastled/FastLED@3.9.10 19 | irmp-org/IRMP@3.6.4 20 | -------------------------------------------------------------------------------- /src/fx/Strobe.h: -------------------------------------------------------------------------------- 1 | #ifndef Strobe_h 2 | #define Strobe_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "audio/AudioTrigger.h" 8 | #include "Fx.h" 9 | #include "Timer.h" 10 | 11 | class Strobe : public Fx { 12 | private: 13 | const uint8_t SEGMENT_SIZE = 10; 14 | const CRGBPalette16 PALETTE = CRGBPalette16(PartyColors_p); 15 | AudioTrigger *audioTrigger; 16 | Timer timer = Timer(100); 17 | 18 | public: 19 | Strobe(Strip *strip, AudioChannel *audioChannel, State *state); 20 | ~Strobe(); 21 | void loop(); 22 | void reset(); 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/fx/Spiral.h: -------------------------------------------------------------------------------- 1 | #ifndef Spiral_h 2 | #define Spiral_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "EllipticMotion.h" 8 | 9 | class Spiral : public Fx { 10 | private: 11 | static constexpr double MIN_ANGULAR_SPEED = 20; 12 | static constexpr double MAX_ANGULAR_SPEED = 120; 13 | Timer fadeTimer = Timer(5); 14 | EllipticMotion *items; 15 | uint16_t count; 16 | double turns; 17 | double eccentricity; 18 | 19 | public: 20 | Spiral(Strip *strip, State *state, uint16_t count = 10, double turns = 1, double eccentricity = 0); 21 | void loop(); 22 | void reset(); 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/fx/Sinelon.h: -------------------------------------------------------------------------------- 1 | #ifndef Sinelon_h 2 | #define Sinelon_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "Pixel.h" 8 | 9 | class Sinelon : public Fx { 10 | private: 11 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::Blue, CRGB::Aqua, CRGB::White); 12 | Pixel pixel1, pixel2, pixel3, pixel4; 13 | uint8_t freq1 = 11; 14 | uint8_t freq2 = 17; 15 | uint8_t freq3 = 23; 16 | uint8_t fade = 10; // How quickly does it fade? Lower = slower fade rate. 17 | 18 | public: 19 | Sinelon(Strip *strip, State *state); 20 | void loop(); 21 | void reset(); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/fx/Juggle.h: -------------------------------------------------------------------------------- 1 | #ifndef Juggle_h 2 | #define Juggle_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "Pixel.h" 8 | 9 | class Juggle : public Fx { 10 | private: 11 | static const uint8_t DOTS = 5; 12 | static const uint8_t HUE_INCREMENT = 40; 13 | static const uint8_t MIN_BEAT = 1; 14 | static const uint8_t MAX_BEAT = 15; 15 | static const uint8_t MIN_FADE_RATE = 1; 16 | static const uint8_t MAX_FADE_RATE = 10; 17 | Pixel pixel[DOTS]; 18 | elapsedMillis fadeTimer; 19 | 20 | public: 21 | Juggle(Strip *strip, State *state); 22 | void loop(); 23 | void reset(); 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/fx/RippleReflections.cpp: -------------------------------------------------------------------------------- 1 | #include "RippleReflections.h" 2 | 3 | RippleReflections::RippleReflections(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | reset(); 5 | } 6 | 7 | void RippleReflections::beforeRender() { 8 | t1 = TWO_PI * int1.time(.03, state->linearFxSpeed); 9 | t2 = TWO_PI * int2.time(.05, state->linearFxSpeed); 10 | t3 = TWO_PI * int3.time(.04, state->linearFxSpeed); 11 | } 12 | 13 | CRGB RippleReflections::render(int16_t index, double x) { 14 | double a = pow(sin(x * PI * 10 + t1), 2); 15 | double b = sin(x * PI * 6 - t2); 16 | double c = triangle((x * 3 + sin(t3)) / 2); 17 | double v = pow((a + b + c) / 3, 2); 18 | return hsv(.3, a, v); 19 | } 20 | -------------------------------------------------------------------------------- /src/fx/PeakMeter.cpp: -------------------------------------------------------------------------------- 1 | #include "PeakMeter.h" 2 | 3 | PeakMeter::PeakMeter(Strip *strip, AudioChannel *audioChannel) : Fx(strip, audioChannel) { 4 | pixel.setup(strip); 5 | reset(); 6 | } 7 | 8 | void PeakMeter::reset() { 9 | clear(); 10 | pixel.reset(); 11 | } 12 | 13 | void PeakMeter::loop() { 14 | strip->off(); 15 | double peakSmooth = audioChannel->peakSmooth; 16 | double peakHold = audioChannel->peakHold; 17 | if (audioChannel->beatDetected) { 18 | beat = 0; 19 | } 20 | strip->paintNormalized(0, peakSmooth, CRGB::Blue, false); 21 | if (beat < 100) { 22 | strip->paintNormalizedSize(0, 5, CRGB::Aqua, false); 23 | } 24 | strip->paintNormalized(peakHold, 1, CRGB::DarkRed, false); 25 | } 26 | -------------------------------------------------------------------------------- /src/audio/AudioTrigger.cpp: -------------------------------------------------------------------------------- 1 | #include "AudioTrigger.h" 2 | 3 | AudioTrigger::AudioTrigger(AudioChannel *audioChannel) { 4 | this->audioChannel = audioChannel; 5 | } 6 | 7 | void AudioTrigger::reset() { 8 | beatDetected = false; 9 | timer = 0; 10 | } 11 | 12 | void AudioTrigger::loop() { 13 | beatDetected = beatDetected || audioChannel->beatDetected; 14 | } 15 | 16 | bool AudioTrigger::triggered(double noSignalEventsPerSecond, double signalEventsPerSecond) { 17 | unsigned long us = timer; 18 | bool trigger = audioChannel->signalDetected 19 | ? beatDetected || audioChannel->beatDetected || random(1e6) < (signalEventsPerSecond * us) 20 | : random(1e6) < (noSignalEventsPerSecond * us); 21 | reset(); 22 | return trigger; 23 | } 24 | -------------------------------------------------------------------------------- /src/audio/PeakDetector.h: -------------------------------------------------------------------------------- 1 | #ifndef PeakDetector_h 2 | #define PeakDetector_h 3 | 4 | #include 5 | 6 | class PeakDetector { 7 | private: 8 | unsigned int bufferSize; 9 | double peakFactor; 10 | double peakInfluence; 11 | unsigned int peakInhibit; 12 | double peakThreshold; 13 | unsigned int skip; 14 | unsigned int ptr; 15 | double *buffer; 16 | double avg(); 17 | double stdDev(double mean); 18 | unsigned long lastPeak; 19 | 20 | public: 21 | PeakDetector(unsigned int bufferSize, double peakFactor, double peakInfluence, unsigned int peakInhibit, double peakThreshold); 22 | ~PeakDetector(); 23 | bool isPeak(double value); 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/fx/Strobe.cpp: -------------------------------------------------------------------------------- 1 | #include "Strobe.h" 2 | 3 | Strobe::Strobe(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | } 6 | 7 | Strobe::~Strobe() { 8 | delete audioTrigger; 9 | } 10 | 11 | void Strobe::reset() { 12 | clear(); 13 | timer.reset(); 14 | audioTrigger->reset(); 15 | } 16 | 17 | void Strobe::loop() { 18 | bool trigger = audioTrigger->triggered(1); 19 | 20 | if (trigger) { 21 | strip->off(); 22 | int count = random8(2, 5); 23 | CRGB color = ColorFromPalette(PALETTE, random8()); 24 | for (int i = 0; i < count; i++) { 25 | strip->paintRandomPos(SEGMENT_SIZE, color, true); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/strip/BufferedStrip.h: -------------------------------------------------------------------------------- 1 | #ifndef BufferedStrip_h 2 | #define BufferedStrip_h 3 | 4 | #include 5 | #include "Strip.h" 6 | #include "StatefulStrip.h" 7 | 8 | class BufferedStrip : public StatefulStrip { 9 | private: 10 | Strip *strip; 11 | double opacity; 12 | 13 | public: 14 | BufferedStrip(Strip *strip, double opacity) : StatefulStrip(new CRGBSet(new CRGB[strip->size()], strip->size())) { 15 | this->strip = strip; 16 | this->opacity = max(0L, min(1L, opacity)); 17 | } 18 | 19 | void flush() override { 20 | for (uint16_t i = 0; i < strip->size(); i++) { 21 | strip->paint(i, (*leds)[i].scale8((uint8_t)(255 * opacity)), true); 22 | } 23 | } 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/fx/SineMeter.h: -------------------------------------------------------------------------------- 1 | #ifndef SineMeter_h 2 | #define SineMeter_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "Pixel.h" 8 | 9 | class SineMeter : public Fx { 10 | private: 11 | // const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::Black, CRGB::Red, CRGB::Yellow, CRGB::White); 12 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::Blue, CRGB::Red); 13 | Pixel pixel; 14 | // uint8_t freq = 20; 15 | uint8_t fade = 1; // How quickly does it fade? Lower = slower fade rate. 16 | elapsedMillis fadeTimer; 17 | 18 | public: 19 | SineMeter(Strip *strip, AudioChannel *audioChannel, State *state); 20 | void loop(); 21 | void reset(); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/sysfx/SpeedMeter.cpp: -------------------------------------------------------------------------------- 1 | #include "SpeedMeter.h" 2 | 3 | SpeedMeter::SpeedMeter(Strip *strip, State *state) : Fx(strip, state) { 4 | slider.setup(strip); 5 | reset(); 6 | } 7 | 8 | void SpeedMeter::reset() { 9 | slider.reset() 10 | .setColor(CHSV(0, 0, 128)) 11 | .setElasticConstant(500) 12 | .setCriticalDamping() 13 | .setRange(-2, 2) 14 | .setPosition(strip->fromNormalizedPosition(state->linearFxSpeed)) 15 | .setFixedPointPosition(strip->fromNormalizedPosition(state->linearFxSpeed)) 16 | .setShowWhenStable(true); 17 | } 18 | 19 | void SpeedMeter::loop() { 20 | strip->paint(CHSV(160, 255, 50), false); 21 | slider 22 | .setFixedPointPosition(strip->fromNormalizedPosition(state->linearFxSpeed)) 23 | .loop(); 24 | } 25 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Striptease", 3 | "version": "1.4.0", 4 | "description": "Sexy, audio-responsive effects on LED strips.", 5 | "keywords": "LED, effects, audio-responsive, audio-reactive-lights", 6 | "url": "https://github.com/lpaolini/Striptease", 7 | "authors": [ 8 | { 9 | "name": "Luca Paolini", 10 | "email": "lookap@gmail.com", 11 | "maintainer": true 12 | } 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/lpaolini/Striptease" 17 | }, 18 | "dependencies": { 19 | "fastled/FastLED": "3.9.10", 20 | "irmp-org/IRMP": "3.6.4" 21 | }, 22 | "license": "GPL-3.0-or-later", 23 | "frameworks": "Arduino", 24 | "platforms": "Teensy 4.0" 25 | } 26 | -------------------------------------------------------------------------------- /src/fx/Fire.h: -------------------------------------------------------------------------------- 1 | #ifndef Fire_h 2 | #define Fire_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "audio/AudioTrigger.h" 8 | #include "Fx.h" 9 | #include "Timer.h" 10 | 11 | class Fire : public Fx { 12 | private: 13 | static const int SPARKING = 4; 14 | static const int COOLING = 55; 15 | const CRGBPalette16 HOT = CRGBPalette16(HeatColors_p); 16 | const CRGBPalette16 COLD = CRGBPalette16(CRGB::Black, CRGB::Blue, CRGB::Aqua, CRGB::White); 17 | AudioTrigger *audioTrigger; 18 | uint8_t *heat; 19 | CRGBPalette16 palette; 20 | 21 | public: 22 | Fire(Strip *strip, AudioChannel *audioChannel); 23 | ~Fire(); 24 | void loop(); 25 | void reset(); 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/fx/Volcane.h: -------------------------------------------------------------------------------- 1 | #ifndef Volcane_h 2 | #define Volcane_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioTrigger.h" 7 | #include "Fx.h" 8 | #include "HarmonicMotion.h" 9 | 10 | class Volcane: public Fx { 11 | private: 12 | static const int NUM_ITEMS = 10; 13 | struct Item { 14 | HarmonicMotion head; 15 | HarmonicMotion tail; 16 | }; 17 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::Blue, CRGB::Green); 18 | AudioTrigger *audioTrigger; 19 | Item items[NUM_ITEMS]; 20 | void restart(Item *item); 21 | 22 | public: 23 | Volcane(Strip *strip, AudioChannel *audioChannel, State *state); 24 | ~Volcane(); 25 | void loop(); 26 | void reset(); 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/sysfx/CycleSpeed.cpp: -------------------------------------------------------------------------------- 1 | #include "CycleSpeed.h" 2 | 3 | CycleSpeed::CycleSpeed(Strip *strip, State *state) : Fx(strip, state) { 4 | slider.setup(strip); 5 | reset(); 6 | } 7 | 8 | void CycleSpeed::reset() { 9 | slider.reset() 10 | .setColor(CHSV(0, 0, 128)) 11 | .setElasticConstant(500) 12 | .setCriticalDamping() 13 | .setRange(-2, 2) 14 | .setPosition(strip->fromNormalizedPosition(state->linearCycleSpeed)) 15 | .setFixedPointPosition(strip->fromNormalizedPosition(state->linearCycleSpeed)) 16 | .setShowWhenStable(true); 17 | } 18 | 19 | void CycleSpeed::loop() { 20 | strip->paint(CHSV(0, 255, 75), false); 21 | slider 22 | .setFixedPointPosition(strip->fromNormalizedPosition(state->linearCycleSpeed)) 23 | .loop(); 24 | } 25 | -------------------------------------------------------------------------------- /src/fx/Juggle.cpp: -------------------------------------------------------------------------------- 1 | #include "Juggle.h" 2 | 3 | Juggle::Juggle(Strip *strip, State *state) : Fx(strip, state) { 4 | for (uint8_t i = 0; i < DOTS; i++) { 5 | pixel[i].setup(strip); 6 | } 7 | } 8 | 9 | void Juggle::reset() { 10 | clear(); 11 | for (uint8_t i = 0; i < DOTS; i++) { 12 | pixel[i].reset(); 13 | } 14 | fadeTimer = 0; 15 | } 16 | 17 | void Juggle::loop() { 18 | if (fadeTimer >= 10) { 19 | fadeTimer -= 10; 20 | strip->fade(MIN_FADE_RATE + state->linearFxSpeed * (MAX_FADE_RATE - MIN_FADE_RATE)); 21 | } 22 | 23 | for (uint8_t i = 0; i < DOTS; i++) { 24 | uint8_t beat = MIN_BEAT + state->linearFxSpeed * (MAX_BEAT - MIN_BEAT) + i; 25 | pixel[i].set(beatsin16(beat, 0, strip->last()), CHSV(i * HUE_INCREMENT, 255, 255)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/fx/Scan.cpp: -------------------------------------------------------------------------------- 1 | #include "Scan.h" 2 | 3 | Scan::Scan(Strip *strip, State *state) : Fx(strip, state) { 4 | item.setup(strip); 5 | } 6 | 7 | void Scan::reset() { 8 | item.reset() 9 | .setGradient(GRADIENT) 10 | .setPosition(strip->center()) 11 | .setFixedPointPosition(strip->center()) 12 | .setVelocity(strip->size() * (.25 + .5 * state->linearFxSpeed)) 13 | .setRange(-strip->size() / 10, strip->size() / 10) 14 | .setLowerBound(strip->first(), -1, HarmonicMotion::INSIDE) 15 | .setUpperBound(strip->last(), -1, HarmonicMotion::INSIDE) 16 | .setShowWhenStable(true); 17 | } 18 | 19 | void Scan::loop() { 20 | clear(); 21 | item.loop(); 22 | strip->paint(strip->first(), CRGB::Gray, false); 23 | strip->paint(strip->last(), CRGB::Gray, false); 24 | } 25 | -------------------------------------------------------------------------------- /src/Easing.h: -------------------------------------------------------------------------------- 1 | #ifndef Easing_h 2 | #define Easing_h 3 | 4 | #include 5 | 6 | class Easing { 7 | public: 8 | static double easeInOutSine(double x) { 9 | return (1 - cos(PI * x)) / 2; 10 | } 11 | 12 | static double easeInSine(double x) { 13 | return 1 - cos((PI * x) / 2); 14 | } 15 | 16 | static double deltaEaseInOutSine(double x1, double x0) { 17 | return easeInOutSine(x1) - easeInOutSine(x0); 18 | } 19 | 20 | static double easeInOutCubic(double x) { 21 | return x < 0.5 22 | ? 4 * pow(x, 3) 23 | : 1 - pow(-2 * x + 2, 3) / 2; 24 | } 25 | 26 | static double deltaEaseInOutCubic(double x1, double x0) { 27 | return easeInOutCubic(x1) - easeInOutCubic(x0); 28 | } 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/Brightness.h: -------------------------------------------------------------------------------- 1 | #ifndef Brightness_h 2 | #define Brightness_h 3 | 4 | #include 5 | 6 | class Brightness { 7 | private: 8 | static const uint8_t BRIGHTNESS_STEPS = 40; 9 | static const uint8_t MIN_BRIGHTNESS = 20; 10 | static const uint8_t INITIAL_BRIGHTNESS = 10; 11 | static const uint16_t ON_DURATION_MS = 50; 12 | static const uint16_t OFF_DURATION_MS = 150; 13 | uint8_t brightness = INITIAL_BRIGHTNESS; 14 | uint8_t count = 0; 15 | bool on = false; 16 | elapsedMillis timer; 17 | void applyBrightness(bool dim = false); 18 | void dim(bool on); 19 | 20 | public: 21 | void init(); 22 | void set(uint8_t brightness); 23 | void increase(); 24 | void decrease(); 25 | void blink(uint8_t count); 26 | void loop(); 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/fx/Bounce.h: -------------------------------------------------------------------------------- 1 | #ifndef Bounce_h 2 | #define Bounce_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "HarmonicMotion.h" 10 | 11 | class Bounce : public Fx { 12 | private: 13 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::Red, CRGB::Gold, CRGB::Red, CRGB::Blue); 14 | static const uint16_t COUNT = 2; 15 | static const uint16_t SIZE = 10; 16 | uint16_t count; 17 | uint16_t size; 18 | HarmonicMotion *items; 19 | void loopItem(HarmonicMotion &item); 20 | void resetItem(HarmonicMotion &item); 21 | 22 | public: 23 | Bounce(Strip *strip, State *state, uint16_t count = COUNT, uint16_t size = SIZE); 24 | ~Bounce(); 25 | void loop(); 26 | void reset(); 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/fx/Ants.h: -------------------------------------------------------------------------------- 1 | #ifndef Ants_h 2 | #define Ants_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "HarmonicMotion.h" 10 | 11 | class Ants : public Fx { 12 | private: 13 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::Red, CRGB::Gold, CRGB::Red, CRGB::Blue); 14 | static const uint8_t ITEMS = 10; 15 | struct Item { 16 | HarmonicMotion item; 17 | uint8_t decay; 18 | }; 19 | AudioTrigger *audioTrigger; 20 | Item *items; 21 | void loopItem(Item &item, bool &trigger); 22 | void resetItem(Item &item); 23 | 24 | public: 25 | Ants(Strip *strip, AudioChannel *audioChannel, State *state); 26 | ~Ants(); 27 | void loop(); 28 | void reset(); 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/fx/Scroller.cpp: -------------------------------------------------------------------------------- 1 | #include "Scroller.h" 2 | 3 | Scroller::Scroller(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | } 6 | 7 | void Scroller::reset() { 8 | clear(); 9 | shiftTimer = 0; 10 | } 11 | 12 | void Scroller::loop() { 13 | if (slowDown) { 14 | if (audioTrigger->triggered(0, .5)) { 15 | slowDown = false; 16 | } 17 | } else { 18 | if (audioTrigger->triggered()) { 19 | slowDown = true; 20 | } 21 | } 22 | 23 | uint16_t delay = slowDown ? 50 * state->linearFxSpeed : 0; 24 | 25 | if (shiftTimer > delay) { 26 | shiftTimer -= delay; 27 | strip->shiftUp( 28 | ColorFromPalette(PALETTE, audioChannel->dominantBand * 16, audioChannel->peak * 255) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/fx/Traffic.h: -------------------------------------------------------------------------------- 1 | #ifndef Traffic_h 2 | #define Traffic_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "Gradient.h" 10 | #include "HarmonicMotion.h" 11 | 12 | class Traffic : public Fx { 13 | private: 14 | Gradient GRADIENT = Gradient(CRGB::Gold, CRGB::Red, CRGB::Blue, CRGB::Teal); 15 | static const uint8_t LAYERS = 10; 16 | static const uint8_t ITEMS = 100; 17 | AudioTrigger *audioTrigger; 18 | HarmonicMotion *items; 19 | void loopItem(HarmonicMotion &item, bool &trigger); 20 | void resetItem(HarmonicMotion &item); 21 | 22 | public: 23 | Traffic(Strip *strip, AudioChannel *audioChannel, State *stateAUpd); 24 | ~Traffic(); 25 | void loop(); 26 | void reset(); 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/fx/Scroller.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Thanks to Ben Rolling for inspiring this effect (color based on dominant frequency), and for the awesome palette! 3 | * Thanks to Antonio Petti for providing a great idea (instantaneous slow down on beat)! 4 | **/ 5 | 6 | #ifndef Scroller_h 7 | #define Scroller_h 8 | 9 | #include 10 | #include 11 | #include "audio/AudioTrigger.h" 12 | #include "Easing.h" 13 | #include "Fx.h" 14 | #include "Pixel.h" 15 | 16 | class Scroller : public Fx { 17 | private: 18 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::Blue, CRGB::Purple, CRGB::Red, CRGB::Yellow); 19 | elapsedMillis shiftTimer; 20 | bool slowDown = false; 21 | AudioTrigger *audioTrigger; 22 | 23 | public: 24 | Scroller(Strip *strip, AudioChannel *audioChannel, State *state); 25 | void loop(); 26 | void reset(); 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/fx/Matrix.h: -------------------------------------------------------------------------------- 1 | #ifndef Matrix_h 2 | #define Matrix_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "audio/AudioTrigger.h" 8 | #include "Fx.h" 9 | #include "Timer.h" 10 | 11 | class Matrix : public Fx { 12 | private: 13 | const CRGB DOWN_COLOR = CRGB::Green; 14 | const CRGB UP_COLOR = CRGB::Blue; 15 | static const unsigned int UP_PERIOD = 2; 16 | static const unsigned int DOWN_PERIOD = 4; 17 | AudioTrigger *audioTrigger; 18 | bool *down; 19 | bool *up; 20 | uint16_t countUp; 21 | uint16_t countDown; 22 | void addFromTop(); 23 | void addFromBottom(); 24 | void show(); 25 | 26 | public: 27 | Matrix(Strip *strip, AudioChannel *audioChannel, State *state); 28 | ~Matrix(); 29 | void loop(); 30 | void reset(); 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/fx/Jelly.h: -------------------------------------------------------------------------------- 1 | #ifndef Jelly_h 2 | #define Jelly_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "HarmonicMotion.h" 10 | #include "Gradient.h" 11 | #include "Timer.h" 12 | 13 | class Jelly : public Fx { 14 | private: 15 | Gradient GRADIENT = Gradient(CRGB::Red, CRGB::Gold, CRGB::Red, CRGB::Blue); 16 | static const uint8_t HALF_SIZE = 3; 17 | static const uint8_t ITEMS = 5; 18 | static const uint8_t FADE_RATE = 100; 19 | AudioTrigger *audioTrigger; 20 | HarmonicMotion items[ITEMS]; 21 | Timer moveTimer = Timer(100, true); 22 | Timer fadeTimer = Timer(10, true); 23 | 24 | public: 25 | Jelly(Strip *strip, AudioChannel *audioChannel, State *state); 26 | ~Jelly(); 27 | void loop(); 28 | void reset(); 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/fx/Elastic.h: -------------------------------------------------------------------------------- 1 | #ifndef Elastic_h 2 | #define Elastic_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "HarmonicMotion.h" 10 | #include "Timer.h" 11 | 12 | class Elastic : public Fx { 13 | private: 14 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::White, CRGB::Blue, CRGB::Aqua, CRGB::Red); 15 | AudioTrigger *audioTrigger; 16 | static const uint8_t ITEMS = 3; 17 | static const uint8_t FADE_RATE = 30; 18 | HarmonicMotion items[ITEMS]; 19 | uint8_t nextItem = 0; 20 | Timer fadeTimer = Timer(10); 21 | void randomizeItem(HarmonicMotion &item, double strength); 22 | 23 | public: 24 | Elastic(Strip *strip, AudioChannel *audioChannel, State *state); 25 | ~Elastic(); 26 | void loop(); 27 | void reset(); 28 | }; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/fx/Chaser.h: -------------------------------------------------------------------------------- 1 | #ifndef Chaser_h 2 | #define Chaser_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "HarmonicMotion.h" 10 | #include "Timer.h" 11 | 12 | class Chaser : public Fx { 13 | private: 14 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::Red, CRGB::Red, CRGB::White, CRGB::Aqua); 15 | const CRGB BACKGROUND_COLOR = CHSV(180, 255, 30); 16 | static const uint8_t ITEMS = 5; 17 | static const uint8_t FADE_RATE = 100; 18 | AudioTrigger *audioTrigger; 19 | HarmonicMotion items[ITEMS]; 20 | Timer moveTimer = Timer(100, true); 21 | Timer fadeTimer = Timer(10, true); 22 | 23 | public: 24 | Chaser(Strip *strip, AudioChannel *audioChannel, State *state); 25 | ~Chaser(); 26 | void loop(); 27 | void reset(); 28 | }; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/sysfx/MicGainMeter.cpp: -------------------------------------------------------------------------------- 1 | #include "MicGainMeter.h" 2 | 3 | MicGainMeter::MicGainMeter(Strip *strip, AudioChannel *audioChannel, AudioSensor *audioSensor) : Fx(strip, audioChannel) { 4 | this->audioSensor = audioSensor; 5 | } 6 | 7 | void MicGainMeter::loop() { 8 | strip->off(); 9 | strip->paintNormalized(0, 1, CHSV(128, 255, 75), false); 10 | strip->paintNormalized(0, audioSensor->getNormalizedMicGain(), CHSV(128, 255, 255), false); 11 | if (audioChannel->clipping) { 12 | clipTimer = 0; 13 | } 14 | if (audioChannel->beatDetected) { 15 | beatTimer = 0; 16 | } 17 | CRGB color = clipTimer < CLIP_HOLD 18 | ? CRGB::Red 19 | : beatTimer < BEAT_HOLD 20 | ? CRGB::Cyan 21 | : CRGB::White; 22 | strip->paintNormalizedSize(audioChannel->peakSmooth, 2, CRGB::White, true); 23 | strip->paintNormalizedSize(audioChannel->peakHold, 4, color, false); 24 | } 25 | -------------------------------------------------------------------------------- /src/fx/Photons.h: -------------------------------------------------------------------------------- 1 | #ifndef Photons_h 2 | #define Photons_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioSensor.h" 7 | #include "audio/AudioTrigger.h" 8 | #include "Fx.h" 9 | #include "HarmonicMotion.h" 10 | 11 | class Photons: public Fx { 12 | private: 13 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::White, CRGB::Aqua, CRGB::Red, CRGB::Blue); 14 | static constexpr double MIN_SPEED = 100; 15 | static constexpr double MAX_SPEED = 1000; 16 | static const uint8_t NUM_PHOTONS = 10; 17 | static const uint8_t MAX_CONCURRENT = 3; 18 | AudioTrigger *audioTrigger; 19 | HarmonicMotion items[NUM_PHOTONS]; 20 | 21 | public: 22 | Photons(Strip *strip, AudioChannel *audioChannel, State *state); 23 | ~Photons(); 24 | void resetItem(HarmonicMotion &item); 25 | void reset(); 26 | void loop(); 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/Pixel.cpp: -------------------------------------------------------------------------------- 1 | #include "Pixel.h" 2 | 3 | Pixel::Pixel() {}; 4 | 5 | void Pixel::setup(Strip *strip) { 6 | this->strip = strip; 7 | reset(); 8 | } 9 | 10 | void Pixel::reset() { 11 | first = true; 12 | } 13 | 14 | bool Pixel::set(int pos, CRGB color, bool overlay) { 15 | if (first) { 16 | pos0 = pos; 17 | first = false; 18 | } 19 | 20 | int posMin; 21 | int posMax; 22 | 23 | if (pos == pos0) { 24 | posMin = pos; 25 | posMax = pos; 26 | } else { 27 | if (pos < pos0) { 28 | posMin = pos; 29 | posMax = pos0 - 1; 30 | } else { 31 | posMin = pos0 + 1; 32 | posMax = pos; 33 | } 34 | } 35 | 36 | pos0 = pos; 37 | 38 | return strip->paint(posMin, posMax, color, overlay); 39 | } 40 | 41 | bool Pixel::setNormalized(double pos, CRGB color) { 42 | return set(pos * strip->last(), color); 43 | } 44 | -------------------------------------------------------------------------------- /src/State.h: -------------------------------------------------------------------------------- 1 | #ifndef State_h 2 | #define State_h 3 | 4 | #include 5 | 6 | class State { 7 | private: 8 | static const uint8_t DEFAULT_FX_SPEED = 20; 9 | static const uint8_t DEFAULT_CYCLE_SPEED = 50; 10 | elapsedMicros microseconds = 0; 11 | double rotatingHueInternal; 12 | uint8_t fxSpeed; 13 | uint8_t cycleSpeed; 14 | 15 | public: 16 | double linearFxSpeed; 17 | double parabolicFxSpeed; 18 | double linearCycleSpeed; 19 | uint8_t rotatingHue; 20 | State(); 21 | void setup(); 22 | void setFxSpeed(uint8_t speed); 23 | uint8_t getFxSpeed(); 24 | void decreaseFxSpeed(); 25 | void increaseFxSpeed(); 26 | void setCycleSpeed(uint8_t speed); 27 | uint8_t getCycleSpeed(); 28 | void decreaseCycleSpeed(); 29 | void increaseCycleSpeed(); 30 | void loop(); 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/fx/VU2.cpp: -------------------------------------------------------------------------------- 1 | #include "VU2.h" 2 | 3 | VU2::VU2(Strip *strip, AudioChannel *audioChannel, uint16_t size, double elasticConstant, CRGB color) : Fx(strip, audioChannel) { 4 | this->size = size; 5 | this->elasticConstant = elasticConstant; 6 | this->color = color; 7 | peak.setup(strip); 8 | reset(); 9 | } 10 | 11 | void VU2::reset() { 12 | clear(); 13 | resetPeak(); 14 | } 15 | 16 | void VU2::resetPeak() { 17 | peak.reset() 18 | .setColor(color) 19 | .setElasticConstant(elasticConstant) 20 | .setCriticalDamping() 21 | .setLowerBound(0) 22 | .setRange(0, max(0, size - 1)) 23 | .setPosition(strip->fromNormalizedPosition(audioChannel->peakSmooth)) 24 | .setShowWhenStable(false); 25 | } 26 | 27 | void VU2::loop() { 28 | strip->fade(20); 29 | peak 30 | .setFixedPointPosition(strip->fromNormalizedPosition(audioChannel->peakSmooth)) 31 | .loop(); 32 | } 33 | -------------------------------------------------------------------------------- /src/sysfx/InputLevelMeter.cpp: -------------------------------------------------------------------------------- 1 | #include "InputLevelMeter.h" 2 | 3 | InputLevelMeter::InputLevelMeter(Strip *strip, AudioChannel *audioChannel, AudioSensor *audioSensor) : Fx(strip, audioChannel) { 4 | this->audioSensor = audioSensor; 5 | } 6 | 7 | void InputLevelMeter::loop() { 8 | strip->off(); 9 | strip->paintNormalized(0, 1, CHSV(210, 255, 75), false); 10 | strip->paintNormalized(0, audioSensor->getNormalizedLineInLevel(), CHSV(210, 255, 255), false); 11 | if (audioChannel->clipping) { 12 | clipTimer = 0; 13 | } 14 | if (audioChannel->beatDetected) { 15 | beatTimer = 0; 16 | } 17 | CRGB color = clipTimer < CLIP_HOLD 18 | ? CRGB::Red 19 | : beatTimer < BEAT_HOLD 20 | ? CRGB::Cyan 21 | : CRGB::White; 22 | strip->paintNormalizedSize(audioChannel->peakSmooth, 2, CRGB::White, true); 23 | strip->paintNormalizedSize(audioChannel->peakHold, 4, color, false); 24 | } 25 | -------------------------------------------------------------------------------- /src/Interval.h: -------------------------------------------------------------------------------- 1 | #ifndef Interval_h 2 | #define Interval_h 3 | 4 | class Interval { 5 | private: 6 | elapsedMicros timeMicroseconds = 0; 7 | 8 | public: 9 | void reset() { 10 | timeMicroseconds = 0; 11 | } 12 | 13 | double time(double interval) { 14 | double intervalMicroseconds = interval * 65536000; 15 | while (timeMicroseconds >= intervalMicroseconds) { 16 | timeMicroseconds -= intervalMicroseconds; 17 | } 18 | return double(timeMicroseconds) / double(intervalMicroseconds); 19 | } 20 | 21 | double time(double stdInterval, double cursor) { 22 | return time(stdInterval / 3, stdInterval * 3, cursor); 23 | } 24 | 25 | double time(double minInterval, double maxInterval, double cursor) { 26 | return time(minInterval + (maxInterval - minInterval) * (1 - cursor)); 27 | } 28 | }; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/fx/Sinelon.cpp: -------------------------------------------------------------------------------- 1 | #include "Sinelon.h" 2 | 3 | Sinelon::Sinelon(Strip *strip, State *state) : Fx(strip, state) { 4 | pixel1.setup(strip); 5 | pixel2.setup(strip); 6 | pixel3.setup(strip); 7 | pixel4.setup(strip); 8 | reset(); 9 | } 10 | 11 | void Sinelon::reset() { 12 | clear(); 13 | pixel1.reset(); 14 | pixel2.reset(); 15 | pixel3.reset(); 16 | pixel4.reset(); 17 | } 18 | 19 | void Sinelon::loop() { 20 | strip->fade(fade); 21 | uint16_t pos1 = beatsin16(freq1, 0, strip->last()); 22 | uint16_t pos2 = beatsin16(freq2, 0, strip->last()); 23 | uint16_t pos3 = beatsin16(freq3, 0, strip->last()); 24 | pixel1.set((pos1 + pos2) / 2, ColorFromPalette(PALETTE, state->rotatingHue)); 25 | pixel2.set((pos2 + pos3) / 2, ColorFromPalette(PALETTE, state->rotatingHue + 64)); 26 | pixel3.set((pos1 + pos2 + pos3) / 3, ColorFromPalette(PALETTE, state->rotatingHue + 128)); 27 | pixel4.set((pos1 + pos3) / 2, ColorFromPalette(PALETTE, state->rotatingHue + 192)); 28 | } 29 | -------------------------------------------------------------------------------- /src/fx/Vertigo.h: -------------------------------------------------------------------------------- 1 | #ifndef Vertigo_h 2 | #define Vertigo_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "HarmonicMotion.h" 10 | #include "Timer.h" 11 | 12 | class Vertigo : public Fx { 13 | private: 14 | static const uint8_t ITEMS = 5; 15 | static const uint8_t FADE_RATE = 50; 16 | static const uint16_t INHIBIT_TIME_MS = 100; 17 | struct Item { 18 | HarmonicMotion ball; 19 | elapsedMillis timer; 20 | }; 21 | CRGB color; 22 | AudioTrigger *audioTrigger; 23 | Item items[ITEMS]; 24 | uint8_t nextItem = 0; 25 | Timer fadeTimer = Timer(10); 26 | Timer inhibitTimer = Timer(INHIBIT_TIME_MS); 27 | void randomizeItem(Item &item, double strength); 28 | 29 | public: 30 | Vertigo(Strip *strip, AudioChannel *audioChannel, State *state, CRGB color = CRGB::Blue); 31 | ~Vertigo(); 32 | void loop(); 33 | void reset(); 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/fx/Drops.h: -------------------------------------------------------------------------------- 1 | #ifndef Drops_h 2 | #define Drops_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "HarmonicMotion.h" 10 | 11 | class Drops : public Fx { 12 | private: 13 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::HotPink, CRGB::Blue, CRGB::Aqua, CRGB::Fuchsia); 14 | const CRGB BACKGROUND_COLOR = CRGB::Black; 15 | // const CRGB BACKGROUND_COLOR = CHSV(180, 255, 30); 16 | static const uint8_t ITEMS = 5; 17 | struct Item { 18 | HarmonicMotion center; 19 | HarmonicMotion sides; 20 | uint8_t decay; 21 | }; 22 | AudioTrigger *audioTrigger; 23 | Item *items; 24 | void loopItem(Item &item, bool &trigger, double strength); 25 | void randomizeItem(Item &item, double strength); 26 | 27 | public: 28 | Drops(Strip *strip, AudioChannel *audioChannel, State *state); 29 | ~Drops(); 30 | void loop(); 31 | void reset(); 32 | }; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/Timer.cpp: -------------------------------------------------------------------------------- 1 | #include "Timer.h" 2 | 3 | Timer::Timer(unsigned long duration, bool autoReset) { 4 | this->duration = duration; 5 | this->autoReset = autoReset; 6 | stop(); 7 | } 8 | 9 | void Timer::stop() { 10 | running = false; 11 | last = millis(); 12 | } 13 | 14 | void Timer::reset() { 15 | running = true; 16 | last = millis(); 17 | } 18 | 19 | void Timer::reset(unsigned long duration) { 20 | this->duration = duration; 21 | reset(); 22 | } 23 | 24 | bool Timer::isElapsed() { 25 | return duration > 0 && isElapsed(duration); 26 | } 27 | 28 | bool Timer::isElapsed(unsigned long duration) { 29 | if (running) { 30 | unsigned long current = millis(); 31 | if (current - last >= duration) { 32 | if (autoReset) { 33 | last += duration; 34 | if (current - last > duration) { 35 | last = current; 36 | } 37 | } else { 38 | running = false; 39 | } 40 | return true; 41 | } 42 | } 43 | return false; 44 | } 45 | 46 | bool Timer::isRunning() { 47 | return running; 48 | } 49 | -------------------------------------------------------------------------------- /src/fx/Motion.h: -------------------------------------------------------------------------------- 1 | #ifndef Motion_h 2 | #define Motion_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "HarmonicMotion.h" 10 | #include "Timer.h" 11 | 12 | class Motion : public Fx { 13 | private: 14 | static const uint8_t ITEMS = 5; 15 | static const uint8_t FADE_RATE = 200; 16 | static const uint16_t INHIBIT_TIME_MS = 100; 17 | struct Item { 18 | HarmonicMotion ball; 19 | }; 20 | CRGB color; 21 | double velocityFactor; 22 | double accelerationFactor; 23 | AudioTrigger *audioTrigger; 24 | Item items[ITEMS]; 25 | uint8_t nextItem = 0; 26 | Timer fadeTimer = Timer(10); 27 | Timer inhibitTimer = Timer(INHIBIT_TIME_MS); 28 | void resetItem(Item &item); 29 | 30 | public: 31 | Motion(Strip *strip, AudioChannel *audioChannel, State *state, CRGB color = CRGB::Blue, double velocityFactor = 100, double accelerationFactor = 100); 32 | ~Motion(); 33 | void loop(); 34 | void reset(); 35 | }; 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/fx/Ripple.h: -------------------------------------------------------------------------------- 1 | #ifndef Ripple_h 2 | #define Ripple_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "HarmonicMotion.h" 10 | 11 | class Ripple : public Fx { 12 | private: 13 | struct Item { 14 | HarmonicMotion ball; 15 | elapsedMillis timer; 16 | uint8_t decay; 17 | }; 18 | const CRGBPalette16 PALETTE = CRGBPalette16(CRGB::HotPink, CRGB::Blue, CRGB::Aqua, CRGB::Fuchsia); 19 | static const uint8_t ITEMS = 10; 20 | static const unsigned int DECAY_DELAY = 500; 21 | AudioTrigger *audioTrigger; 22 | Item *items; 23 | CRGB backgroundColor; 24 | Timer fadeTimer = Timer(10); 25 | void loopItem(Item &item, bool &trigger, double strength); 26 | void fadeItem(Item &item); 27 | void randomizeItem(Item &item, double strength); 28 | 29 | public: 30 | Ripple(Strip *strip, AudioChannel *audioChannel, State *state, CRGB backgroundColor = CHSV(160, 255, 50)); 31 | ~Ripple(); 32 | void loop(); 33 | void reset(); 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/fx/Sparks.h: -------------------------------------------------------------------------------- 1 | #ifndef Sparks_h 2 | #define Sparks_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "HarmonicMotion.h" 10 | #include "Timer.h" 11 | 12 | class Sparks : public Fx { 13 | private: 14 | const CRGB BACKGROUND_COLOR = CRGB::Black; 15 | static const uint8_t ITEMS = 3; 16 | static const uint16_t HOLD_OFF_TIME_MS = 100; 17 | struct Item { 18 | HarmonicMotion center; 19 | HarmonicMotion sides; 20 | }; 21 | CRGB centerColor; 22 | CRGB sidesColor; 23 | AudioTrigger *audioTrigger; 24 | Item items[ITEMS]; 25 | uint8_t nextItem = 0; 26 | Timer holdOffTimer = Timer(HOLD_OFF_TIME_MS); 27 | void loopItem(Item &item, bool &trigger, double strength); 28 | void randomizeItem(Item &item, double strength); 29 | 30 | public: 31 | Sparks(Strip *strip, AudioChannel *audioChannel, State *state, CRGB centerColor = CRGB::DarkOrange, CRGB sidesColor = CRGB::Blue); 32 | ~Sparks(); 33 | void loop(); 34 | void reset(); 35 | }; 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /src/fx/Photons.cpp: -------------------------------------------------------------------------------- 1 | #include "Photons.h" 2 | 3 | Photons::Photons(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | for (uint8_t i = 0; i < NUM_PHOTONS; i++) { 6 | items[i].setup(strip); 7 | } 8 | } 9 | 10 | Photons::~Photons() { 11 | delete audioTrigger; 12 | } 13 | 14 | void Photons::resetItem(HarmonicMotion &item) { 15 | item.reset() 16 | .setColor(ColorFromPalette(PALETTE, random8())) 17 | .setVelocity(MIN_SPEED + state->linearFxSpeed * random16(MAX_SPEED - MIN_SPEED)) 18 | .setUpperBound(strip->last()); 19 | } 20 | 21 | void Photons::reset() { 22 | clear(); 23 | for (uint8_t i = 0; i < NUM_PHOTONS; i++) { 24 | resetItem(items[i]); 25 | } 26 | audioTrigger->reset(); 27 | } 28 | 29 | void Photons::loop() { 30 | strip->fade(30); 31 | bool trigger = audioTrigger->triggered(1); 32 | 33 | uint8_t photons = MAX_CONCURRENT; 34 | 35 | for (uint8_t i = 0; i < NUM_PHOTONS; i++) { 36 | if (photons && trigger && items[i].isStable()) { 37 | resetItem(items[i]); 38 | photons--; 39 | } 40 | items[i].loop(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/fx/Spiral.cpp: -------------------------------------------------------------------------------- 1 | #include "Spiral.h" 2 | 3 | Spiral::Spiral(Strip *strip, State *state, uint16_t count, double turns, double eccentricity) : Fx(strip, state) { 4 | this->count = count; 5 | this->turns = turns; 6 | this->eccentricity = eccentricity; 7 | items = new EllipticMotion[count]; 8 | for (uint16_t i = 0; i < count; i++) { 9 | items[i].setup(strip); 10 | } 11 | } 12 | 13 | void Spiral::reset() { 14 | clear(); 15 | for (uint16_t i = 0; i < count; i++) { 16 | items[i].reset() 17 | // .setCenter(.5 + double(random(-50, 50)) / 100) 18 | .setCenter(.5) 19 | .setRadius(.5 / count * (i + 1)) 20 | .setAngle(turns * 2 * PI / count * i) 21 | .setEccentricity(eccentricity) 22 | .setEccentricityAngle(-HALF_PI) 23 | .setHue(160) 24 | .setAngularSpeed(0); 25 | } 26 | fadeTimer.reset(); 27 | } 28 | 29 | void Spiral::loop() { 30 | if (fadeTimer.isElapsed()) { 31 | strip->fade(100); 32 | } 33 | for (uint16_t i = 0; i < count; i++) { 34 | items[i] 35 | .setAngularSpeed(DEG_TO_RAD * (MIN_ANGULAR_SPEED + (MAX_ANGULAR_SPEED - MIN_ANGULAR_SPEED) * state->linearFxSpeed)) 36 | .loop(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/fx/Fireworks.h: -------------------------------------------------------------------------------- 1 | #ifndef Fireworks_h 2 | #define Fireworks_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "audio/AudioSensor.h" 8 | #include "audio/AudioTrigger.h" 9 | #include "HarmonicMotion.h" 10 | #include "Gradient.h" 11 | #include "Timer.h" 12 | 13 | class Fireworks : public Fx { 14 | private: 15 | Gradient GRADIENT = Gradient(CRGB::Red, CRGB::Gold, CRGB::Red, CRGB::Blue); 16 | static const uint8_t ITEMS = 4; 17 | static const uint8_t FADE_RATE = 5; 18 | static const uint16_t DECAY_RATE = 5; 19 | static const uint16_t DECAY_DELAY = 500; 20 | static const uint16_t INHIBIT_DELAY = 250; 21 | struct Item { 22 | HarmonicMotion ball; 23 | elapsedMillis timer; 24 | uint8_t decay; 25 | }; 26 | AudioTrigger *audioTrigger; 27 | Item items[ITEMS]; 28 | uint8_t nextItem = 0; 29 | Timer fadeTimer = Timer(10); 30 | Timer inhibitTimer = Timer(INHIBIT_DELAY); 31 | void randomizeItem(Item &item, double strength); 32 | 33 | public: 34 | Fireworks(Strip *strip, AudioChannel *audioChannel, State *state); 35 | ~Fireworks(); 36 | void loop(); 37 | void reset(); 38 | }; 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/State.cpp: -------------------------------------------------------------------------------- 1 | #include "State.h" 2 | 3 | State::State() { 4 | rotatingHue = 0; 5 | setFxSpeed(DEFAULT_FX_SPEED); 6 | setCycleSpeed(DEFAULT_CYCLE_SPEED); 7 | } 8 | 9 | void State::setup() {} 10 | 11 | void State::setFxSpeed(uint8_t speed) { 12 | fxSpeed = speed; 13 | linearFxSpeed = double(fxSpeed) / 100; 14 | parabolicFxSpeed = pow(linearFxSpeed, 2); 15 | } 16 | 17 | uint8_t State::getFxSpeed() { 18 | return fxSpeed; 19 | } 20 | 21 | void State::decreaseFxSpeed() { 22 | setFxSpeed(max(fxSpeed - 2, 0)); 23 | } 24 | 25 | void State::increaseFxSpeed() { 26 | setFxSpeed(min(fxSpeed + 2, 100)); 27 | } 28 | 29 | void State::setCycleSpeed(uint8_t speed) { 30 | cycleSpeed = speed; 31 | linearCycleSpeed = double(cycleSpeed) / 100; 32 | } 33 | 34 | uint8_t State::getCycleSpeed() { 35 | return cycleSpeed; 36 | } 37 | 38 | void State::decreaseCycleSpeed() { 39 | setCycleSpeed(max(cycleSpeed - 2, 0)); 40 | } 41 | 42 | void State::increaseCycleSpeed() { 43 | setCycleSpeed(min(cycleSpeed + 2, 100)); 44 | } 45 | 46 | void State::loop() { 47 | double increase = .0001 * parabolicFxSpeed * microseconds; 48 | microseconds = 0; 49 | rotatingHueInternal = fmod(rotatingHueInternal + increase, 256); 50 | rotatingHue = uint8_t(rotatingHueInternal); 51 | } 52 | -------------------------------------------------------------------------------- /src/EllipticMotion.h: -------------------------------------------------------------------------------- 1 | #ifndef EllipticMotion_h 2 | #define EllipticMotion_h 3 | 4 | #include "strip/Strip.h" 5 | #include "Pixel.h" 6 | #include "State.h" 7 | 8 | class EllipticMotion { 9 | private: 10 | static const uint8_t MIN_BRIGHTNESS = 75; 11 | Strip *strip; 12 | elapsedMicros timeElapsed; 13 | double center; 14 | double radius; 15 | double angle; 16 | double angularSpeed; 17 | double eccentricity; 18 | double eccentricityAngle; 19 | bool overwrite; 20 | Pixel pixel; 21 | 22 | public: 23 | uint8_t hue; 24 | uint8_t saturation; 25 | EllipticMotion(); 26 | EllipticMotion& setup(Strip *strip); 27 | EllipticMotion& reset(); 28 | EllipticMotion& setHue(uint8_t hue); 29 | EllipticMotion& setSaturation(uint8_t saturation); 30 | EllipticMotion& setCenter(double center); 31 | EllipticMotion& setRadius(double radius); 32 | EllipticMotion& setAngle(double angle); 33 | EllipticMotion& setAngularSpeed(double angularSpeed); 34 | EllipticMotion& setEccentricity(double eccentricity); 35 | EllipticMotion& setEccentricityAngle(double eccentricityAngle); 36 | EllipticMotion& setOverwrite(bool overwrite); 37 | void loop(); 38 | }; 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/fx/Bounce.cpp: -------------------------------------------------------------------------------- 1 | #include "Bounce.h" 2 | 3 | Bounce::Bounce(Strip *strip, State *state, uint16_t count, uint16_t size) : Fx(strip, state) { 4 | this->count = count; 5 | this->size = size; 6 | items = new HarmonicMotion[count]; 7 | for (uint8_t i = 0; i < count; i++) { 8 | items[i].setup(strip); 9 | resetItem(items[i]); 10 | } 11 | } 12 | 13 | Bounce::~Bounce() { 14 | delete[] items; 15 | } 16 | 17 | void Bounce::reset() { 18 | clear(); 19 | for (uint8_t i = 0; i < count; i++) { 20 | resetItem(items[i]); 21 | } 22 | } 23 | 24 | void Bounce::loop() { 25 | strip->off(); 26 | for (uint8_t i = 0; i < count; i++) { 27 | loopItem(items[i]); 28 | } 29 | } 30 | 31 | void Bounce::loopItem(HarmonicMotion &item) { 32 | item.loop(); 33 | 34 | if (item.isStable()) { 35 | resetItem(item); 36 | } 37 | } 38 | 39 | void Bounce::resetItem(HarmonicMotion &item) { 40 | item.reset() 41 | .setColor(ColorFromPalette(PALETTE, state->rotatingHue)) 42 | .setRandomPosition() 43 | .setVelocity(random(100) + 200 * state->parabolicFxSpeed) 44 | .setDamping(state->linearFxSpeed) 45 | .setLowerBound(strip->first(), -1, HarmonicMotion::OUTSIDE) 46 | .setUpperBound(strip->last(), -1, HarmonicMotion::OUTSIDE) 47 | .setRange(0, size); 48 | } 49 | -------------------------------------------------------------------------------- /src/strip/virtual/ReversedStrip.h: -------------------------------------------------------------------------------- 1 | #ifndef ReversedStrip_h 2 | #define ReversedStrip_h 3 | 4 | #include 5 | #include "strip/Strip.h" 6 | #include "strip/BufferedStrip.h" 7 | 8 | class ReversedStrip : public Strip { 9 | private: 10 | Strip *strip; 11 | int16_t toStrip(int16_t index); 12 | 13 | public: 14 | ReversedStrip(Strip *strip); 15 | Strip *overlay(double opacity = 1) override; 16 | uint16_t size() override; 17 | void _fade(int16_t indexFrom, int16_t indexTo, uint8_t amount) override; 18 | void _blur(int16_t indexFrom, int16_t indexTo, uint8_t amount) override; 19 | CRGB _shiftUp(int16_t indexFrom, int16_t indexTo, CRGB in = CRGB::Black) override; 20 | CRGB _shiftDown(int16_t indexFrom, int16_t indexTo, CRGB in = CRGB::Black) override; 21 | bool _paintSolid(int16_t indexFrom, int16_t indexTo, CRGB color, bool overlay) override; 22 | bool _paintGradient(int16_t indexFrom, int16_t indexTo, Gradient *gradient, double gradientFrom, double gradientTo, bool overlay) override; 23 | bool _paintRainbow(int16_t indexFrom, int16_t indexTo, uint8_t initialHue, uint8_t deltaHue) override; 24 | bool paintNormalizedSize(double positionFrom, int16_t size, CRGB color, bool overlay) override; 25 | CRGB getIndex(int16_t index) override; 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/Brightness.cpp: -------------------------------------------------------------------------------- 1 | #include "Brightness.h" 2 | 3 | void Brightness::applyBrightness(bool dim) { 4 | double correctedBrightness = MIN_BRIGHTNESS + (255 - MIN_BRIGHTNESS) * pow(double(brightness) / (BRIGHTNESS_STEPS - 1), 2); 5 | FastLED.setBrightness(dim ? correctedBrightness / 2 : correctedBrightness); 6 | } 7 | 8 | void Brightness::dim(bool on) { 9 | this->on = on; 10 | applyBrightness(on); 11 | timer = 0; 12 | } 13 | 14 | void Brightness::init() { 15 | applyBrightness(); 16 | } 17 | 18 | void Brightness::set(uint8_t brightness) { 19 | this->brightness = brightness; 20 | applyBrightness(); 21 | Serial.print("Brightness set to "); 22 | Serial.println(brightness); 23 | } 24 | 25 | void Brightness::increase() { 26 | set(min(brightness + 1, BRIGHTNESS_STEPS - 1)); 27 | } 28 | 29 | void Brightness::decrease() { 30 | set(max(brightness - 1, 0)); 31 | } 32 | 33 | void Brightness::blink(uint8_t count) { 34 | this->count = count; 35 | dim(true); 36 | } 37 | 38 | void Brightness::loop() { 39 | if (count) { 40 | if (on) { 41 | if (timer > ON_DURATION_MS) { 42 | dim(false); 43 | } 44 | } else { 45 | if (timer > OFF_DURATION_MS) { 46 | if (--count) { 47 | dim(true); 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/strip/virtual/SubStrip.h: -------------------------------------------------------------------------------- 1 | #ifndef SubStrip_h 2 | #define SubStrip_h 3 | 4 | #include 5 | #include "strip/Strip.h" 6 | #include "strip/BufferedStrip.h" 7 | 8 | class SubStrip : public Strip { 9 | private: 10 | Strip *strip; 11 | uint16_t start; 12 | uint16_t end; 13 | int16_t toStrip(int16_t index); 14 | 15 | public: 16 | SubStrip(Strip *strip, int16_t start, int16_t end); 17 | Strip *overlay(double opacity = 1) override; 18 | uint16_t size() override; 19 | void _fade(int16_t indexFrom, int16_t indexTo, uint8_t amount) override; 20 | void _blur(int16_t indexFrom, int16_t indexTo, uint8_t amount) override; 21 | CRGB _shiftUp(int16_t indexFrom, int16_t indexTo, CRGB in = CRGB::Black) override; 22 | CRGB _shiftDown(int16_t indexFrom, int16_t indexTo, CRGB in = CRGB::Black) override; 23 | bool _paintSolid(int16_t indexFrom, int16_t indexTo, CRGB color, bool overlay) override; 24 | bool _paintGradient(int16_t indexFrom, int16_t indexTo, Gradient *gradient, double gradientFrom, double gradientTo, bool overlay) override; 25 | bool _paintRainbow(int16_t indexFrom, int16_t indexTo, uint8_t initialHue, uint8_t deltaHue) override; 26 | bool paintNormalizedSize(double positionFrom, int16_t size, CRGB color, bool overlay) override; 27 | CRGB getIndex(int16_t index) override; 28 | }; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/Striptease.h: -------------------------------------------------------------------------------- 1 | 2 | /* StripTease Library for Teensy 4.X 3 | * Copyright (c) 2021-2024, Luca Paolini, lucapaolini70gmail.com 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice, development funding notice, and this permission 13 | * notice shall be included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | */ 23 | 24 | #include "audio/AudioSensor.h" 25 | #include "State.h" 26 | #include "Stage.h" 27 | #include "Controller.h" 28 | #include "remote/IRRemote.h" 29 | #include "remote/SerialRemote.h" 30 | 31 | -------------------------------------------------------------------------------- /src/Point.h: -------------------------------------------------------------------------------- 1 | #ifndef Point_h 2 | #define Point_h 3 | 4 | #include 5 | 6 | class Point { 7 | public: 8 | double x; 9 | double y; 10 | 11 | static Point fromPolar(double radius, double angle) { 12 | return Point( 13 | radius * cosf(angle), 14 | radius * sinf(angle) 15 | ); 16 | }; 17 | 18 | Point() {}; 19 | 20 | Point(double x, double y) { 21 | this->x = x; 22 | this->y = y; 23 | } 24 | 25 | double radius() { 26 | return sqrt(pow(x, 2) + pow(y, 2)); 27 | } 28 | 29 | double angle() { 30 | return x == 0 && y == 0 ? 0 : atan2(y, x); 31 | } 32 | 33 | Point& set(double x, double y) { 34 | this->x = x; 35 | this->y = y; 36 | return *this; 37 | } 38 | 39 | Point& translate(Point p) { 40 | return translate(p.x, p.y); 41 | } 42 | 43 | Point& translate(double tx, double ty) { 44 | return set(x - tx, y - ty); 45 | } 46 | 47 | Point& rotate(double theta) { 48 | double cosTheta = cos(theta); 49 | double sinTheta = sin(theta); 50 | return set( 51 | x * cosTheta - y * sinTheta, 52 | x * sinTheta + y * cosTheta 53 | ); 54 | } 55 | }; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/strip/StatefulStrip.h: -------------------------------------------------------------------------------- 1 | #ifndef StatefulStrip_h 2 | #define StatefulStrip_h 3 | 4 | #include 5 | #include "Strip.h" 6 | #include "Gradient.h" 7 | 8 | class StatefulStrip : public Strip { 9 | protected: 10 | CRGBSet *leds; 11 | uint16_t density; 12 | 13 | public: 14 | StatefulStrip(CRGBSet &leds, uint16_t density = 0); 15 | StatefulStrip(CRGBSet *leds, uint16_t density = 0); 16 | StatefulStrip(); 17 | void setLeds(CRGBSet *leds); 18 | Strip *overlay(double opacity = 1) override; 19 | uint16_t size() override; 20 | void _fade(int16_t indexFrom, int16_t indexTo, uint8_t amount) override; 21 | void _blur(int16_t indexFrom, int16_t indexTo, uint8_t amount) override; 22 | CRGB _shiftUp(int16_t indexFrom, int16_t indexTo, CRGB in = CRGB::Black) override; 23 | CRGB _shiftDown(int16_t indexFrom, int16_t indexTo, CRGB in = CRGB::Black) override; 24 | bool _paintSolid(int16_t indexFrom, int16_t indexTo, CRGB color, bool overlay) override; 25 | bool _paintGradient(int16_t indexFrom, int16_t indexTo, Gradient *gradient, double gradientFrom, double gradientTo, bool overlay) override; 26 | bool _paintRainbow(int16_t indexFrom, int16_t indexTo, uint8_t initialHue, uint8_t deltaHue) override; 27 | bool paintNormalizedSize(double positionFrom, int16_t size, CRGB color, bool overlay) override; 28 | CRGB getIndex(int16_t index) override; 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/fx/Vertigo.cpp: -------------------------------------------------------------------------------- 1 | #include "Vertigo.h" 2 | 3 | Vertigo::Vertigo(Strip *strip, AudioChannel *audioChannel, State *state, CRGB color) : Fx(strip, audioChannel, state) { 4 | this->color = color; 5 | audioTrigger = new AudioTrigger(audioChannel); 6 | for (uint8_t i = 0; i < ITEMS; i++) { 7 | items[i].ball.setup(strip); 8 | } 9 | } 10 | 11 | Vertigo::~Vertigo() { 12 | delete audioTrigger; 13 | } 14 | 15 | void Vertigo::reset() { 16 | clear(); 17 | for (int i = 0; i < ITEMS; i++) { 18 | items[i].ball.reset(); 19 | items[i].timer = 0; 20 | } 21 | fadeTimer.reset(); 22 | inhibitTimer.reset(); 23 | } 24 | 25 | void Vertigo::loop() { 26 | if (fadeTimer.isElapsed()) { 27 | strip->fade(FADE_RATE); 28 | } 29 | 30 | bool trigger = audioTrigger->triggered(3); 31 | 32 | if (trigger && items[nextItem].ball.isStable() && inhibitTimer.isElapsed()) { 33 | inhibitTimer.reset(); 34 | randomizeItem(items[nextItem], audioChannel->beatDetected ? audioChannel->rms : .1f); 35 | nextItem = (nextItem + 1) % ITEMS; 36 | } 37 | 38 | for (uint8_t i = 0; i < ITEMS; i++) { 39 | items[i].ball.loop(); 40 | } 41 | } 42 | 43 | void Vertigo::randomizeItem(Item &item, double strength) { 44 | item.ball.reset() 45 | .setColor(color) 46 | .setAcceleration(50, 0, 2000 * state->linearFxSpeed) 47 | .setPosition(0) 48 | .setUpperBound(strip->last()); 49 | item.timer = 0; 50 | } 51 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /src/fx/Jelly.cpp: -------------------------------------------------------------------------------- 1 | #include "Jelly.h" 2 | 3 | Jelly::Jelly(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | for (int i = 0; i < ITEMS; i++) { 6 | items[i].setup(strip); 7 | } 8 | } 9 | 10 | Jelly::~Jelly() { 11 | delete audioTrigger; 12 | } 13 | 14 | void Jelly::reset() { 15 | clear(); 16 | for (int i = 0; i < ITEMS; i++) { 17 | items[i].reset() 18 | .setColor(GRADIENT.getRandomColor(16)) 19 | .setElasticConstant(4) 20 | .setCriticalDamping() 21 | .setFixedPointPosition(strip->random()) 22 | .setVelocity(10) 23 | .setRange(-HALF_SIZE, HALF_SIZE) 24 | .setMirror(false) 25 | .setShowWhenStable(true); 26 | } 27 | moveTimer.reset(); 28 | fadeTimer.reset(); 29 | audioTrigger->reset(); 30 | } 31 | 32 | void Jelly::loop() { 33 | if (fadeTimer.isElapsed()) { 34 | strip->fade(FADE_RATE); 35 | } 36 | 37 | if (audioTrigger->triggered(1)) { 38 | if (moveTimer.isElapsed()) { 39 | for (int i = 0; i < ITEMS; i++) { 40 | items[i].setFixedPointPosition(strip->randomExclude(items[i].getPosition(), 5)); 41 | } 42 | } 43 | } 44 | 45 | for (int i = 0; i < ITEMS; i++) { 46 | items[i] 47 | .setElasticConstant(5 + 100 * state->parabolicFxSpeed) 48 | .setCriticalDamping() 49 | .loop(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/fx/Ants.cpp: -------------------------------------------------------------------------------- 1 | #include "Ants.h" 2 | 3 | Ants::Ants(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | items = new Item[ITEMS]; 6 | for (uint8_t i = 0; i < ITEMS; i++) { 7 | items[i].item.setup(strip); 8 | } 9 | } 10 | 11 | Ants::~Ants() { 12 | delete audioTrigger; 13 | delete[] items; 14 | } 15 | 16 | void Ants::reset() { 17 | clear(); 18 | for (uint8_t i = 0; i < ITEMS; i++) { 19 | items[i].item.reset(); 20 | } 21 | audioTrigger->reset(); 22 | } 23 | 24 | void Ants::loop() { 25 | strip->off(); 26 | 27 | bool trigger = audioTrigger->triggered(.5); 28 | 29 | for (uint8_t i = 0; i < ITEMS; i++) { 30 | loopItem(items[i], trigger); 31 | } 32 | } 33 | 34 | void Ants::loopItem(Item &item, bool &trigger) { 35 | item.item.loop(); 36 | if (item.item.getVelocity() < 0) { 37 | item.item.color.fadeToBlackBy(item.decay); 38 | } 39 | 40 | if (item.item.isStable() && trigger) { 41 | trigger = false; 42 | resetItem(item); 43 | } 44 | } 45 | 46 | void Ants::resetItem(Item &item) { 47 | item.item.reset() 48 | .setColor(ColorFromPalette(PALETTE, state->rotatingHue)) 49 | .setPosition(0) 50 | .setVelocity(100 + 200 * state->parabolicFxSpeed) 51 | .setLowerBound(strip->first(), 0, HarmonicMotion::OUTSIDE) 52 | .setUpperBound(strip->last(), -1, HarmonicMotion::INSIDE) 53 | .setRange(0, 5); 54 | item.decay = 2; 55 | } 56 | -------------------------------------------------------------------------------- /src/fx/Motion.cpp: -------------------------------------------------------------------------------- 1 | #include "Motion.h" 2 | 3 | Motion::Motion(Strip *strip, AudioChannel *audioChannel, State *state, CRGB color, double velocityFactor, double accelerationFactor) : Fx(strip, audioChannel, state) { 4 | this->color = color; 5 | this->velocityFactor = velocityFactor; 6 | this->accelerationFactor = accelerationFactor; 7 | audioTrigger = new AudioTrigger(audioChannel); 8 | for (uint8_t i = 0; i < ITEMS; i++) { 9 | items[i].ball.setup(strip); 10 | } 11 | } 12 | 13 | Motion::~Motion() { 14 | delete audioTrigger; 15 | } 16 | 17 | void Motion::reset() { 18 | clear(); 19 | for (int i = 0; i < ITEMS; i++) { 20 | items[i].ball.reset(); 21 | } 22 | fadeTimer.reset(); 23 | inhibitTimer.reset(); 24 | } 25 | 26 | void Motion::loop() { 27 | if (fadeTimer.isElapsed()) { 28 | strip->fade(FADE_RATE); 29 | } 30 | 31 | bool trigger = audioTrigger->triggered(3); 32 | 33 | if (trigger && items[nextItem].ball.isStable() && inhibitTimer.isElapsed()) { 34 | inhibitTimer.reset(); 35 | resetItem(items[nextItem]); 36 | nextItem = (nextItem + 1) % ITEMS; 37 | } 38 | 39 | for (uint8_t i = 0; i < ITEMS; i++) { 40 | items[i].ball.loop(); 41 | } 42 | } 43 | 44 | void Motion::resetItem(Item &item) { 45 | item.ball.reset() 46 | .setColor(color) 47 | .setVelocity(velocityFactor * state->linearFxSpeed) 48 | .setAcceleration(accelerationFactor * state->linearFxSpeed) 49 | .setPosition(0) 50 | .setUpperBound(strip->last()); 51 | } 52 | -------------------------------------------------------------------------------- /src/fx/Elastic.cpp: -------------------------------------------------------------------------------- 1 | #include "Elastic.h" 2 | 3 | Elastic::Elastic(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | for (uint8_t i = 0; i < ITEMS; i++) { 6 | items[i].setup(strip); 7 | } 8 | } 9 | 10 | Elastic::~Elastic() { 11 | delete audioTrigger; 12 | } 13 | 14 | void Elastic::reset() { 15 | clear(); 16 | for (int i = 0; i < ITEMS; i++) { 17 | items[i].reset(); 18 | } 19 | fadeTimer.reset(); 20 | audioTrigger->reset(); 21 | } 22 | 23 | void Elastic::loop() { 24 | if (fadeTimer.isElapsed()) { 25 | strip->fade(FADE_RATE); 26 | } 27 | 28 | bool trigger = audioTrigger->triggered(1); 29 | 30 | if (trigger) { 31 | clear(); 32 | randomizeItem(items[nextItem], audioChannel->beatDetected ? audioChannel->rms : .1f); 33 | nextItem = (nextItem + 1) % ITEMS; 34 | } 35 | 36 | for (uint8_t i = 0; i < ITEMS; i++) { 37 | items[i].loop(); 38 | } 39 | } 40 | 41 | void Elastic::randomizeItem(HarmonicMotion &item, double strength) { 42 | uint16_t pos = strip->randomExclude(item.getPosition(), strip->size() / 5); 43 | item.reset() 44 | .setColor(ColorFromPalette(PALETTE, random8())) 45 | .setElasticConstant(20) 46 | .setCriticalDamping(.5) 47 | .setPosition(pos) 48 | .setFixedPointPosition(pos) 49 | .setVelocity(random16(100 + 200 * state->linearFxSpeed, 250 + 500 * state->linearFxSpeed)) 50 | .setMirror(true) 51 | .setFill(true); 52 | } 53 | -------------------------------------------------------------------------------- /src/fx/Fire.cpp: -------------------------------------------------------------------------------- 1 | #include "Fire.h" 2 | 3 | Fire::Fire(Strip *strip, AudioChannel *audioChannel) : Fx(strip, audioChannel) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | // Array of temperature readings at each simulation cell 6 | heat = new uint8_t[strip->size()]; 7 | reset(); 8 | } 9 | 10 | Fire::~Fire() { 11 | delete audioTrigger; 12 | delete[] heat; 13 | } 14 | 15 | void Fire::reset() { 16 | clear(); 17 | if (random8(2) == 0) { 18 | palette = HOT; 19 | } else { 20 | palette = COLD; 21 | } 22 | } 23 | 24 | void Fire::loop() { 25 | // Step 1. Cool down every cell a little 26 | for (uint16_t i = 0; i < strip->size(); i++) { 27 | heat[i] = qsub8(heat[i], random8(0, COOLING * 10 / strip->size() + 2)); 28 | } 29 | 30 | // Step 2. Heat from each cell drifts 'up' and diffuses a little 31 | for (uint16_t i = strip->last(); i >= 2; i--) { 32 | heat[i] = (heat[i - 1] + 2 * heat[i - 2]) / 3; 33 | } 34 | 35 | // Step 3. Randomly ignite new 'sparks' of heat near the bottom 36 | if (audioTrigger->triggered(SPARKING, 3 + audioChannel->rms * 10)) { 37 | int y = random8(7); 38 | heat[y] = qadd8(heat[y], random8(160, 255)); 39 | } 40 | 41 | // Step 4. Map from heat cells to LED colors 42 | for (uint16_t i = 0; i < strip->size(); i++) { 43 | // Scale the heat value from 0-255 down to 0-240 for best results with color palettes 44 | uint8_t colorindex = scale8(heat[i], 240); 45 | CRGB color = ColorFromPalette(palette, colorindex); 46 | strip->paint(i, color, false); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/fx/Chaser.cpp: -------------------------------------------------------------------------------- 1 | #include "Chaser.h" 2 | 3 | Chaser::Chaser(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | for (uint8_t i = 0; i < ITEMS; i++) { 6 | items[i].setup(strip); 7 | } 8 | } 9 | 10 | Chaser::~Chaser() { 11 | delete audioTrigger; 12 | } 13 | 14 | void Chaser::reset() { 15 | clear(); 16 | for (int i = 0; i < ITEMS; i++) { 17 | uint8_t size = random8(2, 5); 18 | items[i].reset() 19 | .setColor(ColorFromPalette(PALETTE, random8())) 20 | .setElasticConstant(4) 21 | .setCriticalDamping() 22 | .setFixedPointPosition(strip->random()) 23 | .setVelocity(10) 24 | .setRange(-size, size) 25 | .setMirror(false) 26 | .setShowWhenStable(true); 27 | } 28 | moveTimer.reset(); 29 | fadeTimer.reset(); 30 | audioTrigger->reset(); 31 | } 32 | 33 | void Chaser::loop() { 34 | if (fadeTimer.isElapsed()) { 35 | strip->fade(FADE_RATE); 36 | } 37 | 38 | if (audioTrigger->triggered(1)) { 39 | if (moveTimer.isElapsed()) { 40 | items[0].setFixedPointPosition(strip->randomExclude(items[0].getPosition(), 5)); 41 | } 42 | } 43 | 44 | for (int i = 1; i < ITEMS; i++) { 45 | items[i].setFixedPointPosition(items[i - 1].getPosition()); 46 | } 47 | 48 | for (int i = 0; i < ITEMS; i++) { 49 | items[i] 50 | .setElasticConstant(5 + 100 * state->parabolicFxSpeed) 51 | .setCriticalDamping() 52 | .loop(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/remote/IRRemote.h: -------------------------------------------------------------------------------- 1 | #ifndef IRRemote_h 2 | #define IRRemote_h 3 | 4 | #include 5 | #include "Controller.h" 6 | 7 | #define IRMP_INPUT_PIN 22 8 | #define IRMP_SUPPORT_SIRCS_PROTOCOL 1 // Enable SIRCS (Sony) protocol 9 | #define IRMP_SUPPORT_NEC_PROTOCOL 1 // Enable NEC protocol 10 | #define IRMP_PROTOCOL_NAMES 1 // Enable protocol number mapping to protocol strings 11 | #define IRMP_USE_COMPLETE_CALLBACK 1 // Enable callback functionality 12 | 13 | #include 14 | 15 | class IRRemote { 16 | private: 17 | IRMP_DATA irmp_data; 18 | 19 | protected: 20 | Controller *controller; 21 | virtual void handleCommand(uint8_t protocol, uint16_t command, bool repeated) = 0; 22 | 23 | public: 24 | IRRemote(Controller *controller) { 25 | this->controller = controller; 26 | } 27 | 28 | void setup() { 29 | irmp_init(); 30 | irmp_irsnd_LEDFeedback(true); 31 | Serial.print("Supported IR protocols: "); 32 | irmp_print_active_protocols(&Serial); 33 | Serial.println(); 34 | } 35 | 36 | void loop() { 37 | if (irmp_get_data(&irmp_data)) { 38 | // irmp_result_print(&irmp_data); 39 | uint8_t protocol = irmp_data.protocol; 40 | uint16_t command = irmp_data.command; 41 | bool repeated = irmp_data.flags & IRMP_FLAG_REPETITION; 42 | controller->clearStandbyTimer(); 43 | handleCommand(protocol, command, repeated); 44 | } 45 | } 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/fx/VU1.cpp: -------------------------------------------------------------------------------- 1 | #include "VU1.h" 2 | 3 | VU1::VU1(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | vu.setup(strip); 5 | peak.setup(strip); 6 | peakHold.setup(strip); 7 | reset(); 8 | } 9 | 10 | void VU1::reset() { 11 | clear(); 12 | resetVU(); 13 | resetPeak(); 14 | resetPeakHold(); 15 | fadeTimer.reset(); 16 | } 17 | 18 | void VU1::resetVU() { 19 | vu.reset() 20 | .setColor(CRGB::Blue) 21 | .setElasticConstant(100) 22 | .setCriticalDamping() 23 | .setRange(1, 5) 24 | .setLowerBound(0) 25 | .setShowWhenStable(true); 26 | } 27 | 28 | void VU1::resetPeak() { 29 | peak.reset() 30 | .setColor(CRGB(0, 0, 8)) 31 | .setFill(true) 32 | .setLowerBound(0) 33 | .setShowWhenStable(true); 34 | } 35 | 36 | void VU1::resetPeakHold() { 37 | peakHold.reset() 38 | .setColor(CRGB::Red) 39 | .setRange(1, 5) 40 | .setLowerBound(0) 41 | .setShowWhenStable(true); 42 | } 43 | 44 | void VU1::loop() { 45 | if (fadeTimer.isElapsed()) { 46 | strip->fade(100); 47 | } 48 | 49 | vu 50 | .setFixedPointPosition(strip->fromNormalizedPosition(audioChannel->peak)) 51 | .loop(); 52 | 53 | peak 54 | .setPosition(max(peak.getPosition(), strip->fromNormalizedPosition(audioChannel->peakSmooth))) 55 | .setVelocity(-1000 * (.1 + .9 * state->linearFxSpeed)) 56 | .loop(); 57 | 58 | peakHold 59 | .setPosition(max(peakHold.getPosition(), strip->fromNormalizedPosition(audioChannel->peakSmooth))) 60 | .setVelocity(-100 * (.1 + .9 * state->linearFxSpeed)) 61 | .loop(); 62 | } 63 | -------------------------------------------------------------------------------- /src/fx/DeepSpace.h: -------------------------------------------------------------------------------- 1 | #ifndef DeepSpace_h 2 | #define DeepSpace_h 3 | 4 | #include 5 | #include 6 | #include "audio/AudioTrigger.h" 7 | #include "Easing.h" 8 | #include "Fx.h" 9 | #include "Pixel.h" 10 | #include "Point.h" 11 | #include "Timer.h" 12 | 13 | class DeepSpace : public Fx { 14 | private: 15 | enum Type {HIDDEN, NORMAL, HIGHLIGHT}; 16 | struct Item { 17 | Point point; 18 | Pixel pixel; 19 | Type type; 20 | }; 21 | static const uint16_t ITEMS = 250; 22 | static const uint16_t SPEED = 500; 23 | static constexpr double MIN_DISTANCE = 500; 24 | static constexpr double MAX_DISTANCE = 2000; 25 | static constexpr double MAX_MUTATION_DISTANCE = 500; 26 | static constexpr double MAX_SQUARED_DISTANCE = pow(MAX_DISTANCE, 2); 27 | static constexpr double MIN_STEERING = 30; 28 | static constexpr double MAX_STEERING = 120; 29 | static constexpr double MIN_TRANSITION_SPEED = 100; 30 | static constexpr double MAX_TRANSITION_SPEED = 300; 31 | CRGB baseColor, accentColor; 32 | AudioTrigger *audioTrigger; 33 | Item items[ITEMS]; 34 | elapsedMicros time; 35 | double steeringAngle = 0; 36 | double transitionSpeed = 0; 37 | double transition = 1; 38 | Timer timer = Timer(1000); 39 | void loopItem(Item &item, double translationY, double rotation, bool &trigger); 40 | void randomizeItem(Item &item); 41 | 42 | public: 43 | DeepSpace(Strip *strip, AudioChannel *audioChannel, State *state, CRGB baseColor = CRGB::Blue, CRGB accenttColor = CRGB::Red); 44 | ~DeepSpace(); 45 | void loop(); 46 | void reset(); 47 | }; 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /src/fx/Matrix.cpp: -------------------------------------------------------------------------------- 1 | #include "Matrix.h" 2 | 3 | Matrix::Matrix(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | up = new bool[strip->size()]; 6 | down = new bool[strip->size()]; 7 | for (uint16_t i = 0; i < strip->size() ; i++) { 8 | up[i] = down[i] = false; 9 | } 10 | } 11 | 12 | Matrix::~Matrix() { 13 | delete audioTrigger; 14 | delete[] up; 15 | delete[] down; 16 | } 17 | 18 | void Matrix::reset() { 19 | clear(); 20 | countDown = DOWN_PERIOD; 21 | countUp = UP_PERIOD; 22 | 23 | for (uint16_t i = 0; i < strip->size(); i++) { 24 | up[i] = down[i] = false; 25 | } 26 | 27 | audioTrigger->reset(); 28 | } 29 | 30 | void Matrix::loop() { 31 | audioTrigger->loop(); 32 | addFromTop(); 33 | addFromBottom(); 34 | show(); 35 | } 36 | 37 | void Matrix::addFromTop() { 38 | if (--countDown == 0) { 39 | // down[strip->last()] = !down[strip->last() - 1] && random8() < DOWN_PROBABILITY; 40 | down[strip->last()] = !down[strip->last() - 1] && random8() < 50 * state->parabolicFxSpeed; 41 | for (int i = 0; i < strip->last(); i++) { 42 | down[i] = down[i + 1]; 43 | } 44 | countDown = DOWN_PERIOD; 45 | } 46 | } 47 | 48 | void Matrix::addFromBottom() { 49 | if (--countUp == 0) { 50 | up[0] = audioTrigger->triggered(.5); 51 | for (int i = strip->last(); i > 0; i--) { 52 | up[i] = up[i - 1]; 53 | } 54 | countUp = UP_PERIOD; 55 | } 56 | } 57 | 58 | void Matrix::show() { 59 | for (int i = 0; i < strip->size() ; i++) { 60 | strip->paint(i, (up[i] ? UP_COLOR : CRGB::Black) + (down[i] ? DOWN_COLOR : CRGB::Black), false); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/fx/Volcane.cpp: -------------------------------------------------------------------------------- 1 | #include "Volcane.h" 2 | 3 | Volcane::Volcane(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | for (int i = 0; i < NUM_ITEMS; i++) { 6 | Item *item = &items[i]; 7 | item->head.setup(strip); 8 | item->tail.setup(strip); 9 | } 10 | } 11 | 12 | Volcane::~Volcane() { 13 | delete audioTrigger; 14 | } 15 | 16 | void Volcane::reset() { 17 | clear(); 18 | for (int i = 0; i < NUM_ITEMS; i++) { 19 | Item *item = &items[i]; 20 | item->head.reset(); 21 | item->tail.reset(); 22 | } 23 | audioTrigger->reset(); 24 | } 25 | 26 | void Volcane::restart(Item *item) { 27 | double g = -2500 * state->parabolicFxSpeed; 28 | double v0 = sqrtf(g * -2 * random8(70, 100) / 100 * strip->last()); 29 | item->head.reset() 30 | .setColor(CRGB::Red) 31 | .setAcceleration(g) 32 | .setVelocity(v0) 33 | .setRange(-2, 2) 34 | .setLowerBound(-10); 35 | 36 | item->tail.reset() 37 | .setColor(ColorFromPalette(PALETTE, random8())) 38 | .setPosition(0) 39 | .setElasticConstant(1000 * state->parabolicFxSpeed) 40 | .setCriticalDamping() 41 | .setFill(true) 42 | .setLowerBound(0); 43 | } 44 | 45 | void Volcane::loop() { 46 | strip->off(); 47 | bool trigger = audioTrigger->triggered(1); 48 | 49 | for (int i = 0; i < NUM_ITEMS; i++) { 50 | Item *item = &items[i]; 51 | if (trigger && item->tail.isStable()) { 52 | restart(item); 53 | trigger = false; 54 | } 55 | item->head.loop(); 56 | item->tail 57 | .setFixedPointPosition(item->head.getPosition()) 58 | .loop(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/strip/virtual/JoinedStrip.h: -------------------------------------------------------------------------------- 1 | #ifndef JoinedStrip_h 2 | #define JoinedStrip_h 3 | 4 | #include 5 | #include "strip/Strip.h" 6 | #include "strip/BufferedStrip.h" 7 | #include "strip/StatefulStrip.h" 8 | 9 | class JoinedStrip : public Strip { 10 | private: 11 | Strip *strip1, *strip2; 12 | uint16_t gap; 13 | StatefulStrip *gapStrip; 14 | bool isInStrip1(int16_t index); 15 | bool isInGap(int16_t index); 16 | bool isInStrip2(int16_t index); 17 | int16_t toStrip1(int16_t index); 18 | int16_t toGap(int16_t index); 19 | int16_t toStrip2(int16_t index); 20 | double relativeGradient(int16_t indexFrom, int16_t indexTo, int16_t index, double gradientFrom, double gradientTo); 21 | 22 | public: 23 | JoinedStrip(Strip *strip, Strip *strip2, int16_t gap = 0); 24 | Strip *overlay(double opacity = 1) override; 25 | uint16_t size() override; 26 | void _fade(int16_t indexFrom, int16_t indexTo, uint8_t amount) override; 27 | void _blur(int16_t indexFrom, int16_t indexTo, uint8_t amount) override; 28 | CRGB _shiftUp(int16_t indexFrom, int16_t indexTo, CRGB in = CRGB::Black) override; 29 | CRGB _shiftDown(int16_t indexFrom, int16_t indexTo, CRGB in = CRGB::Black) override; 30 | bool _paintSolid(int16_t indexFrom, int16_t indexTo, CRGB color, bool overlay) override; 31 | bool _paintGradient(int16_t indexFrom, int16_t indexTo, Gradient *gradient, double gradientFrom, double gradientTo, bool overlay) override; 32 | bool _paintRainbow(int16_t indexFrom, int16_t indexTo, uint8_t initialHue, uint8_t deltaHue) override; 33 | bool paintNormalizedSize(double positionFrom, int16_t size, CRGB color, bool overlay) override; 34 | CRGB getIndex(int16_t index) override; 35 | }; 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/fx/Traffic.cpp: -------------------------------------------------------------------------------- 1 | #include "Traffic.h" 2 | 3 | Traffic::Traffic(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | items = new HarmonicMotion[ITEMS]; 6 | for (uint8_t i = 0; i < ITEMS; i++) { 7 | items[i].setup(strip); 8 | } 9 | } 10 | 11 | Traffic::~Traffic() { 12 | delete audioTrigger; 13 | delete[] items; 14 | } 15 | 16 | void Traffic::reset() { 17 | clear(); 18 | for (uint8_t i = 0; i < ITEMS; i++) { 19 | items[i].reset(); 20 | } 21 | audioTrigger->reset(); 22 | } 23 | 24 | void Traffic::loop() { 25 | strip->off(); 26 | 27 | bool trigger = audioTrigger->triggered(.5); 28 | 29 | for (uint8_t i = 0; i < ITEMS; i++) { 30 | loopItem(items[i], trigger); 31 | } 32 | } 33 | 34 | void Traffic::loopItem(HarmonicMotion &item, bool &trigger) { 35 | item.loop(); 36 | 37 | if (item.isStable() && trigger) { 38 | trigger = false; 39 | resetItem(item); 40 | } 41 | } 42 | 43 | void Traffic::resetItem(HarmonicMotion &item) { 44 | uint8_t layer = random(LAYERS); 45 | double factor = pow(1 + 5 * (double(1 + layer) / (1 + LAYERS)), 2); 46 | double speed = factor * (5 + (15 + random(10)) * state->parabolicFxSpeed); 47 | 48 | item.reset() 49 | .setColor(GRADIENT.getRandomColor(8)); 50 | 51 | if (random(10) < 5) { 52 | item 53 | .setPosition(0) 54 | .setVelocity(speed) 55 | .setRange(-factor, 0) 56 | .setUpperBound(strip->last(), 0, HarmonicMotion::OUTSIDE); 57 | } else { 58 | item 59 | .setPosition(strip->last()) 60 | .setVelocity(-speed) 61 | .setRange(0, factor) 62 | .setLowerBound(strip->first(), 0, HarmonicMotion::OUTSIDE); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/audio/PeakDetector.cpp: -------------------------------------------------------------------------------- 1 | #include "PeakDetector.h" 2 | 3 | PeakDetector::PeakDetector(unsigned int bufferSize, double peakFactor, double peakInfluence, unsigned int peakInhibit, double peakThreshold) { 4 | this->bufferSize = bufferSize; 5 | this->peakFactor = peakFactor; 6 | this->peakInfluence = peakInfluence; 7 | this->peakInhibit = peakInhibit; 8 | this->peakThreshold = peakThreshold; 9 | this->buffer = new double[bufferSize]; 10 | this->skip = bufferSize; 11 | this->ptr = 0; 12 | } 13 | 14 | PeakDetector::~PeakDetector() { 15 | delete[] buffer; 16 | } 17 | 18 | double PeakDetector::avg() { 19 | double sum = 0; 20 | for (uint16_t i = 0; i < bufferSize; i++) { 21 | sum += buffer[i]; 22 | } 23 | return sum / bufferSize; 24 | } 25 | 26 | double PeakDetector::stdDev(double mean) { 27 | double sum = 0; 28 | for (uint16_t i = 0; i < bufferSize; i++) { 29 | sum += pow(buffer[i] - mean, 2); 30 | } 31 | return sqrtf(sum / bufferSize); 32 | } 33 | 34 | bool PeakDetector::isPeak(double value) { 35 | bool peakDetected = false; 36 | if (skip > 0) { 37 | buffer[ptr] = value; 38 | } else { 39 | double mean = avg(); 40 | double peak = abs(value - mean); 41 | peakDetected = peak > peakThreshold && peak > peakFactor * stdDev(mean); 42 | if (peakDetected) { 43 | double prevValue = buffer[(ptr + bufferSize - 1) % bufferSize]; 44 | buffer[ptr] = value * peakInfluence + prevValue * (1 - peakInfluence); 45 | } else { 46 | buffer[ptr] = value; 47 | } 48 | } 49 | 50 | ptr = (ptr + 1) % bufferSize; 51 | skip = skip > 0 ? skip - 1 : 0; 52 | 53 | if (peakDetected && (millis() - lastPeak > peakInhibit)) { 54 | lastPeak = millis(); 55 | return true; 56 | } else { 57 | return false; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/fx/Fireworks.cpp: -------------------------------------------------------------------------------- 1 | #include "Fireworks.h" 2 | 3 | Fireworks::Fireworks(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | for (uint8_t i = 0; i < ITEMS; i++) { 6 | items[i].ball.setup(strip); 7 | } 8 | } 9 | 10 | Fireworks::~Fireworks() { 11 | delete audioTrigger; 12 | } 13 | 14 | void Fireworks::reset() { 15 | clear(); 16 | for (int i = 0; i < ITEMS; i++) { 17 | items[i].ball.reset(); 18 | items[i].timer = 0; 19 | items[i].decay = DECAY_RATE; 20 | } 21 | fadeTimer.reset(); 22 | inhibitTimer.reset(); 23 | audioTrigger->reset(); 24 | } 25 | 26 | void Fireworks::loop() { 27 | if (fadeTimer.isElapsed()) { 28 | strip->fade(FADE_RATE); 29 | for (uint8_t i = 0; i < ITEMS; i++) { 30 | if (items[i].timer > DECAY_DELAY) { 31 | items[i].ball.color.fadeToBlackBy(items[i].decay); 32 | } 33 | } 34 | } 35 | 36 | bool trigger = audioTrigger->triggered(1); 37 | 38 | if (trigger && items[nextItem].ball.isStable() && inhibitTimer.isElapsed()) { 39 | inhibitTimer.reset(); 40 | randomizeItem(items[nextItem], audioChannel->beatDetected ? audioChannel->rms : .1f); 41 | nextItem = (nextItem + 1) % ITEMS; 42 | } 43 | 44 | for (uint8_t i = 0; i < ITEMS; i++) { 45 | items[i].ball.loop(); 46 | } 47 | } 48 | 49 | void Fireworks::randomizeItem(Item &item, double strength) { 50 | uint16_t pos = strip->randomInRange(.25, .75); 51 | item.ball.reset() 52 | .setColor(GRADIENT.getRandomColor()) 53 | .setDamping(2) 54 | .setPosition(pos) 55 | .setFixedPointPosition(pos) 56 | .setVelocity(50 + random16(100 * state->linearFxSpeed)) 57 | .setMirror(true) 58 | ; 59 | item.timer = 0; 60 | } 61 | -------------------------------------------------------------------------------- /src/Gradient.h: -------------------------------------------------------------------------------- 1 | #ifndef Palette_h 2 | #define Palette_h 3 | 4 | #include 5 | #include 6 | 7 | class Gradient { 8 | private: 9 | std::vector colors; 10 | 11 | static CRGB interpolate(CRGB &c1, CRGB &c2, double fraction) { 12 | return CRGB( 13 | c1.r + (c2.r - c1.r) * fraction, 14 | c1.g + (c2.g - c1.g) * fraction, 15 | c1.b + (c2.b - c1.b) * fraction 16 | ); 17 | } 18 | 19 | CRGB getInterpolatedColor(uint8_t segments, double fraction) { 20 | uint8_t segment = floor(fraction * segments); 21 | double segmentFraction = fraction * segments - segment; 22 | return interpolate(colors.at(segment), colors.at(segment + 1), segmentFraction); 23 | } 24 | 25 | void addColors() {} 26 | 27 | template 28 | void addColors(CRGB color, Rest... rest) { 29 | colors.push_back(color); 30 | addColors(rest...); 31 | } 32 | 33 | public: 34 | template 35 | Gradient(CRGB... colors) { 36 | addColors(colors...); 37 | } 38 | 39 | CRGB getColor(double fraction) { 40 | if (colors.size()) { 41 | uint8_t segments = colors.size() - 1; 42 | if (segments > 0) { 43 | if (fraction <= 0) return colors.at(0); 44 | if (fraction >= 1) return colors.at(segments); 45 | return getInterpolatedColor(segments, fraction); 46 | } 47 | return colors.at(segments); 48 | } 49 | return CRGB::Black; 50 | } 51 | 52 | CRGB getRandomColor(uint16_t maxColors = UINT16_MAX) { 53 | return getColor((double) (rand() % maxColors) / (maxColors - 1)); 54 | } 55 | }; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/fx/Orbit.cpp: -------------------------------------------------------------------------------- 1 | #include "Orbit.h" 2 | 3 | OrbitItem::OrbitItem() {}; 4 | 5 | void OrbitItem::setup( 6 | Strip *strip, 7 | State *state, 8 | uint8_t hue, 9 | double phase, 10 | double speed, 11 | double eccentricity, 12 | double eccentricityAngle 13 | ) { 14 | this->strip = strip; 15 | this->state = state; 16 | this->hue = hue; 17 | this->phase = DEG_TO_RAD * phase; 18 | this->speed = DEG_TO_RAD * speed; 19 | this->eccentricity = eccentricity; 20 | this->eccentricityAngle = DEG_TO_RAD * eccentricityAngle; 21 | pixel.setup(strip); 22 | reset(); 23 | } 24 | 25 | void OrbitItem::reset() { 26 | angle = phase; 27 | pixel.reset(); 28 | timeElapsed = 0; 29 | } 30 | 31 | void OrbitItem::loop() { 32 | double dT = timeElapsed / 1e6; 33 | timeElapsed = 0; 34 | double baseSpeed = DEG_TO_RAD * (MIN_SPEED + SPEED_FACTOR * state->linearFxSpeed); 35 | angle += dT * (baseSpeed + speed) * (1 + cosf(angle - eccentricityAngle) * eccentricity); 36 | double orbitX = (1 - cosf(angle)) / 2; 37 | double orbitY = (1 - sinf(angle)) / 2; 38 | uint8_t brightness = MIN_BRIGHTNESS + (255 - MIN_BRIGHTNESS) * orbitY; 39 | pixel.setNormalized(orbitX, CHSV(hue, 255, brightness)); 40 | } 41 | 42 | Orbit::Orbit(Strip *strip, State *state, double phase) : Fx(strip, state) { 43 | this->phase = phase; 44 | for (uint8_t i = 0; i < ITEMS; i++) { 45 | item[i].setup(strip, state, HUE_STEP * i, phase + ANGLE_STEP * i, SPEED_STEP * i, ECCENTRICITY, ECCENTRICITY_ANGLE); 46 | } 47 | reset(); 48 | } 49 | 50 | void Orbit::reset() { 51 | clear(); 52 | for (uint8_t i = 0; i < ITEMS; i++) { 53 | item[i].reset(); 54 | } 55 | timer.reset(); 56 | } 57 | 58 | void Orbit::loop() { 59 | if (timer.isElapsed()) { 60 | strip->fade(150); 61 | for (uint8_t i = 0; i < ITEMS; i++) { 62 | item[i].loop(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/fx/Drops.cpp: -------------------------------------------------------------------------------- 1 | #include "Drops.h" 2 | 3 | Drops::Drops(Strip *strip, AudioChannel *audioChannel, State *state) : Fx(strip, audioChannel, state) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | items = new Item[ITEMS]; 6 | for (uint8_t i = 0; i < ITEMS; i++) { 7 | items[i].center.setup(strip); 8 | items[i].sides.setup(strip); 9 | } 10 | } 11 | 12 | Drops::~Drops() { 13 | delete audioTrigger; 14 | delete[] items; 15 | } 16 | 17 | void Drops::reset() { 18 | clear(); 19 | for (uint8_t i = 0; i < ITEMS; i++) { 20 | items[i].center.reset(); 21 | items[i].sides.reset(); 22 | } 23 | } 24 | 25 | void Drops::loop() { 26 | strip->paint(BACKGROUND_COLOR, false); 27 | 28 | bool trigger = audioTrigger->triggered(1); 29 | 30 | for (uint8_t i = 0; i < ITEMS; i++) { 31 | loopItem(items[i], trigger, audioChannel->beatDetected ? audioChannel->rms : .1f); 32 | } 33 | } 34 | 35 | void Drops::loopItem(Item &item, bool &trigger, double strength) { 36 | item.center.loop(); 37 | item.center.color.fadeToBlackBy(item.decay * 5); 38 | 39 | item.sides.loop(); 40 | item.sides.color.fadeToBlackBy(item.decay * 2); 41 | 42 | if (!item.sides.color && trigger) { 43 | trigger = false; 44 | randomizeItem(item, strength); 45 | } 46 | } 47 | 48 | void Drops::randomizeItem(Item &item, double strength) { 49 | uint16_t pos = strip->randomInRange(.1, .9); 50 | item.center.reset() 51 | .setColor(CRGB::Aqua) 52 | .setPosition(pos) 53 | .setFixedPointPosition(pos) 54 | .setVelocity(15 + 100 * state->parabolicFxSpeed * strength) 55 | .setElasticConstant(-10) 56 | .setMirror(true) 57 | .setFill(true); 58 | item.sides.reset() 59 | .setColor(CRGB::Red) 60 | .setPosition(pos) 61 | .setFixedPointPosition(pos) 62 | .setVelocity(15 + 200 * state->parabolicFxSpeed * strength) 63 | .setRange(1, 1) 64 | .setMirror(true); 65 | item.decay = 2; 66 | } 67 | -------------------------------------------------------------------------------- /src/fx/Orbit.h: -------------------------------------------------------------------------------- 1 | #ifndef Orbit_h 2 | #define Orbit_h 3 | 4 | #include 5 | #include 6 | #include "Fx.h" 7 | #include "Pixel.h" 8 | #include "Timer.h" 9 | #include "Pixel.h" 10 | 11 | class OrbitItem { 12 | private: 13 | static const uint8_t MIN_BRIGHTNESS = 75; 14 | static constexpr double MIN_SPEED = 50; 15 | static constexpr double SPEED_FACTOR = 300; 16 | Strip *strip; 17 | State *state; 18 | uint8_t hue; 19 | double phase; // degrees 20 | double speed; // degrees per second 21 | double eccentricity; 22 | double eccentricityAngle; 23 | Pixel pixel; 24 | elapsedMicros timeElapsed; 25 | double angle; 26 | 27 | public: 28 | OrbitItem(); 29 | void setup( 30 | Strip *strip, 31 | State *state, 32 | uint8_t hue, 33 | double phase, 34 | double speed, 35 | double eccentricity = 0, 36 | double eccentricityAngle = 0 37 | ); 38 | void reset(); 39 | void loop(); 40 | }; 41 | 42 | class Orbit : public Fx { 43 | private: 44 | // static const uint8_t ITEMS = 2; 45 | // static const uint8_t HUE_STEP = 160; 46 | // static constexpr double ANGLE_STEP = -180; 47 | // static constexpr double SPEED_STEP = 0; 48 | // static constexpr double ECCENTRICITY = .2; 49 | // static constexpr double ECCENTRICITY_ANGLE = 270; 50 | static const uint8_t ITEMS = 10; 51 | static const uint8_t HUE_STEP = 20; 52 | static constexpr double ANGLE_STEP = -10; 53 | static constexpr double SPEED_STEP = 0; 54 | static constexpr double ECCENTRICITY = .2; 55 | static constexpr double ECCENTRICITY_ANGLE = 270; 56 | double phase; 57 | OrbitItem item[ITEMS]; 58 | Timer timer = Timer(5); 59 | 60 | public: 61 | Orbit(Strip *strip, State *state, double phase = 0); 62 | void loop(); 63 | void reset(); 64 | }; 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /src/fx/Beat.cpp: -------------------------------------------------------------------------------- 1 | #include "Beat.h" 2 | 3 | Beat::Beat(Strip *strip, AudioChannel *audioChannel) : Fx(strip, audioChannel) { 4 | audioTrigger = new AudioTrigger(audioChannel); 5 | peak.setup(strip); 6 | peakHold.setup(strip); 7 | peakHoldSlow.setup(strip); 8 | reset(); 9 | } 10 | 11 | Beat::~Beat() { 12 | delete audioTrigger; 13 | } 14 | 15 | void Beat::reset() { 16 | clear(); 17 | uint16_t center = strip->center(); 18 | 19 | peak.reset() 20 | .setColor(CRGB::Blue) 21 | .setPosition(center) 22 | .setFixedPointPosition(center) 23 | .setLowerBound(center) 24 | .setRange(0, 1) 25 | .setMirror(true) 26 | .setFill(true) 27 | .setShowWhenStable(true); 28 | 29 | peakHold.reset() 30 | .setColor(CRGB::Turquoise) 31 | .setPosition(center) 32 | .setFixedPointPosition(center) 33 | .setAcceleration(-100) 34 | .setLowerBound(center) 35 | .setRange(2, 5) 36 | .setMirror(true) 37 | .setShowWhenStable(true); 38 | 39 | peakHoldSlow.reset() 40 | .setColor(CRGB::Red) 41 | .setPosition(center) 42 | .setFixedPointPosition(center) 43 | .setAcceleration(25) 44 | .setLowerBound(center) 45 | .setRange(6, 7) 46 | .setMirror(true) 47 | .setShowWhenStable(true); 48 | 49 | timer.reset(); 50 | audioTrigger->reset(); 51 | } 52 | 53 | void Beat::loop() { 54 | strip->off(); 55 | if (audioTrigger->triggered(1)) { 56 | if (timer.isElapsed()) { 57 | uint16_t pos = (1 + audioChannel->rms) * strip->center(); 58 | peak.setPosition(max(peak.getPosition(), pos)); 59 | peak.setVelocity(-30); 60 | peakHold.setPosition(max(peakHold.getPosition(), pos)); 61 | peakHold.setVelocity(0); 62 | peakHoldSlow.setPosition(min(peakHoldSlow.getPosition(), pos)); 63 | peakHoldSlow.setVelocity(25); 64 | } 65 | } 66 | 67 | peak.loop(); 68 | peakHold.loop(); 69 | peakHoldSlow.loop(); 70 | } 71 | -------------------------------------------------------------------------------- /src/fx/Ripple.cpp: -------------------------------------------------------------------------------- 1 | #include "Ripple.h" 2 | 3 | Ripple::Ripple(Strip *strip, AudioChannel *audioChannel, State *state, CRGB backgroundColor) : Fx(strip, audioChannel, state) { 4 | this->backgroundColor = backgroundColor; 5 | audioTrigger = new AudioTrigger(audioChannel); 6 | items = new Item[ITEMS]; 7 | for (uint8_t i = 0; i < ITEMS; i++) { 8 | items[i].ball.setup(strip); 9 | } 10 | } 11 | 12 | Ripple::~Ripple() { 13 | delete audioTrigger; 14 | delete[] items; 15 | } 16 | 17 | void Ripple::reset() { 18 | clear(); 19 | for (uint8_t i = 0; i < ITEMS; i++) { 20 | items[i].ball.reset(); 21 | items[i].timer = 0; 22 | items[i].decay = 0; 23 | } 24 | fadeTimer.reset(); 25 | audioTrigger->reset(); 26 | } 27 | 28 | void Ripple::loop() { 29 | strip->paint(backgroundColor, false); 30 | 31 | if (fadeTimer.isElapsed()) { 32 | for (uint8_t i = 0; i < ITEMS; i++) { 33 | fadeItem(items[i]); 34 | } 35 | } 36 | 37 | bool trigger = audioTrigger->triggered(2); 38 | 39 | for (uint8_t i = 0; i < ITEMS; i++) { 40 | loopItem(items[i], trigger, audioChannel->beatDetected ? audioChannel->rms : .1f); 41 | } 42 | } 43 | 44 | void Ripple::fadeItem(Item &item) { 45 | if (item.timer > DECAY_DELAY) { 46 | item.ball.color.fadeToBlackBy(item.decay); 47 | } 48 | } 49 | 50 | void Ripple::loopItem(Item &item, bool &trigger, double strength) { 51 | item.ball.loop(); 52 | 53 | if (!item.ball.color && trigger) { 54 | trigger = false; 55 | randomizeItem(item, strength); 56 | } 57 | } 58 | 59 | void Ripple::randomizeItem(Item &item, double strength) { 60 | uint16_t pos = strip->random(); 61 | uint8_t size = 5; 62 | item.ball.reset() 63 | .setColor(ColorFromPalette(PALETTE, random8())) 64 | .setElasticConstant(random8(10) - 5) 65 | .setPosition(pos) 66 | .setFixedPointPosition(pos) 67 | .setVelocity(15 + 200 * state->parabolicFxSpeed * strength) 68 | .setRange(-size, size) 69 | .setMirror(true); 70 | item.timer = 0; 71 | item.decay = random8(2, 10); 72 | } 73 | -------------------------------------------------------------------------------- /src/audio/AudioChannel.h: -------------------------------------------------------------------------------- 1 | #ifndef AudioChannel_h 2 | #define AudioChannel_h 3 | 4 | #include 5 | #include 6 | #include "PeakDetector.h" 7 | #include "Timer.h" 8 | 9 | class AudioChannel { 10 | private: 11 | static const int BUFFER_SIZE = 256; // 2.9ms * 256 ~= 0.74s 12 | static const int FFT_BINS = 512; 13 | static const int FFT_BANDS = 16; 14 | static const int SIGNAL_HOLD_MS = 10000; 15 | static constexpr double SIGNAL_THRESHOLD = .01f; 16 | static constexpr double PEAK_FACTOR = 2.2f; 17 | static constexpr double PEAK_INFLUENCE = 0.5f; 18 | static constexpr double PEAK_THRESHOLD = .05f; 19 | static const int PEAK_INHIBIT_MS = 100; 20 | static constexpr double CLIPPING_THRESHOLD = .99f; 21 | unsigned long lastSignal = 0; 22 | PeakDetector *beatDetector; 23 | PeakDetector *peakDetectors[FFT_BANDS]; 24 | void feedPeak(double value); 25 | void feedRMS(double value); 26 | void feedRMSLow(double value); 27 | void feedBins(AudioAnalyzeFFT1024 *fft); 28 | void feedBands(AudioAnalyzeFFT1024 *fft); 29 | void detectSignal(double value); 30 | void detectBeat(double value); 31 | void setBand(AudioAnalyzeFFT1024 *fft, uint8_t band, uint16_t fromBin, uint16_t toBin); 32 | Timer peakFadeTimer = Timer(10); 33 | 34 | public: 35 | struct Band { 36 | double peak = 0; 37 | double peakSmooth = 0; 38 | double peakHold = 0; 39 | // bool signalDetected = false; 40 | bool peakDetected = false; 41 | // bool clipping = false; 42 | }; 43 | double rms = 0; 44 | double rmsLow = 0; 45 | double peak = 0; 46 | double peakSmooth = 0; 47 | double peakHold = 0; 48 | bool signalDetected = false; 49 | bool beatDetected = false; 50 | bool clipping = false; 51 | double fftBin[FFT_BINS]; 52 | Band bands[FFT_BANDS]; 53 | uint16_t dominantBand; 54 | 55 | AudioChannel(); 56 | void loop(AudioAnalyzePeak *peak = nullptr, AudioAnalyzeRMS *rms = nullptr, AudioAnalyzeRMS *rmsLow = nullptr, AudioAnalyzeFFT1024 *fft = nullptr); 57 | }; 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /src/EllipticMotion.cpp: -------------------------------------------------------------------------------- 1 | #include "EllipticMotion.h" 2 | 3 | EllipticMotion::EllipticMotion() {}; 4 | 5 | EllipticMotion& EllipticMotion::setup(Strip *strip) { 6 | this->strip = strip; 7 | pixel.setup(strip); 8 | return reset(); 9 | } 10 | 11 | EllipticMotion& EllipticMotion::reset() { 12 | hue = 0; 13 | saturation = 255; 14 | center = 0; 15 | radius = 0; 16 | angle = 0; 17 | angularSpeed = 0; 18 | eccentricity = 0; 19 | eccentricityAngle = 0; 20 | timeElapsed = 0; 21 | return *this; 22 | } 23 | 24 | EllipticMotion& EllipticMotion::setHue(uint8_t hue) { 25 | this->hue = hue; 26 | return *this; 27 | } 28 | 29 | EllipticMotion& EllipticMotion::setSaturation(uint8_t saturation) { 30 | this->saturation = saturation; 31 | return *this; 32 | } 33 | 34 | EllipticMotion& EllipticMotion::setCenter(double center) { 35 | this->center = center; 36 | return *this; 37 | } 38 | 39 | EllipticMotion& EllipticMotion::setRadius(double radius) { 40 | this->radius = radius; 41 | return *this; 42 | } 43 | 44 | EllipticMotion& EllipticMotion::setAngle(double angle) { 45 | this->angle = angle; 46 | return *this; 47 | } 48 | 49 | EllipticMotion& EllipticMotion::setAngularSpeed(double angularSpeed) { 50 | this->angularSpeed = angularSpeed; 51 | return *this; 52 | } 53 | 54 | EllipticMotion& EllipticMotion::setEccentricity(double eccentricity) { 55 | this->eccentricity = eccentricity; 56 | return *this; 57 | } 58 | 59 | EllipticMotion& EllipticMotion::setEccentricityAngle(double eccentricityAngle) { 60 | this->eccentricityAngle = eccentricityAngle; 61 | return *this; 62 | } 63 | 64 | EllipticMotion& EllipticMotion::setOverwrite(bool overwrite) { 65 | this->overwrite = overwrite; 66 | return *this; 67 | } 68 | 69 | void EllipticMotion::loop() { 70 | double dT = timeElapsed / 1e6; 71 | timeElapsed = 0; 72 | 73 | double eccentricCompensation = (1 + cos(angle - eccentricityAngle) * eccentricity); 74 | angle += angularSpeed * dT * eccentricCompensation; 75 | 76 | double x = center + radius * cos(angle); 77 | uint8_t brightness = MIN_BRIGHTNESS + (255 - MIN_BRIGHTNESS) * (sin(angle) + 1) / 2; 78 | 79 | pixel.setNormalized(x, CHSV(hue, saturation, brightness)); 80 | } 81 | -------------------------------------------------------------------------------- /src/fx/Sparks.cpp: -------------------------------------------------------------------------------- 1 | #include "Sparks.h" 2 | 3 | Sparks::Sparks(Strip *strip, AudioChannel *audioChannel, State *state, CRGB centerColor, CRGB sidesColor) : Fx(strip, audioChannel, state) { 4 | this->centerColor = centerColor; 5 | this->sidesColor = sidesColor; 6 | audioTrigger = new AudioTrigger(audioChannel); 7 | for (uint8_t i = 0; i < ITEMS; i++) { 8 | items[i].center.setup(strip); 9 | items[i].sides.setup(strip); 10 | } 11 | } 12 | 13 | Sparks::~Sparks() { 14 | delete audioTrigger; 15 | } 16 | 17 | void Sparks::reset() { 18 | clear(); 19 | for (int i = 0; i < ITEMS; i++) { 20 | items[i].center.reset(); 21 | items[i].sides.reset(); 22 | } 23 | holdOffTimer.reset(); 24 | } 25 | 26 | void Sparks::loop() { 27 | strip->paint(BACKGROUND_COLOR, false); 28 | 29 | bool trigger = audioTrigger->triggered(1); 30 | 31 | for (uint8_t i = 0; i < ITEMS; i++) { 32 | loopItem(items[i], trigger, audioChannel->beatDetected ? audioChannel->rms : .1f); 33 | } 34 | } 35 | 36 | void Sparks::loopItem(Item &item, bool &trigger, double strength) { 37 | item.center.loop(); 38 | item.center.color.fadeToBlackBy(8); 39 | item.sides.loop(); 40 | item.sides.color.fadeToBlackBy(6); 41 | 42 | if (trigger && !(item.center.color && item.sides.color) && holdOffTimer.isElapsed()) { 43 | trigger = false; 44 | randomizeItem(item, strength); 45 | holdOffTimer.reset(); 46 | } 47 | } 48 | 49 | void Sparks::randomizeItem(Item &item, double strength) { 50 | int16_t pos = strip->randomInRange(.2 , .8); 51 | double velocity = 5 + 1000 * state->parabolicFxSpeed * strength; 52 | item.center.reset() 53 | .setColor(centerColor) 54 | .setPosition(pos) 55 | .setFixedPointPosition(pos) 56 | .setVelocity(10) 57 | .setElasticConstant(20) 58 | .setCriticalDamping() 59 | .setShowWhenStable(true) 60 | .setFill(true) 61 | .setMirror(true); 62 | item.sides.reset() 63 | .setColor(sidesColor) 64 | .setPosition(pos) 65 | .setFixedPointPosition(pos) 66 | .setVelocity(velocity) 67 | .setElasticConstant(10) 68 | .setCriticalDamping() 69 | .setShowWhenStable(true) 70 | .setLowerBound(strip->first()) 71 | .setUpperBound(strip->last()) 72 | .setRange(4, 7) 73 | .setMirror(true) 74 | .setFill(true); 75 | } 76 | -------------------------------------------------------------------------------- /src/Multiplex.h: -------------------------------------------------------------------------------- 1 | #ifndef Multiplex_h 2 | #define Multiplex_h 3 | 4 | #include 5 | #include "Fx.h" 6 | 7 | class Multiplex : public Fx { 8 | private: 9 | std::vector fxs; 10 | 11 | public: 12 | Multiplex( 13 | Fx *fx01 = nullptr, 14 | Fx *fx02 = nullptr, 15 | Fx *fx03 = nullptr, 16 | Fx *fx04 = nullptr, 17 | Fx *fx05 = nullptr, 18 | Fx *fx06 = nullptr, 19 | Fx *fx07 = nullptr, 20 | Fx *fx08 = nullptr, 21 | Fx *fx09 = nullptr, 22 | Fx *fx10 = nullptr, 23 | Fx *fx11 = nullptr, 24 | Fx *fx12 = nullptr, 25 | Fx *fx13 = nullptr, 26 | Fx *fx14 = nullptr, 27 | Fx *fx15 = nullptr, 28 | Fx *fx16 = nullptr, 29 | Fx *fx17 = nullptr, 30 | Fx *fx18 = nullptr, 31 | Fx *fx19 = nullptr, 32 | Fx *fx20 = nullptr 33 | ) { 34 | if (fx01 != nullptr) fxs.push_back(fx01); 35 | if (fx02 != nullptr) fxs.push_back(fx02); 36 | if (fx03 != nullptr) fxs.push_back(fx03); 37 | if (fx04 != nullptr) fxs.push_back(fx04); 38 | if (fx05 != nullptr) fxs.push_back(fx05); 39 | if (fx06 != nullptr) fxs.push_back(fx06); 40 | if (fx07 != nullptr) fxs.push_back(fx07); 41 | if (fx08 != nullptr) fxs.push_back(fx08); 42 | if (fx09 != nullptr) fxs.push_back(fx09); 43 | if (fx10 != nullptr) fxs.push_back(fx10); 44 | if (fx11 != nullptr) fxs.push_back(fx11); 45 | if (fx12 != nullptr) fxs.push_back(fx12); 46 | if (fx13 != nullptr) fxs.push_back(fx13); 47 | if (fx14 != nullptr) fxs.push_back(fx14); 48 | if (fx15 != nullptr) fxs.push_back(fx15); 49 | if (fx16 != nullptr) fxs.push_back(fx16); 50 | if (fx17 != nullptr) fxs.push_back(fx17); 51 | if (fx18 != nullptr) fxs.push_back(fx18); 52 | if (fx19 != nullptr) fxs.push_back(fx19); 53 | if (fx20 != nullptr) fxs.push_back(fx20); 54 | } 55 | 56 | void flush() override { 57 | for (Fx *fx : fxs) { 58 | fx->flush(); 59 | } 60 | } 61 | 62 | void loop() { 63 | for (Fx *fx : fxs) { 64 | fx->loop(); 65 | } 66 | } 67 | 68 | void reset() { 69 | for (Fx *fx : fxs) { 70 | fx->reset(); 71 | } 72 | } 73 | 74 | }; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /src/strip/virtual/ReversedStrip.cpp: -------------------------------------------------------------------------------- 1 | #include "ReversedStrip.h" 2 | 3 | ReversedStrip::ReversedStrip(Strip *strip) { 4 | this->strip = strip; 5 | } 6 | 7 | Strip *ReversedStrip::overlay(double opacity) { 8 | return new BufferedStrip(this, opacity); 9 | } 10 | 11 | uint16_t ReversedStrip::size() { 12 | return strip->size(); 13 | } 14 | 15 | int16_t ReversedStrip::toStrip(int16_t index) { 16 | return strip->last() - index; 17 | } 18 | 19 | void ReversedStrip::_fade(int16_t indexFrom, int16_t indexTo, uint8_t amount) { 20 | if (crop(indexFrom, indexTo)) { 21 | strip->fade(amount, toStrip(indexTo), toStrip(indexFrom)); 22 | } 23 | } 24 | 25 | void ReversedStrip::_blur(int16_t indexFrom, int16_t indexTo, uint8_t amount) { 26 | if (crop(indexFrom, indexTo)) { 27 | strip->blur(amount, toStrip(indexTo), toStrip(indexFrom)); 28 | } 29 | } 30 | 31 | CRGB ReversedStrip::_shiftUp(int16_t indexFrom, int16_t indexTo, CRGB in) { 32 | if (crop(indexFrom, indexTo)) { 33 | return strip->shiftDown(toStrip(indexTo), toStrip(indexFrom), in); 34 | } 35 | return CRGB::Black; 36 | } 37 | 38 | CRGB ReversedStrip::_shiftDown(int16_t indexFrom, int16_t indexTo, CRGB in) { 39 | if (crop(indexFrom, indexTo)) { 40 | return strip->shiftUp(toStrip(indexTo), toStrip(indexFrom), in); 41 | } 42 | return CRGB::Black; 43 | } 44 | 45 | bool ReversedStrip::_paintSolid(int16_t indexFrom, int16_t indexTo, CRGB color, bool overlay) { 46 | if (crop(indexFrom, indexTo)) { 47 | return strip->paint(toStrip(indexTo), toStrip(indexFrom), color, overlay); 48 | } 49 | return false; 50 | } 51 | 52 | bool ReversedStrip::_paintGradient(int16_t indexFrom, int16_t indexTo, Gradient *gradient, double gradientFrom, double gradientTo, bool overlay) { 53 | if (crop(indexFrom, indexTo)) { 54 | return strip->paint(toStrip(indexTo), toStrip(indexFrom), gradient, gradientTo, gradientFrom, overlay); 55 | } 56 | return false; 57 | } 58 | 59 | bool ReversedStrip::_paintRainbow(int16_t indexFrom, int16_t indexTo, uint8_t initialHue, uint8_t deltaHue) { 60 | if (crop(indexFrom, indexTo)) { 61 | return strip->rainbow(initialHue + (indexTo - indexFrom + 1) * deltaHue, -deltaHue, toStrip(indexTo), toStrip(indexFrom)); 62 | } 63 | return false; 64 | } 65 | 66 | bool ReversedStrip::paintNormalizedSize(double positionFrom, int16_t size, CRGB color, bool overlay) { 67 | return strip->paintNormalizedSize(1 - positionFrom, size, color, overlay); 68 | } 69 | 70 | CRGB ReversedStrip::getIndex(int16_t index) { 71 | if (isInRange(index)) { 72 | return strip->getIndex(toStrip(index)); 73 | } 74 | return CRGB::Black; 75 | } 76 | -------------------------------------------------------------------------------- /src/strip/virtual/SubStrip.cpp: -------------------------------------------------------------------------------- 1 | #include "SubStrip.h" 2 | 3 | SubStrip::SubStrip(Strip *strip, int16_t start, int16_t end) { 4 | this->strip = strip; 5 | this->start = max(0, start); 6 | this->end = min(strip->last(), end); 7 | } 8 | 9 | Strip *SubStrip::overlay(double opacity) { 10 | return new BufferedStrip(this, opacity); 11 | } 12 | 13 | uint16_t SubStrip::size() { 14 | return end - start + 1; 15 | } 16 | 17 | int16_t SubStrip::toStrip(int16_t index) { 18 | return start + index; 19 | } 20 | 21 | void SubStrip::_fade(int16_t indexFrom, int16_t indexTo, uint8_t amount) { 22 | if (crop(indexFrom, indexTo)) { 23 | strip->fade(amount, toStrip(indexFrom), toStrip(indexTo)); 24 | } 25 | } 26 | 27 | void SubStrip::_blur(int16_t indexFrom, int16_t indexTo, uint8_t amount) { 28 | if (crop(indexFrom, indexTo)) { 29 | strip->blur(amount, toStrip(indexFrom), toStrip(indexTo)); 30 | } 31 | } 32 | 33 | CRGB SubStrip::_shiftUp(int16_t indexFrom, int16_t indexTo, CRGB in) { 34 | if (crop(indexFrom, indexTo)) { 35 | return strip->shiftUp(toStrip(indexFrom), toStrip(indexTo), in); 36 | } 37 | return CRGB::Black; 38 | } 39 | 40 | CRGB SubStrip::_shiftDown(int16_t indexFrom, int16_t indexTo, CRGB in) { 41 | if (crop(indexFrom, indexTo)) { 42 | return strip->shiftDown(toStrip(indexFrom), toStrip(indexTo), in); 43 | } 44 | return CRGB::Black; 45 | } 46 | 47 | bool SubStrip::_paintSolid(int16_t indexFrom, int16_t indexTo, CRGB color, bool overlay) { 48 | if (crop(indexFrom, indexTo)) { 49 | return strip->paint(toStrip(indexFrom), toStrip(indexTo), color, overlay); 50 | } 51 | return false; 52 | } 53 | 54 | bool SubStrip::_paintGradient(int16_t indexFrom, int16_t indexTo, Gradient *gradient, double gradientFrom, double gradientTo, bool overlay) { 55 | if (crop(indexFrom, indexTo)) { 56 | return strip->paint(toStrip(indexFrom), toStrip(indexTo), gradient, gradientFrom, gradientTo, overlay); 57 | } 58 | return false; 59 | } 60 | 61 | bool SubStrip::_paintRainbow(int16_t indexFrom, int16_t indexTo, uint8_t initialHue, uint8_t deltaHue) { 62 | if (crop(indexFrom, indexTo)) { 63 | return strip->rainbow(initialHue, deltaHue, toStrip(indexFrom), toStrip(indexTo)); 64 | } 65 | return false; 66 | } 67 | 68 | bool SubStrip::paintNormalizedSize(double positionFrom, int16_t size, CRGB color, bool overlay) { 69 | uint16_t indexFrom = fromNormalizedPosition(positionFrom, size); 70 | uint16_t indexTo = indexFrom + size - 1; 71 | return paint(indexFrom, indexTo, color, overlay); 72 | } 73 | 74 | CRGB SubStrip::getIndex(int16_t index) { 75 | if (isInRange(index)) { 76 | return strip->getIndex(toStrip(index)); 77 | } 78 | return CRGB::Black; 79 | } 80 | -------------------------------------------------------------------------------- /src/Fx.h: -------------------------------------------------------------------------------- 1 | #ifndef Fx_h 2 | #define Fx_h 3 | 4 | #include "audio/AudioChannel.h" 5 | #include "State.h" 6 | #include "strip/Strip.h" 7 | 8 | class Fx { 9 | protected: 10 | Strip *strip; 11 | AudioChannel *audioChannel; 12 | State *state; 13 | 14 | public: 15 | Fx() {}; 16 | 17 | Fx(Strip *strip) { 18 | this->strip = strip; 19 | }; 20 | 21 | Fx(Strip *strip, AudioChannel *audioChannel) { 22 | this->strip = strip; 23 | this->audioChannel = audioChannel; 24 | }; 25 | 26 | Fx(Strip *strip, AudioChannel *audioChannel, State *state) { 27 | this->strip = strip; 28 | this->audioChannel = audioChannel; 29 | this->state = state; 30 | }; 31 | 32 | Fx(Strip *strip, State *state) { 33 | this->strip = strip; 34 | this->state = state; 35 | }; 36 | 37 | void clear() { 38 | if (strip != nullptr) { 39 | strip->off(); 40 | } 41 | } 42 | 43 | virtual void flush() { 44 | if (strip != nullptr) { 45 | strip->flush(); 46 | } 47 | } 48 | 49 | void loopFlush() { 50 | loop(); 51 | flush(); 52 | } 53 | 54 | // virtual void reset(); 55 | virtual void reset() { 56 | clear(); 57 | } 58 | 59 | virtual void loop() { 60 | strip->off(); 61 | beforeRender(); 62 | for (int16_t index = strip->first(); index < strip->last(); index++) { 63 | double x = double(index) / strip->last(); 64 | strip->paint(index, render(index, x), true); 65 | } 66 | } 67 | 68 | virtual void beforeRender() { 69 | return; 70 | } 71 | 72 | virtual CRGB render(int16_t index, double x) { 73 | return CRGB::Black; 74 | } 75 | 76 | static double frac(double v) { 77 | double whole; 78 | double fractional = modf(v, &whole); 79 | return fractional < 0 ? 1 - fractional : fractional; 80 | } 81 | 82 | static double triangle(double v) { 83 | double v0 = frac(v); 84 | return v0 < .5 ? 2 * v0 : 2 * (1 - v0); 85 | } 86 | 87 | static double square(double v, double duty) { 88 | double v0 = frac(v); 89 | return v0 < duty ? 1 : 0; 90 | } 91 | 92 | static double wave(double v) { 93 | double v0 = frac(v); 94 | return (1 + sin(v0 * TWO_PI)) / 2; 95 | } 96 | 97 | static CRGB hsv(double h, double s, double v) { 98 | return CHSV(255 * h, 255 * s, 255 * v); 99 | } 100 | 101 | static CRGB rgb(double r, double g, double b) { 102 | return CRGB(255 * r, 255 * g, 255 * b); 103 | } 104 | }; 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /src/audio/AudioSensor.h: -------------------------------------------------------------------------------- 1 | #ifndef AudioSensor_h 2 | #define AudioSensor_h 3 | 4 | #include 5 | #include "AudioChannel.h" 6 | 7 | class AudioSensor { 8 | private: 9 | static const uint8_t DEFAULT_MIC_GAIN = 40; 10 | static const uint8_t DEFAULT_LINE_IN_LEVEL = 5; 11 | static const uint8_t MAX_MEMORY_BLOCKS = 30; 12 | static constexpr double LOWPASS_FREQUENCY = 250; 13 | 14 | AudioInputI2S audioInput; 15 | 16 | AudioAmplifier amp_L; 17 | AudioAmplifier amp_R; 18 | 19 | AudioAnalyzePeak peak_L; 20 | AudioAnalyzeRMS rms_L; 21 | AudioFilterBiquad filter_L; 22 | AudioAnalyzeRMS rmsLow_L; 23 | AudioAnalyzeFFT1024 fft_L; 24 | 25 | AudioAnalyzePeak peak_R; 26 | AudioAnalyzeRMS rms_R; 27 | AudioFilterBiquad filter_R; 28 | AudioAnalyzeRMS rmsLow_R; 29 | AudioAnalyzeFFT1024 fft_R; 30 | 31 | AudioMixer4 mixerUnfiltered; 32 | AudioMixer4 mixerFiltered; 33 | 34 | AudioAnalyzePeak peak_M; 35 | AudioAnalyzeRMS rms_M; 36 | AudioFilterBiquad filter_M; 37 | AudioAnalyzeRMS rmsLow_M; 38 | AudioAnalyzeFFT1024 fft_M; 39 | 40 | AudioConnection *patchAmpLeft; 41 | AudioConnection *patchAmpRight; 42 | 43 | AudioConnection *patchPeakLeft; 44 | AudioConnection *patchRMSLeft; 45 | AudioConnection *patchFilterLeft; 46 | AudioConnection *patchRMSLowLeft; 47 | AudioConnection *patchFFTLeft; 48 | 49 | AudioConnection *patchMixerUnfilteredLeft; 50 | AudioConnection *patchMixerFilteredLeft; 51 | 52 | AudioConnection *patchPeakRight; 53 | AudioConnection *patchRMSRight; 54 | AudioConnection *patchFilterRight; 55 | AudioConnection *patchRMSLowRight; 56 | AudioConnection *patchFFTRight; 57 | 58 | AudioConnection *patchMixerUnfilteredRight; 59 | AudioConnection *patchMixerFilteredRight; 60 | 61 | AudioConnection *patchPeakMono; 62 | AudioConnection *patchRMSMono; 63 | AudioConnection *patchFilterMono; 64 | AudioConnection *patchRMSLowMono; 65 | AudioConnection *patchFFTMono; 66 | 67 | AudioControlSGTL5000 *audioShield; 68 | uint8_t micGain = DEFAULT_MIC_GAIN; 69 | uint8_t lineInLevel = DEFAULT_LINE_IN_LEVEL; 70 | 71 | public: 72 | AudioChannel *left = new AudioChannel(); 73 | AudioChannel *right = new AudioChannel(); 74 | AudioChannel *mono = new AudioChannel(); 75 | AudioSensor(); 76 | void setup(); 77 | void loop(); 78 | void setEnabled(bool enabled); 79 | void setMicInput(); 80 | void setLineInput(); 81 | void setMicGain(uint8_t micGain); 82 | uint8_t getMicGain(); 83 | void increaseMicGain(); 84 | void decreaseMicGain(); 85 | void setLineInLevel(uint8_t lineInLevel); 86 | uint8_t getLineInLevel(); 87 | void increaseLineInLevel(); 88 | void decreaseLineInLevel(); 89 | double getNormalizedMicGain(); 90 | double getNormalizedLineInLevel(); 91 | void printStats(); 92 | }; 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /src/HarmonicMotion.h: -------------------------------------------------------------------------------- 1 | #ifndef HarmonicMotion_h 2 | #define HarmonicMotion_h 3 | 4 | #include "strip/Strip.h" 5 | #include "Gradient.h" 6 | 7 | class HarmonicMotion { 8 | public: 9 | enum ReboundMode {INSIDE = -1, OUTSIDE = 1, DEFAULT = 0}; 10 | enum RenderingMode {COLOR = 1, GRADIENT = 2}; 11 | 12 | private: 13 | struct Limit { 14 | double x; 15 | double r = 1; 16 | ReboundMode reboundMode = DEFAULT; 17 | }; 18 | Strip *strip; 19 | elapsedMicros timeElapsed; 20 | double a0 = 0; // first order acceleration 21 | double a1 = 0; // second order acceleration 22 | double a2 = 0; // third order acceleration 23 | double k = 0; // elastic constant 24 | double b = 0; // dampening 25 | double x = 0; // position 26 | double x0 = 0; // fixed point position 27 | double v = 0; // velocity 28 | int start = 0; // start led 29 | int end = 0; // end led 30 | bool mirror; // mirror 31 | bool fill; // fill 32 | bool showWhenStable; // show when stable 33 | bool overwrite; // add to pixel color 34 | double xPrev; // previous position 35 | double vPrev; // previous velocity 36 | Limit lowerLimit; // lower limit 37 | Limit upperLimit; // upper limit 38 | Gradient *gradient; 39 | RenderingMode renderingMode; 40 | int getLowerLimitCompensation(); 41 | int getUpperLimitCompensation(); 42 | bool isLowerLimit(); 43 | bool isUpperLimit(); 44 | void update(); 45 | void show(bool mirror); 46 | void render(double posMin, double posMax); 47 | 48 | public: 49 | CRGB color; 50 | HarmonicMotion(); 51 | HarmonicMotion& setup(Strip *strip); 52 | HarmonicMotion& reset(); 53 | HarmonicMotion& setColor(CRGB color); 54 | HarmonicMotion& setGradient(Gradient *gradient); 55 | HarmonicMotion& setAcceleration(double a0, double a1 = 0, double a2 = 0); 56 | HarmonicMotion& setElasticConstant(double k); 57 | HarmonicMotion& setDamping(double b); 58 | HarmonicMotion& setCriticalDamping(double correctionFactor = 1); 59 | HarmonicMotion& setPosition(double x); 60 | HarmonicMotion& setRandomPosition(); 61 | HarmonicMotion& setVelocity(double v); 62 | HarmonicMotion& setFixedPointPosition(double x0); 63 | HarmonicMotion& setRandomFixedPointPosition(); 64 | HarmonicMotion& setUpperBound(double x, double r = 0, ReboundMode mode = DEFAULT); 65 | HarmonicMotion& setLowerBound(double x, double r = 0, ReboundMode mode = DEFAULT); 66 | HarmonicMotion& setRange(int start, int end); 67 | HarmonicMotion& setMirror(bool mirror); 68 | HarmonicMotion& setFill(bool fill); 69 | HarmonicMotion& setShowWhenStable(bool showWhenStable); 70 | HarmonicMotion& setOverwrite(bool overwrite); 71 | double getFixedPointPosition(); 72 | double getPosition(); 73 | double getVelocity(); 74 | double getAcceleration(); 75 | bool isStable(); 76 | void loop(); 77 | }; 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /src/Controller.h: -------------------------------------------------------------------------------- 1 | #ifndef Controller_h 2 | #define Controller_h 3 | 4 | #include 5 | #include 6 | 7 | #include "audio/AudioSensor.h" 8 | #include "Brightness.h" 9 | #include "Stage.h" 10 | #include "State.h" 11 | #include "strip/Strip.h" 12 | #include "Timer.h" 13 | 14 | #define MIN_MAX(v, vmin, vmax) max(vmin, min(vmax, v)) 15 | 16 | class Controller { 17 | private: 18 | enum Mode {STOP, PLAY, SET_MIC_GAIN, SET_INPUT_LEVEL, SET_CYCLE_SPEED, SET_FX_SPEED}; 19 | enum Input {mic, line}; 20 | static const uint16_t MAX_PATTERN_DELAY_S = 60; 21 | static const unsigned long INPUT_TIMER_DURATION = 5000; 22 | static const unsigned long SENSITIVITY_TIMER_DURATION = 60000; 23 | static const unsigned long FX_SPEED_TIMER_DURATION = 1000; 24 | static const unsigned long CYCLE_SPEED_TIMER_DURATION = 3000; 25 | Stage *stage; 26 | AudioSensor *audioSensor; 27 | State *state; 28 | uint16_t fx = 0; 29 | Brightness *brightness = new Brightness(); 30 | struct PlayMode { 31 | bool manual = false; 32 | bool shuffle = false; 33 | } playMode; 34 | Input input; 35 | bool audioEnabled = false; 36 | Mode mode = PLAY; 37 | Timer cycleTimer = Timer(0, false); 38 | Timer modeTimer = Timer(0, false); 39 | Timer standbyTimer = Timer(0, false); 40 | Timer statsTimer = Timer(0, true); 41 | void saveParam(); 42 | void loadParam(); 43 | void resetCycleTimer(); 44 | void increaseMicGain(); 45 | void decreaseMicGain(); 46 | void increaseLineInLevel(); 47 | void decreaseLineInLevel(); 48 | void run(); 49 | 50 | public: 51 | Controller( 52 | Stage *stage, 53 | AudioSensor *audioSensor, 54 | State *state 55 | ); 56 | void setup(); 57 | void loop(); 58 | 59 | void setStandbyTimer(unsigned long timeout); 60 | void clearStandbyTimer(); 61 | void setStatsTimer(unsigned long timeout); 62 | 63 | void setMode(Mode mode = PLAY, unsigned long duration = 0); 64 | 65 | void setInput(Input input, bool feedback); 66 | void setLineInput(uint8_t level); 67 | void setMicInput(uint8_t gain); 68 | void toggleInput(); 69 | void setAudioEnabled(bool enabled); 70 | void toggleAudio(); 71 | void increaseInputSensitivity(); 72 | void decreaseInputSensitivity(); 73 | 74 | void info(); 75 | void feedback(uint8_t count = 1); 76 | void reset(); 77 | 78 | void setBrightness(uint8_t value); 79 | void increaseBrightness(); 80 | void decreaseBrightness(); 81 | 82 | void setParam(uint8_t value); 83 | void increaseParam(); 84 | void decreaseParam(); 85 | 86 | void selectFx(uint8_t fx); 87 | void selectPreviousFx(); 88 | void selectNextFx(); 89 | void selectRandomFx(); 90 | void play(); 91 | void sequential(); 92 | void shuffle(); 93 | void pause(); 94 | void playPause(); 95 | void stop(); 96 | 97 | void cycleSpeed(); 98 | void setCycleSpeed(uint8_t speed); 99 | void increaseCycleSpeed(); 100 | void decreaseCycleSpeed(); 101 | 102 | void fxSpeed(); 103 | void setFxSpeed(uint8_t speed); 104 | void increaseFxSpeed(); 105 | void decreaseFxSpeed(); 106 | }; 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /src/audio/AudioChannel.cpp: -------------------------------------------------------------------------------- 1 | #include "AudioChannel.h" 2 | 3 | AudioChannel::AudioChannel() { 4 | beatDetector = new PeakDetector(BUFFER_SIZE, PEAK_FACTOR, PEAK_INFLUENCE, PEAK_INHIBIT_MS, PEAK_THRESHOLD); 5 | for (uint16_t i = 0; i < FFT_BANDS; i++) { 6 | peakDetectors[i] = new PeakDetector(BUFFER_SIZE, PEAK_FACTOR, PEAK_INFLUENCE, PEAK_INHIBIT_MS, PEAK_THRESHOLD); 7 | } 8 | peakFadeTimer.reset(); 9 | } 10 | 11 | void AudioChannel::feedPeak(double value) { 12 | peak = value; 13 | peakSmooth = max(peakSmooth, value); 14 | peakHold = max(peakHold, value); 15 | clipping = value > CLIPPING_THRESHOLD; 16 | } 17 | 18 | void AudioChannel::feedRMS(double value) { 19 | rms = value; 20 | detectSignal(value); 21 | } 22 | 23 | void AudioChannel::feedRMSLow(double value) { 24 | rmsLow = value; 25 | detectBeat(value); 26 | } 27 | 28 | void AudioChannel::detectSignal(double value) { 29 | if (millis() - lastSignal > SIGNAL_HOLD_MS) { 30 | signalDetected = false; 31 | } 32 | if (value > SIGNAL_THRESHOLD) { 33 | signalDetected = true; 34 | lastSignal = millis(); 35 | } 36 | } 37 | 38 | void AudioChannel::detectBeat(double value) { 39 | beatDetected = beatDetector->isPeak(value); 40 | } 41 | 42 | void AudioChannel::setBand(AudioAnalyzeFFT1024 *fft, uint8_t band, uint16_t fromBin, uint16_t toBin) { 43 | // bands[band].peak = 20 * log10(max(1e-5, fft->read(fromBin, toBin))); 44 | bands[band].peak = fft->read(fromBin, toBin); 45 | } 46 | 47 | void AudioChannel::feedBins(AudioAnalyzeFFT1024 *fft) { 48 | for (uint16_t i = 0; i < FFT_BINS; i++) { 49 | fftBin[i] = fft->read(i); 50 | } 51 | } 52 | 53 | void AudioChannel::feedBands(AudioAnalyzeFFT1024 *fft) { 54 | // 16 band 55 | setBand(fft, 0, 0, 0); 56 | setBand(fft, 1, 1, 1); 57 | setBand(fft, 2, 2, 3); 58 | setBand(fft, 3, 4, 6); 59 | setBand(fft, 4, 7, 10); 60 | setBand(fft, 5, 11, 15); 61 | setBand(fft, 6, 16, 22); 62 | setBand(fft, 7, 23, 32); 63 | setBand(fft, 8, 33, 46); 64 | setBand(fft, 9, 47, 66); 65 | setBand(fft, 10, 67, 93); 66 | setBand(fft, 11, 94, 131); 67 | setBand(fft, 12, 132, 184); 68 | setBand(fft, 13, 185, 257); 69 | setBand(fft, 14, 258, 359); 70 | setBand(fft, 15, 360, 511); 71 | } 72 | 73 | void AudioChannel::loop(AudioAnalyzePeak *peak, AudioAnalyzeRMS *rms, AudioAnalyzeRMS *rmsLow, AudioAnalyzeFFT1024 *fft) { 74 | if (peak != nullptr && peak->available()) { 75 | feedPeak(peak->read()); 76 | } 77 | if (rms != nullptr && rms->available()) { 78 | feedRMS(rms->read()); 79 | } 80 | if (rmsLow != nullptr && rmsLow->available()) { 81 | feedRMSLow(rmsLow->read()); 82 | } 83 | if (fft != nullptr && fft->available()) { 84 | feedBins(fft); 85 | feedBands(fft); 86 | 87 | double peakBandValue = -60; 88 | dominantBand = 0; 89 | for (uint8_t i = 0; i < FFT_BANDS; i++) { 90 | bands[i].peakSmooth = max(bands[i].peakSmooth, bands[i].peak); 91 | if (bands[i].peak > peakBandValue) { 92 | peakBandValue = bands[i].peak; 93 | dominantBand = i; 94 | } 95 | bands[i].peakDetected = peakDetectors[i]->isPeak(bands[i].peak); 96 | } 97 | 98 | } 99 | if (peakFadeTimer.isElapsed()) { 100 | peakSmooth *= .990; 101 | peakHold *= .999; 102 | for (uint8_t i = 0; i < FFT_BANDS; i++) { 103 | bands[i].peakSmooth = ((bands[i].peakSmooth + 60) * .990 - 60); 104 | bands[i].peakHold = ((bands[i].peakHold + 60) * .999 - 60); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/fx/DeepSpace.cpp: -------------------------------------------------------------------------------- 1 | #include "DeepSpace.h" 2 | 3 | DeepSpace::DeepSpace(Strip *strip, AudioChannel *audioChannel, State *state, CRGB baseColor, CRGB accentColor) : Fx(strip, audioChannel, state) { 4 | this->baseColor = baseColor; 5 | this->accentColor = accentColor; 6 | this->audioTrigger = new AudioTrigger(audioChannel); 7 | for (uint16_t i = 0; i < ITEMS; i++) { 8 | items[i].pixel.setup(strip); 9 | } 10 | } 11 | 12 | DeepSpace::~DeepSpace() { 13 | delete audioTrigger; 14 | } 15 | 16 | void DeepSpace::reset() { 17 | clear(); 18 | for (uint16_t i = 0; i < ITEMS; i++) { 19 | randomizeItem(items[i]); 20 | } 21 | time = 0; 22 | steeringAngle = 0; 23 | transition = 1; 24 | timer.reset(); 25 | audioTrigger->reset(); 26 | } 27 | 28 | void DeepSpace::loop() { 29 | strip->off(); 30 | 31 | double dT = time / 1e6; 32 | time = 0; 33 | 34 | bool trigger = audioTrigger->triggered(1); 35 | 36 | double translationY = SPEED * dT; 37 | double rotation = 0; 38 | 39 | if (transition == 1) { 40 | if (timer.isElapsed()) { 41 | switch (random8(5)) { 42 | case 1: 43 | steeringAngle = -random16(MIN_STEERING, MAX_STEERING); 44 | transitionSpeed = .001 * random16(MIN_TRANSITION_SPEED, MAX_TRANSITION_SPEED); 45 | transition = 0; 46 | break; 47 | case 2: 48 | steeringAngle = random16(MIN_STEERING, MAX_STEERING); 49 | transitionSpeed = .001 * random16(MIN_TRANSITION_SPEED, MAX_TRANSITION_SPEED); 50 | transition = 0; 51 | break; 52 | default: 53 | steeringAngle = 0; 54 | transitionSpeed = 0; 55 | break; 56 | } 57 | timer.reset(); 58 | } 59 | } else { 60 | double previousTransition = transition; 61 | transition = min(1, transition + transitionSpeed * dT); 62 | rotation = steeringAngle * Easing::deltaEaseInOutCubic(transition, previousTransition) / 180 * TWO_PI; 63 | } 64 | 65 | for (uint16_t i = 0; i < ITEMS; i++) { 66 | loopItem(items[i], translationY, rotation, trigger); 67 | } 68 | } 69 | 70 | void DeepSpace::loopItem(Item &item, double translationY, double rotation, bool &trigger) { 71 | item.point.translate(0, translationY).rotate(rotation); 72 | 73 | if (item.point.y <= 0) { 74 | randomizeItem(item); 75 | } 76 | 77 | double angle = item.point.angle(); 78 | double distance = item.point.radius(); 79 | double distanceSquared = pow(distance, 2); 80 | 81 | // double pos = min(1, max(0, 1 - (angle / PI))); 82 | double pos = 1 - (angle / PI); 83 | uint8_t brightness = min(255, 5 * MAX_SQUARED_DISTANCE / distanceSquared); 84 | 85 | if (trigger && item.type == NORMAL && distance < MAX_MUTATION_DISTANCE) { 86 | trigger = false; 87 | item.type = HIGHLIGHT; 88 | } 89 | 90 | CRGB color; 91 | switch (item.type) { 92 | case NORMAL: 93 | color = CRGB(baseColor).fadeToBlackBy(255 - brightness); 94 | break; 95 | case HIGHLIGHT: 96 | color = CRGB(accentColor).fadeToBlackBy(255 - brightness); 97 | break; 98 | default: 99 | color = CRGB::Black; 100 | break; 101 | } 102 | 103 | item.pixel.setNormalized(pos, color); 104 | } 105 | 106 | void DeepSpace::randomizeItem(Item &item) { 107 | double distance = random16(MIN_DISTANCE, MAX_DISTANCE); 108 | double angle = random16(0, 18000) / (100 * PI); 109 | item.point = Point::fromPolar(distance, angle); 110 | item.type = random8(100) < (state->parabolicFxSpeed * 95 + 5) 111 | ? NORMAL 112 | : HIDDEN; 113 | item.pixel.reset(); 114 | } 115 | -------------------------------------------------------------------------------- /src/strip/StatefulStrip.cpp: -------------------------------------------------------------------------------- 1 | #include "StatefulStrip.h" 2 | 3 | StatefulStrip::StatefulStrip(CRGBSet &leds, uint16_t density) { 4 | this->leds = &leds; 5 | this->density = density; 6 | } 7 | 8 | StatefulStrip::StatefulStrip(CRGBSet *leds, uint16_t density) { 9 | this->leds = leds; 10 | this->density = density; 11 | } 12 | 13 | StatefulStrip::StatefulStrip() {} 14 | 15 | void StatefulStrip::setLeds(CRGBSet *leds) { 16 | this->leds = leds; 17 | } 18 | 19 | Strip *StatefulStrip::overlay(double opacity) { 20 | return this; 21 | } 22 | 23 | uint16_t StatefulStrip::size() { 24 | return leds->size(); 25 | } 26 | 27 | void StatefulStrip::_fade(int16_t indexFrom, int16_t indexTo, uint8_t amount) { 28 | if (crop(indexFrom, indexTo, true)) { 29 | (*leds)(indexFrom, indexTo).fadeToBlackBy(amount); 30 | } 31 | } 32 | 33 | void StatefulStrip::_blur(int16_t indexFrom, int16_t indexTo, uint8_t amount) { 34 | if (crop(indexFrom, indexTo, true)) { 35 | (*leds)(indexFrom, indexTo).blur1d(amount); 36 | } 37 | } 38 | 39 | CRGB StatefulStrip::_shiftUp(int16_t indexFrom, int16_t indexTo, CRGB in) { 40 | if (crop(indexFrom, indexTo, true)) { 41 | if (indexFrom == indexTo) { 42 | return in; 43 | } else { 44 | CRGB out = (*leds)[indexTo]; 45 | for (uint16_t i = indexTo; i > indexFrom ; i--) { 46 | (*leds)[i] = (*leds)[i - 1]; 47 | } 48 | (*leds)[indexFrom] = in; 49 | return out; 50 | } 51 | } 52 | return CRGB::Black; 53 | } 54 | 55 | CRGB StatefulStrip::_shiftDown(int16_t indexFrom, int16_t indexTo, CRGB in) { 56 | if (crop(indexFrom, indexTo, true)) { 57 | if (indexFrom == indexTo) { 58 | return in; 59 | } else { 60 | CRGB out = (*leds)[indexFrom]; 61 | for (uint16_t i = indexFrom; i < indexTo; i++) { 62 | (*leds)[i] = (*leds)[i + 1]; 63 | } 64 | (*leds)[indexTo] = in; 65 | return out; 66 | } 67 | } 68 | return CRGB::Black; 69 | } 70 | 71 | bool StatefulStrip::_paintSolid(int16_t indexFrom, int16_t indexTo, CRGB color, bool overlay) { 72 | if (crop(indexFrom, indexTo, true)) { 73 | if (overlay) { 74 | (*leds)(indexFrom, indexTo) |= color; 75 | } else { 76 | (*leds)(indexFrom, indexTo) = color; 77 | } 78 | return true; 79 | } 80 | return false; 81 | } 82 | 83 | bool StatefulStrip::_paintGradient(int16_t indexFrom, int16_t indexTo, Gradient *gradient, double gradientFrom, double gradientTo, bool overlay) { 84 | if (crop(indexFrom, indexTo)) { 85 | for (uint16_t i = limitToRange(indexFrom); i < limitToRange(indexTo); i++) { 86 | CRGB color = gradient->getColor(gradientFrom + (gradientTo - gradientFrom) * (i - indexFrom) / (indexTo - indexFrom)); 87 | if (overlay) { 88 | (*leds)[i] |= color; 89 | } else { 90 | (*leds)[i] = color; 91 | } 92 | } 93 | return true; 94 | } 95 | return false; 96 | } 97 | 98 | bool StatefulStrip::_paintRainbow(int16_t indexFrom, int16_t indexTo, uint8_t initialHue, uint8_t deltaHue) { 99 | if (crop(indexFrom, indexTo, true)) { 100 | (*leds)(indexFrom, indexTo).fill_rainbow(initialHue, deltaHue); 101 | return true; 102 | } 103 | return false; 104 | } 105 | 106 | bool StatefulStrip::paintNormalizedSize(double positionFrom, int16_t size, CRGB color, bool overlay) { 107 | uint16_t indexFrom = fromNormalizedPosition(positionFrom, size); 108 | uint16_t indexTo = indexFrom + size - 1; 109 | return paint(indexFrom, indexTo, color, overlay); 110 | } 111 | 112 | CRGB StatefulStrip::getIndex(int16_t index) { 113 | if (isInRange(index)) { 114 | return (*leds)[index]; 115 | } 116 | return CRGB::Black; 117 | } 118 | -------------------------------------------------------------------------------- /src/remote/SoundbridgeRemote.h: -------------------------------------------------------------------------------- 1 | #ifndef SoundbridgeRemote_h 2 | #define SoundbridgeRemote_h 3 | 4 | #include "remote/IRRemote.h" 5 | 6 | class SoundbridgeRemote : public IRRemote { 7 | private: 8 | static const uint16_t CMD_POWER = 0x16; 9 | static const uint16_t CMD_BRIGHTNESS = 0x42; 10 | static const uint16_t CMD_HOME = 0x17; 11 | static const uint16_t CMD_SEARCH = 0x18; 12 | static const uint16_t CMD_BACK = 0x15; 13 | static const uint16_t CMD_UP = 0x12; 14 | static const uint16_t CMD_LEFT = 0x10; 15 | static const uint16_t CMD_OK = 0x14; 16 | static const uint16_t CMD_RIGHT = 0x11; 17 | static const uint16_t CMD_DOWN = 0x13; 18 | static const uint16_t CMD_PREVIOUS = 0x1B; 19 | static const uint16_t CMD_PLAY = 0x19; 20 | static const uint16_t CMD_NEXT = 0x1A; 21 | static const uint16_t CMD_PAUSE = 0x1C; 22 | static const uint16_t CMD_INCREASE = 0x40; 23 | static const uint16_t CMD_SHUFFLE = 0x1E; 24 | static const uint16_t CMD_PLUS = 0x1D; 25 | static const uint16_t CMD_DECREASE = 0x41; 26 | static const uint16_t CMD_CYCLE = 0x1F; 27 | static const uint16_t MODE_NORMAL = 0; 28 | static const uint16_t MODE_TIME = 1; 29 | 30 | void handleCommand(uint8_t protocol, uint16_t command, bool repeated) override { 31 | if (protocol == 2) { // P=NEC A=0x906F 32 | if (!repeated) { 33 | handleNonRepeatableKey(command); 34 | } 35 | handleRepeatableKey(command); 36 | } 37 | } 38 | 39 | void handleRepeatableKey(uint16_t key) { 40 | switch(key) { 41 | case CMD_UP: 42 | controller->increaseParam(); 43 | break; 44 | case CMD_DOWN: 45 | controller->decreaseParam(); 46 | break; 47 | case CMD_LEFT: 48 | controller->selectPreviousFx(); 49 | break; 50 | case CMD_RIGHT: 51 | controller->selectNextFx(); 52 | break; 53 | case CMD_DECREASE: 54 | controller->decreaseBrightness(); 55 | break; 56 | case CMD_INCREASE: 57 | controller->increaseBrightness(); 58 | break; 59 | } 60 | }; 61 | 62 | void handleNonRepeatableKey(uint16_t key) { 63 | switch(key) { 64 | case CMD_HOME: 65 | controller->selectFx(0); 66 | break; 67 | case CMD_PLAY: 68 | controller->play(); 69 | break; 70 | case CMD_POWER: 71 | controller->stop(); 72 | break; 73 | case CMD_CYCLE: 74 | controller->sequential(); 75 | break; 76 | case CMD_SHUFFLE: 77 | controller->shuffle(); 78 | break; 79 | case CMD_BACK: 80 | controller->reset(); 81 | break; 82 | case CMD_PAUSE: 83 | controller->pause(); 84 | break; 85 | case CMD_PREVIOUS: 86 | controller->selectPreviousFx(); 87 | break; 88 | case CMD_NEXT: 89 | controller->selectNextFx(); 90 | break; 91 | case CMD_LEFT: 92 | // controller->prevParam(); 93 | break; 94 | case CMD_RIGHT: 95 | // controller->nextParam(); 96 | break; 97 | case CMD_SEARCH: 98 | controller->toggleInput(); 99 | break; 100 | case CMD_OK: 101 | controller->reset(); 102 | break; 103 | } 104 | } 105 | 106 | public: 107 | SoundbridgeRemote(Controller *controller) : IRRemote(controller) {}; 108 | }; 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /src/audio/AudioSensor.cpp: -------------------------------------------------------------------------------- 1 | #include "AudioSensor.h" 2 | 3 | AudioSensor::AudioSensor() { 4 | 5 | patchAmpLeft = new AudioConnection(audioInput, 0, amp_L, 0); 6 | patchAmpRight = new AudioConnection(audioInput, 1, amp_R, 0); 7 | 8 | patchPeakLeft = new AudioConnection(amp_L, 0, peak_L, 0); 9 | patchRMSLeft = new AudioConnection(amp_L, 0, rms_L, 0); 10 | patchFFTLeft = new AudioConnection(amp_L, 0, fft_L, 0); 11 | patchFilterLeft = new AudioConnection(amp_L, 0, filter_L, 0); 12 | patchRMSLowLeft = new AudioConnection(filter_L, 0, rmsLow_L, 0); 13 | 14 | patchMixerUnfilteredLeft = new AudioConnection(amp_L, 0, mixerUnfiltered, 0); 15 | patchMixerFilteredLeft = new AudioConnection(filter_L, mixerFiltered); 16 | 17 | patchPeakRight = new AudioConnection(amp_R, 0, peak_R, 0); 18 | patchRMSRight = new AudioConnection(amp_R, 0, rms_R, 0); 19 | patchFFTRight = new AudioConnection(amp_R, 0, fft_R, 0); 20 | patchFilterRight = new AudioConnection(amp_R, 0, filter_R, 0); 21 | patchRMSLowRight = new AudioConnection(filter_R, 0, rmsLow_R, 0); 22 | 23 | patchMixerUnfilteredRight = new AudioConnection(amp_R, 0, mixerUnfiltered, 1); 24 | patchMixerFilteredRight = new AudioConnection(filter_R, mixerFiltered); 25 | 26 | patchPeakMono = new AudioConnection(mixerUnfiltered, peak_M); 27 | patchRMSMono = new AudioConnection(mixerUnfiltered, rms_M); 28 | patchFFTMono = new AudioConnection(mixerUnfiltered, fft_M); 29 | patchRMSLowMono = new AudioConnection(mixerFiltered, rmsLow_M); 30 | 31 | audioShield = new AudioControlSGTL5000(); 32 | 33 | mixerUnfiltered.gain(0, 0.5); 34 | mixerUnfiltered.gain(1, 0.5); 35 | mixerFiltered.gain(0, 0.5); 36 | mixerFiltered.gain(1, 0.5); 37 | 38 | // Butterworth filter, 12 db/octave 39 | // filter_L.setLowpass(0, LOWPASS_FREQUENCY); 40 | // filter_R.setLowpass(0, LOWPASS_FREQUENCY); 41 | 42 | // Linkwitz-Riley filter, 48 dB/octave 43 | filter_L.setLowpass(0, LOWPASS_FREQUENCY, 0.54); 44 | filter_L.setLowpass(1, LOWPASS_FREQUENCY, 1.3); 45 | filter_L.setLowpass(2, LOWPASS_FREQUENCY, 0.54); 46 | filter_L.setLowpass(3, LOWPASS_FREQUENCY, 1.3); 47 | filter_R.setLowpass(0, LOWPASS_FREQUENCY, 0.54); 48 | filter_R.setLowpass(1, LOWPASS_FREQUENCY, 1.3); 49 | filter_R.setLowpass(2, LOWPASS_FREQUENCY, 0.54); 50 | filter_R.setLowpass(3, LOWPASS_FREQUENCY, 1.3); 51 | 52 | } 53 | 54 | void AudioSensor::setup() { 55 | AudioMemory(MAX_MEMORY_BLOCKS); 56 | audioShield->enable(); 57 | audioShield->volume(0); 58 | setLineInput(); 59 | fft_L.windowFunction(AudioWindowHanning1024); 60 | fft_R.windowFunction(AudioWindowHanning1024); 61 | fft_M.windowFunction(AudioWindowHanning1024); 62 | } 63 | 64 | void AudioSensor::loop() { 65 | left->loop(&peak_L, &rms_L, &rmsLow_L, &fft_L); 66 | right->loop(&peak_R, &rms_R, &rmsLow_R, &fft_R); 67 | mono->loop(&peak_M, &rms_M, &rmsLow_M, &fft_M); 68 | } 69 | 70 | void AudioSensor::setEnabled(bool enabled) { 71 | amp_L.gain(enabled ? 1 : 0); 72 | amp_R.gain(enabled ? 1 : 0); 73 | } 74 | 75 | void AudioSensor::setMicInput() { 76 | audioShield->inputSelect(AUDIO_INPUT_MIC); 77 | audioShield->micGain(micGain); 78 | Serial.println("Mic input selected"); 79 | } 80 | 81 | void AudioSensor::setMicGain(uint8_t micGain) { 82 | this->micGain = micGain; 83 | audioShield->micGain(micGain); 84 | Serial.print("Mic gain: "); 85 | Serial.println(micGain); 86 | } 87 | 88 | uint8_t AudioSensor::getMicGain() { 89 | return micGain; 90 | } 91 | 92 | void AudioSensor::increaseMicGain() { 93 | setMicGain(min(63, micGain + 1)); 94 | } 95 | 96 | void AudioSensor::decreaseMicGain() { 97 | setMicGain(max(0, micGain - 1)); 98 | } 99 | 100 | void AudioSensor::setLineInput() { 101 | audioShield->inputSelect(AUDIO_INPUT_LINEIN); 102 | audioShield->lineInLevel(lineInLevel); 103 | Serial.println("Line in input selected"); 104 | } 105 | 106 | void AudioSensor::setLineInLevel(uint8_t lineInLevel) { 107 | this->lineInLevel = lineInLevel; 108 | audioShield->lineInLevel(lineInLevel); 109 | Serial.print("Line in level: "); 110 | Serial.println(lineInLevel); 111 | } 112 | 113 | uint8_t AudioSensor::getLineInLevel() { 114 | return lineInLevel; 115 | } 116 | 117 | void AudioSensor::increaseLineInLevel() { 118 | setLineInLevel(min(15, lineInLevel + 1)); 119 | } 120 | 121 | void AudioSensor::decreaseLineInLevel() { 122 | setLineInLevel(max(0, lineInLevel - 1)); 123 | } 124 | 125 | double AudioSensor::getNormalizedMicGain() { 126 | return double(micGain) / 63; 127 | } 128 | 129 | double AudioSensor::getNormalizedLineInLevel() { 130 | return double(lineInLevel) / 15; 131 | } 132 | 133 | void AudioSensor::printStats() { 134 | Serial.print("AudioMemoryUsage: "); 135 | Serial.print(AudioMemoryUsage()); 136 | Serial.print(", AudioMemoryUsageMax: "); 137 | Serial.print(AudioMemoryUsageMax()); 138 | Serial.print(", AudioProcessorUsage: "); 139 | Serial.print(AudioProcessorUsage()); 140 | Serial.print(", AudioProcessorUsageMax: "); 141 | Serial.print(AudioProcessorUsageMax()); 142 | Serial.println(); 143 | } 144 | -------------------------------------------------------------------------------- /src/remote/SonyRemote_RMD420.h: -------------------------------------------------------------------------------- 1 | #ifndef SonyRemote_RMD420_h 2 | #define SonyRemote_RMD420_h 3 | 4 | #include "remote/IRRemote.h" 5 | 6 | class SonyRemote_RMD420 : public IRRemote { 7 | private: 8 | static const uint16_t CMD_CONTINUE = 29; 9 | static const uint16_t CMD_SHUFFLE = 53; 10 | static const uint16_t CMD_PROGRAM = 31; 11 | static const uint16_t CMD_1 = 0; 12 | static const uint16_t CMD_2 = 1; 13 | static const uint16_t CMD_3 = 2; 14 | static const uint16_t CMD_4 = 3; 15 | static const uint16_t CMD_5 = 4; 16 | static const uint16_t CMD_6 = 5; 17 | static const uint16_t CMD_7 = 6; 18 | static const uint16_t CMD_8 = 7; 19 | static const uint16_t CMD_9 = 8; 20 | static const uint16_t CMD_10PLUS = 39; 21 | static const uint16_t CMD_10 = 32; 22 | static const uint16_t CMD_CLEAR = 15; 23 | static const uint16_t CMD_TIME = 40; 24 | static const uint16_t CMD_CHECK = 13; 25 | static const uint16_t CMD_REPEAT = 44; 26 | static const uint16_t CMD_FADER = 95; 27 | static const uint16_t CMD_PLAY = 50; 28 | static const uint16_t CMD_PAUSE = 57; 29 | static const uint16_t CMD_STOP = 56; 30 | static const uint16_t CMD_PREVIOUS = 48; 31 | static const uint16_t CMD_NEXT = 49; 32 | static const uint16_t CMD_PLUS = 18; 33 | static const uint16_t CMD_BACKWARD = 51; 34 | static const uint16_t CMD_FORWARD = 52; 35 | static const uint16_t CMD_MINUS = 19; 36 | 37 | bool tenPlus = false; 38 | 39 | void handleCommand(uint8_t protocol, uint16_t command, bool repeated) override { 40 | if (protocol == 1) { // P=SIRCS A=0x0) 41 | if (!repeated) { 42 | handleNonRepeatableKey(command); 43 | } 44 | handleRepeatableKey(command); 45 | } 46 | } 47 | 48 | void handleNumericKey(int numKey) { 49 | int num = numKey; 50 | if (tenPlus) { 51 | num += 10; 52 | tenPlus = false; 53 | } 54 | controller->setParam(num); 55 | }; 56 | 57 | void handleRepeatableKey(uint16_t key) { 58 | switch(key) { 59 | case CMD_PLUS: 60 | controller->increaseBrightness(); 61 | break; 62 | case CMD_MINUS: 63 | controller->decreaseBrightness(); 64 | break; 65 | case CMD_BACKWARD: 66 | controller->decreaseParam(); 67 | break; 68 | case CMD_FORWARD: 69 | controller->increaseParam(); 70 | break; 71 | } 72 | }; 73 | 74 | void handleNonRepeatableKey(uint16_t key) { 75 | switch(key) { 76 | case CMD_CONTINUE: 77 | controller->sequential(); 78 | break; 79 | case CMD_SHUFFLE: 80 | controller->shuffle(); 81 | break; 82 | case CMD_PROGRAM: 83 | break; 84 | case CMD_1: 85 | handleNumericKey(1); 86 | break; 87 | case CMD_2: 88 | handleNumericKey(2); 89 | break; 90 | case CMD_3: 91 | handleNumericKey(3); 92 | break; 93 | case CMD_4: 94 | handleNumericKey(4); 95 | break; 96 | case CMD_5: 97 | handleNumericKey(5); 98 | break; 99 | case CMD_6: 100 | handleNumericKey(6); 101 | break; 102 | case CMD_7: 103 | handleNumericKey(7); 104 | break; 105 | case CMD_8: 106 | handleNumericKey(8); 107 | break; 108 | case CMD_9: 109 | handleNumericKey(9); 110 | break; 111 | case CMD_10: 112 | handleNumericKey(10); 113 | break; 114 | case CMD_10PLUS: 115 | tenPlus = true; 116 | break; 117 | case CMD_CLEAR: 118 | controller->setMode(); 119 | break; 120 | case CMD_TIME: 121 | controller->cycleSpeed(); 122 | break; 123 | case CMD_CHECK: 124 | controller->toggleInput(); 125 | break; 126 | case CMD_REPEAT: 127 | controller->reset(); 128 | break; 129 | case CMD_FADER: 130 | controller->toggleAudio(); 131 | break; 132 | case CMD_PLAY: 133 | controller->play(); 134 | break; 135 | case CMD_PAUSE: 136 | controller->pause(); 137 | break; 138 | case CMD_STOP: 139 | controller->stop(); 140 | break; 141 | case CMD_PREVIOUS: 142 | controller->selectPreviousFx(); 143 | break; 144 | case CMD_NEXT: 145 | controller->selectNextFx(); 146 | break; 147 | } 148 | } 149 | 150 | public: 151 | SonyRemote_RMD420(Controller *controller) : IRRemote(controller) {}; 152 | }; 153 | 154 | #endif 155 | -------------------------------------------------------------------------------- /src/remote/SerialRemote.h: -------------------------------------------------------------------------------- 1 | #ifndef SerialRemote_h 2 | #define SerialRemote_h 3 | 4 | #include 5 | #include "Controller.h" 6 | 7 | class SerialRemote { 8 | private: 9 | static const unsigned char CMD_CONTINUE = 'C'; 10 | static const unsigned char CMD_SHUFFLE = 'S'; 11 | static const unsigned char CMD_PROGRAM = 'P'; 12 | static const unsigned char CMD_1 = '1'; 13 | static const unsigned char CMD_2 = '2'; 14 | static const unsigned char CMD_3 = '3'; 15 | static const unsigned char CMD_4 = '4'; 16 | static const unsigned char CMD_5 = '5'; 17 | static const unsigned char CMD_6 = '6'; 18 | static const unsigned char CMD_7 = '7'; 19 | static const unsigned char CMD_8 = '8'; 20 | static const unsigned char CMD_9 = '9'; 21 | static const unsigned char CMD_10PLUS = '.'; 22 | static const unsigned char CMD_10 = '0'; 23 | static const unsigned char CMD_CLEAR = 'X'; 24 | static const unsigned char CMD_TIME = 'T'; 25 | static const unsigned char CMD_CHECK = 'K'; 26 | static const unsigned char CMD_REPEAT = 'R'; 27 | static const unsigned char CMD_FADER = 'F'; 28 | static const unsigned char CMD_PLAY = 'p'; 29 | static const unsigned char CMD_PAUSE = ' '; 30 | static const unsigned char CMD_STOP = 's'; 31 | static const unsigned char CMD_PREVIOUS = '<'; 32 | static const unsigned char CMD_NEXT = '>'; 33 | static const unsigned char CMD_PLUS = '='; 34 | static const unsigned char CMD_MINUS = '-'; 35 | static const unsigned char CMD_BACKWARD = 'b'; 36 | static const unsigned char CMD_FORWARD = 'f'; 37 | static const unsigned char CMD_DECREASE_SPEED = '['; 38 | static const unsigned char CMD_INCREASE_SPEED = ']'; 39 | static const unsigned char CMD_INCREASE_BRIGHTNESS = 'q'; 40 | static const unsigned char CMD_DECREASE_BRIGHTNESS = 'a'; 41 | static const uint8_t MODE_NORMAL = 0; 42 | static const uint8_t MODE_TIME = 1; 43 | 44 | Controller *controller; 45 | bool tenPlus = false; 46 | 47 | void handleNumericKey(int numKey) { 48 | int num = numKey; 49 | if (tenPlus) { 50 | num += 10; 51 | tenPlus = false; 52 | } 53 | controller->setParam(num); 54 | } 55 | 56 | void handleKey(unsigned char key) { 57 | switch(key) { 58 | case CMD_CONTINUE: 59 | controller->sequential(); 60 | break; 61 | case CMD_SHUFFLE: 62 | controller->shuffle(); 63 | break; 64 | case CMD_PROGRAM: 65 | break; 66 | case CMD_1: 67 | handleNumericKey(1); 68 | break; 69 | case CMD_2: 70 | handleNumericKey(2); 71 | break; 72 | case CMD_3: 73 | handleNumericKey(3); 74 | break; 75 | case CMD_4: 76 | handleNumericKey(4); 77 | break; 78 | case CMD_5: 79 | handleNumericKey(5); 80 | break; 81 | case CMD_6: 82 | handleNumericKey(6); 83 | break; 84 | case CMD_7: 85 | handleNumericKey(7); 86 | break; 87 | case CMD_8: 88 | handleNumericKey(8); 89 | break; 90 | case CMD_9: 91 | handleNumericKey(9); 92 | break; 93 | case CMD_10: 94 | handleNumericKey(10); 95 | break; 96 | case CMD_10PLUS: 97 | tenPlus = true; 98 | break; 99 | case CMD_CLEAR: 100 | break; 101 | case CMD_TIME: 102 | controller->cycleSpeed(); 103 | break; 104 | case CMD_CHECK: 105 | break; 106 | case CMD_REPEAT: 107 | controller->reset(); 108 | break; 109 | case CMD_FADER: 110 | break; 111 | case CMD_PLAY: 112 | controller->play(); 113 | break; 114 | case CMD_PAUSE: 115 | controller->pause(); 116 | break; 117 | case CMD_STOP: 118 | controller->stop(); 119 | break; 120 | case CMD_PREVIOUS: 121 | controller->selectPreviousFx(); 122 | break; 123 | case CMD_NEXT: 124 | controller->selectNextFx(); 125 | break; 126 | case CMD_BACKWARD: 127 | break; 128 | case CMD_FORWARD: 129 | break; 130 | case CMD_PLUS: 131 | controller->increaseParam(); 132 | break; 133 | case CMD_MINUS: 134 | controller->decreaseParam(); 135 | break; 136 | case CMD_DECREASE_SPEED: 137 | controller->decreaseFxSpeed(); 138 | break; 139 | case CMD_INCREASE_SPEED: 140 | controller->increaseFxSpeed(); 141 | break; 142 | case CMD_INCREASE_BRIGHTNESS: 143 | controller->increaseBrightness(); 144 | break; 145 | case CMD_DECREASE_BRIGHTNESS: 146 | controller->decreaseBrightness(); 147 | break; 148 | } 149 | } 150 | 151 | public: 152 | SerialRemote(Controller *controller) { 153 | this->controller = controller; 154 | } 155 | 156 | void setup() {} 157 | 158 | void loop() { 159 | if (Serial.available()) { 160 | handleKey(Serial.read()); 161 | } 162 | } 163 | }; 164 | 165 | #endif 166 | -------------------------------------------------------------------------------- /src/Stage.h: -------------------------------------------------------------------------------- 1 | #ifndef Stage_h 2 | #define Stage_h 3 | 4 | #include 5 | #include 6 | #define USE_WS2812SERIAL 7 | #include 8 | #include 9 | 10 | #include "Fx.h" 11 | #include "Multiplex.h" 12 | #include "strip/PhysicalStrip.h" 13 | #include "strip/virtual/JoinedStrip.h" 14 | #include "strip/virtual/ReversedStrip.h" 15 | #include "strip/virtual/SubStrip.h" 16 | 17 | // System fxs 18 | 19 | #include "sysfx/CycleSpeed.h" 20 | #include "sysfx/InputLevelMeter.h" 21 | #include "sysfx/MicGainMeter.h" 22 | #include "sysfx/SpeedMeter.h" 23 | 24 | // Fxs 25 | 26 | #include "fx/Ants.h" 27 | #include "fx/Background.h" 28 | #include "fx/Beat.h" 29 | #include "fx/Blackout.h" 30 | #include "fx/Blur.h" 31 | #include "fx/Bounce.h" 32 | #include "fx/Chaser.h" 33 | #include "fx/ColorBar.h" 34 | #include "fx/ColorTwinkles.h" 35 | #include "fx/DeepSpace.h" 36 | #include "fx/Drops.h" 37 | #include "fx/Elastic.h" 38 | #include "fx/Fire.h" 39 | #include "fx/FastPulse.h" 40 | #include "fx/Fireworks.h" 41 | #include "fx/Glitter.h" 42 | #include "fx/Jelly.h" 43 | #include "fx/Juggle.h" 44 | #include "fx/MarchingRainbow.h" 45 | #include "fx/Matrix.h" 46 | #include "fx/Motion.h" 47 | #include "fx/Orbit.h" 48 | #include "fx/PeakMeter.h" 49 | #include "fx/Photons.h" 50 | #include "fx/Rainbow.h" 51 | #include "fx/RainbowMelt.h" 52 | #include "fx/Ripple.h" 53 | #include "fx/RippleReflections.h" 54 | #include "fx/Scan.h" 55 | #include "fx/Scroller.h" 56 | #include "fx/Sinelon.h" 57 | #include "fx/SineMeter.h" 58 | #include "fx/Sparks.h" 59 | #include "fx/Spectrum.h" 60 | #include "fx/Spiral.h" 61 | #include "fx/Strobe.h" 62 | #include "fx/SubtleWave.h" 63 | #include "fx/Sunset.h" 64 | #include "fx/Traffic.h" 65 | #include "fx/Vertigo.h" 66 | #include "fx/Volcane.h" 67 | #include "fx/VU1.h" 68 | #include "fx/VU2.h" 69 | 70 | class Stage { 71 | private: 72 | std::vector strips; 73 | std::vector fxs; 74 | std::vector randomFxIndexes; 75 | uint16_t randomFxIndex = 0; 76 | Fx *cycleSpeed; 77 | Fx *speedMeter; 78 | Fx *micGainMeter; 79 | Fx *inputLevelMeter; 80 | 81 | protected: 82 | #if defined(ARDUINO_TEENSY40) 83 | const static uint8_t CH1_PIN = 14; 84 | const static uint8_t CH2_PIN = 17; 85 | const static uint8_t CH3_PIN = 1; 86 | #endif 87 | #if defined(ARDUINO_TEENSY41) 88 | const static uint8_t CH1_PIN = 17; 89 | const static uint8_t CH2_PIN = 14; 90 | const static uint8_t CH3_PIN = 35; 91 | const static uint8_t CH4_PIN = 1; 92 | const static uint8_t CH5_PIN = 24; 93 | const static uint8_t CH6_PIN = 29; 94 | #endif 95 | 96 | void addFx( 97 | Fx *fx01 = nullptr, 98 | Fx *fx02 = nullptr, 99 | Fx *fx03 = nullptr, 100 | Fx *fx04 = nullptr, 101 | Fx *fx05 = nullptr, 102 | Fx *fx06 = nullptr, 103 | Fx *fx07 = nullptr, 104 | Fx *fx08 = nullptr, 105 | Fx *fx09 = nullptr, 106 | Fx *fx10 = nullptr, 107 | Fx *fx11 = nullptr, 108 | Fx *fx12 = nullptr, 109 | Fx *fx13 = nullptr, 110 | Fx *fx14 = nullptr, 111 | Fx *fx15 = nullptr, 112 | Fx *fx16 = nullptr, 113 | Fx *fx17 = nullptr, 114 | Fx *fx18 = nullptr, 115 | Fx *fx19 = nullptr, 116 | Fx *fx20 = nullptr 117 | ) { 118 | clear(); 119 | Fx *fx = new Multiplex( 120 | fx01, fx02, fx03, fx04, fx05, fx06, fx07, fx08, fx09, fx10, 121 | fx11, fx12, fx13, fx14, fx15, fx16, fx17, fx18, fx19, fx20 122 | ); 123 | randomFxIndexes.push_back(fxs.size()); 124 | fxs.push_back(fx); 125 | } 126 | 127 | template 128 | Strip *addStrip() { 129 | Strip *strip = new PhysicalStrip(); 130 | strips.push_back(strip); 131 | return strip; 132 | } 133 | 134 | void setCycleSpeedFx(Fx *fx1 = nullptr, Fx *fx2 = nullptr) { 135 | cycleSpeed = new Multiplex(fx1, fx2); 136 | } 137 | 138 | void setSpeedMeterFx(Fx *fx1 = nullptr, Fx *fx2 = nullptr) { 139 | speedMeter = new Multiplex(fx1, fx2); 140 | } 141 | 142 | void setMicGainMeterFx(Fx *fx1 = nullptr, Fx *fx2 = nullptr) { 143 | micGainMeter = new Multiplex(fx1, fx2); 144 | } 145 | 146 | void setInputLevelMeterFx(Fx *fx1 = nullptr, Fx *fx2 = nullptr) { 147 | inputLevelMeter = new Multiplex(fx1, fx2); 148 | } 149 | 150 | public: 151 | void clear() { 152 | for (Strip *strip : strips) { 153 | strip->off(); 154 | } 155 | } 156 | 157 | void fade(uint8_t amount = 1) { 158 | for (Strip *strip : strips) { 159 | strip->fade(amount); 160 | } 161 | } 162 | 163 | void blur(uint8_t amount = 1) { 164 | for (Strip *strip : strips) { 165 | strip->blur(amount); 166 | } 167 | } 168 | 169 | uint16_t getFxCount() { 170 | return fxs.size(); 171 | } 172 | 173 | uint16_t getNextFxIndex(uint16_t index) { 174 | return (index + 1) % getFxCount(); 175 | } 176 | 177 | uint16_t getPrevFxIndex(uint16_t index) { 178 | return (index + getFxCount() - 1) % getFxCount(); 179 | } 180 | 181 | uint16_t getRandomFxIndex() { 182 | if (randomFxIndex == 0) { 183 | std::random_shuffle(randomFxIndexes.begin(), randomFxIndexes.end()); 184 | } 185 | randomFxIndex = (randomFxIndex + 1) % getFxCount(); 186 | return randomFxIndexes[randomFxIndex]; 187 | } 188 | 189 | Fx *getFx(uint16_t index) { 190 | return fxs[index]; 191 | } 192 | 193 | Fx *getCycleSpeedFx() { 194 | return cycleSpeed; 195 | } 196 | 197 | Fx *getSpeedMeterFx() { 198 | return speedMeter; 199 | } 200 | 201 | Fx *getMicGainMeterFx() { 202 | return micGainMeter; 203 | } 204 | 205 | Fx *getInputLevelMeterFx() { 206 | return inputLevelMeter; 207 | } 208 | }; 209 | 210 | #endif 211 | -------------------------------------------------------------------------------- /src/HarmonicMotion.cpp: -------------------------------------------------------------------------------- 1 | #include "HarmonicMotion.h" 2 | 3 | HarmonicMotion::HarmonicMotion() {}; 4 | 5 | HarmonicMotion& HarmonicMotion::setup(Strip *strip) { 6 | this->strip = strip; 7 | return reset(); 8 | } 9 | 10 | HarmonicMotion& HarmonicMotion::reset() { 11 | color = 0; 12 | a0 = 0; 13 | a1 = 0; 14 | a2 = 0; 15 | k = 0; 16 | b = 0; 17 | x = 0; 18 | x0 = 0; 19 | v = 0; 20 | start = 0; 21 | end = 0; 22 | mirror = false; 23 | fill = false; 24 | showWhenStable = false; 25 | overwrite = false; 26 | xPrev = 0; 27 | vPrev = 0; 28 | lowerLimit.x = -1e20; 29 | lowerLimit.r = 1; 30 | upperLimit.x = 1e20; 31 | upperLimit.r = 1; 32 | timeElapsed = 0; 33 | return *this; 34 | } 35 | 36 | HarmonicMotion& HarmonicMotion::setColor(CRGB color) { 37 | this->color = color; 38 | this->renderingMode = COLOR; 39 | return *this; 40 | } 41 | 42 | HarmonicMotion& HarmonicMotion::setGradient(Gradient *gradient) { 43 | this->gradient = gradient; 44 | this->renderingMode = GRADIENT; 45 | return *this; 46 | } 47 | 48 | HarmonicMotion& HarmonicMotion::setAcceleration(double a0, double a1, double a2) { 49 | this->a0 = a0; 50 | this->a1 = a1; 51 | this->a2 = a2; 52 | return *this; 53 | } 54 | 55 | HarmonicMotion& HarmonicMotion::setElasticConstant(double k) { 56 | this->k = k; 57 | return *this; 58 | } 59 | 60 | HarmonicMotion& HarmonicMotion::setDamping(double b) { 61 | this->b = b; 62 | return *this; 63 | } 64 | 65 | HarmonicMotion& HarmonicMotion::setCriticalDamping(double correctionFactor) { 66 | this->b = 2 * sqrtf(k) * correctionFactor; 67 | return *this; 68 | } 69 | 70 | HarmonicMotion& HarmonicMotion::setPosition(double x) { 71 | this->x = x; 72 | return *this; 73 | } 74 | 75 | HarmonicMotion& HarmonicMotion::setRandomPosition() { 76 | return setPosition(strip->random()); 77 | } 78 | 79 | HarmonicMotion& HarmonicMotion::setVelocity(double v) { 80 | this->v = v; 81 | return *this; 82 | } 83 | 84 | HarmonicMotion& HarmonicMotion::setFixedPointPosition(double x0) { 85 | this->x0 = x0; 86 | return *this; 87 | } 88 | 89 | HarmonicMotion& HarmonicMotion::setRandomFixedPointPosition() { 90 | return setFixedPointPosition(strip->random()); 91 | } 92 | 93 | HarmonicMotion& HarmonicMotion::setLowerBound(double x, double r, ReboundMode reboundMode) { 94 | lowerLimit.x = x; 95 | lowerLimit.r = r; 96 | lowerLimit.reboundMode = reboundMode; 97 | return *this; 98 | } 99 | 100 | HarmonicMotion& HarmonicMotion::setUpperBound(double x, double r, ReboundMode reboundMode) { 101 | upperLimit.x = x; 102 | upperLimit.r = r; 103 | upperLimit.reboundMode = reboundMode; 104 | return *this; 105 | } 106 | 107 | HarmonicMotion& HarmonicMotion::setRange(int start, int end) { 108 | this->start = start; 109 | this->end = end; 110 | return *this; 111 | } 112 | 113 | HarmonicMotion& HarmonicMotion::setMirror(bool mirror) { 114 | this->mirror = mirror; 115 | return *this; 116 | } 117 | 118 | HarmonicMotion& HarmonicMotion::setFill(bool fill) { 119 | this->fill = fill; 120 | return *this; 121 | } 122 | 123 | HarmonicMotion& HarmonicMotion::setShowWhenStable(bool showWhenStable) { 124 | this->showWhenStable = showWhenStable; 125 | return *this; 126 | } 127 | 128 | HarmonicMotion& HarmonicMotion::setOverwrite(bool overwrite) { 129 | this->overwrite = overwrite; 130 | return *this; 131 | } 132 | 133 | double HarmonicMotion::getFixedPointPosition() { 134 | return x0; 135 | } 136 | 137 | double HarmonicMotion::getPosition() { 138 | return x; 139 | } 140 | 141 | double HarmonicMotion::getVelocity() { 142 | return v; 143 | } 144 | 145 | double HarmonicMotion::getAcceleration() { 146 | return a0; 147 | } 148 | 149 | int HarmonicMotion::getLowerLimitCompensation() { 150 | switch (lowerLimit.reboundMode) { 151 | case INSIDE: 152 | return start; 153 | case OUTSIDE: 154 | return end + 1; 155 | default: 156 | return 0; 157 | } 158 | } 159 | 160 | int HarmonicMotion::getUpperLimitCompensation() { 161 | switch (upperLimit.reboundMode) { 162 | case INSIDE: 163 | return end; 164 | case OUTSIDE: 165 | return start - 1; 166 | default: 167 | return 0; 168 | } 169 | } 170 | 171 | bool HarmonicMotion::isLowerLimit() { 172 | double limit = lowerLimit.x - getLowerLimitCompensation(); 173 | // if (x <= limit && v < 0) { 174 | if (x <= limit) { 175 | x = limit; 176 | return true; 177 | } 178 | return false; 179 | } 180 | 181 | bool HarmonicMotion::isUpperLimit() { 182 | double limit = upperLimit.x - getUpperLimitCompensation(); 183 | // if (x >= limit && v > 0) { 184 | if (x >= limit) { 185 | x = limit; 186 | return true; 187 | } 188 | return false; 189 | } 190 | 191 | bool HarmonicMotion::isStable() { 192 | double force = a0 - (k * (x - x0)); 193 | bool balanced = abs(force) < 1; 194 | bool bottom = force < 0 && isLowerLimit(); 195 | bool top = force > 0 && isUpperLimit(); 196 | return abs(v) < 1 && (balanced || bottom || top); 197 | } 198 | 199 | void HarmonicMotion::update() { 200 | xPrev = x; 201 | vPrev = v; 202 | double dT = timeElapsed / 1e6; 203 | timeElapsed = 0; 204 | a1 += a2 * dT; 205 | a0 += a1 * dT; 206 | v += (a0 - (k * (x - x0) + b * v)) * dT; 207 | x += v * dT; 208 | 209 | if (isLowerLimit()) { 210 | v *= lowerLimit.r; 211 | } else if (isUpperLimit()) { 212 | v *= upperLimit.r; 213 | } else if (isStable()) { 214 | v = 0; 215 | } 216 | } 217 | 218 | void HarmonicMotion::loop() { 219 | update(); 220 | if (renderingMode && (!isStable() || showWhenStable)) { 221 | show(false); 222 | if (mirror) { 223 | show(true); 224 | } 225 | } 226 | } 227 | 228 | void HarmonicMotion::show(bool mirrored) { 229 | if (mirrored) { 230 | double pos1 = round(2 * x0 - x); 231 | double pos2 = round(fill ? x0 : 2 * x0 - xPrev); 232 | double posMin = min(pos1, pos2) - end; 233 | double posMax = max(pos1, pos2) - start; 234 | render(posMin, posMax); 235 | } else { 236 | double pos1 = round(x); 237 | double pos2 = round(fill ? x0 : xPrev); 238 | double posMin = min(pos1, pos2) + start; 239 | double posMax = max(pos1, pos2) + end; 240 | render(posMin, posMax); 241 | } 242 | } 243 | 244 | void HarmonicMotion::render(double posMin, double posMax) { 245 | switch (renderingMode) { 246 | case COLOR: 247 | strip->paint(posMin, posMax, color, !overwrite); 248 | break; 249 | case GRADIENT: 250 | strip->paint(posMin, posMax, gradient, 0, 1, !overwrite); 251 | break; 252 | default: 253 | break; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/strip/Strip.h: -------------------------------------------------------------------------------- 1 | #ifndef Strip_h 2 | #define Strip_h 3 | 4 | #include 5 | #include "Gradient.h" 6 | 7 | class Strip { 8 | private: 9 | static void enforceOrder(int16_t &indexFrom, int16_t &indexTo) { 10 | int16_t from = indexFrom; 11 | int16_t to = indexTo; 12 | if (from > to) { 13 | indexFrom = to; 14 | indexTo = from; 15 | } 16 | } 17 | 18 | static double clamp16(double value) { 19 | const double clampedDown = value < INT16_MIN ? INT16_MIN : value; 20 | const double clampedUp = clampedDown > INT16_MAX ? INT16_MAX : clampedDown; 21 | return round(clampedUp); 22 | } 23 | 24 | protected: 25 | uint16_t limitToRange(int16_t index) { 26 | return max(min(index, last()), first()); 27 | } 28 | 29 | bool crop(int16_t &indexFrom, int16_t &indexTo, bool limit = false) { 30 | enforceOrder(indexFrom, indexTo); 31 | if (indexTo < first() || indexFrom > last()) { 32 | return false; 33 | } 34 | if (limit) { 35 | indexFrom = limitToRange(indexFrom); 36 | indexTo = limitToRange(indexTo); 37 | } 38 | return true; 39 | } 40 | 41 | virtual void _fade(int16_t indexFrom, int16_t indexTo, uint8_t amount) =0; 42 | virtual void _blur(int16_t indexFrom, int16_t indexTo, uint8_t amount) =0; 43 | virtual CRGB _shiftUp(int16_t indexFrom, int16_t indexTo, CRGB in = CRGB::Black) =0; 44 | virtual CRGB _shiftDown(int16_t indexFrom, int16_t indexTo, CRGB in = CRGB::Black) =0; 45 | virtual bool _paintSolid(int16_t indexFrom, int16_t indexTo, CRGB color, bool overlay) =0; 46 | virtual bool _paintGradient(int16_t indexFrom, int16_t indexTo, Gradient *gradient, double gradientFrom, double gradientTo, bool overlay) =0; 47 | virtual bool _paintRainbow(int16_t indexFrom, int16_t indexTo, uint8_t initialHue, uint8_t deltaHue) =0; 48 | 49 | public: 50 | bool isInRange(int16_t index) { 51 | return index >= first() && index <= last(); 52 | } 53 | 54 | uint16_t first() { 55 | return 0; 56 | } 57 | 58 | uint16_t center() { 59 | return size() / 2; 60 | }; 61 | 62 | uint16_t last() { 63 | return size() - 1; 64 | } 65 | 66 | uint16_t random() { 67 | return random16(last()); 68 | } 69 | 70 | uint16_t randomExclude(int16_t excludeIndex, int16_t excludeCount = 0) { 71 | return (excludeIndex + excludeCount + random16(last() - 2 * excludeCount)) % size(); 72 | } 73 | 74 | uint16_t randomInRange(double from, double to) { 75 | return random16(from * last(), to * last()); 76 | } 77 | 78 | uint16_t fromNormalizedPosition(double normPos, int16_t excludeCount = 0) { 79 | return clamp16(first() + normPos * (last() - excludeCount)); 80 | } 81 | 82 | void off() { 83 | _paintSolid(first(), last(), CRGB::Black, false); 84 | } 85 | 86 | bool rainbow(uint8_t initialHue) { 87 | uint8_t deltaHue = max(255 / size(), 1); 88 | return _paintRainbow(initialHue, deltaHue, first(), last()); 89 | } 90 | 91 | bool rainbow(uint8_t initialHue, uint8_t deltaHue) { 92 | return _paintRainbow(initialHue, deltaHue, first(), last()); 93 | } 94 | 95 | bool rainbow(uint8_t initialHue, double posFrom, double posTo) { 96 | uint16_t indexFrom = clamp16(posFrom); 97 | uint16_t indexTo = clamp16(posTo); 98 | uint8_t deltaHue = max(255 / (indexTo - indexFrom + 1), 1); 99 | return _paintRainbow(initialHue, deltaHue, indexFrom, indexTo); 100 | } 101 | 102 | bool rainbow(uint8_t initialHue, uint8_t deltaHue, double posFrom, double posTo) { 103 | uint16_t indexFrom = clamp16(posFrom); 104 | uint16_t indexTo = clamp16(posTo); 105 | return _paintRainbow(initialHue, deltaHue, indexFrom, indexTo); 106 | } 107 | 108 | void fade(uint8_t amount) { 109 | _fade(first(), last(), amount); 110 | } 111 | 112 | void fade(uint8_t amount, double posFrom, double posTo) { 113 | _fade(clamp16(posFrom), clamp16(posTo), amount); 114 | } 115 | 116 | void blur(uint8_t amount) { 117 | _blur(first(), last(), amount); 118 | } 119 | 120 | void blur(uint8_t amount, double posFrom, double posTo) { 121 | _blur(clamp16(posFrom), clamp16(posTo), amount); 122 | } 123 | 124 | CRGB shiftUp(CRGB in = CRGB::Black) { 125 | return _shiftUp(first(), last(), in); 126 | } 127 | 128 | CRGB shiftUp(double posFrom, double posTo, CRGB in = CRGB::Black) { 129 | return _shiftUp(clamp16(posFrom), clamp16(posTo), in); 130 | } 131 | 132 | CRGB shiftDown(CRGB in = CRGB::Black) { 133 | return _shiftDown(first(), last(), in); 134 | } 135 | 136 | CRGB shiftDown(double posFrom, double posTo, CRGB in = CRGB::Black) { 137 | return _shiftDown(clamp16(posFrom), clamp16(posTo), in); 138 | } 139 | 140 | void paint(CRGB color, bool overlay) { 141 | _paintSolid(first(), last(), color, overlay); 142 | } 143 | 144 | void paint(CHSV color, bool overlay) { 145 | CRGB rgb; 146 | hsv2rgb_rainbow(color, rgb); 147 | _paintSolid(first(), last(), rgb, overlay); 148 | } 149 | 150 | void paint(Gradient *gradient, double gradientFrom, double gradientTo, bool overlay) { 151 | _paintGradient(first(), last(), gradient, gradientFrom, gradientTo, overlay); 152 | } 153 | 154 | bool paint(double pos, CRGB color, bool overlay) { 155 | return _paintSolid(clamp16(pos), clamp16(pos), color, overlay); 156 | } 157 | 158 | bool paint(double posFrom, double posTo, CRGB color, bool overlay) { 159 | return _paintSolid(clamp16(posFrom), clamp16(posTo), color, overlay); 160 | } 161 | 162 | bool paint(double posFrom, double posTo, Gradient *gradient, double gradientFrom, double gradientTo, bool overlay) { 163 | return _paintGradient(clamp16(posFrom), clamp16(posTo), gradient, gradientFrom, gradientTo, overlay); 164 | } 165 | 166 | bool paintNormalized(double normPos, CRGB color, bool overlay) { 167 | return _paintSolid(fromNormalizedPosition(normPos), fromNormalizedPosition(normPos), color, overlay); 168 | } 169 | 170 | bool paintNormalized(double normPosFrom, double norPosTo, CRGB color, bool overlay) { 171 | return _paintSolid(fromNormalizedPosition(normPosFrom), fromNormalizedPosition(norPosTo), color, overlay); 172 | } 173 | 174 | bool paintRandomPos(int16_t length, CRGB color, bool overlay) { 175 | uint16_t pos = random16(size() - length); 176 | return _paintSolid(pos, pos + length, color, overlay); 177 | } 178 | 179 | CRGB getPosition(double normPos) { 180 | return getIndex(fromNormalizedPosition(normPos)); 181 | } 182 | 183 | virtual void flush() {}; 184 | virtual Strip *overlay(double opacity = 1) =0; 185 | virtual uint16_t size() =0; 186 | virtual bool paintNormalizedSize(double positionFrom, int16_t size, CRGB color, bool overlay) =0; 187 | virtual CRGB getIndex(int16_t index) =0; 188 | }; 189 | 190 | #endif 191 | --------------------------------------------------------------------------------