├── Editor └── editor.png ├── Schematic └── arduinoboy_schematic_1_1_0.png ├── platformio.ini ├── Arduinoboy ├── Memory_Functions.ino ├── UsbMidi.ino ├── Mode.ino ├── Mode_Nanoloop.ino ├── Mode_LSDJ_MasterSync.ino ├── Mode_Programmer.ino ├── Mode_LSDJ_Map.ino ├── Mode_LSDJ_Midiout.ino ├── Mode_MidiGb.ino ├── Led_Functions.ino ├── Mode_LSDJ_SlaveSync.ino ├── Mode_LSDJ_Keyboard.ino └── Arduinoboy.ino ├── Changelog.md ├── README.md └── LICENSE /Editor/editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trash80/Arduinoboy/HEAD/Editor/editor.png -------------------------------------------------------------------------------- /Schematic/arduinoboy_schematic_1_1_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trash80/Arduinoboy/HEAD/Schematic/arduinoboy_schematic_1_1_0.png -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Project Configuration File 3 | # 4 | # A detailed documentation with the EXAMPLES is located here: 5 | # http://docs.platformio.org/en/latest/projectconf.html 6 | # 7 | 8 | # A sign `#` at the beginning of the line indicates a comment 9 | # Comment lines are ignored. 10 | 11 | # Simple and base environment 12 | # [env:mybaseenv] 13 | # platform = %INSTALLED_PLATFORM_NAME_HERE% 14 | # framework = 15 | # board = 16 | # 17 | # Automatic targets - enable auto-uploading 18 | # targets = upload 19 | 20 | [platformio] 21 | src_dir = Arduinoboy 22 | 23 | [env:teensy31] 24 | platform = teensy 25 | framework = arduino 26 | board = teensy31 27 | targets = upload 28 | build_flags = -UUSB_SERIAL -DUSB_MIDI 29 | board_f_cpu = 24000000L 30 | -------------------------------------------------------------------------------- /Arduinoboy/Memory_Functions.ino: -------------------------------------------------------------------------------- 1 | 2 | boolean checkMemory() 3 | { 4 | byte chk; 5 | #ifndef USE_DUE 6 | for(int m=0;m<4;m++){ 7 | chk = EEPROM.read(MEM_CHECK+m); 8 | if(chk != defaultMemoryMap[MEM_CHECK+m]) { 9 | return false; 10 | } 11 | } 12 | #endif 13 | return true; 14 | } 15 | 16 | void initMemory(boolean reinit) 17 | { 18 | if(!alwaysUseDefaultSettings) { 19 | #ifndef USE_DUE 20 | if(reinit || !checkMemory()) { 21 | for(int m=(MEM_MAX);m>=0;m--){ 22 | EEPROM.write(m,defaultMemoryMap[m]); 23 | } 24 | } 25 | #endif 26 | loadMemory(); 27 | } else { 28 | for(int m=0;m<=MEM_MAX;m++){ 29 | memory[m] = defaultMemoryMap[m]; 30 | } 31 | } 32 | changeTasks(); 33 | } 34 | 35 | 36 | void loadMemory() 37 | { 38 | #ifndef USE_DUE 39 | for(int m=(MEM_MAX);m>=0;m--){ 40 | memory[m] = EEPROM.read(m); 41 | } 42 | #endif 43 | changeTasks(); 44 | } 45 | 46 | void printMemory() 47 | { 48 | for(int m=0;m<=MEM_MAX;m++){ 49 | serial->println(memory[m],HEX); 50 | } 51 | } 52 | 53 | void saveMemory() 54 | { 55 | #ifndef USE_DUE 56 | for(int m=(MEM_MAX-1);m>=0;m--){ 57 | EEPROM.write(m,memory[m]); 58 | } 59 | changeTasks(); 60 | #endif 61 | } 62 | 63 | void changeTasks() 64 | { 65 | midioutByteDelay = memory[MEM_MIDIOUT_BYTE_DELAY] * memory[MEM_MIDIOUT_BYTE_DELAY+1]; 66 | midioutBitDelay = memory[MEM_MIDIOUT_BIT_DELAY] * memory[MEM_MIDIOUT_BIT_DELAY+1]; 67 | } 68 | -------------------------------------------------------------------------------- /Arduinoboy/UsbMidi.ino: -------------------------------------------------------------------------------- 1 | 2 | #ifndef USE_TEENSY 3 | void usbMidiSendTwoByteMessage(uint8_t b1, uint8_t b2) {}; 4 | void usbMidiSendThreeByteMessage(uint8_t b1, uint8_t b2, uint8_t b3) {}; 5 | void usbMidiSendRTMessage(uint8_t b) {}; 6 | void usbMidiHandleSysEx(const uint8_t *data, uint16_t length, bool complete) {}; 7 | void usbMidiInit() {}; 8 | void usbMidiUpdate() {}; 9 | #else 10 | 11 | void usbMidiSendTwoByteMessage(uint8_t b1, uint8_t b2) 12 | { 13 | uint8_t stat = b1 & 0xf0; 14 | uint8_t chan = (b1 & 0x0f)+1; 15 | if(stat == 0xC0) { 16 | usbMIDI.sendProgramChange(b2, chan); 17 | } else if (stat == 0xD0) { 18 | usbMIDI.sendAfterTouch(b2, chan); 19 | } 20 | } 21 | 22 | void usbMidiSendThreeByteMessage(uint8_t b1, uint8_t b2, uint8_t b3) 23 | { 24 | uint8_t channel = (b1&0x0F)+1; 25 | 26 | switch(midiData[0] & 0xF0) { 27 | case 0x80: 28 | usbMIDI.sendNoteOff(b2, b3, channel); 29 | usbMIDI.send_now(); 30 | break; 31 | case 0x90: 32 | usbMIDI.sendNoteOn(b2, b3, channel); 33 | usbMIDI.send_now(); 34 | break; 35 | case 0xA0: 36 | usbMIDI.sendPolyPressure(b2, b3, channel); 37 | break; 38 | case 0xB0: 39 | usbMIDI.sendControlChange(b2, b3, channel); 40 | usbMIDI.send_now(); 41 | break; 42 | case 0xE0: 43 | unsigned short v = (unsigned short)b3; 44 | v<<=7; 45 | v|=(unsigned short)b2; 46 | usbMIDI.sendPitchBend(v, channel); 47 | break; 48 | } 49 | } 50 | 51 | void usbMidiSendRTMessage(uint8_t b) 52 | { 53 | usbMIDI.sendRealTime(b); 54 | } 55 | 56 | void usbMidiUpdate() 57 | { 58 | usbMIDI.read(); 59 | } 60 | 61 | void usbMidiHandleSysEx(const uint8_t *data, uint16_t length, bool complete) 62 | { 63 | if(sysexPosition + length >= longestSysexMessage || (length < 3 && complete)) { 64 | //wrapped! 65 | sysexPosition = 0; 66 | return ; 67 | } 68 | 69 | if(sysexPosition == 0 && complete) { 70 | memcpy(&sysexData[0], &data[1], length-2); 71 | sysexPosition += length-2; 72 | } else if (sysexPosition == 0 && !complete) { 73 | memcpy(&sysexData[0], &data[1], length-1); 74 | sysexPosition += length-1; 75 | } else if (!complete) { 76 | memcpy(&sysexData[sysexPosition], &data[0], length); 77 | sysexPosition += length; 78 | } else { 79 | memcpy(&sysexData[sysexPosition], &data[0], length-1); 80 | sysexPosition += length-1; 81 | } 82 | 83 | if(complete) { 84 | getSysexData(); 85 | } 86 | } 87 | 88 | void usbMidiInit() 89 | { 90 | usbMIDI.setHandleSysEx(usbMidiHandleSysEx); 91 | } 92 | 93 | #endif 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Arduinoboy/Mode.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * Name: Timothy Lamb * 3 | * Email: trash80@gmail.com * 4 | ***************************************************************************/ 5 | /*************************************************************************** 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation; either version 2 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | ***************************************************************************/ 13 | 14 | /* ***************************************************************************/ 15 | /* "Mode" Functions. Deals with changing the setup of arduino. */ 16 | /* ***************************************************************************/ 17 | 18 | /* 19 | setMode will check if the push button is depressed, If it is it will 20 | increment the mode number and make sure its in the 21 | range 0 to 4 by mod (%). It will then write the mode to memory, 22 | set the leds to display the mode, and switch the code over to the 23 | right function. 24 | */ 25 | 26 | 27 | void setMode() 28 | { 29 | buttonDepressed = digitalRead(pinButtonMode); 30 | if(!memory[MEM_FORCE_MODE] && buttonDepressed) { //if the button is pressed 31 | memory[MEM_MODE]++; //increment the mode number 32 | if(memory[MEM_MODE] > (NUMBER_OF_MODES - 1)) memory[MEM_MODE]=0; //if the mode is greater then 4 it will wrap back to 0 33 | #ifndef USE_DUE 34 | if(!memory[MEM_FORCE_MODE]) EEPROM.write(MEM_MODE, memory[MEM_MODE]); //write mode to eeprom if we arnt forcing a mode in the config 35 | #endif 36 | showSelectedMode(); //set the LEDS 37 | switchMode(); 38 | } 39 | } 40 | 41 | 42 | /* 43 | switchMode is only called from setMode. its responsible for 44 | linking the mode number to its corrisponding function, 45 | and then calling that function. function. function. 46 | */ 47 | void switchMode() 48 | { 49 | switch(memory[MEM_MODE]) 50 | { 51 | case 0: 52 | modeLSDJSlaveSyncSetup(); 53 | break; 54 | case 1: 55 | modeLSDJMasterSyncSetup(); 56 | break; 57 | case 2: 58 | modeLSDJKeyboardSetup(); 59 | break; 60 | case 3: 61 | modeNanoloopSetup(); 62 | break; 63 | case 4: 64 | modeMidiGbSetup(); 65 | break; 66 | case 5: 67 | modeLSDJMapSetup(); 68 | break; 69 | case 6: 70 | modeLSDJMidioutSetup(); 71 | break; 72 | } 73 | } 74 | 75 | 76 | /* ***************************************************************************/ 77 | /* General Global Functions Used in more then one of the modes */ 78 | /* ***************************************************************************/ 79 | 80 | /* 81 | sequencerStart is called when either LSDJ has started to play in master mode, 82 | or when a MIDI Start or continue command is received in lsdj slave mode. 83 | Basically it just resets some counters we use and sets a "start" flag. 84 | */ 85 | 86 | void sequencerStart() 87 | { 88 | sequencerStarted = true; //Sequencer has started? 89 | countSyncPulse = 0; //Used for status LED, counts 24 ticks (quarter notes) 90 | countSyncTime = 0; //Used to count a custom amount of clock ticks (2/4/8) for sync effects 91 | countSyncLightTime=0; 92 | switchLight=0; 93 | } 94 | 95 | /* 96 | sequencerStop is called when either LSDJ has stopped sending sync commands for 97 | some time in LSDJ Master mode, or when a MIDI Stop command is received in 98 | lsdj slave mode. 99 | Basically it just resets some counters we use and sets the "start" flag to false. 100 | */ 101 | void sequencerStop() 102 | { 103 | midiSyncEffectsTime = false;//Turn off MIDI sync effects in LSDJ slave mode 104 | sequencerStarted = false; //Sequencer has started? 105 | countSyncPulse = 0; //Used for status LED, counts 24 ticks (quarter notes) 106 | countSyncTime = 0; //Used to count a custom amount of clock ticks (2/4/8) for sync effects 107 | countSyncLightTime=0; 108 | switchLight=0; 109 | digitalWrite(pinLeds[0],LOW); 110 | digitalWrite(pinLeds[1],LOW); 111 | digitalWrite(pinLeds[2],LOW); 112 | digitalWrite(pinLeds[3],LOW); 113 | digitalWrite(pinLeds[memory[MEM_MODE]],HIGH); 114 | } 115 | -------------------------------------------------------------------------------- /Arduinoboy/Mode_Nanoloop.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * Name: Timothy Lamb * 3 | * Email: trash80@gmail.com * 4 | ***************************************************************************/ 5 | /*************************************************************************** 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation; either version 2 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | ***************************************************************************/ 13 | 14 | void modeNanoloopSetup() 15 | { 16 | digitalWrite(pinStatusLed,LOW); 17 | pinMode(pinGBClock,OUTPUT); 18 | digitalWrite(pinGBClock,HIGH); 19 | 20 | #ifdef USE_TEENSY 21 | usbMIDI.setHandleRealTimeSystem(usbMidiNanoloopRealtimeMessage); 22 | #endif 23 | 24 | blinkMaxCount=1000; 25 | modeNanoloopSync(); 26 | } 27 | 28 | void modeNanoloopSync() 29 | { 30 | while(1){ //Loop forever 31 | modeNanoloopUsbMidiReceive(); 32 | if (serial->available()) { //If MIDI Byte Availaibleleleiel 33 | incomingMidiByte = serial->read(); //Read it 34 | if(!checkForProgrammerSysex(incomingMidiByte) && !usbMode) serial->write(incomingMidiByte); //Send it back to the Midi out 35 | 36 | 37 | if(incomingMidiByte & 0x80) { 38 | switch (incomingMidiByte) { 39 | case 0xF8: // Clock Message Recieved 40 | // Send a clock tick out if the sequencer is running 41 | if(sequencerStarted) { 42 | nanoSkipSync = !nanoSkipSync; 43 | if(countSyncTime) { 44 | nanoState = sendTickToNanoloop(nanoState, false); 45 | } else { 46 | nanoState = sendTickToNanoloop(true, true); 47 | } 48 | nanoState = sendTickToNanoloop(nanoState, nanoSkipSync); 49 | updateVisualSync(); 50 | break; 51 | } 52 | break; 53 | case 0xFA: // Transport Start Message 54 | case 0xFB: // Transport Continue Message 55 | sequencerStart(); 56 | break; 57 | case 0xFC: // Transport Stop Message 58 | sequencerStop(); 59 | break; 60 | default: 61 | break; 62 | } 63 | } 64 | } 65 | setMode(); //Check if the mode button was depressed 66 | updateStatusLight(); 67 | } 68 | } 69 | 70 | boolean sendTickToNanoloop(boolean state, boolean last_state) 71 | { 72 | if(!state) { 73 | if(last_state) { 74 | GB_SET(0,1,0); 75 | GB_SET(1,1,0); 76 | } else { 77 | GB_SET(0,0,0); 78 | GB_SET(1,0,0); 79 | } 80 | return true; 81 | } else { 82 | GB_SET(0,1,0); 83 | GB_SET(1,1,0); 84 | return false; 85 | } 86 | } 87 | 88 | void usbMidiNanoloopRealtimeMessage(uint8_t message) 89 | { 90 | switch(message) { 91 | case 0xF8: 92 | if(sequencerStarted) { 93 | nanoSkipSync = !nanoSkipSync; 94 | if(countSyncTime) { 95 | nanoState = sendTickToNanoloop(nanoState, false); 96 | } else { 97 | nanoState = sendTickToNanoloop(true, true); 98 | } 99 | nanoState = sendTickToNanoloop(nanoState, nanoSkipSync); 100 | updateVisualSync(); 101 | } 102 | break; 103 | case 0xFA: // Case: Transport Start Message 104 | case 0xFB: // and Case: Transport Continue Message 105 | sequencerStart(); // Start the sequencer 106 | break; 107 | case 0xFC: // Case: Transport Stop Message 108 | sequencerStop(); 109 | break; 110 | } 111 | } 112 | 113 | 114 | void modeNanoloopUsbMidiReceive() 115 | { 116 | #ifdef USE_TEENSY 117 | while(usbMIDI.read(memory[MEM_LSDJSLAVE_MIDI_CH]+1)) { 118 | switch(usbMIDI.getType()) { 119 | case 0x90: // note on 120 | getSlaveSyncEffect(usbMIDI.getData1()); 121 | break; 122 | /* 123 | case 0: // note on 124 | break; 125 | case 3: // CC 126 | break; 127 | case 4: // PG 128 | break; 129 | case 5: // AT 130 | break; 131 | case 6: // PB 132 | break; 133 | */ 134 | } 135 | } 136 | #endif 137 | 138 | #ifdef USE_LEONARDO 139 | midiEventPacket_t rx; 140 | do 141 | { 142 | rx = MidiUSB.read(); 143 | usbMidiNanoloopRealtimeMessage(rx.byte1); 144 | } while (rx.header != 0); 145 | #endif 146 | 147 | } 148 | -------------------------------------------------------------------------------- /Arduinoboy/Mode_LSDJ_MasterSync.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * Name: Timothy Lamb * 3 | * Email: trash80@gmail.com * 4 | ***************************************************************************/ 5 | /*************************************************************************** 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation; either version 2 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | ***************************************************************************/ 13 | 14 | 15 | void modeLSDJMasterSyncSetup() 16 | { 17 | digitalWrite(pinStatusLed,LOW); 18 | pinMode(pinGBClock,INPUT); 19 | 20 | #ifdef USE_TEENSY 21 | usbMIDI.setHandleRealTimeSystem(NULL); 22 | #endif 23 | 24 | countSyncTime=0; 25 | blinkMaxCount=1000; 26 | modeLSDJMasterSync(); 27 | } 28 | 29 | void modeLSDJMasterSync() 30 | { 31 | while(1){ 32 | 33 | #ifdef USE_TEENSY 34 | while(usbMIDI.read()) ; 35 | #endif 36 | 37 | if (serial->available()) { //If serial data was send to midi input 38 | incomingMidiByte = serial->read(); //Read it 39 | if(!checkForProgrammerSysex(incomingMidiByte) && !usbMode) serial->write(incomingMidiByte); //Send it to the midi output 40 | } 41 | readgbClockLine = digitalRead(pinGBClock); //Read gameboy's clock line 42 | if(readgbClockLine) { //If Gb's Clock is On 43 | while(readgbClockLine) { //Loop untill its off 44 | readgbClockLine = digitalRead(pinGBClock);//Read the clock again 45 | bit = digitalRead(pinGBSerialIn); //Read the serial input for song position 46 | checkActions(); 47 | } 48 | 49 | countClockPause= 0; //Reset our wait timer for detecting a sequencer stop 50 | 51 | readGbSerialIn = readGbSerialIn << 1; //left shift the serial byte by one to append new bit from last loop 52 | readGbSerialIn = readGbSerialIn + bit; //and then add the bit that was read 53 | 54 | sendMidiClockSlaveFromLSDJ(); //send the clock & start offset data to midi 55 | 56 | } 57 | setMode(); 58 | } 59 | } 60 | 61 | void checkActions() 62 | { 63 | checkLSDJStopped(); //Check if LSDJ hit Stop 64 | setMode(); 65 | updateStatusLight(); 66 | } 67 | 68 | /* 69 | checkLSDJStopped counts how long the clock was on, if its been on too long we assume 70 | LSDJ has stopped- Send a MIDI transport stop message and return true. 71 | */ 72 | boolean checkLSDJStopped() 73 | { 74 | countClockPause++; //Increment the counter 75 | if(countClockPause > 16000) { //if we've reached our waiting period 76 | if(sequencerStarted) { 77 | readgbClockLine=false; 78 | countClockPause = 0; //reset our clock 79 | serial->write(0xFC); //send the transport stop message 80 | #ifdef USE_TEENSY 81 | usbMIDI.sendRealTime(0xFC); 82 | #endif 83 | #ifdef USE_LEONARDO 84 | midiEventPacket_t event = {0x0F, 0xFC}; 85 | MidiUSB.sendMIDI(event); 86 | MidiUSB.flush(); 87 | #endif 88 | sequencerStop(); //call the global sequencer stop function 89 | } 90 | return true; 91 | } 92 | return false; 93 | } 94 | 95 | /* 96 | sendMidiClockSlaveFromLSDJ waits for 8 clock bits from LSDJ, 97 | sends the transport start command if sequencer hasnt started yet, 98 | sends the midi clock tick, and sends a note value that corrisponds to 99 | LSDJ's row number on start (LSDJ only sends this once when it starts) 100 | */ 101 | void sendMidiClockSlaveFromLSDJ() 102 | { 103 | if(!countGbClockTicks) { //If we hit 8 bits 104 | if(!sequencerStarted) { //If the sequencer hasnt started 105 | serial->write((0x90+memory[MEM_LSDJMASTER_MIDI_CH])); //Send the midi channel byte 106 | serial->write(readGbSerialIn); //Send the row value as a note 107 | serial->write(0x7F); //Send a velocity 127 108 | serial->write(0xFA); //send MIDI transport start message 109 | 110 | #ifdef USE_TEENSY 111 | usbMIDI.sendNoteOn(memory[MEM_LSDJMASTER_MIDI_CH]+1,readGbSerialIn,0x7F); 112 | usbMIDI.sendRealTime(0xFA); 113 | #endif 114 | #ifdef USE_LEONARDO 115 | midiEventPacket_t event = {0x09, 0x90 | memory[MEM_LSDJMASTER_MIDI_CH] + 1, readGbSerialIn, 0x7F}; 116 | MidiUSB.sendMIDI(event); 117 | MidiUSB.flush(); 118 | event = {0x0F, 0xFA}; 119 | MidiUSB.sendMIDI(event); 120 | MidiUSB.flush(); 121 | #endif 122 | sequencerStart(); //call the global sequencer start function 123 | } 124 | serial->write(0xF8); //Send the MIDI Clock Tick 125 | 126 | #ifdef USE_TEENSY 127 | usbMIDI.sendRealTime(0xF8); 128 | #endif 129 | #ifdef USE_LEONARDO 130 | midiEventPacket_t event = {0x0F, 0xF8}; 131 | MidiUSB.sendMIDI(event); 132 | MidiUSB.flush(); 133 | #endif 134 | countGbClockTicks=0; //Reset the bit counter 135 | readGbSerialIn = 0x00; //Reset our serial read value 136 | 137 | updateVisualSync(); 138 | } 139 | countGbClockTicks++; //Increment the bit counter 140 | if(countGbClockTicks==8) countGbClockTicks=0; 141 | } 142 | -------------------------------------------------------------------------------- /Arduinoboy/Mode_Programmer.ino: -------------------------------------------------------------------------------- 1 | void modeProgrammer() 2 | { 3 | while(sysexProgrammingConnected || sysexProgrammingMode) { 4 | checkProgrammerConnected(); 5 | if (serial->available()) checkForProgrammerSysex(serial->read()); 6 | updateProgrammerLeds(); 7 | setMode(); 8 | usbMidiUpdate(); 9 | } 10 | showSelectedMode(); 11 | switchMode(); 12 | } 13 | 14 | void setProgrammerConnected() 15 | { 16 | sysexProgrammerLastResponse = millis(); 17 | if(!sysexProgrammingConnected) { 18 | programmerSendSettings(); 19 | } 20 | sysexProgrammingConnected = 1; 21 | } 22 | 23 | void checkProgrammerConnected() 24 | { 25 | programmerSendConnectRequest(); 26 | programmerCheckTimeout(); 27 | } 28 | 29 | void programmerSendSettings() 30 | { 31 | sysexData[0] = 0xF0; 32 | sysexData[1] = sysexManufacturerId; 33 | sysexData[2] = 0x40; 34 | memcpy(&sysexData[3], memory, MEM_MAX+1); 35 | sysexData[MEM_MAX+3] = 0xF7; 36 | serial->write(sysexData, MEM_MAX+4); 37 | #ifdef USE_TEENSY 38 | usbMIDI.sendSysEx(MEM_MAX+4, sysexData); 39 | #endif 40 | } 41 | 42 | void setProgrammerRequestConnect() 43 | { 44 | uint8_t data[4] = {0xF0,sysexManufacturerId,65,0xF7}; 45 | serial->write(data, 4); 46 | #ifdef USE_TEENSY 47 | usbMIDI.sendSysEx(4, data); 48 | #endif 49 | } 50 | 51 | void setProgrammerMemorySave() 52 | { 53 | byte offset = 2; 54 | for(byte m=4;m<=MEM_MAX;m++) { 55 | memory[m] = sysexData[offset]; 56 | offset++; 57 | } 58 | saveMemory(); 59 | loadMemory(); 60 | programmerSendSettings(); 61 | } 62 | 63 | void setProgrammerRestoreMemory() 64 | { 65 | initMemory(1); 66 | programmerSendSettings(); 67 | } 68 | 69 | void programmerCheckTimeout() 70 | { 71 | if(sysexProgrammingConnected && millis() > (sysexProgrammerLastResponse+sysexProgrammerWaitTime)) { 72 | //programmer timeout! 73 | sysexProgrammingConnected = 0; 74 | sysexProgrammingMode = 0; 75 | } 76 | if(sysexProgrammingMode && millis() > (sysexProgrammerLastResponse+sysexProgrammerWaitTime)) { 77 | //programmer timeout! 78 | sysexProgrammingConnected = 0; 79 | sysexProgrammingMode = 0; 80 | } 81 | } 82 | 83 | void programmerSendConnectRequest() 84 | { 85 | if(millis() > (sysexProgrammerLastSent+sysexProgrammerCallTime)) { 86 | uint8_t data[6] = {0xF0, sysexManufacturerId, 0x7F, defaultMemoryMap[MEM_VERSION_FIRST], defaultMemoryMap[MEM_VERSION_SECOND], 0xF7}; 87 | serial->write(data, 6); 88 | #ifdef USE_TEENSY 89 | usbMIDI.sendSysEx(6, data); 90 | #endif 91 | sysexProgrammerLastSent = millis(); 92 | } 93 | } 94 | 95 | boolean checkSysexChecksum() 96 | { 97 | byte checksum = sysexData[(sysexPosition - 1)]; 98 | byte checkdata= 0; 99 | if(checksum) { 100 | for(int x=2;x!=(sysexPosition - 2);x++) { 101 | checkdata += sysexData[x]; 102 | } 103 | if(checkdata & 0x80) checkdata -= 0x7F; 104 | if(checkdata == checksum) { 105 | return true; 106 | } 107 | } 108 | return true; 109 | } 110 | 111 | 112 | void clearSysexBuffer() 113 | { 114 | for(int x=0;x!=sysexPosition;x++) { 115 | sysexData[x]= 0; 116 | } 117 | sysexPosition = 0; 118 | } 119 | 120 | void setMode(byte mode) 121 | { 122 | memory[MEM_MODE] = mode; 123 | #ifndef USE_DUE 124 | EEPROM.write(MEM_MODE, memory[MEM_MODE]); 125 | #endif 126 | showSelectedMode(); 127 | switchMode(); 128 | } 129 | 130 | void sendMode() 131 | { 132 | uint8_t data[4] = {0xF0, sysexManufacturerId, memory[MEM_MODE], 0xF7}; 133 | serial->write(data, 4); 134 | #ifdef USE_TEENSY 135 | usbMIDI.sendSysEx(4, data); 136 | #endif 137 | } 138 | 139 | void setMidioutDelay(byte a,byte b,byte c,byte d) 140 | { 141 | memory[MEM_MIDIOUT_BIT_DELAY] = a; 142 | memory[MEM_MIDIOUT_BIT_DELAY+1] = b; 143 | memory[MEM_MIDIOUT_BYTE_DELAY] = c; 144 | memory[MEM_MIDIOUT_BYTE_DELAY+1] = d; 145 | saveMemory(); 146 | changeTasks(); 147 | } 148 | 149 | void getSysexData() 150 | { 151 | if(sysexData[0] == 0x69 && checkSysexChecksum()) { 152 | //sysex good, do stuff 153 | sysexPosition = 0; 154 | if(sysexProgrammingMode) { 155 | if(sysexData[1] == 64 156 | && sysexData[2] == defaultMemoryMap[MEM_VERSION_FIRST] 157 | && sysexData[3] == defaultMemoryMap[MEM_VERSION_SECOND]) { 158 | //serial connected to programmer 159 | setProgrammerRequestConnect(); 160 | } 161 | if(sysexData[1] == 66 162 | && sysexData[2] == defaultMemoryMap[MEM_VERSION_FIRST] 163 | && sysexData[3] == defaultMemoryMap[MEM_VERSION_SECOND]) { 164 | //serial connected to programmer 165 | setProgrammerConnected(); 166 | } 167 | if(sysexData[1] == 70) { 168 | //save states 169 | setProgrammerMemorySave(); 170 | } 171 | if(sysexData[1] == 71) { 172 | //save states 173 | setProgrammerRestoreMemory(); 174 | } 175 | } 176 | if(sysexData[1] == 72) { 177 | sysexProgrammingMode = true; 178 | sysexProgrammerLastResponse = millis(); 179 | modeProgrammer(); 180 | } 181 | if(sysexData[1] == 73) { 182 | sendMode(); 183 | } 184 | if(sysexData[1] == 74) { 185 | setMode(sysexData[2]); 186 | } 187 | if(sysexData[1] == 75) { 188 | setMidioutDelay(sysexData[2],sysexData[3],sysexData[4],sysexData[5]); 189 | } 190 | } 191 | clearSysexBuffer(); 192 | } 193 | 194 | 195 | 196 | boolean checkForProgrammerSysex(byte sin) 197 | { 198 | if(sin == 0xF0) { 199 | sysexReceiveMode = true; 200 | sysexPosition= 0; 201 | return true; 202 | } else if (sin == 0xF7 && sysexReceiveMode) { 203 | sysexReceiveMode = false; 204 | getSysexData(); 205 | sysexPosition= 0; 206 | return true; 207 | } else if (sysexReceiveMode == true) { 208 | sysexData[sysexPosition] = sin; 209 | sysexPosition++; 210 | if(sysexPosition > longestSysexMessage) { 211 | clearSysexBuffer(); 212 | sysexReceiveMode = false; 213 | } 214 | return true; 215 | } 216 | return false; 217 | } 218 | 219 | 220 | void blinkSelectedLight(int led) 221 | { 222 | if(!blinkSwitch[led]) digitalWrite(pinLeds[led],HIGH); 223 | blinkSwitch[led]=1; 224 | blinkSwitchTime[led]=0; 225 | } 226 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### 05/19/20 4 | * Arduinoboy to version 1.3.4 5 | * Added support for Arduino Mega 2560 and Arduino UNO Wifi 6 | 7 | ### 05/17/20 8 | * Arduinoboy to version 1.3.3 9 | * Added support for Arduino Due 10 | 11 | ### 05/03/20 12 | * Arduinoboy to version 1.3.2 13 | * Added support for Arduino Leonardo/Yún/Micro (ATmega32U4) (only Leonardo tested) 14 | 15 | ### 04/19/20 16 | * Arduinoboy to version 1.3.1 17 | * Fixed Nanoloop sync through USB (teensy and Leonardo tested) 18 | 19 | ### 07/10/16 20 | * New Version 1.3.0a, please download from the [Releases] (https://github.com/trash80/Arduinoboy/releases) if you want the most recent stable version. 21 | * Added Teensy 3.x & LC support. Check the main file for the pin configuration. 22 | * Added USB MIDI support for Teensy 23 | * Updated LSDJ LIVEMAP / SYNCMAP mode. This only works with LSDJ Arduinoboy version 4.8.7 and above. Licensed users can [download it here](http://littlesounddj.com/lsd/latest/full_version/lsdj4_8_7-arduinoboy.zip) Livemap has been removed in favor of stable sync slave mode. 24 | 25 | ### 06/26/15 26 | * Project has been moved to GitHub. mGB has been moved to it's own project [Available here](https://github.com/trash80/mGB) 27 | 28 | ### 12/23/11 29 | * Arduinoboy to version 1.2.3 30 | * Fixed code to work in Arduino 1.0 Software. 31 | 32 | ### 09/14/11 33 | * Arduinoboy to version 1.2.2 34 | * Fixed bug in mGB mode causing the Arduinoboy to respond to other MIDI channels besides the ones assigned to mGB. 35 | 36 | ### 04/03/11 37 | * Arduinoboy to version 1.2.0 38 | * Added EEPROM based settings to all modes which include reassigning MIDI channels in mGB 39 | * Created software editor for Arduinoboy settings. The file is in the zip and requires the free Max Runtime: http://cycling74.com/downloads/ 40 | * Added 2 new Modes that require a new version of LSDJ. There is a unofficial release of LSDJ which supports these new modes, available here: http://littlesounddj.com/lsd/latest/full_version 41 | * LIVEMAP / SYNCMAP 42 | * LIVEMAP - Lsdj will use its own clock, but a incoming midi note will cue midi note # to song row # in live mode. 43 | * SYNCMAP - Lsdj will sync to incoming MIDI sync, and notes immediately change the song row #. 44 | * MIDIOUT - LSDJ to MIDIOUT. Each of the 4 gameboy channels send MIDI data on 4 midi channels by the use of effects commands: 45 | * N - Sends a MIDI Note - Absolute to the value placed in the effect. N00 sends note off, N01-N6F send notes 1 to 112. 46 | * Q - Sends a MIDI Note relative to the current channel's pitch. The effect value is a offset. so Q0C in PU1 would send a note 1 octave higher than what Pu1 is currently playing. This is useful as a table command to track midi notes as normal notes in the sequencer. 47 | * X - Sends a MIDI CC - By default in Arduinoboy the high nibble selects a CC#, and the low nibble sends a value [0-F] to [0-127]. This can be changed to allow just 1 midi CC with a range of 00-6F, or 7 CCs with scaled or unscaled values. 48 | * Y - Sends a program/patch/preset change. 49 | 50 | ### 02/07/09 51 | * Arduinoboy to version 1.1.0 52 | * Changed Gameboy Pins to the Arduino's "Analog In" pins. From the old version's 5,6,7 to A0,A1,A2. This allows better more stable behavior. View the source file for changes. And the new "schematic" at the bottom of the page. 53 | 54 | ### 02/02/09 55 | * mGB to version 1.3.0 56 | * Rewrote 90% of code into assembly for much faster performance- especially noticeable on DMG. 57 | * Changed note behavior. Removed Monophonic note memory to increase performance. 58 | * Envelope does not retrigger if the notes overlap and have the same velocity- Good for arpeggios / broken chords. 59 | * Note off has a slight delay so immediate retrigged notes don't cause "clicking" effect due to turning off the synth. 60 | * Added screen off mode for great signal-to-noise ratio, longer battery life, and better performance on DMG. (To toggle the screen mode hold Select and press A.) 61 | * Created back-end routine that prioritizes processes for better performance. 62 | * Added 8 "noise" shapes to the Wav synth for more interesting effects. 63 | * Made Wav pitch sweep stable and changed it so it glitches out at values above 8. :D 64 | 65 | ### 11/05/08 66 | * New Google group created for Arduinoboy and mGB. http://groups.google.com/group/arduinoboy 67 | * New Flickr Group. Share your Arduinoboy pics. :) http://flickr.com/groups/arduinoboy 68 | * mGB to version 1.2.4 69 | * Fixed small bug with the indicator arrow, it was offset vertically a bit. 70 | * Fixed bug with unexpected behavior with large PB Ranges 71 | * PB Range Max is now 48 notes. (hehe) 72 | * Octave Shift max is now -2/+3 73 | * Added some Octave shift logic. If the current note is greater than what the GB can play due to octave shifting, it will select the lower octave note, so no off key notes will play. 74 | * Added Gameboy Color fast-cpu mode- better performance with newer Gameboys. 75 | 76 | ### 10/28/08 77 | * mGB to version 1.2.3 78 | * Added Note markers above synth number, so you can tell if the synth is on. ;) 79 | * Added PB wheel reset to MIDI panic (Start button) 80 | * Code more efficient (I'm sure there is still more to do here) 81 | * nitro2k01 @ http://gameboygenius.8bitcollective.com rewrote my gamejack serial function to make it as fast as possible. THANKS!! 82 | * Arduinoboy 1.0.5 yet again its just a release to work with mGB 1.2.3 - Everytime the code is sped up on mGB, I remove some delay in Arduinoboy to make the communication faster. This is why you always see a update of Arduinoboy with mGB 83 | 84 | 85 | ### 10/25/08 86 | * Added Program Change messages to mGB 87 | * Rewrote MIDI data input for mGB. (Rewrote the function in ASM to make it faster) 88 | * Added Controller Priority. While changing parameters on the gameboy itself, MIDI messages will not overwrite your changes while your editing them. This is a good live mode feature 89 | * Arduinoboy code updated yet again for mGB behavior. Arduinoboy release will now always be included in a zip file inside the mGB zip so you know that the version of Arduinoboy thats included with the mGB archive is the one to use. 90 | 91 | ### 10/23/08 92 | * Found & Fixed various bugs in 1.2.0 93 | * Changed help text. Made it more clear. 94 | 95 | ### 10/23/08 96 | * Added preset load/save memory to mGB. 16 Storage slots for each synth. Recalled via interface or by Midi CC 5 for now, I want to add program change message but would require further rewrites. Check the information below for mGB usage. 97 | * mGB is now rock solid stable *(It needs Arduinoboy 1.0.3 to work properly)* 98 | * Changed mGB interface to use less sprites, as a result the look has changed. 99 | * Arduinoboy 1.0.3 has better behavior for mGB 1.2.0 but should also work better for old mGB versions as well. 100 | 101 | ### 10/20/08 102 | * Added interface to mGB. 103 | * Changed WAV CC Parameters 104 | * Arduinoboy 1.0.2 has better behavior for mGB 1.1.0 105 | -------------------------------------------------------------------------------- /Arduinoboy/Mode_LSDJ_Map.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * Name: Timothy Lamb * 3 | * Email: trash80@gmail.com * 4 | ***************************************************************************/ 5 | /*************************************************************************** 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation; either version 2 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | ***************************************************************************/ 13 | 14 | void modeLSDJMapSetup() 15 | { 16 | digitalWrite(pinStatusLed,LOW); 17 | pinMode(pinGBClock,OUTPUT); 18 | digitalWrite(pinGBClock, HIGH); 19 | #ifdef USE_TEENSY 20 | usbMIDI.setHandleRealTimeSystem(usbMidiLSDJMapRealtimeMessage); 21 | #endif 22 | blinkMaxCount=1000; 23 | modeLSDJMap(); 24 | } 25 | 26 | void modeLSDJMap() 27 | { 28 | while(1){ //Loop forever 29 | 30 | modeLSDJMapUsbMidiReceive(); 31 | checkMapQueue(); 32 | if (serial->available()) { //If MIDI Byte Availaibleleleiel 33 | incomingMidiByte = serial->read(); //Read it 34 | 35 | checkForProgrammerSysex(incomingMidiByte); 36 | 37 | if(incomingMidiByte & 0x80) { //If we have received a MIDI Status Byte 38 | switch (incomingMidiByte) { 39 | case 0xF8: 40 | setMapByte(0xFF, false); 41 | usbMidiSendRTMessage(incomingMidiByte); 42 | break; 43 | case 0xFA: // Case: Transport Start Message 44 | case 0xFB: // and Case: Transport Continue Message 45 | sequencerStart(); // Start the sequencer 46 | usbMidiSendRTMessage(incomingMidiByte); 47 | break; 48 | case 0xFC: // Case: Transport Stop Message 49 | sequencerStop(); 50 | setMapByte(0xFE, false); 51 | usbMidiSendRTMessage(incomingMidiByte); 52 | break; 53 | default: 54 | midiData[0] = incomingMidiByte; 55 | midiNoteOnMode = true; 56 | 57 | if(midiData[0] == (0x90+memory[MEM_LIVEMAP_CH]) 58 | || midiData[0] == (0x90+(memory[MEM_LIVEMAP_CH]+1))) resetMapCue(); 59 | } 60 | } else if(midiNoteOnMode) { //if we've received a message thats not a status and our note capture mode is true 61 | 62 | midiNoteOnMode = false; 63 | midiData[1] = incomingMidiByte; 64 | 65 | usbMidiSendTwoByteMessage(midiData[0],midiData[1]); 66 | if(midiData[0] == (0x90+memory[MEM_LIVEMAP_CH]) 67 | || midiData[0] == (0x90+(memory[MEM_LIVEMAP_CH]+1))) resetMapCue(); 68 | 69 | } else { 70 | midiNoteOnMode = true; 71 | if(midiData[0] == (0x90+memory[MEM_LIVEMAP_CH]) 72 | || midiData[0] == (0x90+(memory[MEM_LIVEMAP_CH]+1))) { 73 | if(incomingMidiByte) { 74 | if(midiData[0] == (0x90+(memory[MEM_LIVEMAP_CH]+1))) { 75 | setMapByte(128+midiData[1], false); 76 | } else { 77 | setMapByte(midiData[1], false); 78 | } 79 | } else { 80 | setMapByte(0xFE, false); 81 | } 82 | } else if (midiData[0] == (0x80+memory[MEM_LIVEMAP_CH]) 83 | || midiData[0] == (0x80+(memory[MEM_LIVEMAP_CH]+1))) { 84 | setMapByte(0xFE, false); 85 | } 86 | usbMidiSendThreeByteMessage(midiData[0], midiData[1], incomingMidiByte); 87 | checkMapQueue(); 88 | } 89 | } else { 90 | setMode(); //Check if the mode button was depressed 91 | updateStatusLight(); 92 | checkMapQueue(); 93 | updateBlinkLights(); 94 | } 95 | } 96 | } 97 | 98 | void setMapByte(uint8_t b, boolean usb) 99 | { 100 | uint8_t wait = mapQueueWaitSerial; 101 | if(usb) { 102 | wait = mapQueueWaitUsb; 103 | } 104 | 105 | switch(b) { 106 | case 0xFF: 107 | setMapQueueMessage(0xFF, wait); 108 | break; 109 | case 0xFE: 110 | if(!sequencerStarted) { 111 | sendByteToGameboy(0xFE); 112 | } else if (mapCurrentRow >= 0) { 113 | setMapQueueMessage(mapCurrentRow, wait); 114 | } 115 | break; 116 | default: 117 | mapCurrentRow = b; 118 | sendByteToGameboy(b); 119 | resetMapCue(); 120 | } 121 | } 122 | 123 | void setMapQueueMessage(uint8_t m, uint8_t wait) 124 | { 125 | if(mapQueueMessage == -1 || mapQueueMessage == 0xFF) { 126 | mapQueueTime=millis()+wait; 127 | mapQueueMessage=m; 128 | } 129 | } 130 | 131 | void resetMapCue() 132 | { 133 | mapQueueMessage=-1; 134 | } 135 | 136 | void checkMapQueue() 137 | { 138 | if(mapQueueMessage >= 0 && millis()>mapQueueTime) { 139 | if(mapQueueMessage == 0xFF) { 140 | sendByteToGameboy(mapQueueMessage); 141 | } else { 142 | if(mapQueueMessage == 0xFE || mapCurrentRow == mapQueueMessage) { 143 | // Only kill playback if the row is the last one that's been played. 144 | mapCurrentRow = -1; 145 | sendByteToGameboy(0xFE); 146 | } 147 | } 148 | mapQueueMessage=-1; 149 | updateVisualSync(); 150 | } 151 | } 152 | 153 | 154 | void usbMidiLSDJMapRealtimeMessage(uint8_t message) 155 | { 156 | switch(message) { 157 | case 0xF8: 158 | setMapByte(0xFF, true); 159 | break; 160 | case 0xFA: // Case: Transport Start Message 161 | case 0xFB: // and Case: Transport Continue Message 162 | resetMapCue(); 163 | sequencerStart(); // Start the sequencer 164 | break; 165 | case 0xFC: // Case: Transport Stop Message 166 | sequencerStop(); // Stop the sequencer 167 | setMapByte(0xFE, true); 168 | break; 169 | } 170 | } 171 | 172 | void modeLSDJMapUsbMidiReceive() 173 | { 174 | #ifdef USE_TEENSY 175 | 176 | while(usbMIDI.read()) { 177 | uint8_t ch = usbMIDI.getChannel() - 1; 178 | if(ch != memory[MEM_LIVEMAP_CH] && ch != (memory[MEM_LIVEMAP_CH] + 1)){ 179 | continue; 180 | } 181 | 182 | switch(usbMIDI.getType()) { 183 | case 0x80: // note off 184 | setMapByte(0xFE, true); 185 | break; 186 | case 0x90: // note on 187 | if(ch == (memory[MEM_LIVEMAP_CH] + 1)) { 188 | setMapByte(128+usbMIDI.getData1(), true); 189 | } else { 190 | setMapByte(usbMIDI.getData1(), true); 191 | } 192 | break; 193 | /* 194 | case 3: // CC 195 | break; 196 | case 4: // PG 197 | break; 198 | case 5: // AT 199 | break; 200 | case 6: // PB 201 | break; 202 | */ 203 | } 204 | } 205 | #endif 206 | #ifdef USE_LEONARDO 207 | midiEventPacket_t rx; 208 | do 209 | { 210 | rx = MidiUSB.read(); 211 | usbMidiLSDJMapRealtimeMessage(rx.byte1); 212 | uint8_t ch = rx.byte1 & 0x0F; 213 | if (ch != memory[MEM_LIVEMAP_CH] && ch != (memory[MEM_LIVEMAP_CH] + 1)) 214 | { 215 | continue; 216 | } 217 | switch (rx.header) 218 | { 219 | case 0x08: // note off 220 | setMapByte(0xFE, true); 221 | break; 222 | case 0x09: // note on 223 | if (ch == (memory[MEM_LIVEMAP_CH] + 1)) 224 | { 225 | setMapByte(128 + rx.byte2, true); 226 | } 227 | else 228 | { 229 | setMapByte(rx.byte2, true); 230 | } 231 | break; 232 | } 233 | } while (rx.header != 0); 234 | #endif 235 | } 236 | -------------------------------------------------------------------------------- /Arduinoboy/Mode_LSDJ_Midiout.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * Name: Timothy Lamb * 3 | * Email: trash80@gmail.com * 4 | ***************************************************************************/ 5 | /*************************************************************************** 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation; either version 2 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | ***************************************************************************/ 13 | 14 | void modeLSDJMidioutSetup() 15 | { 16 | digitalWrite(pinStatusLed,LOW); 17 | pinMode(pinGBClock,OUTPUT); 18 | digitalWrite(pinGBClock,HIGH); 19 | 20 | #ifdef USE_TEENSY 21 | usbMIDI.setHandleRealTimeSystem(NULL); 22 | #endif 23 | 24 | countGbClockTicks=0; 25 | lastMidiData[0] = -1; 26 | lastMidiData[1] = -1; 27 | midiValueMode = false; 28 | blinkMaxCount=60; 29 | modeLSDJMidiout(); 30 | } 31 | 32 | void modeLSDJMidiout() 33 | { 34 | #ifdef USE_LEONARDO 35 | midiEventPacket_t packet; 36 | #endif 37 | while(1){ 38 | if(getIncommingSlaveByte()) { 39 | if(incomingMidiByte > 0x6f) { 40 | switch(incomingMidiByte) 41 | { 42 | case 0x7F: //clock tick 43 | serial->write(0xF8); 44 | #ifdef USE_TEENSY 45 | usbMIDI.sendRealTime((int)0xF8); 46 | #endif 47 | #ifdef USE_LEONARDO 48 | packet = {0x0F, 0xF8}; 49 | MidiUSB.sendMIDI(packet); 50 | MidiUSB.flush(); 51 | #endif 52 | break; 53 | case 0x7E: //seq stop 54 | serial->write(0xFC); 55 | #ifdef USE_TEENSY 56 | usbMIDI.sendRealTime((int)0xFC); 57 | #endif 58 | #ifdef USE_LEONARDO 59 | packet = {0x0F, 0xFC}; 60 | MidiUSB.sendMIDI(packet); 61 | MidiUSB.flush(); 62 | #endif 63 | stopAllNotes(); 64 | break; 65 | case 0x7D: //seq start 66 | serial->write(0xFA); 67 | #ifdef USE_TEENSY 68 | usbMIDI.sendRealTime((int)0xFA); 69 | #endif 70 | #ifdef USE_LEONARDO 71 | packet = {0x0F, 0xFA}; 72 | MidiUSB.sendMIDI(packet); 73 | MidiUSB.flush(); 74 | #endif 75 | break; 76 | default: 77 | midiData[0] = (incomingMidiByte - 0x70); 78 | midiValueMode = true; 79 | break; 80 | } 81 | } else if (midiValueMode == true) { 82 | midiValueMode = false; 83 | midioutDoAction(midiData[0],incomingMidiByte); 84 | } 85 | 86 | } else { 87 | setMode(); // Check if mode button was depressed 88 | updateBlinkLights(); 89 | #ifdef USE_TEENSY 90 | while(usbMIDI.read()) ; 91 | #endif 92 | #ifdef USE_LEONARDO 93 | // while (MidiUSB.read()) ; 94 | #endif 95 | if (serial->available()) { //If serial data was send to midi inp 96 | checkForProgrammerSysex(serial->read()); 97 | } 98 | } 99 | } 100 | } 101 | 102 | void midioutDoAction(byte m, byte v) 103 | { 104 | if(m < 4) { 105 | //note message 106 | if(v) { 107 | checkStopNote(m); 108 | playNote(m,v); 109 | } else if (midiOutLastNote[m]>=0) { 110 | stopNote(m); 111 | } 112 | } else if (m < 8) { 113 | m-=4; 114 | //cc message 115 | playCC(m,v); 116 | } else if(m < 0x0C) { 117 | m-=8; 118 | playPC(m,v); 119 | } 120 | blinkLight(0x90+m,v); 121 | } 122 | 123 | void checkStopNote(byte m) 124 | { 125 | if((midioutNoteTimer[m]+midioutNoteTimerThreshold) < millis()) { 126 | stopNote(m); 127 | } 128 | } 129 | 130 | void stopNote(byte m) 131 | { 132 | for(int x=0;xwrite(midiData,3); 137 | #ifdef USE_TEENSY 138 | usbMIDI.sendNoteOff(midioutNoteHold[m][x], 0, memory[MEM_MIDIOUT_NOTE_CH+m]+1); 139 | #endif 140 | #ifdef USE_LEONARDO 141 | midiEventPacket_t packet = { 0x08, 0x80 | memory[MEM_MIDIOUT_NOTE_CH + m], midioutNoteHold[m][x], 0 }; 142 | MidiUSB.sendMIDI(packet); 143 | MidiUSB.flush(); 144 | #endif 145 | } 146 | midiOutLastNote[m] = -1; 147 | midioutNoteHoldCounter[m] = 0; 148 | } 149 | 150 | void playNote(byte m, byte n) 151 | { 152 | midiData[0] = (0x90 + (memory[MEM_MIDIOUT_NOTE_CH+m])); 153 | midiData[1] = n; 154 | midiData[2] = 0x7F; 155 | serial->write(midiData,3); 156 | #ifdef USE_TEENSY 157 | usbMIDI.sendNoteOn(n, 127, memory[MEM_MIDIOUT_NOTE_CH+m]+1); 158 | #endif 159 | #ifdef USE_LEONARDO 160 | midiEventPacket_t packet = { 0x09, 0x90 | memory[MEM_MIDIOUT_NOTE_CH + m], n, 127 }; 161 | MidiUSB.sendMIDI(packet); 162 | MidiUSB.flush(); 163 | #endif 164 | 165 | midioutNoteHold[m][midioutNoteHoldCounter[m]] =n; 166 | midioutNoteHoldCounter[m]++; 167 | midioutNoteTimer[m] = millis(); 168 | midiOutLastNote[m] =n; 169 | } 170 | 171 | void playCC(byte m, byte n) 172 | { 173 | byte v = n; 174 | 175 | if(memory[MEM_MIDIOUT_CC_MODE+m]) { 176 | if(memory[MEM_MIDIOUT_CC_SCALING+m]) { 177 | v = (v & 0x0F)*8; 178 | //if(v) v --; 179 | } 180 | n=(m*7)+((n>>4) & 0x07); 181 | midiData[0] = (0xB0 + (memory[MEM_MIDIOUT_CC_CH+m])); 182 | midiData[1] = (memory[MEM_MIDIOUT_CC_NUMBERS+n]); 183 | midiData[2] = v; 184 | serial->write(midiData,3); 185 | #ifdef USE_TEENSY 186 | usbMIDI.sendControlChange((memory[MEM_MIDIOUT_CC_NUMBERS+n]), v, memory[MEM_MIDIOUT_NOTE_CH+m]+1); 187 | #endif 188 | #ifdef USE_LEONARDO 189 | midiEventPacket_t packet = {0x0B, 0xB0 | (memory[MEM_MIDIOUT_NOTE_CH + m]+1), (memory[MEM_MIDIOUT_CC_NUMBERS + n]), v}; 190 | MidiUSB.sendMIDI(packet); 191 | MidiUSB.flush(); 192 | #endif 193 | } else { 194 | if(memory[MEM_MIDIOUT_CC_SCALING+m]) { 195 | float s; 196 | s = n; 197 | v = ((s / 0x6f) * 0x7f); 198 | } 199 | n=(m*7); 200 | midiData[0] = (0xB0 + (memory[MEM_MIDIOUT_CC_CH+m])); 201 | midiData[1] = (memory[MEM_MIDIOUT_CC_NUMBERS+n]); 202 | midiData[2] = v; 203 | serial->write(midiData,3); 204 | #ifdef USE_TEENSY 205 | usbMIDI.sendControlChange((memory[MEM_MIDIOUT_CC_NUMBERS+n]), v, memory[MEM_MIDIOUT_NOTE_CH+m]+1); 206 | #endif 207 | #ifdef USE_LEONARDO 208 | midiEventPacket_t packet = {0x0B, 0xB0 | (memory[MEM_MIDIOUT_NOTE_CH + m]+1), (memory[MEM_MIDIOUT_CC_NUMBERS + n]), v}; 209 | MidiUSB.sendMIDI(packet); 210 | MidiUSB.flush(); 211 | #endif 212 | } 213 | } 214 | 215 | void playPC(byte m, byte n) 216 | { 217 | midiData[0] = (0xC0 + (memory[MEM_MIDIOUT_NOTE_CH+m])); 218 | midiData[1] = n; 219 | serial->write(midiData,2); 220 | #ifdef USE_TEENSY 221 | usbMIDI.sendProgramChange(n, memory[MEM_MIDIOUT_NOTE_CH+m]+1); 222 | #endif 223 | #ifdef USE_LEONARDO 224 | midiEventPacket_t packet = {0x0C, 0xC0 | (memory[MEM_MIDIOUT_NOTE_CH + m]+1), n}; 225 | MidiUSB.sendMIDI(packet); 226 | MidiUSB.flush(); 227 | #endif 228 | } 229 | 230 | void stopAllNotes() 231 | { 232 | for(int m=0;m<4;m++) { 233 | if(midiOutLastNote[m]>=0) { 234 | stopNote(m); 235 | } 236 | midiData[0] = (0xB0 + (memory[MEM_MIDIOUT_NOTE_CH+m])); 237 | midiData[1] = 123; 238 | midiData[2] = 0x7F; 239 | serial->write(midiData,3); //Send midi 240 | #ifdef USE_TEENSY 241 | usbMIDI.sendControlChange(123, 127, memory[MEM_MIDIOUT_NOTE_CH+m]+1); 242 | #endif 243 | #ifdef USE_LEONARDO 244 | midiEventPacket_t packet = {0x0B, 0xB0 | memory[MEM_MIDIOUT_NOTE_CH + m], 123, 127}; 245 | MidiUSB.sendMIDI(packet); 246 | MidiUSB.flush(); 247 | #endif 248 | } 249 | } 250 | 251 | boolean getIncommingSlaveByte() 252 | { 253 | delayMicroseconds(midioutBitDelay); 254 | GB_SET(0,0,0); 255 | delayMicroseconds(midioutBitDelay); 256 | GB_SET(1,0,0); 257 | delayMicroseconds(2); 258 | if(digitalRead(pinGBSerialIn)) { 259 | incomingMidiByte = 0; 260 | for(countClockPause=0;countClockPause!=7;countClockPause++) { 261 | GB_SET(0,0,0); 262 | delayMicroseconds(2); 263 | GB_SET(1,0,0); 264 | incomingMidiByte = (incomingMidiByte << 1) + digitalRead(pinGBSerialIn); 265 | } 266 | return true; 267 | } 268 | return false; 269 | } 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduinoboy 2 | Official ArduinoBoy Repository for serial MIDI communication to the Nintendo Gameboy. 3 | 4 | ![ScreenShot](http://trash80.net/arduinoboy/aboy1_2_0.jpg) 5 | 6 | ## About 7 | Arduinoboy is software for the [Arduino hardware platform](http://arduino.cc) that allows serial communication (MIDI) to the Nintendo Gameboy for music applications such as [LittleSoundDJ](http://littlesounddj.com), [Nanoloop.](http://www.nanoloop.com/), and [mGB](https://github.com/trash80/mGB) 8 | 9 | 10 | ## Current Features 11 | * Affordable and easily accessible parts for assembly. 12 | * Accurate MIDI Sync, Start and Stop commands. 13 | * Push Button selector sets the sync/state modes [(7 modes available)](#modes-details) 14 | * [mGB](https://github.com/trash80/mGB) 15 | Mode: Full MIDI in support across all Gameboy Channels, including a unique "poly" mode allows you to play your Game Boy like a synthesizer. 16 | * Midi Out Doubles as a Midi Thru 17 | * "Filtering" data for only sync messages, no dedicated MIDI line required. 18 | * Can be powered by the Game Boy's gamelink port. 19 | * USB upgradeable via Arduino. 20 | * Midi settings configurable using a Mac/PC editor built in Max. 21 | * Tested and works with DMG (Original), Gameboy Color, and Advance/SP. 22 | 23 | ## Modes Details 24 | #### Mode 1 - LSDJ as MIDI Slave Sync 25 | Slave your Game Boy running [LittleSoundDJ](http://littlesounddj.com) to your midi sequencer or Digital audio workstation. 26 | 27 | You can send the arduinoboy midi notes to change sync resolution and start/stop the LSDJ sequencer. 28 | 29 | _LSDJ Slave Mode Midi Note Effects:_ 30 | 31 | * 48 - `C-2` Sends a Sequencer Start Command 32 | * 49 - `C#2` Sends a Sequencer Stop Command 33 | * 50 - `D-2` Toggles Normal Tempo 34 | * 51 - `D#2` Toggles 1/2 Tempo 35 | * 52 - `E-2` Toggles 1/4 Tempo 36 | * 53 - `F-2` Toggles 1/8 Tempo 37 | 38 | Higher note values than these map LSDJ song position row offset on a Song Start. 39 | 40 | In LSDJ the `sync` mode should be set to `Slave` 41 | 42 | #### Mode 2 LSDJ as MIDI Master Sync. 43 | 44 | Send Midi sync with LSDJ as a midi clock master, LSDJ also sends a Midi Note on message that corresponds to the song row number on play. 45 | 46 | In LSDJ the `sync` mode should be set to `Master` 47 | 48 | 49 | #### Mode 3 LSDJ PC Keyboard mode. 50 | This mode emulates the [PC Keyboard Mode](http://littlesounddj.wikia.com/wiki/PC_Keyboard_Interface) built into LSDJ, allowing you to control the following aspects of LSDJ: 51 | 52 | Features: 53 | 54 | * The first octave controls M-U-T-E, 55 | * Cursor control (LSDJ Live mode only), 56 | * Table selection 57 | * Table cue. 58 | 59 | _PC Keyboard mode midi note map_ 60 | 61 | * 36 - `C-1` Mute Pu1 Off/On 62 | * 37 - `C#1` Mute Pu2 Off/On 63 | * 38 - `D-1` Mute Wav Off/On 64 | * 39 - `D#1` Mute Noi Off/On 65 | * 40 - `E-1` Livemode Cue Sequence 66 | * 41 - `F-1` Livemode Cursor Up 67 | * 42 - `F#1` Livemode Cursor Down 68 | * 43 - `G-1` Livemode Cursor Left 69 | * 44 - `G#1` Live mode Cursor Right 70 | * 45 - `A-1` Table Up 71 | * 46 - `A#1` Table Down 72 | * 47 - `B-1` Cue Table 73 | * 48+ - Notes from this note up will accept midi in from an external keyboard or sequencer and allow you to play the notes`C-2 to C-8`. 74 | * Midi Program Change messages will select from instrument table 75 | * Default Midi channel is 16. You can set this in the top of the main source file in the archive, or via the [Max Pat editor](#max). 76 | 77 | In LSDJ the `sync` mode should be set to `Keyboard` 78 | 79 | #### Mode 4 MIDI to Nanoloop sync 80 | Sync [Nanoloop.](http://www.nanoloop.com/) to external midi clock signals sent to the midi in. 81 | 82 | In Nanoloop, the sync mode should be set to `slave`. 83 | 84 | #### Mode 5 Full MIDI with mGB 85 | [mGB](https://github.com/trash80/mGB) is a Gameboy cartridge program (You need a Flash Cart and Transfer hardware) That enables the Gameboy to act as a MIDI supported sound module that allows full control of the Game Boy sound hardware. 86 | 87 | It works with the old DMG Gameboy as well as GBC/GBA. 88 | 89 | #### Mode 6 LSDJ MIDIMAP 90 | Lsdj will sync to incoming MIDI sync, and incoming MIDI notes are mapped to LSDJ's song row #. The currently selected row's MIDI note is displayed on the top right of the LSDJ screen, and incoming MIDI notes will also display the relative song row number in the same location. 91 | 92 | In LSDJ the `sync` mode should be set to `MI.MAP`. 93 | 94 | *This requires a special version of LSDJ, which can be found in your account on the [LSDJ website](http://littlesounddj.com/lsd/latest/full_version/).* 95 | 96 | #### Mode 7 LSDJ MIDIOUT 97 | Each of the 4 gameboy channels send MIDI data on 4 midi channels by the use of effects commands: 98 | 99 | * `Nxx` - Sends a MIDI Note - Absolute to the value placed in the effect. N00 sends note off, `N01`-`N6F` send midi notes 1 to 112. 100 | * `Qxx` - Sends a [MIDI Note](http://www.electronics.dit.ie/staff/tscarff/Music_technology/midi/midi_note_numbers_for_octaves.htm) relative to the current channel's pitch. The effect value is a offset. so `Q0C` in `PU1` would send a note 1 octave higher than what `PU1` is currently playing. This is useful as a table command to track midi notes as normal notes in the sequencer. 101 | * `Xxx` - Sends a MIDI CC - By default in Arduinoboy the high nibble selects a CC#, and the low nibble sends a value `0-F` to `0-127`. This can be changed to allow just 1 midi CC with a range of `00`-`6F`, or 7 CCs with scaled or unscaled values. 102 | * `Yxx` - Sends a program/patch/preset change. 103 | 104 | By default each channel of LSDJ is mapped to midi channels 1-4. For example note commands from PU1 will be sent to midi channel 1. 105 | 106 | In LSDJ the `sync` mode should be set to `Midiout`. 107 | 108 | *This requires a special version of LSDJ, which can be found in your account on the [LSDJ website](http://littlesounddj.com/lsd/latest/full_version/).* 109 | 110 | 111 | ## Max Editor 112 | ![Editor gui](Editor/editor.png) 113 | [The Arduinoboy Editor for Max](https://github.com/trash80/Arduinoboy/tree/master/Editor) for PC/OSX machines is a gui editor that allows you to edit the various global midi settings of your arduinoboy without editing or flashing code, over midi. It used to required the now deprecated Max Runtime, but you can also run it using a demo of [Cycling '74's Max application](https://cycling74.com/downloads/) 114 | 115 | ### Maxpat Settings 116 | * `Midi In/Out` 117 | Connect your arduinoboy to these ports on your system. Once it has connected, all the lights on your arduinoboy should flash in order, and the editor will show a green `Connected`. 118 | * `Mode` 119 | Setting this will tell your arduinoboy what mode to boot into automatically. This is handy if you built your own arduinoboy and decided you wanted to skimp out on LEDs and a button. 120 | * `LSDJ Slave Mode settings` - The midi channel LSDJ slave mode will receive its commands on. 121 | * `LSDJ Master Mode settings` - The midi channel LSDJ Master mode will send its midi notes mapped to row number on. 122 | * `Keyboard Mode settings` - What channel LSDJ will look for its keyboard mode midi commands. No idea what compatibility mode does. 123 | * `mGB midi settings` - Map each incoming midi channel to a specific Gameboy channel in mGB. 124 | * `LSDJ Livesync/Livemap settings` - The midi channel Livesync/Livemap will listen to incoming midi commands from. 125 | * `LSDJ Midiout settings` - Here you can set the following: 126 | * `Note midi channel` - The channel each LSDJ channel will send it's midi note commands on. 127 | * `CC midi channel` - The channel each LSDJ channel will send it's Continuous Controller commands on. 128 | * `CC 0` - The initial CC each channel will send. The type of data it will send is based on the next setting. 129 | * `CC Mode` - Game Boys have limitations! You can either have arduinoboy send one CC with many values, or 7 with limited 8bit values. By default in Arduinoboy the high nibble selects a CC#, and the low nibble sends a value [0-F] to [0-127]. This can be changed to allow just 1 midi CC with a range of 00-6F, or 7 CCs with scaled or unscaled values. 130 | * `CC Scaling ` - Set wether the 7 CCs are scaled or unscaled. 131 | 132 | 133 | ## Future Features & wishlist 134 | * Build instructions, and a Arduino Shield 135 | 136 | ## How To build an Arduinoboy 137 | ![ScreenShot](http://farm3.static.flickr.com/2229/2316803721_c22f9c2387.jpg) 138 | ![ScreenShot](http://trash80.net/arduinoboy/arduinoboy_schematic_1_1_0.png) 139 | 140 | * [Build Photos](http://flickr.com/photos/trash80/2316803175/in/set-72157604068871573/) 141 | * [Old version (Pre 1.1.0)](http://trash80.net/junkfood/arduinoboy/arduinoboy-schem-v.0.9.8-r1.png) 142 | 143 | ### Video Demos 144 | 145 | * [Keyboard Mode Test](http://youtube.com/watch?v=TnLUuvc78XY) 146 | * [Sync Demos](http://youtube.com/watch?v=iVmhy-Lo7BI) 147 | * [Arduino inside of Gameboy DMG](http://youtube.com/watch?v=VwrMuOA0VnY) 148 | * [mGB Example & Arduinoboy build into a DMG](http://vimeo.com/1853931) 149 | * [PDF explores mGB with a MIDI guitar](http://www.youtube.com/watch?v=HAU9MzZ2qeE) 150 | 151 | ## Thanks To 152 | * [Arduino](http://arduino.cc) 153 | * [Nitro2k01](http://blog.gg8.se/wordpress/) for ASM help with mGB 154 | * [GWEM](http://www.preromanbritain.com/gwem/lsdj_midi/g33k.html) g33k page 155 | * [Midines](http://wayfar.net) Thanks for the help x|k! 156 | * [firestARTer](http://www.firestarter-music.de) help with keyboard & Midi handling information. 157 | * [Gijs Gieskes](http://gieskes.nl) Found source code that gave insight into Nanoloop sync 158 | * [Little Sound DJ](http://littlesounddj.com) 159 | * [http://devrs.com/gb](http://devrs.com/gb) Madcatz PC link port for gb serial specs 160 | * [http://chipmusic.org](http://chipmusic.org) For all things Chipmusic. 161 | 162 | -------------------------------------------------------------------------------- /Arduinoboy/Mode_MidiGb.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * Name: Timothy Lamb * 3 | * Email: trash80@gmail.com * 4 | ***************************************************************************/ 5 | /*************************************************************************** 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation; either version 2 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | ***************************************************************************/ 13 | 14 | void modeMidiGbSetup() 15 | { 16 | digitalWrite(pinStatusLed,LOW); 17 | pinMode(pinGBClock,OUTPUT); 18 | digitalWrite(pinGBClock,HIGH); 19 | 20 | #ifdef USE_TEENSY 21 | usbMIDI.setHandleRealTimeSystem(NULL); 22 | #endif 23 | 24 | blinkMaxCount=1000; 25 | modeMidiGb(); 26 | } 27 | 28 | void modeMidiGb() 29 | { 30 | boolean sendByte = false; 31 | while(1){ //Loop foreverrrr 32 | modeMidiGbUsbMidiReceive(); 33 | 34 | if (serial->available()) { //If MIDI is sending 35 | incomingMidiByte = serial->read(); //Get the byte sent from MIDI 36 | 37 | if(!checkForProgrammerSysex(incomingMidiByte) && !usbMode) serial->write(incomingMidiByte); //Echo the Byte to MIDI Output 38 | 39 | if(incomingMidiByte & 0x80) { 40 | switch (incomingMidiByte & 0xF0) { 41 | case 0xF0: 42 | midiValueMode = false; 43 | break; 44 | default: 45 | sendByte = false; 46 | midiStatusChannel = incomingMidiByte&0x0F; 47 | midiStatusType = incomingMidiByte&0xF0; 48 | if(midiStatusChannel == memory[MEM_MGB_CH]) { 49 | midiData[0] = midiStatusType; 50 | sendByte = true; 51 | } else if (midiStatusChannel == memory[MEM_MGB_CH+1]) { 52 | midiData[0] = midiStatusType+1; 53 | sendByte = true; 54 | } else if (midiStatusChannel == memory[MEM_MGB_CH+2]) { 55 | midiData[0] = midiStatusType+2; 56 | sendByte = true; 57 | } else if (midiStatusChannel == memory[MEM_MGB_CH+3]) { 58 | midiData[0] = midiStatusType+3; 59 | sendByte = true; 60 | } else if (midiStatusChannel == memory[MEM_MGB_CH+4]) { 61 | midiData[0] = midiStatusType+4; 62 | sendByte = true; 63 | } else { 64 | midiValueMode =false; 65 | midiAddressMode=false; 66 | } 67 | if(sendByte) { 68 | statusLedOn(); 69 | sendByteToGameboy(midiData[0]); 70 | delayMicroseconds(GB_MIDI_DELAY); 71 | midiValueMode =false; 72 | midiAddressMode=true; 73 | } 74 | break; 75 | } 76 | } else if (midiAddressMode){ 77 | midiAddressMode = false; 78 | midiValueMode = true; 79 | midiData[1] = incomingMidiByte; 80 | sendByteToGameboy(midiData[1]); 81 | delayMicroseconds(GB_MIDI_DELAY); 82 | } else if (midiValueMode) { 83 | midiData[2] = incomingMidiByte; 84 | midiAddressMode = true; 85 | midiValueMode = false; 86 | 87 | sendByteToGameboy(midiData[2]); 88 | delayMicroseconds(GB_MIDI_DELAY); 89 | statusLedOn(); 90 | blinkLight(midiData[0],midiData[2]); 91 | } 92 | } else { 93 | setMode(); // Check if mode button was depressed 94 | updateBlinkLights(); 95 | updateStatusLed(); 96 | } 97 | } 98 | } 99 | 100 | /* 101 | sendByteToGameboy does what it says. yay magic 102 | */ 103 | void sendByteToGameboy(byte send_byte) 104 | { 105 | for(countLSDJTicks=0;countLSDJTicks!=8;countLSDJTicks++) { //we are going to send 8 bits, so do a loop 8 times 106 | if(send_byte & 0x80) { 107 | GB_SET(0,1,0); 108 | GB_SET(1,1,0); 109 | } else { 110 | GB_SET(0,0,0); 111 | GB_SET(1,0,0); 112 | } 113 | 114 | #if defined (F_CPU) && (F_CPU > 24000000) 115 | // Delays for Teensy etc where CPU speed might be clocked too fast for cable & shift register on gameboy. 116 | delayMicroseconds(1); 117 | #endif 118 | send_byte <<= 1; 119 | } 120 | } 121 | 122 | void modeMidiGbUsbMidiReceive() 123 | { 124 | #ifdef USE_TEENSY 125 | 126 | while(usbMIDI.read()) { 127 | uint8_t ch = usbMIDI.getChannel() - 1; 128 | boolean send = false; 129 | if(ch == memory[MEM_MGB_CH]) { 130 | ch = 0; 131 | send = true; 132 | } else if (ch == memory[MEM_MGB_CH+1]) { 133 | ch = 1; 134 | send = true; 135 | } else if (ch == memory[MEM_MGB_CH+2]) { 136 | ch = 2; 137 | send = true; 138 | } else if (ch == memory[MEM_MGB_CH+3]) { 139 | ch = 3; 140 | send = true; 141 | } else if (ch == memory[MEM_MGB_CH+4]) { 142 | ch = 4; 143 | send = true; 144 | } 145 | if(!send) return; 146 | uint8_t s; 147 | switch(usbMIDI.getType()) { 148 | case 0x80: // note off 149 | case 0x90: // note on 150 | s = 0x90 + ch; 151 | if(usbMIDI.getType() == 0x80) { 152 | s = 0x80 + ch; 153 | } 154 | sendByteToGameboy(s); 155 | delayMicroseconds(GB_MIDI_DELAY); 156 | sendByteToGameboy(usbMIDI.getData1()); 157 | delayMicroseconds(GB_MIDI_DELAY); 158 | sendByteToGameboy(usbMIDI.getData2()); 159 | delayMicroseconds(GB_MIDI_DELAY); 160 | blinkLight(s, usbMIDI.getData2()); 161 | break; 162 | case 0xB0: // CC 163 | sendByteToGameboy(0xB0+ch); 164 | delayMicroseconds(GB_MIDI_DELAY); 165 | sendByteToGameboy(usbMIDI.getData1()); 166 | delayMicroseconds(GB_MIDI_DELAY); 167 | sendByteToGameboy(usbMIDI.getData2()); 168 | delayMicroseconds(GB_MIDI_DELAY); 169 | blinkLight(0xB0+ch, usbMIDI.getData2()); 170 | break; 171 | case 0xC0: // PG 172 | sendByteToGameboy(0xC0+ch); 173 | delayMicroseconds(GB_MIDI_DELAY); 174 | sendByteToGameboy(usbMIDI.getData1()); 175 | delayMicroseconds(GB_MIDI_DELAY); 176 | blinkLight(0xC0+ch, usbMIDI.getData2()); 177 | break; 178 | case 0xE0: // PB 179 | sendByteToGameboy(0xE0+ch); 180 | delayMicroseconds(GB_MIDI_DELAY); 181 | sendByteToGameboy(usbMIDI.getData1()); 182 | delayMicroseconds(GB_MIDI_DELAY); 183 | sendByteToGameboy(usbMIDI.getData2()); 184 | delayMicroseconds(GB_MIDI_DELAY); 185 | break; 186 | } 187 | 188 | statusLedOn(); 189 | } 190 | #endif 191 | 192 | #ifdef USE_LEONARDO 193 | 194 | midiEventPacket_t rx; 195 | do 196 | { 197 | rx = MidiUSB.read(); 198 | uint8_t ch = rx.byte1 & 0x0F; 199 | boolean send = false; 200 | if(ch == memory[MEM_MGB_CH]) { 201 | ch = 0; 202 | send = true; 203 | } else if (ch == memory[MEM_MGB_CH+1]) { 204 | ch = 1; 205 | send = true; 206 | } else if (ch == memory[MEM_MGB_CH+2]) { 207 | ch = 2; 208 | send = true; 209 | } else if (ch == memory[MEM_MGB_CH+3]) { 210 | ch = 3; 211 | send = true; 212 | } else if (ch == memory[MEM_MGB_CH+4]) { 213 | ch = 4; 214 | send = true; 215 | } 216 | if (!send) return; 217 | uint8_t s; 218 | switch (rx.header) 219 | { 220 | case 0x08: // note off 221 | case 0x09: // note on 222 | s = 0x90 + ch; 223 | if (rx.header == 0x08) 224 | { 225 | s = 0x80 + ch; 226 | } 227 | sendByteToGameboy(s); 228 | delayMicroseconds(GB_MIDI_DELAY); 229 | sendByteToGameboy(rx.byte2); 230 | delayMicroseconds(GB_MIDI_DELAY); 231 | sendByteToGameboy(rx.byte3); 232 | delayMicroseconds(GB_MIDI_DELAY); 233 | blinkLight(s, rx.byte2); 234 | break; 235 | case 0x0B: // CC 236 | sendByteToGameboy(0xB0 + ch); 237 | delayMicroseconds(GB_MIDI_DELAY); 238 | sendByteToGameboy(rx.byte2); 239 | delayMicroseconds(GB_MIDI_DELAY); 240 | sendByteToGameboy(rx.byte3); 241 | delayMicroseconds(GB_MIDI_DELAY); 242 | blinkLight(0xB0 + ch, rx.byte2); 243 | break; 244 | case 0x0C: // PG 245 | sendByteToGameboy(0xC0 + ch); 246 | delayMicroseconds(GB_MIDI_DELAY); 247 | sendByteToGameboy(rx.byte2); 248 | delayMicroseconds(GB_MIDI_DELAY); 249 | blinkLight(0xC0 + ch, rx.byte2); 250 | break; 251 | case 0x0E: // PB 252 | sendByteToGameboy(0xE0 + ch); 253 | delayMicroseconds(GB_MIDI_DELAY); 254 | sendByteToGameboy(rx.byte2); 255 | delayMicroseconds(GB_MIDI_DELAY); 256 | sendByteToGameboy(rx.byte3); 257 | delayMicroseconds(GB_MIDI_DELAY); 258 | break; 259 | default: 260 | return; 261 | } 262 | 263 | statusLedOn(); 264 | } while (rx.header != 0); 265 | #endif 266 | } 267 | -------------------------------------------------------------------------------- /Arduinoboy/Led_Functions.ino: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | showSelectedMode1 turns off the last mode led, turns on the new mode led 4 | and delays for a period of time to reduce jitter behavior from the mode 5 | changing too fast. 6 | */ 7 | void showSelectedMode() 8 | { 9 | digitalWrite(pinStatusLed,LOW); 10 | 11 | for(int m=0;m<3;m++) { 12 | switch(memory[MEM_MODE]) { 13 | case 0: 14 | case 1: 15 | case 2: 16 | case 3: 17 | case 4: 18 | digitalWrite(pinLeds[memory[MEM_MODE]],HIGH); 19 | break; 20 | case 5: 21 | digitalWrite(pinStatusLed,HIGH); 22 | digitalWrite(pinLeds[0],HIGH); 23 | digitalWrite(pinLeds[1],HIGH); 24 | break; 25 | case 6: 26 | digitalWrite(pinLeds[0],HIGH); 27 | digitalWrite(pinLeds[1],HIGH); 28 | digitalWrite(pinLeds[2],HIGH); 29 | digitalWrite(pinLeds[3],HIGH); 30 | digitalWrite(pinLeds[4],HIGH); 31 | digitalWrite(pinLeds[5],HIGH); 32 | break; 33 | 34 | } 35 | delay(100); 36 | digitalWrite(pinLeds[0],LOW); 37 | digitalWrite(pinLeds[1],LOW); 38 | digitalWrite(pinLeds[2],LOW); 39 | digitalWrite(pinLeds[3],LOW); 40 | digitalWrite(pinLeds[4],LOW); 41 | digitalWrite(pinLeds[5],LOW); 42 | delay(100); 43 | } 44 | lastMode = memory[MEM_MODE]; 45 | delay(300); 46 | } 47 | 48 | void updateVisualSync() 49 | { 50 | if(!countSyncTime) { 51 | if(!blinkSwitch[5]) digitalWrite(pinStatusLed,HIGH); 52 | digitalWrite(pinLeds[0],LOW); 53 | digitalWrite(pinLeds[1],LOW); 54 | digitalWrite(pinLeds[2],LOW); 55 | digitalWrite(pinLeds[3],LOW); 56 | digitalWrite(pinLeds[switchLight],HIGH); 57 | blinkSwitch[5]=1; 58 | blinkSwitchTime[5]=0; 59 | countSyncLightTime = 0; 60 | switchLight++; 61 | if(switchLight==4) switchLight=0; 62 | } 63 | countSyncTime++; 64 | if(countSyncTime == 24) countSyncTime=0; 65 | } 66 | 67 | 68 | 69 | void updateBlinkLights() 70 | { 71 | updateBlinkLight(0); 72 | updateBlinkLight(1); 73 | updateBlinkLight(2); 74 | updateBlinkLight(3); 75 | updateBlinkLight(4); 76 | updateBlinkLight(5); 77 | } 78 | 79 | void updateBlinkLight(uint8_t light) 80 | { 81 | if(blinkSwitch[light]) { 82 | blinkSwitchTime[light]++; 83 | if(blinkSwitchTime[light] == blinkMaxCount) { 84 | blinkSwitch[light]=0; 85 | blinkSwitchTime[light]=0; 86 | digitalWrite(pinLeds[light],LOW); 87 | } 88 | } 89 | } 90 | 91 | void updateStatusLight() 92 | { 93 | if(blinkSwitch[5]) { 94 | blinkSwitchTime[5]++; 95 | if(blinkSwitchTime[5] == blinkMaxCount) { 96 | blinkSwitch[5]=0; 97 | blinkSwitchTime[5]=0; 98 | digitalWrite(pinStatusLed,LOW); 99 | } 100 | } 101 | } 102 | 103 | void blinkLight(byte midiMessage, byte midiValue) 104 | { 105 | if(midiValue) { 106 | switch(midiMessage) { 107 | case 0x90: 108 | if(!blinkSwitch[0]) digitalWrite(pinLeds[0],HIGH); 109 | blinkSwitch[0]=1; 110 | blinkSwitchTime[0]=0; 111 | break; 112 | case 0x95: 113 | if(!blinkSwitch[0]) digitalWrite(pinLeds[0],HIGH); 114 | blinkSwitch[0]=1; 115 | blinkSwitchTime[0]=0; 116 | break; 117 | case 0x9A: 118 | if(!blinkSwitch[0]) digitalWrite(pinLeds[0],HIGH); 119 | blinkSwitch[0]=1; 120 | blinkSwitchTime[0]=0; 121 | break; 122 | case 0x91: 123 | if(!blinkSwitch[1]) digitalWrite(pinLeds[1],HIGH); 124 | blinkSwitch[1]=1; 125 | blinkSwitchTime[1]=0; 126 | break; 127 | case 0x96: 128 | if(!blinkSwitch[1]) digitalWrite(pinLeds[1],HIGH); 129 | blinkSwitch[1]=1; 130 | blinkSwitchTime[1]=0; 131 | break; 132 | case 0x9B: 133 | if(!blinkSwitch[1]) digitalWrite(pinLeds[1],HIGH); 134 | blinkSwitch[1]=1; 135 | blinkSwitchTime[1]=0; 136 | break; 137 | case 0x92: 138 | if(!blinkSwitch[2]) digitalWrite(pinLeds[2],HIGH); 139 | blinkSwitch[2]=1; 140 | blinkSwitchTime[2]=0; 141 | break; 142 | case 0x97: 143 | if(!blinkSwitch[2]) digitalWrite(pinLeds[2],HIGH); 144 | blinkSwitch[2]=1; 145 | blinkSwitchTime[2]=0; 146 | break; 147 | case 0x9C: 148 | if(!blinkSwitch[2]) digitalWrite(pinLeds[2],HIGH); 149 | blinkSwitch[2]=1; 150 | blinkSwitchTime[2]=0; 151 | break; 152 | case 0x93: 153 | if(!blinkSwitch[3]) digitalWrite(pinLeds[3],HIGH); 154 | blinkSwitch[3]=1; 155 | blinkSwitchTime[3]=0; 156 | break; 157 | case 0x98: 158 | if(!blinkSwitch[3]) digitalWrite(pinLeds[3],HIGH); 159 | blinkSwitch[3]=1; 160 | blinkSwitchTime[3]=0; 161 | break; 162 | case 0x9D: 163 | if(!blinkSwitch[3]) digitalWrite(pinLeds[3],HIGH); 164 | blinkSwitch[3]=1; 165 | blinkSwitchTime[3]=0; 166 | break; 167 | case 0x94: 168 | if(!blinkSwitch[0]) digitalWrite(pinLeds[0],HIGH); 169 | blinkSwitch[0]=1; 170 | blinkSwitchTime[0]=0; 171 | if(!blinkSwitch[1]) digitalWrite(pinLeds[1],HIGH); 172 | blinkSwitch[1]=1; 173 | blinkSwitchTime[1]=0; 174 | if(!blinkSwitch[2]) digitalWrite(pinLeds[2],HIGH); 175 | blinkSwitch[2]=1; 176 | blinkSwitchTime[2]=0; 177 | break; 178 | case 0x99: 179 | if(!blinkSwitch[0]) digitalWrite(pinLeds[0],HIGH); 180 | blinkSwitch[0]=1; 181 | blinkSwitchTime[0]=0; 182 | if(!blinkSwitch[1]) digitalWrite(pinLeds[1],HIGH); 183 | blinkSwitch[1]=1; 184 | blinkSwitchTime[1]=0; 185 | if(!blinkSwitch[2]) digitalWrite(pinLeds[2],HIGH); 186 | blinkSwitch[2]=1; 187 | blinkSwitchTime[2]=0; 188 | break; 189 | case 0x9E: 190 | if(!blinkSwitch[0]) digitalWrite(pinLeds[0],HIGH); 191 | blinkSwitch[0]=1; 192 | blinkSwitchTime[0]=0; 193 | if(!blinkSwitch[1]) digitalWrite(pinLeds[1],HIGH); 194 | blinkSwitch[1]=1; 195 | blinkSwitchTime[1]=0; 196 | if(!blinkSwitch[2]) digitalWrite(pinLeds[2],HIGH); 197 | blinkSwitch[2]=1; 198 | blinkSwitchTime[2]=0; 199 | break; 200 | } 201 | } 202 | switch(midiMessage) { 203 | case 0xE0: 204 | case 0xE1: 205 | case 0xE2: 206 | case 0xE3: 207 | case 0xE4: 208 | case 0xB0: 209 | case 0xB1: 210 | case 0xB2: 211 | case 0xB3: 212 | case 0xB4: 213 | if(!blinkSwitch[5]) digitalWrite(pinStatusLed,HIGH); 214 | blinkSwitch[5]=1; 215 | blinkSwitchTime[5]=0; 216 | break; 217 | default: 218 | break; 219 | } 220 | } 221 | 222 | void updateProgrammerLeds() 223 | { 224 | if(miscLedTime == miscLedMaxTime) { 225 | if(sysexProgrammingConnected) { 226 | miscLedMaxTime = 400; 227 | blinkSelectedLight(miscLastLed); 228 | miscLastLed++; 229 | if(miscLastLed == 5) miscLastLed = 0; 230 | } else { 231 | blinkSelectedLight(5); 232 | miscLedMaxTime = 3000; 233 | } 234 | miscLedTime=0; 235 | } 236 | miscLedTime++; 237 | updateBlinkLights(); 238 | } 239 | /* 240 | updateStatusLed should be placed inside of the main loop cycle of a mode function. It counts to a 241 | certain number to delay the action of turning off the status led, so the blink is visible to the human eye. ;)> 242 | I guess this could be called the blinking routine. 243 | */ 244 | void updateStatusLed() 245 | { 246 | if(statusLedIsOn) { //is the led on? 247 | countStatusLedOn++; //then increment the counter by 1 248 | if(countStatusLedOn > 3000) { //if the counter is pretty high 249 | countStatusLedOn = 0; //then reset it to zero. 250 | digitalWrite(pinStatusLed,LOW); //and turn off the status led 251 | statusLedIsOn = false; //and set our "is it on?" to false, cause its off now. ;p 252 | 253 | } else if (statusLedBlink && countStatusLedOn == 1) { //someone told me to blink, because i was already on 254 | digitalWrite(pinStatusLed,LOW); //so I'll turn off and turn back on later.. 255 | 256 | } else if (statusLedBlink && countStatusLedOn > 1000) {//Now that I've waited long enough I'll finish my blink. 257 | statusLedBlink = false; //Turn off the issued blink 258 | digitalWrite(pinStatusLed,HIGH); //... and finally turn back on. 259 | } 260 | } 261 | } 262 | 263 | /* 264 | statusLedOn is the function to call when we want the status led to blink for us. 265 | all it does is check if its been already asked to turn on, if it has it will set a flag 266 | to make it blink. Either way it will reset the blink timer and turn on the LED 267 | */ 268 | void statusLedOn() 269 | { 270 | if(statusLedIsOn) { 271 | statusLedBlink = true; //Make it blink even though its already on 272 | } 273 | statusLedIsOn = true; //This is the flag the updator function looks for to know if its ok to increment the timer and wait to turn off the led 274 | countStatusLedOn = 0; //Reset the timer 275 | digitalWrite(pinStatusLed,HIGH); //Turn on the led 276 | } 277 | 278 | /* cute startup sequence */ 279 | void startupSequence() 280 | { 281 | int ledFxA; 282 | int ledFxB; 283 | 284 | for(ledFxB=0;ledFxB<2;ledFxB++) { 285 | for(ledFxA=0;ledFxA<6;ledFxA++) { 286 | digitalWrite(pinLeds[ledFxA], HIGH); 287 | delay(25); 288 | digitalWrite(pinLeds[ledFxA], LOW); 289 | } 290 | for(ledFxA=4;ledFxA>=0;ledFxA--) { 291 | digitalWrite(pinLeds[ledFxA], HIGH); 292 | delay(25); 293 | digitalWrite(pinLeds[ledFxA], LOW); 294 | } 295 | } 296 | delay(50); 297 | 298 | for(ledFxA=0;ledFxA<6;ledFxA++) digitalWrite(pinLeds[ledFxA], HIGH); // sets the LED on 299 | delay(100); 300 | for(ledFxA=0;ledFxA<6;ledFxA++) digitalWrite(pinLeds[ledFxA], LOW); // sets the digital pin as output 301 | delay(100); 302 | for(ledFxA=0;ledFxA<6;ledFxA++) digitalWrite(pinLeds[ledFxA], HIGH); // sets the LED on 303 | delay(100); 304 | for(ledFxA=0;ledFxA<6;ledFxA++) digitalWrite(pinLeds[ledFxA], LOW); // sets the digital pin as output 305 | delay(500); 306 | 307 | } 308 | -------------------------------------------------------------------------------- /Arduinoboy/Mode_LSDJ_SlaveSync.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * Name: Timothy Lamb * 3 | * Email: trash80@gmail.com * 4 | ***************************************************************************/ 5 | /*************************************************************************** 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation; either version 2 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | ***************************************************************************/ 13 | 14 | void modeLSDJSlaveSyncSetup() 15 | { 16 | digitalWrite(pinStatusLed,LOW); 17 | pinMode(pinGBClock,OUTPUT); 18 | digitalWrite(pinGBClock,HIGH); 19 | 20 | #ifdef USE_TEENSY 21 | usbMIDI.setHandleRealTimeSystem(usbMidiLSDJSlaveRealtimeMessage); 22 | #endif 23 | 24 | blinkMaxCount=1000; 25 | modeLSDJSlaveSync(); 26 | } 27 | 28 | void modeLSDJSlaveSync() 29 | { 30 | while(1){ //Loop forever 31 | modeLSDJSlaveSyncUsbMidiReceive(); 32 | if (serial->available()) { //If MIDI Byte Availaibleleleiel 33 | incomingMidiByte = serial->read(); //Read it 34 | 35 | if(!checkForProgrammerSysex(incomingMidiByte) && !usbMode) serial->write(incomingMidiByte); //Send it back to the Midi out 36 | 37 | if(incomingMidiByte & 0x80) { //If we have received a MIDI Status Byte 38 | switch (incomingMidiByte) { 39 | case 0xF8: //Case: Clock Message Recieved 40 | if((sequencerStarted && midiSyncEffectsTime && !countSyncTime) //If the seq has started and our sync effect is on and at zero 41 | || (sequencerStarted && !midiSyncEffectsTime)) { //or seq is started and there is no sync effects 42 | if(!countSyncPulse && midiDefaultStartOffset) { //if we received a note for start offset 43 | //sendByteToGameboy(midiDefaultStartOffset); //send the offset 44 | } 45 | sendClockTickToLSDJ(); //send the clock tick 46 | updateVisualSync(); 47 | } 48 | if(midiSyncEffectsTime) { //If sync effects are turned on 49 | countSyncTime++; //increment our tick counter 50 | countSyncTime = countSyncTime % countSyncSteps; //and mod it by the number of steps we want for the effect 51 | } 52 | break; 53 | case 0xFA: // Case: Transport Start Message 54 | case 0xFB: // and Case: Transport Continue Message 55 | sequencerStart(); // Start the sequencer 56 | break; 57 | case 0xFC: // Case: Transport Stop Message 58 | sequencerStop(); // Stop the sequencer 59 | break; 60 | default: 61 | if(incomingMidiByte == (0x90+memory[MEM_LSDJSLAVE_MIDI_CH])) { //if a midi note was received and its on the channel of the sync effects channel 62 | midiNoteOnMode = true; //turn on note capture 63 | midiData[0] = false; //and reset the captured note 64 | } else { 65 | midiNoteOnMode = false; //turn off note capture 66 | } 67 | } 68 | } else if(midiNoteOnMode) { //if we've received a message thats not a status and our note capture mode is true 69 | if(!midiData[0]) { //if there is no note number yet 70 | midiData[0] = incomingMidiByte; //then assume the byte is a note and assign it to a place holder 71 | } else { //else assumed velocity 72 | if(incomingMidiByte > 0x00) { 73 | getSlaveSyncEffect(midiData[0]); //then call our sync effects function 74 | } 75 | midiData[0] = false; //and reset the captured note 76 | } 77 | } 78 | } 79 | setMode(); //Check if the mode button was depressed 80 | updateStatusLight(); 81 | } 82 | } 83 | 84 | /* 85 | sendClockTickToLSDJ is a lovely loving simple function I wish they where all this short 86 | Technicallyly we are sending nothing but a 8bit clock pulse 87 | */ 88 | void sendClockTickToLSDJ() 89 | { 90 | for(countLSDJTicks=0;countLSDJTicks<8;countLSDJTicks++) { 91 | GB_SET(0,0,0); 92 | GB_SET(1,0,0); 93 | } 94 | } 95 | 96 | 97 | /* 98 | getSlaveSyncEffect receives a note, and assigns the propper effect of that note 99 | */ 100 | void getSlaveSyncEffect(byte note) 101 | { 102 | switch(note) { 103 | case 48: //C-3ish, Transport Start 104 | sequencerStart(); 105 | break; 106 | case 49: //C#3 Transport Stop 107 | sequencerStop(); 108 | break; 109 | case 50: //D-3 Turn off sync effects 110 | midiSyncEffectsTime = false; 111 | break; 112 | case 51: //D#3 Sync effect, 1/2 time 113 | midiSyncEffectsTime = true; 114 | countSyncTime = 0; 115 | countSyncSteps = 2; 116 | break; 117 | case 52: //E-3 Sync Effect, 1/4 time 118 | midiSyncEffectsTime = true; 119 | countSyncTime = 0; 120 | countSyncSteps = 4; 121 | break; 122 | case 53: //F-3 Sync Effect, 1/8 time 123 | midiSyncEffectsTime = true; 124 | countSyncTime = 0; 125 | countSyncSteps = 8; 126 | break; 127 | default: //All other notes will make LSDJ Start at the row number thats the same as the note number. 128 | midiDefaultStartOffset = midiData[0]; 129 | break; 130 | } 131 | } 132 | 133 | void usbMidiLSDJSlaveRealtimeMessage(uint8_t message) 134 | { 135 | switch(message) { 136 | case 0xF8: 137 | if((sequencerStarted && midiSyncEffectsTime && !countSyncTime) //If the seq has started and our sync effect is on and at zero 138 | || (sequencerStarted && !midiSyncEffectsTime)) { //or seq is started and there is no sync effects 139 | if(!countSyncPulse && midiDefaultStartOffset) { //if we received a note for start offset 140 | //sendByteToGameboy(midiDefaultStartOffset); //send the offset 141 | } 142 | sendClockTickToLSDJ(); //send the clock tick 143 | updateVisualSync(); 144 | } 145 | if(midiSyncEffectsTime) { //If sync effects are turned on 146 | countSyncTime++; //increment our tick counter 147 | countSyncTime = countSyncTime % countSyncSteps; //and mod it by the number of steps we want for the effect 148 | } 149 | break; 150 | case 0xFA: // Case: Transport Start Message 151 | case 0xFB: // and Case: Transport Continue Message 152 | sequencerStart(); // Start the sequencer 153 | break; 154 | case 0xFC: // Case: Transport Stop Message 155 | sequencerStop(); 156 | break; 157 | } 158 | } 159 | 160 | 161 | void modeLSDJSlaveSyncUsbMidiReceive() 162 | { 163 | #ifdef USE_TEENSY 164 | 165 | while(usbMIDI.read(memory[MEM_LSDJSLAVE_MIDI_CH]+1)) { 166 | switch(usbMIDI.getType()) { 167 | case 0x90: // note on 168 | getSlaveSyncEffect(usbMIDI.getData1()); 169 | break; 170 | /* 171 | case 0: // note on 172 | break; 173 | case 3: // CC 174 | break; 175 | case 4: // PG 176 | break; 177 | case 5: // AT 178 | break; 179 | case 6: // PB 180 | break; 181 | */ 182 | } 183 | } 184 | #endif 185 | #ifdef USE_LEONARDO 186 | 187 | midiEventPacket_t rx; 188 | do 189 | { 190 | rx = MidiUSB.read(); 191 | uint8_t ch = rx.byte1 & 0x0F; 192 | if (ch == memory[MEM_LSDJSLAVE_MIDI_CH] && rx.header == 0x09) 193 | { 194 | getSlaveSyncEffect(rx.byte2); 195 | } 196 | switch (rx.byte1) 197 | { 198 | case 0xF8: 199 | if ((sequencerStarted && midiSyncEffectsTime && !countSyncTime) //If the seq has started and our sync effect is on and at zero 200 | || (sequencerStarted && !midiSyncEffectsTime)) 201 | { //or seq is started and there is no sync effects 202 | if (!countSyncPulse && midiDefaultStartOffset) 203 | { //if we received a note for start offset 204 | //sendByteToGameboy(midiDefaultStartOffset); //send the offset 205 | } 206 | sendClockTickToLSDJ(); //send the clock tick 207 | updateVisualSync(); 208 | } 209 | if (midiSyncEffectsTime) 210 | { //If sync effects are turned on 211 | countSyncTime++; //increment our tick counter 212 | countSyncTime = countSyncTime % countSyncSteps; //and mod it by the number of steps we want for the effect 213 | } 214 | break; 215 | case 0xFA: // Case: Transport Start Message 216 | case 0xFB: // and Case: Transport Continue Message 217 | sequencerStart(); // Start the sequencer 218 | break; 219 | case 0xFC: // Case: Transport Stop Message 220 | sequencerStop(); 221 | break; 222 | } 223 | } while (rx.header != 0); 224 | #endif 225 | } 226 | -------------------------------------------------------------------------------- /Arduinoboy/Mode_LSDJ_Keyboard.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * Name: Timothy Lamb * 3 | * Email: trash80@gmail.com * 4 | ***************************************************************************/ 5 | /*************************************************************************** 6 | * * 7 | * This program is free software; you can redistribute it and/or modify * 8 | * it under the terms of the GNU General Public License as published by * 9 | * the Free Software Foundation; either version 2 of the License, or * 10 | * (at your option) any later version. * 11 | * * 12 | ***************************************************************************/ 13 | 14 | void modeLSDJKeyboardSetup() 15 | { 16 | digitalWrite(pinStatusLed,LOW); 17 | pinMode(pinGBClock,OUTPUT); 18 | digitalWrite(pinGBClock,HIGH); 19 | #ifdef USE_TEENSY 20 | usbMIDI.setHandleRealTimeSystem(NULL); 21 | #endif 22 | blinkMaxCount=1000; 23 | 24 | /* The stuff below makes sure the code is in the same state as LSDJ on reset / restart, mode switched, etc. */ 25 | 26 | for(int rst=0;rst<5;rst++) sendKeyboardByteToGameboy(keyboardOctDn); //Return lsdj to the first octave 27 | for(int rst=0;rst<41;rst++) sendKeyboardByteToGameboy(keyboardInsDn); //Return lsdj to the first instrument 28 | 29 | keyboardCurrentOct = 0; //Set our current octave to 0. 30 | keyboardLastOct = 0; //Set our last octave to 0. 31 | keyboardCurrentIns = 0; //Set out current instrument to 0. 32 | keyboardLastIns = 0; //Set out last used instrument to 0. 33 | 34 | 35 | modeLSDJKeyboard(); //.... And start the fun 36 | } 37 | 38 | 39 | void modeLSDJKeyboard() 40 | { 41 | while(1){ //Loop foreverrrr 42 | modeLSDJKeyboardMidiReceive(); 43 | if (serial->available()) { //If MIDI is sending 44 | incomingMidiByte = serial->read(); //Get the byte sent from MIDI 45 | if(!checkForProgrammerSysex(incomingMidiByte) && !usbMode) serial->write(incomingMidiByte);//Echo the Byte to MIDI Output 46 | 47 | 48 | /*************************************************************************** 49 | * Midi to LSDJ Keyboard Handling * 50 | ***************************************************************************/ 51 | //If the byte is a Status Message 52 | if(incomingMidiByte & 0x80) { 53 | /* Status message Information (# = midi channel 0 to F [1-16] ) 54 | 0x8# = Note Off 55 | 0x9# = Note On 56 | 0xA# = AfterTouch (ie, key pressure) 57 | 0xB# = Control Change 58 | 0xC# = Program (patch) change 59 | 0xD# = Channel Pressure 60 | 0xE# = Pitch Wheel 61 | 0xF0 - 0xF7 = System Common Messages 62 | 0xF8 - 0xFF = System Realtime Messages 63 | */ 64 | 65 | //Weee hello world bitwise and. ... make the second hex digit zero so we can have a simple case statement 66 | // - the second digit is usually the midi channel 0 to F (1-16) unless its a 0xF0 message... 67 | switch (incomingMidiByte & 0xF0) { 68 | case 0x90: 69 | //Note-On Status Message (Note: we have to treat this carefully because note status isnt sent on every note-on, damn it) 70 | //There are 3 bytes total we need: Channel, Note, and velocity, these wil be assigned to a array until we have the velocity, 71 | //at that point we can then call our note out function to LSDJ 72 | midiNoteOnMode = true; //Set our stupid "Note on mode" on 73 | midiData[0] = incomingMidiByte; //Assign the byte to the first position of a data array. (this is the midi channel) 74 | midiData[1] = false; //Force the second position to false (this will hold the note number) 75 | break; 76 | case 0xC0: 77 | //Program change message 78 | midiProgramChange = true; //Set our silly "Program Change mode" ... we need to get the next byte later 79 | midiNoteOnMode = false; //Turn Note-on mode off 80 | midiData[0] = incomingMidiByte - 48;//Set the number to a "note on" message so we can use the same "channel" variable as note on messages 81 | break; 82 | case 0xF0: 83 | //Do nothing, these dont interfear with our note-on mode 84 | break; 85 | default: 86 | //Turn Note-On mode off 87 | midiNoteOnMode = false; 88 | break; 89 | } 90 | } else if(midiNoteOnMode) { 91 | //It wasnt a status bit, so lets assume it was a note message if the last status message was note-on. 92 | if(!midiData[1]) { 93 | //If we dont have a note number, we assume this byte is the note number, get it... 94 | midiData[1] = incomingMidiByte; 95 | } else { 96 | //We have our note and channel, so call our note function... 97 | 98 | playLSDJNote(midiData[0], midiData[1], incomingMidiByte); 99 | midiData[1] = false; //Set the note to false, forcing to capture the next note 100 | } 101 | } else if (midiProgramChange) { 102 | changeLSDJInstrument(midiData[0], incomingMidiByte); 103 | midiProgramChange = false; 104 | midiData[0] = false; 105 | } 106 | } 107 | 108 | updateStatusLed(); // Update our status blinker 109 | setMode(); // Check if mode button was depressed 110 | } 111 | } 112 | 113 | 114 | /* 115 | changeLSDJInstrument does what it does via magic (rtfm, realize the fucking magic) 116 | */ 117 | void changeLSDJInstrument(byte channel,byte message) 118 | { 119 | keyboardCurrentIns = message; //set the current instrument number 120 | 121 | if(channel == (0x90+memory[MEM_KEYBD_CH]) && keyboardCurrentIns != keyboardLastIns) { 122 | //if its on our midi channel and the instrument isnt the same as our currrent 123 | if(!memory[MEM_KEYBD_COMPAT_MODE]) { 124 | sendKeyboardByteToGameboy(0x80 | message); // <- this is suppose to work but doesn't :/ 125 | } else { 126 | //We will find out which is greater, the current instrument or the last instrument. then 127 | //cycle up or down to that instrument 128 | if(keyboardCurrentIns > keyboardLastIns) { 129 | keyboardDiff = keyboardCurrentIns - keyboardLastIns; 130 | for(keyboardCount=0;keyboardCount 0x00) { //If midi channel = ours and the velocity is greater then 0 152 | if(note >= keyboardNoteStart) { 153 | keyboardNoteOffset = 0; 154 | note = note - keyboardNoteStart; //subtract the octave offset to get a value ranging from 0 to 48 for comparison 155 | 156 | keyboardCurrentOct = note / 0x0C; //get a octave value from 0 to 4 by deviding the current note by 12 157 | changeLSDJOctave(); 158 | 159 | if(note >= 0x3C) keyboardNoteOffset = 0x0C; //if the note really high we need to use the second row of keyboard keys 160 | note = (note % 12) + keyboardNoteOffset; //get a 0 to 11 range of notes and add the offset 161 | sendKeyboardByteToGameboy(keyboardNotes[note]); // and finally send the note 162 | 163 | } else if (note >= keyboardStartOctave) { //If we are at the octave below notes 164 | keyboardDiff = note - keyboardStartOctave; //Get a value between 0 and 11 165 | if(keyboardDiff < 8 && keyboardDiff > 3) sendKeyboardByteToGameboy(0xE0); //if we are sending cursor values we have to send a 0xE0 byte for "extended" pc keyboard mode 166 | sendKeyboardByteToGameboy(keyboardCommands[note - keyboardStartOctave]); //send the byte corrisponding to the note number in the keyboard command array 167 | } 168 | } 169 | } 170 | 171 | 172 | /* 173 | changeLSDJOctave compares the last octave with the current one and then sends a byte 174 | to shift the octave to match if need be. its pretty much the same as the changeLSDJInstrument function. 175 | */ 176 | void changeLSDJOctave() 177 | { 178 | if(keyboardCurrentOct != keyboardLastOct) { 179 | if(!memory[MEM_KEYBD_COMPAT_MODE]) { // This new mode doesnt work yet. :/ 180 | keyboardCurrentOct = 0xB3 + keyboardCurrentOct; 181 | sendKeyboardByteToGameboy(keyboardCurrentOct); 182 | } else { 183 | ///We will find out which is greater, the current octave or the last. then 184 | //cycle up or down to that octave 185 | if(keyboardCurrentOct > keyboardLastOct) { 186 | keyboardDiff = keyboardCurrentOct - keyboardLastOct; 187 | for(keyboardCount=0;keyboardCount>= 1; //bitshift right once for the next bit we are going to send 226 | } 227 | 228 | GB_SET(0,0,0); 229 | delayMicroseconds(50); 230 | GB_SET(1,0,0); 231 | delayMicroseconds(50); 232 | GB_SET(0,1,0); 233 | delayMicroseconds(50); 234 | GB_SET(1,1,0); 235 | delayMicroseconds(4000); 236 | } 237 | 238 | void modeLSDJKeyboardMidiReceive() 239 | { 240 | #ifdef USE_TEENSY 241 | 242 | while(usbMIDI.read(memory[MEM_KEYBD_CH]+1)) { 243 | switch(usbMIDI.getType()) { 244 | case 0x80: // note off 245 | playLSDJNote(0x90+memory[MEM_KEYBD_CH], usbMIDI.getData1(), 0); 246 | break; 247 | case 0x90: // note on 248 | playLSDJNote(0x90+memory[MEM_KEYBD_CH], usbMIDI.getData1(), usbMIDI.getData2()); 249 | break; 250 | case 0xC0: // PG 251 | changeLSDJInstrument(0xC0+memory[MEM_KEYBD_CH], usbMIDI.getData1()); 252 | break; 253 | /* 254 | case 3: // CC 255 | break; 256 | case 5: // AT 257 | break; 258 | case 6: // PB 259 | break; 260 | */ 261 | } 262 | } 263 | #endif 264 | #ifdef USE_LEONARDO 265 | 266 | midiEventPacket_t rx; 267 | do { 268 | rx = MidiUSB.read(); 269 | switch (rx.header) 270 | { 271 | case 0x08: // note off 272 | playLSDJNote(0x90+memory[MEM_KEYBD_CH], rx.byte2, 0); 273 | statusLedOn(); 274 | break; 275 | case 0x09: // note on 276 | playLSDJNote(0x90+memory[MEM_KEYBD_CH], rx.byte2, rx.byte3); 277 | statusLedOn(); 278 | break; 279 | case 0x0C: // PG 280 | changeLSDJInstrument(0xC0+memory[MEM_KEYBD_CH], rx.byte2); 281 | statusLedOn(); 282 | break; 283 | } 284 | } while (rx.header != 0); 285 | #endif 286 | } 287 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /Arduinoboy/Arduinoboy.ino: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | *************************************************************************** 3 | * __ _ __ * 4 | * ____ __________/ /_ __(_)___ ____ / /_ ____ __ __ * 5 | * / __ `/ ___/ __ / / / / / __ \/ __ \/ __ \/ __ \/ / / / * 6 | * / /_/ / / / /_/ / /_/ / / / / / /_/ / /_/ / /_/ / /_/ / * 7 | * \__,_/_/ \__,_/\__,_/_/_/ /_/\____/_.___/\____/\__, / * 8 | * /____/ * 9 | * * 10 | *************************************************************************** 11 | *************************************************************************** 12 | * * 13 | * Version: 1.3.4 * 14 | * Date: May 19 2020 * 15 | * Name: Timothy Lamb * 16 | * Email: trash80@gmail.com * 17 | * * 18 | *************************************************************************** 19 | *************************************************************************** 20 | * * 21 | * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE * 22 | * * 23 | * https://github.com/trash80/Arduinoboy * 24 | * * 25 | * Arduino pin settings: (Layout is final) * 26 | * - 6 LEDS on pins 8 to 13 * 27 | * - Push button on pin 3 (for selecting mode) * 28 | * - MIDI Opto-isolator power on pin 4 * 29 | * - Gameboy Clock line on analog in pin 0 * 30 | * - Gameboy Serial Data input on analog in pin 1 * 31 | * - Serial Data from gameboy on analog in pin 2 * 32 | * * 33 | * Teensy pin settings: * 34 | * - 6 LEDS on pins 23,22,21,20,4,13 * 35 | * - Push button on pin 2 (for selecting mode) * 36 | * - MIDI Opto-isolator power connected to +3v * 37 | * - Gameboy Clock line on pin 16 * 38 | * - Gameboy Serial Data input on analog in pin 17 * 39 | * - Serial Data from gameboy on analog in pin 18 * 40 | * * 41 | * Teensy USB MIDI is supported * 42 | * Teensy LC should work but untested * 43 | * * 44 | * Program Information: * 45 | * LSDJ Slave Mode Midi Note Effects: * 46 | * 48 - C-2 Sends a Sequencer Start Command * 47 | * 49 - C#2 Sends a Sequencer Stop Command * 48 | * 50 - D-2 Toggles Normal Tempo * 49 | * 51 - D#2 Toggles 1/2 Tempo * 50 | * 52 - E-2 Toggles 1/4 Tempo * 51 | * 53 - F-2 Toggles 1/8 Tempo * 52 | * * 53 | * LSDJ Keyboard Mode: * 54 | * 48 - C-2 Mute Pu1 Off/On * 55 | * 49 - C#2 Mute Pu2 Off/On * 56 | * 50 - D-2 Mute Wav Off/On * 57 | * 51 - D#2 Mute Noi Off/On * 58 | * 52 - E-2 Livemode Cue Sequence * 59 | * 53 - F-2 Livemode Cursor Up * 60 | * 54 - F#2 Livemode Cursor Down * 61 | * 55 - G-2 Livemode Cursor Left * 62 | * 56 - G#2 Livemode Cursor Right * 63 | * 57 - A-2 Table Up * 64 | * 58 - A#2 Table Down * 65 | * 59 - B-2 Cue Table * 66 | * 60 - C-3 to C-8 Notes! * 67 | * Prgram Change to select from instrument table * 68 | * * 69 | ***************************************************************************/ 70 | /*************************************************************************** 71 | * * 72 | * This program is free software; you can redistribute it and/or modify * 73 | * it under the terms of the GNU General Public License as published by * 74 | * the Free Software Foundation; either version 2 of the License, or * 75 | * (at your option) any later version. * 76 | * * 77 | ***************************************************************************/ 78 | #define MEM_MAX 65 79 | #define NUMBER_OF_MODES 7 //Right now there are 7 modes, Might be more in the future 80 | 81 | //!!! do not edit these, they are the position in EEPROM memory that contain the value of each stored setting 82 | #define MEM_CHECK 0 83 | #define MEM_VERSION_FIRST 1 84 | #define MEM_VERSION_SECOND 2 85 | #define MEM_MODE 5 86 | #define MEM_FORCE_MODE 4 87 | 88 | #define MEM_LSDJSLAVE_MIDI_CH 6 89 | 90 | #define MEM_LSDJMASTER_MIDI_CH 7 91 | #define MEM_KEYBD_CH 8 92 | 93 | #define MEM_KEYBD_COMPAT_MODE 9 94 | #define MEM_KEYBD_CH_TO_INST 10 95 | 96 | #define MEM_MIDIOUT_NOTE_CH 11 97 | #define MEM_MIDIOUT_CC_CH 15 98 | #define MEM_MIDIOUT_CC_MODE 19 99 | #define MEM_MIDIOUT_CC_SCALING 23 100 | #define MEM_MIDIOUT_CC_NUMBERS 27 101 | 102 | #define MEM_MGB_CH 55 103 | #define MEM_LIVEMAP_CH 60 104 | 105 | #define MEM_MIDIOUT_BIT_DELAY 61 106 | #define MEM_MIDIOUT_BYTE_DELAY 63 107 | 108 | /*************************************************************************** 109 | * User Settings 110 | ***************************************************************************/ 111 | 112 | boolean usbMode = false; //to use usb for serial communication as oppose to MIDI - sets baud rate to 38400 113 | 114 | byte defaultMemoryMap[MEM_MAX] = { 115 | 0x7F,0x01,0x03,0x7F, //memory init check 116 | 0x00, //force mode (forces lsdj to be sl) 117 | 0x00, //mode 118 | 119 | 15, //sync effects midi channel (0-15 = 1-16) 120 | 15, //masterNotePositionMidiChannel - LSDJ in master mode will send its song position on the start button via midi note. (0-15 = 1-16) 121 | 122 | 15, //keyboardInstrumentMidiChannel - midi channel for keyboard instruments in lsdj. (0-15 = 1-16) 123 | 1, //Keyboard Compatability Mode 124 | 1, //Set to true if you want to have midi channel set the instrument number / doesnt do anything anymore 125 | 126 | 0,1,2,3, //midiOutNoteMessageChannels - midi channels for lsdj midi out note messages Default: channels 1,2,3,4 127 | 0,1,2,3, //midiOutCCMessageChannels - midi channels for lsdj midi out CC messages Default: channels 1,2,3,4 128 | 1,1,1,1, //midiOutCCMode - CC Mode, 0=use 1 midi CC, with the range of 00-6F, 1=uses 7 midi CCs with the 129 | //range of 0-F (the command's first digit would be the CC#), either way the value is scaled to 0-127 on output 130 | 1,1,1,1, //midiOutCCScaling - CC Scaling- Setting to 1 scales the CC value range to 0-127 as oppose to lsdj's incomming 00-6F (0-112) or 0-F (0-15) 131 | 1,2,3,7,10,11,12, //pu1: midiOutCCMessageNumbers - CC numbers for lsdj midi out, if CCMode is 1, all 7 ccs are used per channel at the cost of a limited resolution of 0-F 132 | 1,2,3,7,10,11,12, //pu2 133 | 1,2,3,7,10,11,12, //wav 134 | 1,2,3,7,10,11,12, //noi 135 | 136 | 0, 1, 2, 3, 4, //mGB midi channels (0-15 = 1-16) 137 | 0, //sync map midi channel start (0-15 = 1-16) (for song rows 0x80 to 0xFF it's this channel plus 1) 138 | 80,1, //midiout bit check delay & bit check delay multiplier 139 | 0,0//midiout byte received delay & byte received delay multiplier 140 | }; 141 | byte memory[MEM_MAX]; 142 | 143 | /*************************************************************************** 144 | * Lets Assign our Arduino Pins ..... 145 | ***************************************************************************/ 146 | 147 | 148 | /*************************************************************************** 149 | * Teensy 3.2, Teensy LC 150 | * 151 | * Notes on Teensy: Pins are not the same as in the schematic, the mapping is below. 152 | * Feel free to change, all related config in is this block. 153 | * Be sure to compile 154 | ***************************************************************************/ 155 | #if defined (__MK20DX256__) || defined (__MK20DX128__) || defined (__MKL26Z64__) 156 | #define USE_TEENSY 1 157 | #define USE_USB 1 158 | #include 159 | 160 | #if defined (__MKL26Z64__) 161 | #define GB_SET(bit_cl,bit_out,bit_in) GPIOB_PDOR = ((bit_in<<3) | (bit_out<<1) | bit_cl) 162 | #else 163 | #define GB_SET(bit_cl,bit_out,bit_in) GPIOB_PDOR = (GPIOB_PDIR & 0xfffffff4) | ((bit_in<<3) | (bit_out<<1) | bit_cl) 164 | #endif 165 | 166 | int pinGBClock = 16; // Analog In 0 - clock out to gameboy 167 | int pinGBSerialOut = 17; // Analog In 1 - serial data to gameboy 168 | int pinGBSerialIn = 18; // Analog In 2 - serial data from gameboy 169 | int pinMidiInputPower = 0; // Not used! 170 | int pinStatusLed = 13; // Status LED 171 | int pinLeds[] = {23,22,21,20,4,13}; // LED Pins 172 | int pinButtonMode = 2; //toggle button for selecting the mode 173 | 174 | HardwareSerial *serial = &Serial1; 175 | 176 | /*************************************************************************** 177 | * Arduino Leonardo/Yún/Micro (ATmega32U4) 178 | ***************************************************************************/ 179 | #elif defined (__AVR_ATmega32U4__) 180 | #define USE_LEONARDO 181 | #include 182 | 183 | #define GB_SET(bit_cl, bit_out, bit_in) PORTF = (PINF & B00011111) | ((bit_cl<<7) | ((bit_out)<<6) | ((bit_in)<<5)) 184 | // ^ The reason for not using digitalWrite is to allign clock and data pins for the GB shift reg. 185 | // Pin distribution comes from official Arduino Leonardo documentation 186 | 187 | int pinGBClock = A0; // Analog In 0 - clock out to gameboy 188 | int pinGBSerialOut = A1; // Analog In 1 - serial data to gameboy 189 | int pinGBSerialIn = A2; // Analog In 2 - serial data from gameboy 190 | int pinMidiInputPower = 4; // power pin for midi input opto-isolator 191 | int pinStatusLed = 13; // Status LED 192 | int pinLeds[] = {12,11,10,9,8,13}; // LED Pins 193 | int pinButtonMode = 3; //toggle button for selecting the mode 194 | 195 | HardwareSerial *serial = &Serial1; 196 | 197 | 198 | /*************************************************************************** 199 | * Arduino Due (ATmSAM3X8E) 200 | ***************************************************************************/ 201 | #elif defined (__SAM3X8E__) 202 | #define USE_DUE 203 | 204 | #define USE_LEONARDO 205 | #include 206 | #include 207 | 208 | 209 | #define GB_SET(bit_cl, bit_out, bit_in) digitalWriteFast(A0, bit_cl); digitalWriteFast(A1, bit_out); digitalWriteFast(A2, bit_in); 210 | // ^ The reason for not using digitalWrite is to allign clock and data pins for the GB shift reg. 211 | 212 | int pinGBClock = A0; // Analog In 0 - clock out to gameboy 213 | int pinGBSerialOut = A1; // Analog In 1 - serial data to gameboy 214 | int pinGBSerialIn = A2; // Analog In 2 - serial data from gameboy 215 | int pinMidiInputPower = 4; // power pin for midi input opto-isolator 216 | int pinStatusLed = 13; // Status LED 217 | int pinLeds[] = {12,11,10,9,8,13}; // LED Pins 218 | int pinButtonMode = 3; //toggle button for selecting the mode 219 | 220 | HardwareSerial *serial = &Serial; 221 | 222 | 223 | /*************************************************************************** 224 | * Arduino UNO/Ethernet/Nano (ATmega328), Arduino UNO Wifi (ATmega4809) or Mega 2560 (ATmega2560/ATmega1280) (assumed) 225 | ***************************************************************************/ 226 | #else 227 | 228 | #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) 229 | #define GB_SET(bit_cl,bit_out,bit_in) PORTF = (PINF & B11111000) | ((bit_in<<2) | ((bit_out)<<1) | bit_cl) 230 | #elif defined(__AVR_ATmega4809__) 231 | #define GB_SET(bit_cl,bit_out,bit_in) PORTD = (PIND & B11111000) | ((bit_in<<2) | ((bit_out)<<1) | bit_cl) 232 | #else 233 | #define GB_SET(bit_cl,bit_out,bit_in) PORTC = (PINC & B11111000) | ((bit_in<<2) | ((bit_out)<<1) | bit_cl) 234 | #endif 235 | // ^ The reason for not using digitalWrite is to allign clock and data pins for the GB shift reg. 236 | 237 | int pinGBClock = A0; // Analog In 0 - clock out to gameboy 238 | int pinGBSerialOut = A1; // Analog In 1 - serial data to gameboy 239 | int pinGBSerialIn = A2; // Analog In 2 - serial data from gameboy 240 | int pinMidiInputPower = 4; // power pin for midi input opto-isolator 241 | int pinStatusLed = 13; // Status LED 242 | int pinLeds[] = {12,11,10,9,8,13}; // LED Pins 243 | int pinButtonMode = 3; //toggle button for selecting the mode 244 | 245 | HardwareSerial *serial = &Serial; 246 | 247 | #endif 248 | 249 | /*************************************************************************** 250 | * Memory 251 | ***************************************************************************/ 252 | #ifndef USE_DUE 253 | #include 254 | boolean alwaysUseDefaultSettings = false; //set to true to always use the settings below, else they are pulled from memory for the software editor 255 | #else 256 | boolean alwaysUseDefaultSettings = true; //set to true to always use the default settings, in Due board is necessary as it doesn't have EEPROM 257 | #endif 258 | 259 | int eepromMemoryByte = 0; //Location of where to store settings from mem 260 | 261 | /*************************************************************************** 262 | * Sysex Settings & vars 263 | ***************************************************************************/ 264 | 265 | boolean sysexReceiveMode = 0; 266 | boolean sysexProgrammingMode = 0; 267 | boolean sysexProgrammingWaiting = 0; 268 | boolean sysexProgrammingConnected = 0; 269 | 270 | unsigned long sysexProgrammerWaitTime = 2000; //2 seconds 271 | unsigned long sysexProgrammerCallTime = 1000; //1 second 272 | unsigned long sysexProgrammerLastResponse = 0; 273 | unsigned long sysexProgrammerLastSent = 0; 274 | 275 | byte sysexManufacturerId = 0x69; //har har harrrrr :) 276 | int sysexPosition; 277 | byte sysexData[128]; 278 | byte longestSysexMessage = 128; 279 | 280 | int midioutBitDelay = 0; 281 | int midioutByteDelay = 0; 282 | 283 | /*************************************************************************** 284 | * Switches and states 285 | ***************************************************************************/ 286 | boolean sequencerStarted = false; //Sequencer has Started 287 | boolean midiSyncEffectsTime = false; 288 | boolean midiNoteOnMode =false; 289 | boolean midiNoteOffMode =false; 290 | boolean midiProgramChange=false; 291 | boolean midiAddressMode =false; 292 | boolean midiValueMode =false; 293 | 294 | int midiOutLastNote[4] = {-1,-1,-1,-1}; 295 | 296 | boolean statusLedIsOn =false; 297 | boolean statusLedBlink =false; 298 | 299 | boolean nanoState =false; 300 | boolean nanoSkipSync =false; 301 | 302 | int buttonDepressed; 303 | int buttonState; 304 | unsigned long int buttonProgrammerWaitTime = 2000; //2 whole seconds 305 | unsigned long int buttonTime; 306 | 307 | 308 | boolean blinkSwitch[6]; 309 | unsigned long int blinkSwitchTime[6]; 310 | uint8_t switchLight = 0; 311 | 312 | uint16_t blinkMaxCount = 1000; 313 | 314 | unsigned long midioutNoteTimer[4]; 315 | byte midioutNoteHold[4][4]; 316 | byte midioutNoteHoldCounter[4]; 317 | int midioutNoteTimerThreshold = 10; 318 | 319 | /*************************************************************************** 320 | * Counter vars 321 | ***************************************************************************/ 322 | int countLSDJTicks = 0; //for loop int (we need to cycle 8 pulses) 323 | int countSyncTime = 0; 324 | int countSyncLightTime=0; 325 | int countSyncSteps = 0; 326 | int countSyncPulse = 0; 327 | int countGbClockTicks =0; 328 | int countClockPause =0; 329 | int countIncommingMidiByte =0; 330 | int countStatusLedOn =0; 331 | unsigned int waitClock =0; 332 | 333 | 334 | int miscLastLed; 335 | unsigned long int miscLedTime; 336 | unsigned long int miscLedMaxTime; 337 | 338 | /*************************************************************************** 339 | * Inbound Data Placeholders 340 | ***************************************************************************/ 341 | byte incomingMidiByte; //incomming midi message 342 | byte readgbClockLine; 343 | byte readGbSerialIn; 344 | byte bit; 345 | byte midiData[] = {0, 0, 0}; 346 | byte lastMidiData[] = {0, 0, 0}; 347 | 348 | int incomingMidiNote = 0; 349 | int incomingMidiVel = 0; 350 | byte readToggleMode; 351 | byte serialWriteBuffer[256]; 352 | byte midiDefaultStartOffset; 353 | int writePosition=0; 354 | int readPosition=0; 355 | int lastMode=0; //Stores the last selected mode for leds. 356 | 357 | byte midiSyncByte; 358 | byte midiSyncByteLast; 359 | 360 | byte midiStatusType; 361 | byte midiStatusChannel; 362 | 363 | /*************************************************************************** 364 | * LSDJ Keyboard mode settings 365 | ***************************************************************************/ 366 | byte keyboardNotes[] = {0x1A,0x1B,0x22,0x23,0x21,0x2A,0x34,0x32,0x33,0x31,0x3B,0x3A, 367 | 0x15,0x1E,0x1D,0x26,0x24,0x2D,0x2E,0x2C,0x36,0x35,0x3D,0x3C}; 368 | byte keyboardOctDn = 0x05; 369 | byte keyboardOctUp = 0x06; 370 | 371 | byte keyboardInsDn = 0x04; 372 | byte keyboardInsUp = 0x0C; 373 | 374 | byte keyboardTblDn = 0x03; 375 | byte keyboardTblUp = 0x0B; 376 | 377 | byte keyboardTblCue= 0x29; 378 | 379 | byte keyboardMut1 = 0x01; 380 | byte keyboardMut2 = 0x09; 381 | byte keyboardMut3 = 0x78; 382 | byte keyboardMut4 = 0x07; 383 | 384 | byte keyboardCurL = 0x6B; 385 | byte keyboardCurR = 0x74; 386 | byte keyboardCurU = 0x75; 387 | byte keyboardCurD = 0x72; 388 | byte keyboardPgUp = 0x7D; 389 | byte keyboardPgDn = 0x7A; 390 | byte keyboardEntr = 0x5A; 391 | 392 | int keyboardCurrentOct = 0; 393 | int keyboardCurrentIns = 0; 394 | int keyboardCurrentTbl = 0; 395 | 396 | int keyboardLastOct = 0; 397 | int keyboardLastIns = 0; 398 | int keyboardLastTbl = 0; 399 | 400 | int keyboardDiff = 0; 401 | int keyboardCount = 0; 402 | byte keyboardStartOctave = 0x24; 403 | byte keyboardNoteStart = 0; 404 | byte keyboardNoteOffset = 0; 405 | byte keyboardCommands[12]; 406 | 407 | /*************************************************************************** 408 | * LSDJ Midi Map mode vars 409 | ***************************************************************************/ 410 | int mapCurrentRow = -1; 411 | int mapQueueMessage = -1; 412 | unsigned long mapQueueTime; 413 | // mapQueueWait is used for delaying a sync byte 414 | // if it is called right before a note on message on sequencer start 415 | // (Note value is also a clock tick) 416 | uint8_t mapQueueWaitSerial = 2; //2ms 417 | uint8_t mapQueueWaitUsb = 5; //5ms - Needs to be longer because message packet is processed all at once 418 | 419 | /*************************************************************************** 420 | * mGB Settings 421 | ***************************************************************************/ 422 | #define GB_MIDI_DELAY 500 //Microseconds to delay the sending of a byte to gb 423 | 424 | void setup() { 425 | /* 426 | Init Memory 427 | */ 428 | initMemory(0); 429 | /* 430 | Init Pins 431 | */ 432 | for(int led=0;led<=5;led++) pinMode(pinLeds[led],OUTPUT); 433 | pinMode(pinStatusLed,OUTPUT); 434 | pinMode(pinButtonMode,INPUT); 435 | 436 | pinMode(pinGBClock,OUTPUT); 437 | pinMode(pinGBSerialIn,INPUT); 438 | pinMode(pinGBSerialOut,OUTPUT); 439 | 440 | /* 441 | Set MIDI Serial Rate 442 | */ 443 | 444 | #ifdef USE_USB 445 | serial->begin(31250); //31250 446 | #else 447 | if(usbMode == true) { 448 | serial->begin(38400); 449 | } else { 450 | pinMode(pinMidiInputPower,OUTPUT); 451 | digitalWrite(pinMidiInputPower,HIGH); // turn on the optoisolator 452 | #ifdef USE_LEONARDO 453 | Serial1.begin(31250); //31250 454 | #else 455 | Serial.begin(31250); //31250 456 | #endif 457 | } 458 | #endif 459 | 460 | /* 461 | Set Pin States 462 | */ 463 | digitalWrite(pinGBClock,HIGH); // gameboy wants a HIGH line 464 | digitalWrite(pinGBSerialOut,LOW); // no data to send 465 | /* 466 | Misc Startup 467 | */ 468 | keyboardNoteStart = keyboardStartOctave + 12; // Set the octave where the actual notes start (the octave below is for the mutes, cursor, etc) 469 | /* 470 | Assign the keyboard mode command array for the first octave 471 | */ 472 | keyboardCommands[0] = keyboardMut1; 473 | keyboardCommands[1] = keyboardMut2; 474 | keyboardCommands[2] = keyboardMut3; 475 | keyboardCommands[3] = keyboardMut4; 476 | keyboardCommands[4] = keyboardCurL; 477 | keyboardCommands[5] = keyboardCurR; 478 | keyboardCommands[6] = keyboardCurU; 479 | keyboardCommands[7] = keyboardCurD; 480 | keyboardCommands[8] = keyboardEntr; 481 | keyboardCommands[9] = keyboardTblDn; 482 | keyboardCommands[10] = keyboardTblUp; 483 | keyboardCommands[11] = keyboardTblCue; 484 | /* 485 | Load Settings from EEPROM 486 | */ 487 | #ifndef USE_DUE 488 | if(!memory[MEM_FORCE_MODE]) memory[MEM_MODE] = EEPROM.read(MEM_MODE); 489 | #endif 490 | lastMode = memory[MEM_MODE]; 491 | 492 | /* 493 | usbMidi sysex support 494 | */ 495 | usbMidiInit(); 496 | 497 | startupSequence(); 498 | 499 | showSelectedMode(); //Light up the LED that shows which mode we are in. 500 | } 501 | 502 | /* 503 | Main Loop, which we don't use to be able to isolate each mode into its own setup and loop functions 504 | */ 505 | void loop () { 506 | setMode(); 507 | switchMode(); 508 | } 509 | --------------------------------------------------------------------------------