-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlights_utils.cpp
More file actions
309 lines (265 loc) · 11.2 KB
/
Copy pathlights_utils.cpp
File metadata and controls
309 lines (265 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
#include "lights_utils.h"
// Perform the various lights related tasks on a cadence of 30Hz
void LightsUtils::PerformLightsTasks(Connection& con) {
// Read all the light states from SpiceAPI, for both the regular lights and the tape LEDs
light_states_.clear();
tape_led_states_.clear();
if (POLL_LIGHTS) {
lights_read(con, light_states_);
}
if (POLL_TAPE_LED) {
ddr_tapeled_get(con, tape_led_states_);
}
if (OUTPUT_LIGHTS) {
// Output the stage lights first, since those go as a single update to a single API
HandleStageLightsUpdate();
// Next, output the cabinet lights, since those are all handled separately, by a different API
HandleMarqueeLightsUpdate();
HandleVerticalStripLightsUpdate();
HandleSpotlightLightsUpdate();
}
}
// Handles sending the lights updates to the stages. This sources data from both the "normal" LEDs,
// as well as the tape LEDs, since we want the RGB strips and also the corner lights. The StepManiaX
// SDK accepts one large payload for the lights for all 18 panels at once (both players).
void LightsUtils::HandleStageLightsUpdate() {
if (light_states_.empty() || tape_led_states_.empty())
return;
string light_data;
// Iterate through each panel in the order SMX expects the output to be in
for (size_t pad = 0; pad < 2; pad++) {
for (size_t panel = 0; panel < 9; panel++) {
switch (panel) {
case UP:
case LEFT:
case RIGHT:
case DOWN:
HandleArrowPanelLight(light_data, pad, panel);
break;
case UP_LEFT:
case UP_RIGHT:
case DOWN_LEFT:
case DOWN_RIGHT:
HandleCornerPanelLight(light_data, pad, panel);
break;
case CENTER:
// Just make the center panel a static color
FillStagePanelColor(light_data, kPadRed, kPadGreen, kPadBlue);
break;
default:
break;
}
}
}
// Send the lights update
SMXWrapper::getInstance().SMX_SetLights2(light_data.data(), light_data.size());
}
// Handles the lights updates for an arrow panel of a stage by appending the appropriate
// lights data to the given string
void LightsUtils::HandleArrowPanelLight(string& light_data, size_t pad, size_t panel_index) {
if (tape_led_states_.empty())
return;
string device_name;
string device_prefix = "p" + to_string(pad + 1) + "_foot_";
// Figure out which device name we need to index on when pulling the LED data
switch (panel_index) {
case UP:
device_name = device_prefix + "up";
break;
case LEFT:
device_name = device_prefix + "left";
break;
case DOWN:
device_name = device_prefix + "down";
break;
case RIGHT:
device_name = device_prefix + "right";
break;
default:
return;
}
// Pull the LED data for this device, and output it to the light string. All
// arrow panel LED PCBs have 25 LEDs, which matches SMX exactly.
vector<uint8_t> tapeled = tape_led_states_[device_name];
for (size_t led = 0; led < kSmxArrowLedCount; led++) {
uint8_t r = tapeled[(led * 3)];
uint8_t g = tapeled[(led * 3) + 1];
uint8_t b = tapeled[(led * 3) + 2];
AddColor(light_data, r, g, b);
}
}
// Handles the lights update for a corner panel of a stage by appending the appropiate
// lights data to the given string based on the given flags (indicating which corner
// this is for), combined with the incoming lights data from SpiceAPI.
void LightsUtils::HandleCornerPanelLight(string& light_data, size_t pad, size_t panel_index) {
if (light_states_.empty())
return;
string device_name;
string device_prefix = "GOLD P" + to_string(pad + 1) + " ";
const uint8_t(*flags)[4] = nullptr;
// Figure out which device name we need to index on when pulling the LED data, and
// also which set of LED flags we should use when constructing the outputs
switch (panel_index) {
case UP_LEFT:
device_name = device_prefix + "Stage Corner Up-Left";
flags = kPadUpperLeftLeds;
break;
case UP_RIGHT:
device_name = device_prefix + "Stage Corner Up-Right";
flags = kPadUpperRightLeds;
break;
case DOWN_LEFT:
device_name = device_prefix + "Stage Corner Down-Left";
flags = kPadLowerLeftLeds;
break;
case DOWN_RIGHT:
device_name = device_prefix + "Stage Corner Down-Right";
flags = kPadLowerRightLeds;
break;
default:
return;
}
// Read the value of the lights from the given device name
uint8_t light_value = light_states_[device_name] * 255.f;
// Iterate through the flags and write data based on whether each LED should be lit or not. We are
// only populating the data for the 4x4 grid of outer LEDs with controllable data. Anything that's
// flagged as "off" will be statically gold to simulate a gold pad.
for (size_t row = 0; row < 4; row++) {
for (size_t col = 0; col < 4; col++) {
if (flags[row][col] != 0) {
AddColor(light_data, light_value, light_value, light_value);
}
else {
AddColor(light_data, kPadRed, kPadGreen, kPadBlue);
}
}
}
// Just make the inner 3x3 grid also gold, to match the rest of the pad
for (size_t i = 0; i < 9; i++) {
AddColor(light_data, kPadRed, kPadGreen, kPadBlue);
}
}
// Outputs data to fill an entire stage LED panel with a single color
void LightsUtils::FillStagePanelColor(string& lights_data, uint8_t red, uint8_t green, uint8_t blue) {
for (size_t i = 0; i < kSmxArrowLedCount; i++) {
AddColor(lights_data, red, green, blue);
}
}
// Handles the lights updates for the marquee
void LightsUtils::HandleMarqueeLightsUpdate() {
if (tape_led_states_.empty())
return;
// Read the lights values for the top panel strip
vector<uint8_t> tapeled = tape_led_states_["top_panel"];
if (tapeled.empty()) {
return;
}
// Map the 40 DDR LEDs onto the 12 SMX marquee LEDs. Since we're mapping more LEDs onto less LEDs,
// we'll prefer lit LEDs over blank ones. This will at least make sure that things like single
// pixel sweeps won't be missed due to integer mappings going poorly. If we have a conflict between
// two lit LEDs, we'll just average them. This is the only strip we need to do this for, since the others
// are mapping less LEDs onto more, so we just repeat some, rather than needing a conflict resolution strategy.
uint8_t smx_led_out[kSmxMarqueeLogicalLedCount * 3] = { 0 };
for (size_t ddr_i = 0; ddr_i < kDdrTopPanelLedCount; ddr_i++) {
size_t smx_i = MapValue(ddr_i, 0, kDdrTopPanelLedCount, 12, 0);
// See what we need to do based on if the current DDR LED is lit, and if we've already
// lit the SMX LED at the mapped index
uint8_t ddr_r = tapeled[(ddr_i * 3)];
uint8_t ddr_g = tapeled[(ddr_i * 3) + 1];
uint8_t ddr_b = tapeled[(ddr_i * 3) + 2];
bool ddr_is_on = (ddr_r != 0 || ddr_g != 0 || ddr_b != 0);
// If the current LED isn't on, then it's a no-op, just go to the next LED
// since we don't overwrite or average lit LEDs with unlit ones
if (!ddr_is_on)
continue;
uint8_t smx_r = smx_led_out[(smx_i * 3)];
uint8_t smx_g = smx_led_out[(smx_i * 3) + 1];
uint8_t smx_b = smx_led_out[(smx_i * 3) + 2];
bool smx_is_on = (smx_r != 0 || smx_g != 0 || smx_b != 0);
if (!smx_is_on) {
// If the DDR LED is on and the SMX LED is off, just replace the SMX LED
smx_led_out[(smx_i * 3)] = ddr_r;
smx_led_out[(smx_i * 3) + 1] = ddr_g;
smx_led_out[(smx_i * 3) + 2] = ddr_b;
}
else {
// If both LEDs are on, then just average them
smx_led_out[(smx_i * 3)] = Average(ddr_r, smx_r);
smx_led_out[(smx_i * 3) + 1] = Average(ddr_g, smx_g);
smx_led_out[(smx_i * 3) + 2] = Average(ddr_b, smx_b);
}
}
// Send the lights update
SMXWrapper::getInstance().SMX_SetDedicatedCabinetLights(
SMXDedicatedCabinetLights::MARQUEE,
reinterpret_cast<const char*>(&(smx_led_out[0])),
kSmxMarqueeLogicalLedCount * 3
);
}
// Handles the lights updates for the vertical strip lights
void LightsUtils::HandleVerticalStripLightsUpdate() {
if (tape_led_states_.empty())
return;
// Read the lights values for the monitor strips
vector<uint8_t> tapeled[2] = {
tape_led_states_["monitor_left"],
tape_led_states_["monitor_right"]
};
if (tapeled[0].empty() || tapeled[1].empty())
return;
static SMXDedicatedCabinetLights device_ids[2] = {
LEFT_STRIP,
RIGHT_STRIP
};
// Iterate over both strips, map the monitor LEDs to the vertical strips (26 -> 28 LEDs)
for (size_t strip = 0; strip < 2; strip++) {
string light_data;
for (size_t smx_i = 0; smx_i < kSmxVerticalStripLedCount; smx_i++) {
// Map the 26 gold cab LEDs to our 28 strip LEDs on SMX
size_t ddr_i = MapValue(smx_i, 0, kSmxVerticalStripLedCount, kDdrVerticalStripLedCount, 0);
uint8_t r = tapeled[strip][(ddr_i * 3)];
uint8_t g = tapeled[strip][(ddr_i * 3) + 1];
uint8_t b = tapeled[strip][(ddr_i * 3) + 2];
AddColor(light_data, r, g, b);
}
// Send the lights update
SMXWrapper::getInstance().SMX_SetDedicatedCabinetLights(
device_ids[strip], light_data.data(), light_data.size()
);
}
}
// Handles the lights updates for the 3 spotlights
void LightsUtils::HandleSpotlightLightsUpdate() {
// Read the lights values for the subwoofer corner lights
uint8_t light1 = light_states_["GOLD P1 Woofer Corner"] * 255.f;
uint8_t light2 = light_states_["GOLD P2 Woofer Corner"] * 255.f;
vector<uint8_t> light_values = { light1, light2 };
static SMXDedicatedCabinetLights device_ids[2] = {
LEFT_SPOTLIGHTS,
RIGHT_SPOTLIGHTS
};
// Iterate over each set of spotlights, turn them all white according to the brightness of the
// woofer corner lights for each player
for (size_t device = 0; device < 2; device++) {
uint8_t light_value = light_values[device];
string light_data;
for (size_t i = 0; i < 8; i++) {
AddColor(light_data, light_value, light_value, light_value);
}
// Send the lights update
SMXWrapper::getInstance().SMX_SetDedicatedCabinetLights(
device_ids[device], light_data.data(), light_data.size()
);
}
}
// Adds an RGB color to the given string, so we can send the string to the SMX SDK
// as lights data
inline void LightsUtils::AddColor(string& lights_data, uint8_t red, uint8_t green, uint8_t blue) {
lights_data.append(1, red);
lights_data.append(1, green);
lights_data.append(1, blue);
}
// Average two bytes together, for crude color averaging during LED interpolation
inline uint8_t LightsUtils::Average(uint8_t a, uint8_t b) {
return (uint8_t)(((uint16_t)a + (uint16_t)b) / 2);
}