-
Notifications
You must be signed in to change notification settings - Fork 84
/
ls_leds.ino
417 lines (357 loc) · 13.8 KB
/
ls_leds.ino
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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
/*********************************** ls_leds: LinnStrument LEDS ***********************************
Copyright 2023 Roger Linn Design (https://www.rogerlinndesign.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
***************************************************************************************************
These functions handle the low-level communication with LinnStrument's 208 RGB LEDs.
**************************************************************************************************/
/*
LinnStrument contains an array of 208 RGB LEDs arranged in a 26 by 8 matrix.
Only one column (8 LEDs) can be turned on at a time and the columns are refreshed one at a time.
This works out to a duty cycle of 1/26, keeping the total current low enough for USB bus power.
These are the various functions that together perform the LED tasks:
Data is sent to the LED board over a 32-bit SPI channel, arranged as follows:
Byte 0 (column select):
7: 6: 5: 4: 3: 2: 1: 0:
colAdr4Inv colAdr4 colAdr3 colAdr2 colAdr1 colAdr0 n/a n/a
Note: colAdr4Inv is inverted state of colAdr4, to save an inverter
Byte 1 (blue on/off bits for each row):
7: 6: 5: 4: 3: 2: 1: 0:
blueRow7 blueRow6 blueRow5 blueRow4 blueRow3 blueRow2 blueRow1 blueRow0
Byte 2 (green on/off bits for each row):
7: 6: 5: 4: 3: 2: 1: 0:
greenRow7 greenRow6 greenRow5 greenRow4 greenRow3 greenRow2 greenRow1 greenRow0
Byte 3 (red on/off bits for each row):
7: 6: 5: 4: 3: 2: 1: 0:
redRow7 redRow6 redRow5 redRow4 redRow3 redRow2 redRow1 redRow0
*/
byte COL_INDEX[MAXCOLS];
const byte COL_INDEX_200[MAXCOLS] = {0, 1, 6, 11, 16, 21, 2, 7, 12, 17, 22, 3, 8, 13, 18, 23, 4, 9, 14, 19, 24, 5, 10, 15, 20, 25};
const byte COL_INDEX_128[MAXCOLS] = {0, 1, 6, 11, 16, 2, 7, 12, 3, 8, 13, 4, 9, 14, 5, 10, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0};
// array holding contents of display
byte leds[2][LED_ARRAY_SIZE];
byte visibleLeds = 0;
byte bufferedLeds = 0;
#define ledBuffered(layer, col, row) leds[bufferedLeds][layer * LED_LAYER_SIZE + row * MAXCOLS + col]
#define ledVisible(layer, col, row) leds[visibleLeds][layer * LED_LAYER_SIZE + row * MAXCOLS + col]
bool ledDisplayEnabled = true;
void initializeLeds() {
if (LINNMODEL == 200) {
for (byte i = 0; i < MAXCOLS; ++i) {
COL_INDEX[i] = COL_INDEX_200[i];
}
}
else if (LINNMODEL == 128) {
for (byte i = 0; i < MAXCOLS; ++i) {
COL_INDEX[i] = COL_INDEX_128[i];
}
}
}
void initializeLedLayers() {
memset(leds[bufferedLeds], 0, LED_ARRAY_SIZE);
}
void initializeLedsLayer(byte layer) {
memset(&leds[bufferedLeds][layer * LED_LAYER_SIZE], 0, LED_LAYER_SIZE);
}
int getActiveCustomLedPattern() {
return Global.activeNotes - 9;
}
void loadCustomLedLayer(int pattern)
{
if (pattern < 0 || pattern >= LED_PATTERNS) {
if (customLedPatternActive) {
memset(&leds[0][LED_LAYER_CUSTOM1 * LED_LAYER_SIZE], 0, LED_LAYER_SIZE);
memset(&leds[1][LED_LAYER_CUSTOM1 * LED_LAYER_SIZE], 0, LED_LAYER_SIZE);
}
customLedPatternActive = false;
return;
}
memcpy(&leds[0][LED_LAYER_CUSTOM1 * LED_LAYER_SIZE], &Device.customLeds[pattern][0], LED_LAYER_SIZE);
memcpy(&leds[1][LED_LAYER_CUSTOM1 * LED_LAYER_SIZE], &Device.customLeds[pattern][0], LED_LAYER_SIZE);
customLedPatternActive = true;
lightSettings = 2;
completelyRefreshLeds();
}
void storeCustomLedLayer(int pattern)
{
if (pattern < 0 || pattern >= LED_PATTERNS) {
if (customLedPatternActive) {
memset(&leds[0][LED_LAYER_CUSTOM1 * LED_LAYER_SIZE], 0, LED_LAYER_SIZE);
memset(&leds[1][LED_LAYER_CUSTOM1 * LED_LAYER_SIZE], 0, LED_LAYER_SIZE);
}
customLedPatternActive = false;
return;
}
memcpy(&Device.customLeds[pattern][0], &leds[visibleLeds][LED_LAYER_CUSTOM1 * LED_LAYER_SIZE], LED_LAYER_SIZE);
customLedPatternActive = true;
lightSettings = 2;
}
void clearStoredCustomLedLayer(int pattern)
{
if (pattern < 0 || pattern >= LED_PATTERNS) return;
memset(&Device.customLeds[pattern][0], 0, LED_LAYER_SIZE);
if (getActiveCustomLedPattern() == pattern) {
loadCustomLedLayer(pattern);
}
}
void startBufferedLeds() {
bufferedLeds = 1;
memcpy(leds[bufferedLeds], leds[visibleLeds], LED_ARRAY_SIZE);
}
void finishBufferedLeds() {
memcpy(leds[visibleLeds], leds[bufferedLeds], LED_ARRAY_SIZE);
bufferedLeds = 0;
}
inline byte getCombinedLedData(byte col, byte row) {
byte data = 0;
byte layer = MAX_LED_LAYERS;
do {
layer -= 1;
// when custom LED editor is active, only display those LEDs
if (col > 0 && displayMode == displayCustomLedsEditor) {
if (layer != LED_LAYER_CUSTOM1) continue;
data = ledBuffered(layer, col, row);
}
// don't show the custom layer 1 in user firmware mode
if (userFirmwareActive || isVisibleSequencer()) {
if (layer == LED_LAYER_CUSTOM1) continue;
}
// don't show the custom layer 2 in regular firmware mode
if (!userFirmwareActive) {
if (layer == LED_LAYER_CUSTOM2) continue;
}
if (!isVisibleSequencer()) {
if (layer == LED_LAYER_SEQUENCER) continue;
}
// in normal and split point display mode, show all layers and only show the main in other display modes
if (displayMode == displayNormal || displayMode == displaySplitPoint || layer == LED_LAYER_MAIN) {
data = ledBuffered(layer, col, row);
}
}
while (layer > 0 && (data & B00000111) == cellOff);
return data;
}
void setLed(byte col, byte row, byte color, CellDisplay disp) {
setLed(col, row, color, disp, LED_LAYER_MAIN);
}
void setLed(byte col, byte row, byte color, CellDisplay disp, byte layer) {
if (col >= NUMCOLS || row >= NUMROWS || layer > MAX_LED_LAYERS) return;
if (color == COLOR_OFF) {
disp = cellOff;
}
else if (disp == cellOff) {
color = COLOR_OFF;
}
// packs color and display into this cell within array
byte data = ((color & B00011111) << 3) | (disp & B00000111);
if (ledBuffered(layer, col, row) != data) {
ledBuffered(layer, col, row) = data;
ledBuffered(LED_LAYER_COMBINED, col, row) = getCombinedLedData(col, row);
}
if (bufferedLeds == 1) {
performContinuousTasks();
}
}
byte getLedColor(byte col, byte row, byte layer) {
if (col >= NUMCOLS || row >= NUMROWS || layer > MAX_LED_LAYERS) return COLOR_OFF;
return (ledVisible(layer, col, row) >> 3) & B00011111;
}
// light up a single LED with the default color
void lightLed(byte col, byte row) {
setLed(col, row, globalColor, cellOn);
}
// clear a single LED
void clearLed(byte col, byte row) {
clearLed(col, row, LED_LAYER_MAIN);
clearLed(col, row, LED_LAYER_LOWROW);
}
void clearLed(byte col, byte row, byte layer) {
setLed(col, row, COLOR_OFF, cellOff, layer);
}
// Turns all LEDs off
void clearFullDisplay() {
clearSwitches();
clearDisplay();
}
// Turns all LEDs off in columns 1 or higher
void clearDisplay() {
for (byte col = 1; col < NUMCOLS; ++col) {
clearColumn(col);
}
}
// Turns all LEDs off in column 0
void clearSwitches() {
clearColumn(0);
}
void clearColumn(byte col) {
for (byte row = 0; row < NUMROWS; ++row) {
clearLed(col, row);
}
}
void clearRow(byte row) {
for (byte col = 0; col < NUMCOLS; ++col) {
clearLed(col, row);
}
}
void completelyRefreshLeds() {
for (byte row = 0; row < NUMROWS; ++row) {
for (byte col = 0; col < NUMCOLS; ++col) {
ledBuffered(LED_LAYER_COMBINED, col, row) = getCombinedLedData(col, row);
}
performContinuousTasks();
}
}
void clearDisplayImmediately() {
// disable the outputs of the LED driver chips
digitalWrite(37, HIGH);
}
void disableLedDisplay() {
ledDisplayEnabled = false;
clearDisplayImmediately();
}
void enableLedDisplay() {
// enable the outputs of the LED driver chips
digitalWrite(37, LOW);
ledDisplayEnabled = true;
}
// refreshLedColumn:
// Called when it's time to refresh the next column of LEDs. Internally increments the column number every time it's called.
void refreshLedColumn(unsigned long now) {
if (!ledDisplayEnabled) return;
// disabling the power output from the LED driver pins early prevents power leaking into unwanted cells.
digitalWrite(37, HIGH); // disable the outputs of the LED driver chips
// keep a steady pulsating going for those leds that need it
static unsigned long lastPulse = 0;
static boolean lastPulseOn = true;
static unsigned long lastSlowPulse = 0;
static boolean lastSlowPulseOn = true;
static boolean lastFocusPulseOn = true;
if (calcTimeDelta(now, lastPulse) > 80000) {
lastPulse = now;
lastPulseOn = !lastPulseOn;
}
if (calcTimeDelta(now, lastSlowPulse) > 120000) {
lastSlowPulse = now;
lastSlowPulseOn = !lastSlowPulseOn;
}
if (clock24PPQ < 6) {
lastFocusPulseOn = false;
}
else {
lastFocusPulseOn = true;
}
static byte ledCol = 0;
static byte displayInterval[MAXCOLS][MAXROWS];
byte red = 0; // red value to be sent
byte green = 0; // green value to be sent
byte blue = 0; // blue value to be sent
byte actualCol = 0; // actual column being refreshed, permitting columns to be lit non-sequentially by using COL_INDEX[] array
byte ledColShifted = 0; // LED column address, which is shifted 2 bits to left within byte
actualCol = COL_INDEX[ledCol]; // using COL_INDEX[], permits non-sequential lighting of LED columns, which doesn't seem to improve appearance
// Initialize bytes to send to LEDs over SPI. Each bit represents a single LED on or off
for (byte rowCount = 0; rowCount < NUMROWS; ++rowCount) { // step through the 8 rows
// allow several levels of brightness by modulating LED's ON time
if (++displayInterval[actualCol][rowCount] >= 12) {
displayInterval[actualCol][rowCount] = 0;
}
byte color = (ledVisible(LED_LAYER_COMBINED, actualCol, rowCount) & B11111000) >> 3; // set temp value 'color' to 4 color bits of this LED within array
byte cellDisplay = ledVisible(LED_LAYER_COMBINED, actualCol, rowCount) & B00000111; // get cell display value
switch (cellDisplay) {
case cellFastPulse:
cellDisplay = lastPulseOn ? cellOn : cellOff;
break;
case cellSlowPulse:
cellDisplay = lastSlowPulseOn ? cellOn : cellOff;
break;
case cellFocusPulse:
cellDisplay = lastFocusPulseOn ? cellOn : cellOff;
break;
}
if (Device.operatingLowPower) {
if (displayInterval[actualCol][rowCount] % 2 != 0) {
cellDisplay = cellOff;
}
}
// if this LED is not off, process it
// set the color bytes to the correct color
if (cellDisplay) {
// construct composite colors
if ((!Device.operatingLowPower && displayInterval[actualCol][rowCount] % 2 != 0) ||
(Device.operatingLowPower && displayInterval[actualCol][rowCount] % 4 != 0)) {
switch (color)
{
case COLOR_WHITE:
color = COLOR_CYAN;
break;
case COLOR_ORANGE:
color = COLOR_YELLOW;
break;
case COLOR_LIME:
color = COLOR_GREEN;
break;
case COLOR_PINK:
color = COLOR_YELLOW;
break;
}
}
switch (color)
{
case COLOR_OFF:
case COLOR_BLACK:
break;
case COLOR_RED:
red = red | (B00000001 << rowCount);
break;
case COLOR_YELLOW:
red = red | (B00000001 << rowCount);
green = green | (B00000001 << rowCount);
break;
case COLOR_GREEN:
green = green | (B00000001 << rowCount);
break;
case COLOR_CYAN:
green = green | (B00000001 << rowCount);
blue = blue | (B00000001 << rowCount);
break;
case COLOR_BLUE:
blue = blue | (B00000001 << rowCount);
break;
case COLOR_MAGENTA:
blue = blue | (B00000001 << rowCount);
red = red | (B00000001 << rowCount);
break;
case COLOR_WHITE:
blue = blue | (B00000001 << rowCount);
red = red | (B00000001 << rowCount);
green = green | (B00000001 << rowCount);
break;
case COLOR_ORANGE:
red = red | (B00000001 << rowCount);
break;
case COLOR_LIME:
red = red | (B00000001 << rowCount);
green = green | (B00000001 << rowCount);
break;
case COLOR_PINK:
blue = blue | (B00000001 << rowCount);
red = red | (B00000001 << rowCount);
break;
}
}
}
if (++ledCol >= NUMCOLS) ledCol = 0;
ledColShifted = actualCol << 2;
if ((actualCol & 16) == 0) ledColShifted |= B10000000; // if column address 4 is 0, set bit 7
SPI.transfer(SPI_LEDS, ~ledColShifted, SPI_CONTINUE); // send column address
SPI.transfer(SPI_LEDS, blue, SPI_CONTINUE); // send blue byte
SPI.transfer(SPI_LEDS, green, SPI_CONTINUE); // send green byte
SPI.transfer(SPI_LEDS, red); // send red byte
digitalWrite(37, LOW); // enable the outputs of the LED driver chips
}