├── 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 | --------------------------------------------------------------------------------