@@ -748,6 +748,223 @@ class SphereMoveEffect: public Node {
748748 }
749749}; // SphereMove3DEffect
750750
751+ class TroyMovingHeadEffect : public Node {
752+ public:
753+
754+ static const char * name () {return " Troy's Moving Heads 15 CH 🔥♫💫🐺" ;}
755+
756+ uint8_t bpm;
757+ uint8_t pan;
758+ uint8_t tilt;
759+ uint8_t zoom;
760+ uint8_t range;
761+ uint8_t colorwheel;
762+ uint8_t light_number;
763+ uint8_t colorwheelbrightness; // 0-255, 0 = off, 255 = full brightness
764+ time_t cooldown = millis();
765+ uint16_t start_channel = 0 ; // start channel for this effect, used to set the DMX map
766+
767+ bool autoMove;
768+ bool audioReactive;
769+ bool invert;
770+ int closestColorIndex = -1 ;
771+
772+ std::vector<CRGB> colorwheelpalette = {
773+ CRGB (255 , 255 , 255 ), // White
774+ CRGB (255 , 0 , 0 ), // Red
775+ CRGB (0 , 255 , 0 ), // Green
776+ CRGB (0 , 0 , 255 ), // Blue
777+ CRGB (255 , 255 , 0 ), // Yellow
778+ CRGB (0 , 255 , 255 ), // Cyan
779+ CRGB (255 , 165 , 0 ), // Orange
780+ CRGB (128 , 0 , 128 ) // Purple
781+ };
782+
783+ void setup () override {
784+ addControl (&start_channel, " start_channel" , " range" , 0 , 0 , 512 ); // DMX start channel for this effect
785+ addControl (&light_number, " light_number" , " range" , 0 , 0 , 512 );
786+ addControl (&bpm, " bpm" , " range" , 30 );
787+ addControl (&pan, " pan" , " range" , 175 );
788+ addControl (&tilt, " tilt" , " range" , 90 );
789+ addControl (&zoom, " zoom" , " range" , 20 );
790+ addControl (&colorwheel, " colorwheel" , " range" , 0 , 0 , 7 ); // 0-7 for 8 colors in the colorwheel
791+ addControl (&colorwheelbrightness, " colorwheelbrightness" , " range" , 255 ); // 0-7 for 8 colors in the colorwheel
792+ addControl (&autoMove, " autoMove" , " checkbox" , false );
793+ addControl (&range, " range" , " range" , 20 );
794+ addControl (&audioReactive, " audioReactive" , " checkbox" , false );
795+ addControl (&invert, " invert" , " checkbox" , false );
796+ layerV->layerP ->lights .header .channelsPerLight = 15 ; // set channels per light to 15 (RGB + Pan + Tilt + Zoom + Brightness)
797+ layerV->layerP ->lights .header .offsetRGB = start_channel+10 ; // set offset for RGB lights in DMX map
798+ layerV->layerP ->lights .header .offsetPan = start_channel+0 ;
799+ layerV->layerP ->lights .header .offsetTilt = start_channel+1 ;
800+ layerV->layerP ->lights .header .offsetZoom = start_channel+7 ;
801+ layerV->layerP ->lights .header .offsetBrightness = start_channel+8 ; // set offset for brightness
802+ layerV->layerP ->lights .header .offsetColorWheel = start_channel+5 ; // set offset for color wheel in DMX map
803+ layerV->layerP ->lights .header .offsetColorWheelBrightness = start_channel+3 ; // set offset for color wheel brightness in DMX map
804+ }
805+
806+ // Function to compute Euclidean distance between two colors
807+ double colorDistance (const CRGB& c1, const CRGB& c2) {
808+ return std::sqrt (std::pow (c1.r - c2.r , 2 ) + std::pow (c1.g - c2.g , 2 ) + std::pow (c1.b - c2.b , 2 ));
809+ }
810+
811+ // Function to find the index of the closest color
812+ int findClosestColorWheelIndex (const CRGB& inputColor, const std::vector<CRGB>& palette) {
813+ int closestIndex = 0 ;
814+ double minDistance = colorDistance (inputColor, palette[0 ]);
815+
816+ for (size_t i = 1 ; i < palette.size (); ++i) {
817+ double distance = colorDistance (inputColor, palette[i]);
818+ if (distance < minDistance) {
819+ minDistance = distance;
820+ closestIndex = static_cast <int >(i);
821+ }
822+ }
823+
824+ return closestIndex;
825+ }
826+
827+ void loop () override {
828+
829+ uint8_t light[15 ]; // layerV->layerP->lights.header.channelsPerLight is not accepted by setLight (yet)
830+ memset (light, 0 , sizeof (light)); // set light to 0
831+
832+ if (audioReactive) {
833+ // layerV->layerP->lights.header.offsetRGB = 10;
834+ layerV->layerP ->lights .header .setRGB (light, CRGB (audio.bands [15 ],audio.bands [7 ],audio.bands [0 ]));
835+ if (audio.bands [2 ] > 200 && cooldown + 3000 < millis ()) { // cooldown for 3 seconds
836+ cooldown = millis ();
837+ colorwheel = random8 (8 )*5 ; // random colorwheel index and convert to 0-35 range
838+ }
839+ layerV->layerP ->lights .header .setColorWheel (light, colorwheel);
840+ layerV->layerP ->lights .header .setColorWheelBrightness (light, (audio.bands [0 ]>200 )?audio.bands [0 ]:0 ); // Use the first band for brightness
841+ layerV->layerP ->lights .header .setZoom (light, (audio.bands [0 ]>200 )?0 :128 );
842+ uint8_t mypan = beatsin8 (bpm, pan-range, pan+range, 0 , audio.bands [0 ]/2 );
843+ uint8_t mytilt = beatsin8 (bpm, tilt-range, tilt+range, 0 , audio.bands [0 ]/2 );
844+ if (invert) {
845+ mypan = 255 - mypan; // invert pan
846+ mytilt = 255 - mytilt; // invert tilt
847+ }
848+ layerV->layerP ->lights .header .setPan (light, mypan);
849+ layerV->layerP ->lights .header .setTilt (light, mytilt);
850+ layerV->layerP ->lights .header .setBrightness (light, (audio.bands [0 ]>200 )?0 :layerV->layerP ->lights .header .brightness );
851+ } else {
852+ // layerV->layerP->lights.header.offsetRGB = 10;
853+ layerV->layerP ->lights .header .setRGB (light, CHSV ( beatsin8 (10 ), 255 , 255 ));
854+ layerV->layerP ->lights .header .setColorWheel (light,colorwheel);
855+ layerV->layerP ->lights .header .setColorWheelBrightness (light, colorwheelbrightness); // layerV->layerP->lights.header.brightness);
856+ layerV->layerP ->lights .header .setPan (light, autoMove?beatsin8 (bpm, pan-range, pan + range, 0 , (invert)?128 :0 ): pan); // if automove, pan the light over a range
857+ layerV->layerP ->lights .header .setTilt (light, autoMove?beatsin8 (bpm, tilt - range, tilt + range, 0 , (invert)?128 :0 ): tilt);
858+ layerV->layerP ->lights .header .setBrightness (light, layerV->layerP ->lights .header .brightness );
859+ }
860+ layerV->setLight ({light_number,0 ,0 }, light);
861+ }
862+ };
863+
864+ class TroyMovingHead32Effect : public Node {
865+ public:
866+
867+ static const char * name () {return " Troy's Moving Heads 32 CH 🔥♫💫🐺" ;}
868+
869+ uint8_t bpm;
870+ uint8_t pan;
871+ uint8_t tilt;
872+ uint8_t zoom;
873+ uint8_t range;
874+ uint8_t cutin;
875+ uint8_t light_number;
876+ time_t cooldown = millis();
877+ uint16_t start_channel = 0 ; // start channel for this effect, used to set the DMX map
878+
879+ bool autoMove;
880+ bool audioReactive;
881+ bool invert;
882+
883+ void setup () override {
884+ addControl (&start_channel, " start_channel" , " range" , 0 , 0 , 512 ); // DMX start channel for this effect
885+ addControl (&light_number, " light_number" , " range" , 0 , 0 , 512 ); // DMX start channel for this effect
886+ addControl (&bpm, " bpm" , " range" , 30 );
887+ addControl (&pan, " pan" , " range" , 175 );
888+ addControl (&tilt, " tilt" , " range" , 90 );
889+ addControl (&zoom, " zoom" , " range" , 20 );
890+ addControl (&cutin, " cutin" , " range" , 200 );
891+ addControl (&autoMove, " autoMove" , " checkbox" , false );
892+ addControl (&range, " range" , " range" , 20 );
893+ addControl (&audioReactive, " audioReactive" , " checkbox" , false );
894+ addControl (&invert, " invert" , " checkbox" , false );
895+ layerV->layerP ->lights .header .channelsPerLight = 32 ;
896+ layerV->layerP ->lights .header .offsetRGB = start_channel+9 ;
897+ layerV->layerP ->lights .header .offsetPan = start_channel+0 ;
898+ layerV->layerP ->lights .header .offsetTilt = start_channel+2 ;
899+ layerV->layerP ->lights .header .offsetZoom = start_channel+5 ;
900+ layerV->layerP ->lights .header .offsetBrightness = start_channel+6 ;
901+ }
902+
903+ // Function to compute Euclidean distance between two colors
904+ double colorDistance (const CRGB& c1, const CRGB& c2) {
905+ return std::sqrt (std::pow (c1.r - c2.r , 2 ) + std::pow (c1.g - c2.g , 2 ) + std::pow (c1.b - c2.b , 2 ));
906+ }
907+
908+ // Function to find the index of the closest color
909+ int findClosestColorWheelIndex (const CRGB& inputColor, const std::vector<CRGB>& palette) {
910+ int closestIndex = 0 ;
911+ double minDistance = colorDistance (inputColor, palette[0 ]);
912+ for (size_t i = 1 ; i < palette.size (); ++i) {
913+ double distance = colorDistance (inputColor, palette[i]);
914+ if (distance < minDistance) {
915+ minDistance = distance;
916+ closestIndex = static_cast <int >(i);
917+ }
918+ }
919+ return closestIndex;
920+ }
921+
922+ void loop () override {
923+
924+ uint8_t light[32 ];
925+ memset (light, 0 , sizeof (light)); // set light to 0
926+
927+ if (audioReactive) {
928+ layerV->layerP ->lights .header .offsetRGB = start_channel+9 ;
929+ layerV->layerP ->lights .header .setRGB (light, CRGB (audio.bands [15 ]>cutin?audio.bands [15 ]:0 ,audio.bands [7 ]>cutin?audio.bands [7 ]:0 ,audio.bands [0 ]));
930+ layerV->layerP ->lights .header .offsetRGB += 4 ;
931+ layerV->layerP ->lights .header .setRGB (light, CRGB (audio.bands [15 ],audio.bands [7 ]>cutin?audio.bands [7 ]:0 ,audio.bands [0 ]>cutin?audio.bands [0 ]:0 ));
932+ layerV->layerP ->lights .header .offsetRGB += 4 ;
933+ layerV->layerP ->lights .header .setRGB (light, CRGB (audio.bands [15 ]>cutin?audio.bands [15 ]:0 ,audio.bands [7 ],audio.bands [0 ]>cutin?audio.bands [0 ]:0 ));
934+ layerV->layerP ->lights .header .offsetRGB += 7 ;
935+ layerV->layerP ->lights .header .setRGB (light, CRGB (audio.bands [15 ]>cutin?map (audio.bands [15 ],cutin-1 ,255 ,0 ,255 ):0 ,audio.bands [7 ]>cutin?map (audio.bands [7 ],cutin-1 ,255 ,0 ,255 ):0 ,audio.bands [0 ]>cutin?map (audio.bands [0 ],cutin-1 ,255 ,0 ,255 ):0 ));
936+ if (audio.bands [0 ] > cutin) {
937+ layerV->layerP ->lights .header .setZoom (light, 255 );
938+ cooldown = millis ();
939+ } else if (cooldown + 5000 < millis ()) {
940+ layerV->layerP ->lights .header .setZoom (light, 0 );
941+ cooldown = millis ();
942+ }
943+ // layerV->layerP->lights.header.setZoom(light, (audio.bands[0]>cutin)?255:0);
944+ uint8_t mypan = beatsin8 (bpm, pan-range, pan+range, 0 , audio.bands [0 ]/2 );
945+ uint8_t mytilt = beatsin8 (bpm, tilt-range, tilt+range, 0 , audio.bands [0 ]/2 );
946+ if (invert) {
947+ mypan = 255 - mypan; // invert pan
948+ mytilt = 255 - mytilt; // invert tilt
949+ }
950+ if (audio.bands [0 ]+audio.bands [7 ]+audio.bands [15 ] > 1 ) {
951+ layerV->layerP ->lights .header .setPan (light, mypan);
952+ layerV->layerP ->lights .header .setTilt (light, mytilt);
953+ layerV->layerP ->lights .header .setBrightness (light, 255 );
954+ } else {
955+ layerV->layerP ->lights .header .setBrightness (light, 0 );
956+ }
957+ } else {
958+ // layerV->layerP->lights.header.offsetRGB = 10;
959+ layerV->layerP ->lights .header .setRGB (light, CHSV ( beatsin8 (10 ), 255 , 255 ));
960+ layerV->layerP ->lights .header .setPan (light, autoMove?beatsin8 (bpm, pan-range, pan + range, 0 , (invert)?128 :0 ): pan); // if automove, pan the light over a range
961+ layerV->layerP ->lights .header .setTilt (light, autoMove?beatsin8 (bpm, tilt - range, tilt + range, 0 , (invert)?128 :0 ): tilt);
962+ layerV->layerP ->lights .header .setBrightness (light, layerV->layerP ->lights .header .brightness );
963+ }
964+ layerV->setLight ({light_number,0 ,0 }, light);
965+ }
966+ };
967+
751968class WaveEffect : public Node {
752969public:
753970
0 commit comments