├── README.md
├── LICENSE
└── midiSwitch.ino
/README.md:
--------------------------------------------------------------------------------
1 | # midiSwitch
2 | Arduino based midi switcher - looper.
3 |
4 | - 4 switches
5 | - 2 loops
6 | - 128 presets, activated via program change midi messages
7 | - Switches / loops can be turn on/off using control change midi messages
8 |
9 | Parts used:
10 | - Arduino UNO / Nano or equivalent.
11 | - 8 relay module
12 | - 16x2 characters LCD
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Nacho Lucia
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 and this permission notice shall be included in all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/midiSwitch.ino:
--------------------------------------------------------------------------------
1 | /*
2 | * midiSwitch - Arduino based midi switcher - looper
3 | * Copyright (C) 2016 Nacho Lucia
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | /*
20 | * Pinout:
21 | *
22 | * d0 -> MIDI in (disconnect when programming arduino via USB)
23 | * d1 -> MIDI out/thru
24 | * d2 -> LCD RS
25 | * d3 -> LCD E (Enable)
26 | * d4 -> LCD D4
27 | * d5 -> LCD D5
28 | * d6 -> LCD D6
29 | * d7 -> LCD D7
30 | * d8 -> BTN 0 Config/back/store
31 | * d9 -> LCD A (backlight led anode)
32 | * d10 -> BTN 1 Down
33 | * d11 -> BTN 2 Up
34 | * d12 -> BTN 3 Ok/select/toggle
35 | *
36 | * a0-a5 -> OUT relay 0-5 (active high)
37 | *
38 | * Buttons use internal pull-ups, connect other end to ground.
39 | */
40 |
41 | const char* VERSION = "1.31";
42 |
43 | #include
44 | #include
45 | #include
46 | #include
47 | #include
48 | #include
49 |
50 |
51 | const byte NUM_PATCHES = 128; //number of patches
52 | const byte NUM_CONTROLS = 6; //number of switches/loops
53 | const byte MIDI_CC_ON_THRESHOLD = 64; //cc values >= this are considered 'on'
54 | const int MESSAGE_DISPLAY_TIME = 1200; //ms flashed mesages are shown
55 |
56 | const byte CONFIG_START_ADDRESS = 0; //start address of settings in eeprom
57 | const byte EEPROM_PATCH_START_ADDRESS = 64; //start address of patches in eeprom
58 |
59 | const byte LCD_ROWS = 2;
60 | const byte LCD_COLS = 16;
61 |
62 | // I/O pins settings
63 | // LCD pins
64 | const byte LCD_RS = 2;
65 | const byte LCD_EN = 3;
66 | const byte LCD_D4 = 4;
67 | const byte LCD_D5 = 5;
68 | const byte LCD_D6 = 6;
69 | const byte LCD_D7 = 7;
70 | const byte LCD_PWM = 9;
71 |
72 | // button input pins
73 | const byte BTN_0 = 8;
74 | const byte BTN_1 = 10;
75 | const byte BTN_2 = 11;
76 | const byte BTN_3 = 12;
77 |
78 | // relay output pins
79 | const byte RLY_0 = A0;
80 | const byte RLY_1 = A1;
81 | const byte RLY_2 = A2;
82 | const byte RLY_3 = A3;
83 | const byte RLY_4 = A4;
84 | const byte RLY_5 = A5;
85 |
86 | /*
87 | * CONSTANT / GLOBAL VARS
88 | */
89 | // FSM
90 | typedef enum {
91 | EVT_TO_MAIN,
92 | EVT_TO_EDIT_PATCH,
93 | EVT_TO_EDIT_PATCH_PARAMETER,
94 | EVT_TO_SAVE_PATCH,
95 | EVT_TO_SETTINGS,
96 | EVT_TO_EDIT_OPTION,
97 | EVT_TO_RESET
98 | } eventsEnum;
99 |
100 | // Custom characters
101 | const byte blqcarac[][8] PROGMEM = {
102 | {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f},
103 | {0x1f, 0x18, 0x10, 0x10, 0x1f, 0x1f, 0x1f, 0x1c},
104 | {0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x10},
105 | {0x1f, 0x18, 0x10, 0x10, 0x11, 0x11, 0x11, 0x10},
106 | {0x1f, 0x18, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11},
107 | {0x1f, 0x3, 0x1, 0x1, 0x11, 0x11, 0x11, 0x11},
108 | {0x1f, 0x3, 0x1, 0x1, 0x11, 0x11, 0x11, 0x1},
109 | {0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1},
110 | {0x1f, 0x3, 0x1, 0x1, 0x1f, 0x1f, 0x1f, 0x7},
111 | {0x10, 0x11, 0x11, 0x11, 0x10, 0x10, 0x18, 0x1f},
112 | {0x1c, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f},
113 | {0x1c, 0x1f, 0x1f, 0x1f, 0x10, 0x10, 0x18, 0x1f},
114 | {0x11, 0x11, 0x11, 0x11, 0x10, 0x10, 0x18, 0x1f},
115 | {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f},
116 | {0x7, 0x1f, 0x1f, 0x1f, 0x1, 0x1, 0x3, 0x1f},
117 | {0x1, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f},
118 | {0x1, 0x11, 0x11, 0x11, 0x1, 0x1, 0x3, 0x1f},
119 | {0x11, 0x11, 0x11, 0x11, 0x1, 0x1, 0x3, 0x1f}
120 | };
121 |
122 | const byte blqnums[10][4] PROGMEM = {
123 | {4,5,12,17},
124 | {0,5,0,13},
125 | {1,6,9,14},
126 | {1,6,11,16},
127 | {2,7,10,15},
128 | {3,8,11,16},//5
129 | {3,8,9,16},
130 | {4,5,0,13},
131 | {3,6,9,16},
132 | {3,6,11,16}
133 | };
134 |
135 | // Settings
136 | const char* CONFIG_VERSION = "v2"; // ID of the settings block, change if struct is modified
137 |
138 | struct settingsStruct {
139 | char version[2];
140 |
141 | byte midiChannel;
142 | byte ccRelay1, ccRelay2, ccRelay3, ccRelay4;
143 | byte ccLoopA, ccLoopB;
144 | bool toggleMode;
145 | byte brightness;
146 | } _settings;
147 |
148 | typedef enum {
149 | ST_CHANNEL,
150 | ST_TOGGLE,
151 | ST_CC_R1,
152 | ST_CC_R2,
153 | ST_CC_R3,
154 | ST_CC_R4,
155 | ST_CC_LA,
156 | ST_CC_LB,
157 | ST_BRIGHTNESS,
158 | ST_INFO,
159 |
160 | OPTIONSENUM_LENGTH
161 | } optionsEnum;
162 |
163 | const char optionName00[] PROGMEM = "Canal MIDI";
164 | const char optionName01[] PROGMEM = "Modo CC";
165 | const char optionName02[] PROGMEM = "CC Switch 1";
166 | const char optionName03[] PROGMEM = "CC Switch 2";
167 | const char optionName04[] PROGMEM = "CC Switch 3";
168 | const char optionName05[] PROGMEM = "CC Switch 4";
169 | const char optionName06[] PROGMEM = "CC Loop A";
170 | const char optionName07[] PROGMEM = "CC Loop B";
171 | const char optionName08[] PROGMEM = "Brillo";
172 | const char optionName09[] PROGMEM = "Informacion";
173 |
174 | const char* const _settingsItemName[] PROGMEM = {
175 | optionName00, optionName01, optionName02, optionName03, optionName04,
176 | optionName05, optionName06, optionName07, optionName08, optionName09
177 | };
178 |
179 | typedef void (*callback) (void);
180 |
181 | //Global vars
182 | byte _patch = 0;
183 | byte _relayStatus = 0xFF; //all off
184 |
185 | byte _editPatchPosition = 0;
186 | bool _isPatchModified = false;
187 | bool _isConfigModified = false;
188 | byte _tempPatchNumber = 0;
189 |
190 | optionsEnum _settingsItem;
191 | byte _settingsValue;
192 | byte _tempSettingsValue;
193 |
194 | /*
195 | * Global instances
196 | */
197 | SimpleTimer _timer;
198 |
199 | PButton btn_back = PButton(BTN_0);
200 | PButton btn_down = PButton(BTN_1);
201 | PButton btn_up = PButton(BTN_2);
202 | PButton btn_ok = PButton(BTN_3);
203 |
204 | LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7);
205 |
206 | MIDI_CREATE_DEFAULT_INSTANCE();
207 |
208 | //fsm
209 | State stateSplash(NULL, NULL);
210 | State stateMain(&onMainEnter, NULL);
211 | State stateEditPatch(&onEditPatchEnter, &onEditPatchExit);
212 | State stateSavePatch(&onSavePatchEnter, &onSavePatchExit);
213 | State stateSettings(&onSettingsEnter, NULL);
214 | State stateEditOption(&onEditOptionEnter, &onEditOptionExit);
215 | State stateReset(&onResetEnter, NULL);
216 |
217 | Fsm fsm(&stateSplash);
218 |
219 |
220 | //buttons
221 | void resetButtons() {
222 | btn_ok.resetEvents();
223 | btn_back.resetEvents();
224 | btn_up.resetEvents();
225 | btn_down.resetEvents();
226 | }
227 |
228 |
229 | /*
230 | * CHANGE STATE
231 | */
232 | void goToMain() { fsm.trigger(EVT_TO_MAIN); }
233 | void goToEditPatch() { fsm.trigger(EVT_TO_EDIT_PATCH); }
234 | void goToSavePatch() { fsm.trigger(EVT_TO_SAVE_PATCH); }
235 | void goToSettings() { fsm.trigger(EVT_TO_SETTINGS); }
236 | void goToEditOption() { fsm.trigger(EVT_TO_EDIT_OPTION); }
237 | void goToReset() { fsm.trigger(EVT_TO_RESET); }
238 |
239 | /*
240 | * STATES
241 | */
242 |
243 | /*
244 | * MAIN
245 | */
246 | void onMainEnter() {
247 |
248 | MIDI.setHandleControlChange(midiControlChangeHandler);
249 | MIDI.setHandleProgramChange(midiProgramChangeHandler);
250 |
251 | updateMainDisplay();
252 |
253 | _tempPatchNumber = _patch;
254 |
255 | btn_up.addPushEvent(increasePatch); //immediate change
256 | btn_up.addRepeatEvent(increaseTempPatchNumber, updatePatchNumber);
257 |
258 | btn_down.addPushEvent(decreasePatch);
259 | btn_down.addRepeatEvent(decreaseTempPatchNumber, updatePatchNumber);
260 |
261 | btn_back.addShortEvent(goToEditPatch);
262 | btn_back.addLongEvent(goToSettings);
263 | }
264 |
265 | void increasePatch(){
266 | changePatch(incDecValue(_patch, NUM_PATCHES, +1));
267 | _tempPatchNumber = _patch;
268 | }
269 |
270 | void decreasePatch(){
271 | changePatch(incDecValue(_patch, NUM_PATCHES, -1));
272 | _tempPatchNumber = _patch;
273 | }
274 |
275 | void updatePatchNumber() {
276 | changePatch(_tempPatchNumber);
277 | }
278 |
279 | /*
280 | * EDIT PATCH
281 | */
282 | void onEditPatchEnter() {
283 |
284 | updateMainDisplay();
285 | updateEditPatchDisplay();
286 |
287 | btn_up.addPushEvent(editNextRelay);
288 | btn_down.addPushEvent(editPrevRelay);
289 |
290 | btn_back.addShortEvent(goToMain);
291 | btn_back.addLongEvent(goToSavePatch);
292 |
293 | btn_ok.addPushEvent(editToggleRelay);
294 | }
295 |
296 | void editNextRelay(){
297 | changeEditPatchPosition(incDecValue(_editPatchPosition, NUM_CONTROLS, +1));
298 | updateEditPatchDisplay();
299 | }
300 |
301 | void editPrevRelay(){
302 | changeEditPatchPosition(incDecValue(_editPatchPosition, NUM_CONTROLS, -1));
303 | updateEditPatchDisplay();
304 | }
305 |
306 | void editToggleRelay() {
307 | toggleRelayStatus(_editPatchPosition);
308 | }
309 |
310 | void onEditPatchExit() {
311 | lcdClearEditPatchPosition();
312 | }
313 |
314 | /*
315 | * SAVE PATCH
316 | */
317 | void onSavePatchEnter() {
318 |
319 | MIDI.setHandleProgramChange(midiLearnPC);
320 |
321 | btn_up.addRepeatEvent(increaseTempPatchNumber);
322 | btn_down.addRepeatEvent(decreaseTempPatchNumber);
323 | btn_back.addShortEvent(goToEditPatch);
324 | btn_ok.addShortEvent(saveModifiedPatch);
325 |
326 | _tempPatchNumber = _patch;
327 |
328 | blinkBigNumbers(true);
329 | lcd.setCursor(4,1);
330 | lcd.write(0x7F);
331 | lcd.print(F(" Guardar en"));
332 | }
333 |
334 | void onSavePatchExit() {
335 | blinkBigNumbers(false);
336 | }
337 |
338 | void increaseTempPatchNumber(){
339 | _tempPatchNumber = incDecValue(_tempPatchNumber, NUM_PATCHES, +1);
340 | lcdPrintBigNumber(_tempPatchNumber);
341 | }
342 |
343 | void decreaseTempPatchNumber(){
344 | _tempPatchNumber = incDecValue(_tempPatchNumber, NUM_PATCHES, -1);
345 | lcdPrintBigNumber(_tempPatchNumber);
346 | }
347 |
348 | void saveModifiedPatch() {
349 | savePatch(_tempPatchNumber, _relayStatus);
350 | if (_tempPatchNumber != _patch) {
351 | changePatch(_tempPatchNumber);
352 | }
353 | goToMain();
354 | flashMessage(F(" Guardado "), lcdInitStatus);
355 | }
356 |
357 | void midiLearnPC(byte channel, byte number) {
358 | _tempPatchNumber = number;
359 | lcdPrintBigNumber(_tempPatchNumber);
360 | }
361 |
362 | /*
363 | * SETTINGS
364 | */
365 | void onSettingsEnter() {
366 | btn_up.addRepeatEvent(nextOption);
367 | btn_down.addRepeatEvent(prevOption);
368 |
369 | btn_back.addShortEvent(goToMain);
370 | btn_ok.addShortEvent(okEditOption);
371 |
372 | btn_back.addLongEvent(saveSettingsAndExit);
373 |
374 | _settingsValue = *optionValuePt(_settingsItem);
375 |
376 | lcdPrintSettingsDisplay(_settingsValue, _settingsItem);
377 | }
378 |
379 | void nextOption(){
380 | changeSettingsItem((optionsEnum) incDecValue(_settingsItem, OPTIONSENUM_LENGTH, +1));
381 | }
382 |
383 | void prevOption(){
384 | changeSettingsItem((optionsEnum) incDecValue(_settingsItem, OPTIONSENUM_LENGTH, -1));
385 | }
386 |
387 | void okEditOption(){
388 | if(_settingsItem == ST_INFO) {
389 | splash();
390 | resetButtons();
391 | btn_back.addShortEvent(onSettingsEnter);
392 | btn_ok.addShortEvent(onSettingsEnter);
393 | }
394 | else {
395 | goToEditOption();
396 | }
397 | }
398 |
399 | void saveSettingsAndExit() {
400 | if (_isConfigModified) {
401 | saveSettings();
402 | _isConfigModified = false;
403 | }
404 | goToMain();
405 | flashMessage(F(" Guardado "), lcdInitStatus);
406 | }
407 |
408 | /*
409 | * EDIT OPTION
410 | */
411 | void onEditOptionEnter() {
412 |
413 | btn_up.addRepeatEvent(increaseOptionValue, applySettings);
414 | btn_down.addRepeatEvent(decreaseOptionValue, applySettings);
415 |
416 | btn_ok.addShortEvent(okOptionEdit);
417 | btn_back.addShortEvent(cancelOptionEdit);
418 |
419 | _settingsValue = *optionValuePt(_settingsItem);
420 | _tempSettingsValue = _settingsValue;
421 | lcdPrintEditOptionDisplay(_settingsValue, _settingsItem);
422 |
423 | if (_settingsItem >= ST_CC_R1 && _settingsItem <= ST_CC_LB) {
424 | MIDI.setHandleControlChange(midiLearnCC); //midi learn CC
425 | } else {
426 | MIDI.setHandleControlChange(NULL);
427 | }
428 | }
429 |
430 | void increaseOptionValue() {
431 | changeOptionValue(incOptionValue(_settingsValue, _settingsItem), _settingsItem);
432 | updateEditOptionDisplay();
433 | }
434 | void decreaseOptionValue() {
435 | changeOptionValue(decOptionValue(_settingsValue, _settingsItem), _settingsItem);
436 | updateEditOptionDisplay();
437 | }
438 | void cancelOptionEdit() {
439 | changeOptionValue(_tempSettingsValue, _settingsItem);
440 | applySettings();
441 | goToSettings();
442 | }
443 | void okOptionEdit() {
444 | _isConfigModified = _isConfigModified || (_settingsValue != _tempSettingsValue);
445 | goToSettings();
446 | }
447 | void onEditOptionExit() {
448 | lcdClearEditOptionDisplay();
449 | }
450 | void midiLearnCC(byte channel, byte number, byte value) {
451 | changeOptionValue(number, _settingsItem);
452 | updateEditOptionDisplay();
453 | }
454 | /*
455 | * RESET
456 | */
457 | void onResetEnter(){
458 | _tempSettingsValue = false;
459 | lcdPrintResetDisplay(false, F(" Borrar todo!!?"));
460 |
461 | btn_up.addShortEvent(nextResetValue);
462 | btn_down.addShortEvent(nextResetValue);
463 |
464 | btn_ok.addShortEvent(confirmReset);
465 | btn_back.addShortEvent(goToMain);
466 | }
467 |
468 | void nextResetValue(){
469 | _tempSettingsValue = !_tempSettingsValue;
470 | lcdPrintResetDisplay(_tempSettingsValue,"");
471 | }
472 |
473 | void confirmReset() {
474 | if (_tempSettingsValue) { // yes
475 | askAgainReset();
476 | } else {
477 | goToMain();
478 | }
479 | }
480 | void askAgainReset() {
481 | _tempSettingsValue = false;
482 | btn_ok.addShortEvent(lastConfirmReset);
483 | lcdPrintResetDisplay(_tempSettingsValue, F(" Seguro? "));
484 | }
485 | void lastConfirmReset() {
486 | if (_tempSettingsValue) { // yes
487 | factoryReset();
488 | applySettings();
489 | goToMain();
490 | flashMessage(F(" Borrado! "), lcdInitStatus);
491 | } else {
492 | goToMain();
493 | }
494 | }
495 |
496 | void flashMessage(const String& message, callback cb){
497 | lcd.setCursor(4,1);
498 | lcd.print(message);
499 | _timer.setTimeout(MESSAGE_DISPLAY_TIME, cb);
500 | }
501 | /*
502 | * TRANSITIONS
503 | */
504 | void stateTransition(){
505 | resetButtons();
506 | }
507 |
508 | void initMainTransition(){
509 | initMainDisplay();
510 | resetButtons();
511 | }
512 |
513 | /*
514 | * MAIN
515 | */
516 | void lcdInitBigNumbers() {
517 | lcd.setCursor(0,0);
518 | lcd.write((byte) 0);
519 | lcd.write(1);
520 | lcd.write(4);
521 | lcd.write(5);
522 | lcd.setCursor(0,1);
523 | lcd.write(2);
524 | lcd.write(3);
525 | lcd.write(6);
526 | lcd.write(7);
527 | }
528 |
529 | void lcdInitStatus() {
530 | lcd.setCursor(4,1);
531 | lcd.print(F(" 1 2 3 4 A B"));
532 | }
533 |
534 | void initLcd() {
535 | lcd.begin(LCD_COLS, LCD_ROWS);
536 | analogWrite(LCD_PWM, brightnessLevelToValue(_settings.brightness));
537 | }
538 |
539 | void initMainDisplay() {
540 | lcdInitBigNumbers();
541 | lcdInitStatus();
542 | }
543 | /*
544 | * SETTINGS -- LCD
545 | */
546 |
547 | void lcdPrintSettingsDisplay(byte value, optionsEnum option){
548 | const byte ellipsisChar[8] = {0, 0, 0, 0, 0, 0, 0x15, 0};
549 | lcd.createChar(2, (byte*)ellipsisChar);
550 | //top row
551 | lcd.setCursor(0,0);
552 | lcd.print(F("Opciones "));
553 |
554 | lcd.setCursor(LCD_COLS - 1 - (OPTIONSENUM_LENGTH < 10 ? 1 : 2)
555 | - (option +1 <10 ? 1 : 2),0);
556 | lcd.print(option + 1);
557 | lcd.print('/');
558 | lcd.print(OPTIONSENUM_LENGTH);
559 |
560 | //bottom row
561 | lcd.setCursor(0,1);
562 |
563 |
564 | lcd.print(optionText(option));
565 | if (settingsValueText(value, option).length() > 0) lcd.print(':');
566 | else lcd.write(2);
567 | lcd.print(F(" "));
568 |
569 | lcdPrintOptionValue(value, option);
570 |
571 | //config modified flag
572 | lcd.setCursor(8,0); //8 = opciones text length
573 | lcd.write(_isConfigModified ? 0xEB : ' ');
574 | }
575 |
576 | void lcdPrintEditOptionDisplay(byte value, optionsEnum option) {
577 | lcdPrintSettingsDisplay(value, option);
578 | const byte arrowsChar[8] = {0x1F, 0x1B, 0x11, 0x1F, 0x1F, 0x11, 0x1B, 0x1F};
579 | lcd.createChar(1, (byte*)arrowsChar);
580 | lcdPrintEditOptionMarker(value, option);
581 | lcd.blink();
582 | }
583 | void lcdPrintOptionValue(byte value, optionsEnum option) {
584 | String valueText = settingsValueText(value, option);
585 | lcd.setCursor(optionText(option).length() + 1,1); //right to option name
586 | lcd.print(F(" "));
587 | lcd.setCursor(LCD_COLS - valueText.length(),1);
588 |
589 | lcd.print(valueText);
590 | }
591 | void lcdPrintEditOptionMarker(byte value, optionsEnum option) {
592 | byte pos = LCD_COLS - 1 - settingsValueText(value, option).length();
593 |
594 | lcd.setCursor(pos,1);
595 | lcd.write(1);
596 | lcd.setCursor(pos,1);
597 | }
598 | void updateEditOptionDisplay(){
599 | lcdPrintOptionValue(_settingsValue, _settingsItem);
600 | lcdPrintEditOptionMarker(_settingsValue, _settingsItem);
601 | }
602 | void lcdClearEditOptionDisplay() {
603 | lcd.noBlink();
604 | }
605 |
606 | void changeSettingsItem(optionsEnum item){
607 | _settingsItem = item;
608 | lcdPrintSettingsDisplay(*optionValuePt(item), item);
609 | }
610 |
611 | byte* optionValuePt(optionsEnum option) {
612 | switch (_settingsItem) {
613 | case ST_CHANNEL: return &_settings.midiChannel;
614 | case ST_TOGGLE: return (byte*) &_settings.toggleMode;
615 | case ST_CC_R1: return &_settings.ccRelay1;
616 | case ST_CC_R2: return &_settings.ccRelay2;
617 | case ST_CC_R3: return &_settings.ccRelay3;
618 | case ST_CC_R4: return &_settings.ccRelay4;
619 | case ST_CC_LA: return &_settings.ccLoopA;
620 | case ST_CC_LB: return &_settings.ccLoopB;
621 | case ST_BRIGHTNESS: return &_settings.brightness;
622 | }
623 | }
624 |
625 | String optionText(optionsEnum option){
626 | char buffer[16];
627 | strcpy_P(buffer, (char*)pgm_read_word(&(_settingsItemName[option])));
628 | return buffer;
629 | }
630 | String settingsValueText(byte value, optionsEnum option){
631 | char buffer[8];
632 | // byte value = *optionValuePt(option);
633 | switch (_settingsItem) {
634 | case ST_CHANNEL:
635 | if (value == 0) return F("Omni");
636 | break;
637 | case ST_TOGGLE:
638 | if (value) return F("Toggle");
639 | else return F("Normal");
640 | /* case ST_BRIGHTNESS:
641 | if (value <= 0) return F("Off");
642 | if (value >= 5) return F("Max");
643 | break; */
644 | case ST_INFO:
645 | return "";
646 | }
647 | sprintf(buffer, "%d",value);
648 | return buffer;
649 | }
650 |
651 | void applySettings() {
652 | analogWrite(LCD_PWM, brightnessLevelToValue(_settings.brightness));
653 | MIDI.setInputChannel(_settings.midiChannel);
654 | }
655 |
656 | byte incDecValue(byte value, byte max, int offset) {
657 | return (offset >= 0) ? ((value + offset) % (max))
658 | : (((value + offset) < 0) ? max + offset
659 | : value + offset); }
660 |
661 | byte incDecOptionValue(byte value, optionsEnum option, int offset){
662 | switch (option) {
663 | case ST_CHANNEL: return incDecValue(value, 17, offset);
664 | case ST_TOGGLE: return !value;
665 | case ST_CC_R1:
666 | case ST_CC_R2:
667 | case ST_CC_R3:
668 | case ST_CC_R4:
669 | case ST_CC_LA:
670 | case ST_CC_LB: return incDecValue(value, 128, offset);
671 | case ST_BRIGHTNESS: return incDecValue(value, 6, offset);
672 | }
673 | }
674 |
675 | byte incOptionValue(byte value, optionsEnum option) {
676 | return incDecOptionValue(value, option, +1);
677 | }
678 | byte decOptionValue(byte value, optionsEnum option) {
679 | return incDecOptionValue(value, option, -1);
680 | }
681 |
682 | void updateOptionValue(byte value, optionsEnum option){
683 | *optionValuePt(option) = value;
684 | }
685 |
686 | void changeOptionValue(byte value, optionsEnum option) {
687 | _settingsValue = value;
688 | updateOptionValue(value, option);
689 | }
690 |
691 | byte brightnessLevelToValue(byte level){
692 | if (level == 5) return 255;
693 | else if (level == 0) return 0;
694 | return 1 << level + 3;
695 | }
696 | /*
697 | * MAIN -- LCD
698 | */
699 |
700 | void lcdPrintBigNumber(byte number){
701 | byte tens=((number+1)/10)%10;
702 | byte units=(number+1)%10;
703 |
704 | byte bufferTens[8];
705 | byte bufferUnits[8];
706 |
707 | for (byte i=0; i<4; i++) {
708 | for (byte j=0; j<8; j++) {
709 | bufferTens[j] = pgm_read_byte( &blqcarac[pgm_read_byte( &blqnums[tens][i] )][j] );
710 | bufferUnits[j]= pgm_read_byte( &blqcarac[pgm_read_byte( &blqnums[units][i] )][j] );
711 | if (number >= 99) { // invert character
712 | bufferTens[j] = ~bufferTens[j];
713 | bufferUnits[j] = ~bufferUnits[j];
714 | }
715 | }
716 | lcd.createChar(i,bufferTens);
717 | lcd.createChar(i+4,bufferUnits);
718 | }
719 | }
720 |
721 | void lcdClearBigNumbers() {
722 | lcd.setCursor(0,0);
723 | lcd.print(F(" "));
724 | lcd.setCursor(0,1);
725 | lcd.print(F(" "));
726 | }
727 |
728 | void lcdPrintRelayStatus(byte status) {
729 | for (byte i=0; i> i & 1 ? 0x10 : 0xFF);
732 | lcd.print(' ');
733 | }
734 | }
735 |
736 | void updateMainDisplay(){
737 | lcdPrintBigNumber(_patch);
738 | lcdPrintRelayStatus(_relayStatus);
739 | lcdPrintPatchModifiedFlag(_isPatchModified);
740 | }
741 |
742 | void blinkBigNumbers(bool active) {
743 | static byte timerId;
744 | if (active) {
745 | timerId = _timer.setInterval(600, toggleBlinkBigNumbers);
746 | } else {
747 | _timer.deleteTimer(timerId);
748 | lcdInitBigNumbers();
749 | }
750 | }
751 |
752 | void toggleBlinkBigNumbers(){
753 | lcdClearBigNumbers();
754 | _timer.setTimeout(200, lcdInitBigNumbers);
755 | }
756 |
757 | void changeEditPatchPosition(byte position) {
758 | if (position < 0) position = NUM_CONTROLS - 1;
759 | else if (position >= NUM_CONTROLS) position = 0;
760 | _editPatchPosition = position;
761 | }
762 | void updateEditPatchDisplay() {
763 | lcdClearEditPatchPosition();
764 | lcd.setCursor(4+2*_editPatchPosition,1);
765 | lcd.write(0x7e);
766 | }
767 | void lcdClearEditPatchPosition() {
768 | lcdInitStatus();
769 | }
770 |
771 | void lcdPrintResetDisplay(bool reset, const String& message){
772 | if (message.length() > 0) {
773 | lcd.clear();
774 | lcd.setCursor(0,0);
775 | lcd.print(message);
776 | }
777 | lcd.setCursor(1,1);
778 | lcd.print(F(" No Si"));
779 | lcd.setCursor(reset ? 11 : 1, 1);
780 | lcd.write(0x7E);
781 | }
782 | /*
783 | * CONTROL -- PATCH
784 | */
785 | byte changePatch(byte number){
786 | if (number >= 0 && number < NUM_PATCHES) {
787 | _patch = number;
788 | _isPatchModified = false;
789 | changeRelayStatus(loadPatch(number));
790 | updateMainDisplay();
791 |
792 | // _tempPatchNumber = _patch;
793 | }
794 | }
795 |
796 | byte loadPatch(byte number) {
797 | if (number >= 0 && number < NUM_PATCHES) {
798 | byte status = EEPROM.read(EEPROM_PATCH_START_ADDRESS + number);
799 | return status; //EEPROM has value 0xFF if never written to
800 | }
801 | }
802 |
803 | void savePatch(byte number, byte status) {
804 | if (number >= 0 && number < NUM_PATCHES) {
805 | EEPROM.update(EEPROM_PATCH_START_ADDRESS + number, status);
806 | _isPatchModified = false;
807 | }
808 | }
809 |
810 | void factoryReset() {
811 | resetPatchMemory();
812 | resetSettingsMemory();
813 | }
814 |
815 | void resetPatchMemory(){
816 | for (int i = 0; i < NUM_PATCHES; i++) {
817 | EEPROM.update(EEPROM_PATCH_START_ADDRESS + i, 0xFF);
818 | }
819 | _patch = 0;
820 | _isPatchModified = false;
821 | changeRelayStatus(0xFF);
822 | }
823 |
824 | void resetSettingsMemory(){
825 | for (int i = 0; i < sizeof(settingsStruct); i++) {
826 | EEPROM.update(CONFIG_START_ADDRESS + i, 0xFF);
827 | }
828 | loadDefaultSettings();
829 | }
830 |
831 | void lcdPrintPatchModifiedFlag(bool modified) {
832 | lcd.setCursor(4,0);
833 | lcd.write(_isPatchModified ? 0xEB : ' ');
834 | }
835 |
836 | void loadDefaultSettings(){
837 | strcpy(_settings.version,CONFIG_VERSION);
838 | _settings.midiChannel = MIDI_CHANNEL_OMNI;
839 | _settings.ccRelay1 = 75;
840 | _settings.ccRelay2 = 76;
841 | _settings.ccRelay3 = 77;
842 | _settings.ccRelay4 = 78;
843 | _settings.ccLoopA = 82;
844 | _settings.ccLoopB = 83;
845 | _settings.toggleMode = false;
846 | _settings.brightness = 5;
847 | }
848 | /*
849 | * CONTROL -- STATUS
850 | */
851 | void changeRelayStatus(byte status) {
852 | // inverted status, relays are active low
853 | digitalWrite(RLY_0, status & 1 << 0); //status bit 0
854 | digitalWrite(RLY_1, status & 1 << 1); //status bit 1
855 | digitalWrite(RLY_2, status & 1 << 2);
856 | digitalWrite(RLY_3, status & 1 << 3);
857 |
858 | digitalWrite(RLY_4, status & 1 << 4);
859 | digitalWrite(RLY_5, status & 1 << 5);
860 |
861 | _relayStatus = status;
862 | }
863 |
864 |
865 | void setRelayStatus(byte pos, bool active) {
866 | if (active) changeRelayStatus(_relayStatus & ~(1 << pos));
867 | else changeRelayStatus(_relayStatus | (1 << pos));
868 | _isPatchModified = true;
869 | lcdPrintRelayStatus(_relayStatus);
870 | lcdPrintPatchModifiedFlag(_isPatchModified);
871 | }
872 |
873 | void toggleRelayStatus(byte pos) {
874 | changeRelayStatus(_relayStatus ^ (1 << pos));
875 | _isPatchModified = true;
876 | lcdPrintRelayStatus(_relayStatus);
877 | lcdPrintPatchModifiedFlag(_isPatchModified);
878 | }
879 |
880 | /*
881 | * MIDI
882 | */
883 | void midiControlChangeHandler(byte channel, byte number, byte value) {
884 |
885 | if (_settings.toggleMode && value >= MIDI_CC_ON_THRESHOLD) {
886 | if (number == _settings.ccRelay1) toggleRelayStatus(0);
887 | if (number == _settings.ccRelay2) toggleRelayStatus(1);
888 | if (number == _settings.ccRelay3) toggleRelayStatus(2);
889 | if (number == _settings.ccRelay4) toggleRelayStatus(3);
890 | if (number == _settings.ccLoopA) toggleRelayStatus(4);
891 | if (number == _settings.ccLoopB) toggleRelayStatus(5);
892 | }
893 | else if (!_settings.toggleMode) {
894 | if (number == _settings.ccRelay1) setRelayStatus(0, value >= MIDI_CC_ON_THRESHOLD);
895 | if (number == _settings.ccRelay2) setRelayStatus(1, value >= MIDI_CC_ON_THRESHOLD);
896 | if (number == _settings.ccRelay3) setRelayStatus(2, value >= MIDI_CC_ON_THRESHOLD);
897 | if (number == _settings.ccRelay4) setRelayStatus(3, value >= MIDI_CC_ON_THRESHOLD);
898 | if (number == _settings.ccLoopA) setRelayStatus(4, value >= MIDI_CC_ON_THRESHOLD);
899 | if (number == _settings.ccLoopB) setRelayStatus(5, value >= MIDI_CC_ON_THRESHOLD);
900 | }
901 | }
902 |
903 | void midiProgramChangeHandler(byte channel, byte number) {
904 | changePatch(number);
905 | }
906 |
907 | /*
908 | * CONFIG
909 | */
910 |
911 | void loadSettings() {
912 | EEPROM.get(CONFIG_START_ADDRESS, _settings);
913 | if (strcmp(_settings.version,CONFIG_VERSION) != 0) {
914 | loadDefaultSettings();
915 | }
916 | }
917 |
918 | void saveSettings() {
919 | EEPROM.put(CONFIG_START_ADDRESS, _settings);
920 | }
921 |
922 | /*
923 | * SPLASH
924 | */
925 | void splash() {
926 | lcd.setCursor(0,0);
927 | lcd.print(F("midiSwitch v"));
928 | lcd.print(VERSION);
929 | lcd.setCursor(0,1);
930 | lcd.print(F("N. Lucia - 2016"));
931 | }
932 |
933 | /*
934 | * SETUP
935 | */
936 | void setup() {
937 | pinMode(LCD_PWM,OUTPUT);
938 |
939 | pinMode(BTN_0, INPUT_PULLUP);
940 | pinMode(BTN_1, INPUT_PULLUP);
941 | pinMode(BTN_2, INPUT_PULLUP);
942 | pinMode(BTN_3, INPUT_PULLUP);
943 |
944 | pinMode(RLY_0, OUTPUT);
945 | pinMode(RLY_1, OUTPUT);
946 | pinMode(RLY_2, OUTPUT);
947 | pinMode(RLY_3, OUTPUT);
948 | pinMode(RLY_4, OUTPUT);
949 | pinMode(RLY_5, OUTPUT);
950 |
951 | //Serial.begin(9600);
952 | //Serial.println("INICIO");
953 |
954 | loadSettings();
955 | changePatch(_patch);
956 |
957 | initLcd();
958 |
959 | splash();
960 | _timer.setTimeout(MESSAGE_DISPLAY_TIME, goToMain); //exit splash screen
961 |
962 |
963 | MIDI.begin(_settings.midiChannel);
964 | //Serial.begin(115200); //for hairless midi testing
965 |
966 | fsm.add_transition(&stateSplash, &stateMain, EVT_TO_MAIN, &initMainTransition);
967 | fsm.add_transition(&stateEditPatch, &stateMain, EVT_TO_MAIN, &stateTransition);
968 | fsm.add_transition(&stateSavePatch, &stateMain, EVT_TO_MAIN, &stateTransition);
969 | fsm.add_transition(&stateSettings, &stateMain, EVT_TO_MAIN, &initMainTransition);
970 | fsm.add_transition(&stateReset, &stateMain, EVT_TO_MAIN, &initMainTransition);
971 |
972 | fsm.add_transition(&stateEditPatch, &stateSavePatch, EVT_TO_SAVE_PATCH, &stateTransition);
973 |
974 | fsm.add_transition(&stateMain, &stateEditPatch, EVT_TO_EDIT_PATCH, &stateTransition);
975 | fsm.add_transition(&stateSavePatch, &stateEditPatch, EVT_TO_EDIT_PATCH, &stateTransition);
976 |
977 | fsm.add_transition(&stateMain, &stateSettings, EVT_TO_SETTINGS, &stateTransition);
978 | fsm.add_transition(&stateEditOption, &stateSettings, EVT_TO_SETTINGS, &stateTransition);
979 |
980 | fsm.add_transition(&stateSettings, &stateEditOption, EVT_TO_EDIT_OPTION, &stateTransition);
981 |
982 | fsm.add_transition(&stateSplash, &stateReset, EVT_TO_RESET, NULL);
983 |
984 | if (digitalRead(BTN_0) == LOW) { //back pressed on startup
985 | _timer.deleteTimer(0);
986 | goToReset();
987 | }
988 | }
989 |
990 | void loop() {
991 | _timer.run();
992 | MIDI.read();
993 | btn_up.update();
994 | btn_down.update();
995 | btn_back.update();
996 | btn_ok.update();
997 | }
998 |
--------------------------------------------------------------------------------