Skip to content

Commit 8c7aa9b

Browse files
committed
Merge branch 'troy-heads'
2 parents f118ea3 + 485c2ae commit 8c7aa9b

File tree

7 files changed

+234
-8
lines changed

7 files changed

+234
-8
lines changed

docs/moonbase/module/editor.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Each task is defined as a node. A node can be precompiled in the firmware or def
99

1010
Ultimately the nodes will be displayed in a graphical interface where nodes are connected by 'noodles' to define dependencies between nodes. For the time being nodes will be shown in a list.
1111

12-
Typically a node will define a layout (🚥), or an effect (🔥), or a modifier (💎) or a supporting process (☸️) but can also combine these tasks (experimental at the moment). To avoid duplication it's in most cases recommended to keep them seperated so an effect can run on multiple layouts and a modifier can modify any effect.
12+
Typically a node will define a layout (🚥), or an effect (🔥), or a modifier (💎) or a supporting process (☸️) but can also combine these tasks (experimental at the moment). To avoid duplication it's in most cases recommended to keep them separated so an effect can run on multiple layouts and a modifier can modify any effect.
1313

1414
* **Layout** 🚥: a layout defines what lights are connected to MoonLight. It defines the coordinates of all lights (addLight) and assigns lights to the GPIO pins of the ESP32 (addPin) and how many channels each light has (normal LEDs 3: Red, Green and Blue).
1515
* The **coordinates** of each light are defined in a 3D coordinate space where each coordinate range between 1 and 255. Currently a strip until 255 leds is supported, a panel until 128x96 LEDS and a cube max 20x20x20.
@@ -60,7 +60,7 @@ Precompiled effects can be found in [effects](https://github.com/MoonModules/Moo
6060

6161
* Sends a beatsin to Pan and Tilt which can be sent to Moving Heads (add a Moving head layout node to configure the MHs)
6262
* Controls: BPM, Middle Pan and Tilt, Range and invert
63-
* Usage: Add this effect if moving heads are configured. RGB effects can be added seperately e.g. wave to light up the moving heads in wave patterns
63+
* Usage: Add this effect if moving heads are configured. RGB effects can be added separately e.g. wave to light up the moving heads in wave patterns
6464
* See [E_PanTilt](https://github.com/MoonModules/MoonLight/blob/main/misc/livescripts/E_PanTilt.sc)
6565
* Run script see [How to run a live script](https://moonmodules.org/MoonLight/moonbase/module/liveScripts/#how-to-run-a-live-script)
6666

docs/moonbase/module/lightsControl.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Controls:
88

99
* On: lights on or off
1010
* Brightness: brightness of the LEDs when on
11-
* RGB Sliders: control each color seperately.
11+
* RGB Sliders: control each color separately.
1212
* Palette: Global palette setting. Effects with the palette icon 🎨 use this palette setting.
1313
* Presets: Control pad style, store or retrieve a set of nodes with their controls. WIP
1414
* Driver On: sends LED output to ESP32 gpio pins.

src/MoonLight/Effects.h

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,219 @@ 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+
//set default values here
757+
uint8_t bpm = 30;
758+
uint8_t pan = 175;
759+
uint8_t tilt = 90;
760+
uint8_t zoom = 20;
761+
uint8_t range = 20;
762+
uint8_t colorwheel = 0;
763+
uint16_t light_number = 0;
764+
uint8_t colorwheelbrightness = 255; // 0-255, 0 = off, 255 = full brightness
765+
time_t cooldown = millis();
766+
uint16_t start_channel = 0; //start channel for this effect, used to set the DMX map
767+
768+
bool autoMove = false;
769+
bool audioReactive = false;
770+
bool invert = false;
771+
int closestColorIndex = -1;
772+
773+
std::vector<CRGB> colorwheelpalette = {
774+
CRGB(255, 255, 255), // White
775+
CRGB(255, 0, 0), // Red
776+
CRGB(0, 255, 0), // Green
777+
CRGB(0, 0, 255), // Blue
778+
CRGB(255, 255, 0), // Yellow
779+
CRGB(0, 255, 255), // Cyan
780+
CRGB(255, 165, 0), // Orange
781+
CRGB(128, 0, 128) // Purple
782+
};
783+
784+
void setup() override {
785+
addControl(start_channel, "start_channel", "number", 0, 512); // DMX start channel for this effect
786+
addControl(light_number, "light_number", "number", 0, 512); // changed type to number as range (currently only supports 0..255)
787+
addControl(bpm, "bpm", "range", 30);
788+
addControl(pan, "pan", "range", 175);
789+
addControl(tilt, "tilt", "range", 90);
790+
addControl(zoom, "zoom", "range", 20);
791+
addControl(colorwheel, "colorwheel", "range", 0, 7); // 0-7 for 8 colors in the colorwheel
792+
addControl(colorwheelbrightness, "colorwheelbrightness", "range"); // 0-7 for 8 colors in the colorwheel
793+
addControl(autoMove, "autoMove", "checkbox");
794+
addControl(range, "range", "range");
795+
addControl(audioReactive, "audioReactive", "checkbox");
796+
addControl(invert, "invert", "checkbox");
797+
hasLayout = true; //so the system knows to rebuild the mapping if needed
798+
layerV->layerP->lights.header.channelsPerLight = 15; //set channels per light to 15 (RGB + Pan + Tilt + Zoom + Brightness)
799+
layerV->layerP->lights.header.offsetRGB = start_channel+10; //set offset for RGB lights in DMX map
800+
layerV->layerP->lights.header.offsetPan = start_channel+0;
801+
layerV->layerP->lights.header.offsetTilt = start_channel+1;
802+
layerV->layerP->lights.header.offsetZoom = start_channel+7;
803+
layerV->layerP->lights.header.offsetBrightness = start_channel+8; //set offset for brightness
804+
layerV->layerP->lights.header.offsetGobo = start_channel+5; //set offset for color wheel in DMX map
805+
layerV->layerP->lights.header.offsetBrightness2 = start_channel+3; //set offset for color wheel brightness in DMX map
806+
}
807+
808+
// Function to compute Euclidean distance between two colors
809+
double colorDistance(const CRGB& c1, const CRGB& c2) {
810+
return std::sqrt(std::pow(c1.r - c2.r, 2) + std::pow(c1.g - c2.g, 2) + std::pow(c1.b - c2.b, 2));
811+
}
812+
813+
// Function to find the index of the closest color
814+
int findClosestColorWheelIndex(const CRGB& inputColor, const std::vector<CRGB>& palette) {
815+
int closestIndex = 0;
816+
double minDistance = colorDistance(inputColor, palette[0]);
817+
818+
for (size_t i = 1; i < palette.size(); ++i) {
819+
double distance = colorDistance(inputColor, palette[i]);
820+
if (distance < minDistance) {
821+
minDistance = distance;
822+
closestIndex = static_cast<int>(i);
823+
}
824+
}
825+
826+
return closestIndex;
827+
}
828+
829+
void loop() override {
830+
831+
if (audioReactive) {
832+
// layerV->layerP->lights.header.offsetRGB = 10;
833+
layerV->setRGB(light_number, CRGB(audio.bands[15],audio.bands[7],audio.bands[0]));
834+
if (audio.bands[2] > 200 && cooldown + 3000 < millis()) { //cooldown for 3 seconds
835+
cooldown = millis();
836+
colorwheel = random8(8)*5; //random colorwheel index and convert to 0-35 range
837+
}
838+
layerV->setGobo(light_number, colorwheel);
839+
layerV->setBrightness2(light_number, (audio.bands[0]>200)?audio.bands[0]:0); // Use the first band for brightness
840+
layerV->setZoom(light_number, (audio.bands[0]>200)?0:128);
841+
uint8_t mypan = beatsin8(bpm, pan-range, pan+range, 0, audio.bands[0]/2);
842+
uint8_t mytilt = beatsin8(bpm, tilt-range, tilt+range, 0, audio.bands[0]/2);
843+
if (invert) {
844+
mypan = 255 - mypan; // invert pan
845+
mytilt = 255 - mytilt; // invert tilt
846+
}
847+
layerV->setPan(light_number, mypan);
848+
layerV->setTilt(light_number, mytilt);
849+
layerV->setBrightness(light_number, (audio.bands[0]>200)?0:layerV->layerP->lights.header.brightness);
850+
} else {
851+
// layerV->layerP->lights.header.offsetRGB = 10;
852+
layerV->setRGB(light_number, CHSV( beatsin8(10), 255, 255));
853+
layerV->setGobo(light_number,colorwheel);
854+
layerV->setBrightness2(light_number, colorwheelbrightness); // layerV->layerP->lights.header.brightness);
855+
layerV->setPan(light_number, autoMove?beatsin8(bpm, pan-range, pan + range, 0, (invert)?128:0): pan); //if automove, pan the light over a range
856+
layerV->setTilt(light_number, autoMove?beatsin8(bpm, tilt - range, tilt + range, 0, (invert)?128:0): tilt);
857+
layerV->setBrightness(light_number, layerV->layerP->lights.header.brightness);
858+
}
859+
}
860+
};
861+
862+
class TroyMovingHead32Effect: public Node {
863+
public:
864+
865+
static const char * name() {return "Troy's Moving Heads 32 CH 🔥♫💫🐺";}
866+
867+
uint8_t bpm = 30;
868+
uint8_t pan = 175;
869+
uint8_t tilt = 90;
870+
uint8_t zoom = 20;
871+
uint8_t range = 20;
872+
uint8_t cutin = 200;
873+
uint16_t light_number;
874+
time_t cooldown = millis();
875+
uint16_t start_channel = 0; //start channel for this effect, used to set the DMX map
876+
877+
bool autoMove = false;
878+
bool audioReactive = false;
879+
bool invert = false;
880+
881+
void setup() override {
882+
addControl(start_channel, "start_channel", "number", 0, 512); // DMX start channel for this effect
883+
addControl(light_number, "light_number", "number", 0, 512); // DMX start channel for this effect
884+
addControl(bpm, "bpm", "range");
885+
addControl(pan, "pan", "range");
886+
addControl(tilt, "tilt", "range");
887+
addControl(zoom, "zoom", "range");
888+
addControl(cutin, "cutin", "range");
889+
addControl(autoMove, "autoMove", "checkbox");
890+
addControl(range, "range", "range");
891+
addControl(audioReactive, "audioReactive", "checkbox");
892+
addControl(invert, "invert", "checkbox");
893+
894+
hasLayout = true;
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+
if (audioReactive) {
925+
layerV->layerP->lights.header.offsetRGB = start_channel+9;
926+
layerV->setRGB(light_number, CRGB(audio.bands[15]>cutin?audio.bands[15]:0,audio.bands[7]>cutin?audio.bands[7]:0,audio.bands[0]));
927+
layerV->layerP->lights.header.offsetRGB += 4;
928+
layerV->setRGB(light_number, CRGB(audio.bands[15],audio.bands[7]>cutin?audio.bands[7]:0,audio.bands[0]>cutin?audio.bands[0]:0));
929+
layerV->layerP->lights.header.offsetRGB += 4;
930+
layerV->setRGB(light_number, CRGB(audio.bands[15]>cutin?audio.bands[15]:0,audio.bands[7],audio.bands[0]>cutin?audio.bands[0]:0));
931+
layerV->layerP->lights.header.offsetRGB += 7;
932+
layerV->setRGB(light_number, 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));
933+
if (audio.bands[0] > cutin) {
934+
layerV->setZoom(light_number, 255);
935+
cooldown = millis();
936+
} else if (cooldown + 5000 < millis()) {
937+
layerV->setZoom(light_number, 0);
938+
cooldown = millis();
939+
}
940+
// layerV->setZoom(light_number, (audio.bands[0]>cutin)?255:0);
941+
uint8_t mypan = beatsin8(bpm, pan-range, pan+range, 0, audio.bands[0]/2);
942+
uint8_t mytilt = beatsin8(bpm, tilt-range, tilt+range, 0, audio.bands[0]/2);
943+
if (invert) {
944+
mypan = 255 - mypan; // invert pan
945+
mytilt = 255 - mytilt; // invert tilt
946+
}
947+
if (audio.bands[0]+audio.bands[7]+audio.bands[15] > 1) {
948+
layerV->setPan(light_number, mypan);
949+
layerV->setTilt(light_number, mytilt);
950+
layerV->setBrightness(light_number, 255);
951+
} else {
952+
layerV->setBrightness(light_number, 0);
953+
}
954+
} else {
955+
// layerV->layerP->lights.header.offsetRGB = 10;
956+
layerV->setRGB(light_number, CHSV( beatsin8(10), 255, 255));
957+
layerV->setPan(light_number, autoMove?beatsin8(bpm, pan-range, pan + range, 0, (invert)?128:0): pan); //if automove, pan the light over a range
958+
layerV->setTilt(light_number, autoMove?beatsin8(bpm, tilt - range, tilt + range, 0, (invert)?128:0): tilt);
959+
layerV->setBrightness(light_number, layerV->layerP->lights.header.brightness);
960+
}
961+
}
962+
};
963+
751964
class WaveEffect: public Node {
752965
public:
753966

src/MoonLight/ModuleEditor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ class ModuleEditor : public Module
123123
values.add(RGBWParEffect::name());
124124
values.add(SinusEffect::name());
125125
values.add(SphereMoveEffect::name());
126+
values.add(TroyMovingHeadEffect::name());
127+
values.add(TroyMovingHead32Effect::name());
126128
values.add(WaveEffect::name());
127129

128130
values.add(HumanSizedCubeLayout::name());

src/MoonLight/PhysicalLayer.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,11 @@ PhysicalLayer::PhysicalLayer() {
165165
else if (equal(name, RandomEffect::name())) node = new RandomEffect();
166166
else if (equal(name, RipplesEffect::name())) node = new RipplesEffect();
167167
else if (equal(name, RGBWParEffect::name())) node = new RGBWParEffect();
168-
else if (equal(name, WaveEffect::name())) node = new WaveEffect();
169168
else if (equal(name, SinusEffect::name())) node = new SinusEffect();
170169
else if (equal(name, SphereMoveEffect::name())) node = new SphereMoveEffect();
170+
else if (equal(name, TroyMovingHeadEffect::name())) node = new TroyMovingHeadEffect();
171+
else if (equal(name, TroyMovingHead32Effect::name())) node = new TroyMovingHead32Effect();
172+
else if (equal(name, WaveEffect::name())) node = new WaveEffect();
171173

172174
else if (equal(name, HumanSizedCubeLayout::name())) node = new HumanSizedCubeLayout();
173175
else if (equal(name, PanelLayout::name())) node = new PanelLayout();

src/MoonLight/PhysicalLayer.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,23 @@ struct LightsHeader {
3939
uint8_t isPositions = 0; //0is the lights.positions array filled with positions
4040
Coord3D size = {16,16,1}; //1 max position of light, counted by addLayoutPre/Post and addLight. 12 bytes not 0,0,0 to prevent div0 eg in Octopus2D
4141
uint16_t nrOfLights = 256; //4 nr of physical lights, counted by addLight
42-
uint8_t brightness; //6 brightness set by light control
42+
uint8_t brightness; //6 brightness set by light control (sent to LEDs driver normally)
4343
uint8_t channelsPerLight = 3; //7 RGB default
4444
uint8_t offsetRGB = 0; //RGB default
4545
uint8_t offsetWhite = UINT8_MAX;
46-
uint8_t offsetBrightness = UINT8_MAX;
46+
uint8_t offsetBrightness = UINT8_MAX; //in case the light has a separate brightness channel
4747
uint8_t offsetPan = UINT8_MAX;
4848
uint8_t offsetTilt = UINT8_MAX;
4949
uint8_t offsetZoom = UINT8_MAX;
5050
uint8_t offsetRotate = UINT8_MAX;
5151
uint8_t offsetGobo = UINT8_MAX;
5252
uint8_t offsetRGB1 = UINT8_MAX;
5353
uint8_t offsetRGB2 = UINT8_MAX;
54-
//18 bytes until here
54+
uint8_t offsetBrightness2 = UINT8_MAX;
55+
//19 bytes until here
5556
// uint8_t ledFactor = 1; //factor to multiply the positions with
5657
// uint8_t ledSize = 4; //mm size of each light, used in monitor ...
57-
uint8_t dummy[6];
58+
uint8_t dummy[5];
5859
//24 bytes total
5960

6061
//support for more channels, like white, pan, tilt etc.
@@ -71,6 +72,7 @@ struct LightsHeader {
7172
offsetGobo = UINT8_MAX;
7273
offsetRGB1 = UINT8_MAX;
7374
offsetRGB2 = UINT8_MAX;
75+
offsetBrightness2 = UINT8_MAX;
7476
}
7577

7678
}; // fill with dummies to make size 24, be aware of padding so do not change order of vars

src/MoonLight/VirtualLayer.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ class VirtualLayer {
142142
}
143143
void setRGB2(Coord3D pos, CRGB color) { setRGB2(XYZ(pos), color); }
144144

145+
void setBrightness2(const uint16_t indexV, const uint8_t value) {
146+
if (layerP->lights.header.offsetBrightness2 != UINT8_MAX)
147+
setLight(indexV, &value, layerP->lights.header.offsetBrightness2, sizeof(value));
148+
}
149+
void setBrightness2(Coord3D pos, const uint8_t value) { setBrightness2(XYZ(pos), value); }
150+
151+
145152
void setLight(const uint16_t indexV, const uint8_t* channels, uint8_t offset, uint8_t length);
146153

147154
CRGB getRGB(const uint16_t indexV) {

0 commit comments

Comments
 (0)