├── NES2A03.cpp ├── NES2A03.h ├── README.md ├── Rectangle1.cpp ├── Rectangle1.h ├── Rectangle2.cpp ├── Rectangle2.h ├── Triangle.cpp ├── Triangle.h ├── WaveGen.cpp ├── WaveGen.h └── examples └── NES_SynthShield_MIDI └── NES_SynthShield_MIDI.ino /NES2A03.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | //#include 3 | 4 | 5 | 6 | 7 | // 8 | // get everything set up to talk to the 2A03 9 | // 10 | void NES2A03::init(){ 11 | 12 | pinMode(PIN_PWR, OUTPUT); // set up pins 13 | pinMode(PIN_RST, OUTPUT); 14 | pinMode(PIN_INTERRUPT, INPUT); 15 | 16 | DDRA = B11111111; 17 | DDRE = B11111111; 18 | 19 | PORTE = 0x15; 20 | PORTA = B00000111; // enable rect 1+2 and tri only 21 | //PORTA = 0x0F; // B000001111 22 | 23 | // SPI.begin(); // enable SPI communication 24 | 25 | // pinMode(PIN_PWR, OUTPUT); // set up pins 26 | // pinMode(PIN_RST, OUTPUT); 27 | // pinMode(PIN_LATCH, OUTPUT); 28 | // pinMode(PIN_INTERRUPT, INPUT); 29 | 30 | // digitalWrite(PIN_LATCH, LOW); // populate the address and data busses with some harmless inital values 31 | // SPI.transfer(0x15); 32 | // SPI.transfer(0x0F); 33 | // digitalWrite(PIN_LATCH, HIGH); 34 | 35 | digitalWrite(PIN_PWR, LOW); // Turn power to NES CPU off 36 | digitalWrite(PIN_RST, LOW); // Bring /RST low 37 | delay(500); // wait .5 seconds 38 | digitalWrite(PIN_PWR,HIGH); // turn on NES CPU 39 | delay(500); // wait .5 seconds 40 | digitalWrite(PIN_RST,HIGH); // bring /RST pin high 41 | 42 | rectangle1.init(); 43 | rectangle2.init(); 44 | triangle.init(); 45 | 46 | 47 | //rectangle1._cycle_applyAttack = 10000; 48 | //rectangle2._cycle_applyAttack = 100000; 49 | } 50 | 51 | void NES2A03::run(){ 52 | rectangle1._handleNoteStates(); 53 | rectangle2._handleNoteStates(); 54 | triangle._handleNoteStates(); 55 | } 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /NES2A03.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_NES2A03_H_ 2 | #define LIB_NES2A03_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define PIN_PWR 12 //Pin to control power to 2A03 10 | #define PIN_RST 13 //Pin to reset 2A03 11 | 12 | // #define PIN_LATCH 10 //SPI Latch Pin 13 | // #define PIN_PWR 3 //Pin to control power to 2A03 14 | // #define PIN_RST 4 //Pin to reset 2A03 15 | 16 | 17 | //#define QUEUE_SIZE 10 18 | 19 | class NES2A03{ 20 | public: 21 | void init(); 22 | void run(); 23 | Rectangle1 rectangle1; 24 | Rectangle2 rectangle2; 25 | Triangle triangle; 26 | 27 | private: 28 | 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Arduino-NES-Synth 2 | ================= 3 | 4 | Arduino library for real time control of wave generators on the Rikoh 2A03 (the Nintendo Entertainment System chip). 5 | 6 | See http://shiftmore.blogspot.com/2012/11/arduino-controlled-nes-2a03-synth-part-i.html for hardware details. 7 | -------------------------------------------------------------------------------- /Rectangle1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | /* 5 | * send the note data to the 2A03 6 | * a quirk of the 2A03 is that the rect wave gen counter gets reset whenever a value is written to the high byte address register 7 | * when this happens while a note is currently playing, there is a noticable click 8 | */ 9 | void Rectangle1::_setWavelength(uint16_t newWavelength,bool force){ 10 | 11 | uint16_t detunedWavelength = newWavelength;// - _getFineDetuneAmount(newWavelength); 12 | 13 | //uint16_t wordVal = word(tableHi[noteTableIndex],tableLo[noteTableIndex]); 14 | //uint16_t currentInterval = wordVal - word(tableHi[noteTableIndex+12],tableLo[noteTableIndex+12]); 15 | //wordVal -= (uint16_t)((((float)(rect2Detune))/(float)127.0) * (float)currentInterval); 16 | //sendAddrData(0x06, lowByte(wordVal)); 17 | //sendAddrData(0x07, highByte(wordVal)); 18 | 19 | 20 | if(force || (_notesPressed == 0 && _noteState != NOTESTATE_RELEASE)){ // if there are no notes currently being played (including release phase) 21 | _sendAddrData(0x00, _getWaveDataMessage()); 22 | if(detunedWavelength != _wavelength) _sendAddrData(0x03, highByte(detunedWavelength)); // and set high byte directly only if the note has changed.. if we are in a long release, we don;t want to click.. 23 | }else{ // otherwise, the wave gen should already be running (we are ignoring velocity for now) and we need to use the frequency sweep to set the high byte to avoid the click.. 24 | if(detunedWavelength > _wavelength){ // if new note is lower, we need to increase the note period 25 | for(int i=0; i<(highByte(detunedWavelength)-highByte(_wavelength)); i++){ 26 | _incrementHighByte(); 27 | } 28 | }else{ // otherwise decrease the period 29 | for(int i=0; i<(highByte(_wavelength)-highByte(detunedWavelength)); i++){ 30 | _decrementHighByte(); 31 | } 32 | } 33 | } 34 | 35 | _sendAddrData(0x02, lowByte(detunedWavelength)); // set low byte 36 | _sendAddrData(0x1F, 0x00); // end write 37 | 38 | _wavelength = newWavelength; // update the current note 39 | } 40 | 41 | void Rectangle1::_sendWaveDataMessage(){ 42 | _sendAddrData(0x00, _getWaveDataMessage()); 43 | _sendAddrData(0x1F, 0x00); 44 | } 45 | 46 | /* 47 | * http://forums.nesdev.com/viewtopic.php?f=2&t=231&p=99658#p99658 48 | * Writing to the high byte address of either of the square wave generators will reset that wave's phase 49 | * causing an audible click if the current note being played is cut off a the right (wrong) moment. 50 | * writing the low byte does not have this effect, nor does applying a frequency shift instruction. 51 | * This routine will effectively notch the high byte of the rectangle wave genarator period up one without resetting the phase 52 | * by setting the low byte as close to the edge as possible (0x00 or 0xFF depending) and performing a freq shift for a single clock cycle. 53 | */ 54 | void Rectangle1::_incrementHighByte(){ 55 | _sendAddrData(0x17,0x40); //reset frame counter in case it was about to clock 56 | _sendAddrData(0x02,0xFF); //be sure low 8 bits of timer period are $FF 57 | _sendAddrData(0x01,0x87); //sweep enabled, shift = 7 (1/128) 58 | _sendAddrData(0x17,0xC0); //clock sweep immediately 59 | _sendAddrData(0x01,0x0F); //disable sweep 60 | } 61 | 62 | /* 63 | * The same as above, in the opposite direction. 64 | */ 65 | void Rectangle1::_decrementHighByte(){ 66 | _sendAddrData(0x17,0x40); //reset frame counter in case it was about to clock 67 | _sendAddrData(0x02,0x00); //be sure low 8 bits of timer period are $00 68 | _sendAddrData(0x01,0x8F); //sweep enabled, shift = 7 (1/128) 69 | _sendAddrData(0x17,0xC0); //clock sweep immediately 70 | _sendAddrData(0x01,0x0F); //disable sweep 71 | } 72 | 73 | void Rectangle1::_stop(){ 74 | //_sendAddrData(0x00, B00110000); 75 | //_sendAddrData(0x1F, 0x00); 76 | } 77 | 78 | 79 | 80 | uint8_t Rectangle1::_getWaveDataMessage(){ 81 | 82 | uint8_t data = B00110000 + _getVolume(); 83 | //uint8_t data = B00100000 + _getVolume(); 84 | 85 | if( _getDutyCycle() == 3 ) return data | B11000000; 86 | if( _getDutyCycle() == 2 ) return data | B10000000; 87 | if( _getDutyCycle() == 1 ) return data | B01000000; 88 | return data + B00000000; 89 | 90 | } 91 | 92 | void Rectangle1::_applyAttack(){ 93 | if(_currentVolume < _volume){ 94 | if(_cycleCheck(&_timer_applyAttack, _cycle_applyAttack)){ 95 | _currentVolume++; 96 | _sendWaveDataMessage(); 97 | } 98 | }else{ 99 | _noteState = NOTESTATE_SUSTAIN; 100 | _timer_applyAttack=0; 101 | } 102 | } 103 | 104 | void Rectangle1::_applyRelease(){ 105 | if(_currentVolume > 0){ 106 | if(_cycleCheck(&_timer_applyRelease, _cycle_applyRelease)){ 107 | _currentVolume--; 108 | _sendWaveDataMessage(); 109 | } 110 | }else{ 111 | _noteState = NOTESTATE_OFF; 112 | _timer_applyRelease=0; 113 | } 114 | } 115 | 116 | 117 | -------------------------------------------------------------------------------- /Rectangle1.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_RECTANGLE1_H_ 2 | #define LIB_RECTANGLE1_H_ 3 | 4 | #include 5 | #include 6 | 7 | class Rectangle1 : public WaveGen{ 8 | public: 9 | 10 | private: 11 | virtual void _setWavelength(uint16_t,bool); 12 | virtual void _stop(); 13 | virtual uint8_t _getWaveDataMessage(); 14 | virtual void _sendWaveDataMessage(); 15 | virtual void _applyAttack(); 16 | virtual void _applyRelease(); 17 | 18 | void _incrementHighByte(); 19 | void _decrementHighByte(); 20 | }; 21 | 22 | #endif -------------------------------------------------------------------------------- /Rectangle2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | 5 | /* 6 | * send the note data to the 2A03 7 | * a quirk of the 2A03 is that the rect wave gen counter gets reset whenever a value is written to the high byte address register 8 | * when this happens while a note is currently playing, there is a noticable click 9 | */ 10 | void Rectangle2::_setWavelength(uint16_t newWavelength, bool force){ 11 | 12 | if(force || (_notesPressed == 0 && _noteState != NOTESTATE_RELEASE)){ // if there are no notes currently being played 13 | _sendAddrData(0x04, _getWaveDataMessage()); 14 | if(newWavelength != _wavelength) _sendAddrData(0x07, highByte(newWavelength)); // and set high byte directly 15 | }else{ // otherwise, the wave gen should already be running (we are ignoring velocity for now) and we need to use the frequency sweep to set the high byte to avoid the click.. 16 | if(newWavelength > _wavelength){ // if new note is lower, we need to increase the note period 17 | for(int i=0; i<(highByte(newWavelength)-highByte(_wavelength)); i++){ 18 | _incrementHighByte(); 19 | } 20 | }else{ // otherwise decrease the period 21 | for(int i=0; i<(highByte(_wavelength)-highByte(newWavelength)); i++){ 22 | _decrementHighByte(); 23 | } 24 | } 25 | } 26 | 27 | _sendAddrData(0x06, lowByte(newWavelength)); // set low byte 28 | _sendAddrData(0x1F, 0x00); // end write 29 | 30 | _wavelength = newWavelength; // update the current note 31 | } 32 | 33 | void Rectangle2::_sendWaveDataMessage(){ 34 | _sendAddrData(0x04, _getWaveDataMessage()); 35 | _sendAddrData(0x1F, 0x00); 36 | } 37 | 38 | /* 39 | * http://forums.nesdev.com/viewtopic.php?f=2&t=231&p=99658#p99658 40 | * Writing to the high byte address of either of the square wave generators will reset that wave's phase 41 | * causing an audible click if the current note being played is cut off a the right (wrong) moment. 42 | * writing the low byte does not have this effect, nor does applying a frequency shift instruction. 43 | * This routine will effectively notch the high byte of the rectangle wave genarator period up one without resetting the phase 44 | * by setting the low byte as close to the edge as possible (0x00 or 0xFF depending) and performing a freq shift for a single clock cycle. 45 | */ 46 | void Rectangle2::_incrementHighByte(){ 47 | _sendAddrData(0x17,0x40); //reset frame counter in case it was about to clock 48 | _sendAddrData(0x06,0xFF); //be sure low 8 bits of timer period are $FF 49 | _sendAddrData(0x05,0x87); //sweep enabled, shift = 7 (1/128) 50 | _sendAddrData(0x17,0xC0); //clock sweep immediately 51 | _sendAddrData(0x05,0x0F); //disable sweep 52 | } 53 | 54 | /* 55 | * The same as above, in the opposite direction. 56 | */ 57 | void Rectangle2::_decrementHighByte(){ 58 | _sendAddrData(0x17,0x40); //reset frame counter in case it was about to clock 59 | _sendAddrData(0x06,0x00); //be sure low 8 bits of timer period are $00 60 | _sendAddrData(0x05,0x8F); //sweep enabled, shift = 7 (1/128) 61 | _sendAddrData(0x17,0xC0); //clock sweep immediately 62 | _sendAddrData(0x05,0x0F); //disable sweep 63 | } 64 | 65 | void Rectangle2::_stop(){ 66 | //_sendAddrData(0x04, B00110000); 67 | //_sendAddrData(0x1F, 0x00); 68 | } 69 | 70 | uint8_t Rectangle2::_getWaveDataMessage(){ 71 | 72 | uint8_t data = B00110000 + _getVolume(); 73 | //uint8_t data = B00100000 + _getVolume(); 74 | 75 | if( _getDutyCycle() == 3 ) return data | B11000000; 76 | if( _getDutyCycle() == 2 ) return data | B10000000; 77 | if( _getDutyCycle() == 1 ) return data | B01000000; 78 | return data + B00000000; 79 | 80 | } 81 | 82 | void Rectangle2::_applyAttack(){ 83 | if(_currentVolume < _volume){ 84 | if(_cycleCheck(&_timer_applyAttack, _cycle_applyAttack)){ 85 | _currentVolume++; 86 | _sendWaveDataMessage(); 87 | } 88 | }else{ 89 | _noteState = NOTESTATE_SUSTAIN; 90 | _timer_applyAttack=0; 91 | } 92 | } 93 | 94 | void Rectangle2::_applyRelease(){ 95 | if(_currentVolume > 0){ 96 | if(_cycleCheck(&_timer_applyRelease, _cycle_applyRelease)){ 97 | _currentVolume--; 98 | _sendWaveDataMessage(); 99 | } 100 | }else{ 101 | _noteState = NOTESTATE_OFF; 102 | _timer_applyRelease=0; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Rectangle2.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_RECTANGLE2_H_ 2 | #define LIB_RECTANGLE2_H_ 3 | 4 | #include 5 | #include 6 | 7 | class Rectangle2 : public WaveGen{ 8 | public: 9 | 10 | private: 11 | virtual void _setWavelength(uint16_t,bool); 12 | virtual void _stop(); 13 | virtual uint8_t _getWaveDataMessage(); 14 | virtual void _sendWaveDataMessage(); 15 | virtual void _applyAttack(); 16 | virtual void _applyRelease(); 17 | 18 | void _incrementHighByte(); 19 | void _decrementHighByte(); 20 | }; 21 | 22 | #endif -------------------------------------------------------------------------------- /Triangle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | * We don't need to do all the fancy moves on the tri wave. just set the freq. 5 | */ 6 | void Triangle::_setWavelength(uint16_t newWavelength,bool force){ 7 | //if(_notesPressed == 0 || _wavelength != newWavelength){ // i don't think we need to make this check.. what situation would the same pitch come therough twice? 8 | if(_notesPressed == 0){ // if there are no notes currently being played 9 | _sendAddrData(0x08, B11111111); // turn square 1 wave to max volume, set duty cycle, turn off sweeps 10 | 11 | } 12 | 13 | //uint16_t detunedWL = (uint16_t)((int)newWavelength - (int)_getDetune()); 14 | 15 | _sendAddrData(0x0B, highByte(newWavelength)); // and set high byte directly 16 | _sendAddrData(0x0A, lowByte(newWavelength)); // set low byte 17 | _sendAddrData(0x1F, 0x00); // end write 18 | _wavelength = newWavelength; // update the current note 19 | //} 20 | } 21 | 22 | void Triangle::_stop(){ 23 | _sendAddrData(0x08, 0x00); 24 | _sendAddrData(0x1F, 0x00); 25 | } 26 | 27 | uint8_t Triangle::_getWaveDataMessage(){ 28 | return B11111111; 29 | } 30 | 31 | void Triangle::_sendWaveDataMessage(){ 32 | _sendAddrData(0x08, B11111111); 33 | _sendAddrData(0x1F, 0x00); 34 | } 35 | 36 | void Triangle::_applyAttack(){ 37 | // do nothing 38 | } 39 | 40 | void Triangle::_applyRelease(){ 41 | // do nothing 42 | } -------------------------------------------------------------------------------- /Triangle.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_TRIANGLE_H_ 2 | #define LIB_TRIANGLE_H_ 3 | 4 | #include 5 | #include 6 | 7 | class Triangle : public WaveGen{ 8 | public: 9 | 10 | public: 11 | virtual void _setWavelength(uint16_t,bool); 12 | virtual void _stop(); 13 | virtual uint8_t _getWaveDataMessage(); 14 | virtual void _sendWaveDataMessage(); 15 | virtual void _applyAttack(); 16 | virtual void _applyRelease(); 17 | 18 | }; 19 | 20 | #endif -------------------------------------------------------------------------------- /WaveGen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | //#include 3 | 4 | //These tables were taken from http://www.freewebs.com/the_bott/NotesTableNTSC.txt 5 | const uint8_t _noteTableHi[] = 6 | {0x07,0x07,0x07,0x06,0x06,0x05,0x05,0x05,0x05,0x04,0x04,0x04,0x03,0x03,0x03,0x03,0x03,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; 7 | //{0x07,0x07,0x07,0x06,0x06,0x05,0x05,0x05,0x05,0x04,0x04,0x04,0x03,0x03,0x03,0x03,0x03,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; 8 | const uint8_t _noteTableLo[] = 9 | {0xF1,0x80,0x13,0xAD,0x4D,0xF3,0x9D,0x4D,0x00,0xB8,0x75,0x35,0xF8,0xBF,0x89,0x56,0x26,0xF9,0xCE,0xA6,0x80,0x5C,0x3A,0x1A,0xFC,0xDF,0xC4,0xAB,0x93,0x7C,0x67,0x52,0x3F,0x2D,0x1C,0x0C,0xFD,0xEF,0xE2,0xD5,0xC9,0xBD,0xB3,0xA9,0x9F,0x96,0x8E,0x86,0x7E,0x77,0x70,0x6A,0x64,0x5E,0x59,0x54,0x4F,0x4B,0x46,0x42,0x3F,0x3B,0x38,0x34,0x31,0x2F,0x2C,0x29,0x27,0x25,0x23,0x21,0x1F,0x1D,0x1B,0x1A,0x18,0x17,0x15,0x14,0x13,0x12,0x11,0x10,0x0F,0x0E,0x0D}; 10 | //{0xF1,0x80,0x13,0xAD,0x4D,0xF3,0x9D,0x4D,0x00,0xB8,0x75,0x35,0xF8,0xBF,0x89,0x56,0x26,0xF9,0xCE,0xA6,0x7F,0x5C,0x3A,0x1A,0xFB,0xDF,0xC4,0xAB,0x93,0x7C,0x67,0x52,0x3F,0x2D,0x1C,0x0C,0xFD,0xEF,0xE2,0xD2,0xC9,0xBD,0xB3,0xA9,0x9F,0x96,0x8E,0x86,0x7E,0x77,0x70,0x6A,0x64,0x5E,0x59,0x54,0x4F,0x4B,0x46,0x42}; 11 | 12 | 13 | 14 | 15 | void WaveGen::init(){ 16 | pinMode(PIN_INTERRUPT,INPUT); 17 | 18 | _noteState = NOTESTATE_OFF; 19 | _timer_applyAttack = 0; 20 | _cycle_applyAttack = 1; 21 | 22 | _timer_applyRelease = 0; 23 | _cycle_applyRelease = 1; 24 | 25 | _timer_applyMod = 0; 26 | _cycle_applyMod = 1000; 27 | 28 | _modDepth = 0; 29 | _modWaveForm = MODWAVEFORM_SQUARE; 30 | _modMode = MOD_TREMALO; 31 | 32 | _noteOffset = 0; 33 | _currentNote = 43; //midway 34 | _wavelength = word(_getNoteHighByte(_currentNote),_getNoteLowByte(_currentNote)); 35 | 36 | _currentBend = 0; 37 | _notesPressed = 0; 38 | _fineDetune = 0; 39 | 40 | _LFOMode = LFOMODE_DISABLE; 41 | _LFOMillis = 1000; 42 | 43 | _arpNoteQueuePosition = 0; 44 | _arpDirectionAscend = true; 45 | _arpStyle = ARPSTYLE_ASPLAYED; 46 | 47 | _volume = 15; 48 | 49 | 50 | } 51 | 52 | // 53 | // write data byte to the specific 2A03 address 54 | // 55 | void WaveGen::_sendAddrData(uint8_t address, uint8_t data){ 56 | while(digitalRead(PIN_INTERRUPT) == HIGH); 57 | while(digitalRead(PIN_INTERRUPT) == LOW); // the interrupt worked well in a sketch, but got messy with a class. this works fine. 58 | // we are just waiting for the rising edge on this pin which is our next opportunity to write. 59 | //delay(10); 60 | 61 | PORTE = address; 62 | PORTA = data; 63 | 64 | // digitalWrite(PIN_LATCH, LOW); // set latch LOW to enable write 65 | // SPI.transfer(address); // send address first 66 | // SPI.transfer(data); // then data.. 67 | // digitalWrite(PIN_LATCH, HIGH); // set latch HIGH to end the write 68 | 69 | } 70 | 71 | /* 72 | * handle MIDI key press 73 | */ 74 | void WaveGen::handleNoteOn(uint8_t channel, uint8_t pitch, uint8_t velocity){ 75 | if(_notesPressed 0 ) { 100 | // _setWavelength(word(_getNoteHighByte(_currentNote),_getNoteLowByte(_currentNote))); 101 | // } 102 | // } 103 | // int currentBendDifference = bend - _currentBend; 104 | // //uint16_t currentInterval = word(_getNoteHighByte(_currentNote-PITCHBENDRANGE),_getNoteLowByte(_currentNote-PITCHBENDRANGE)) - word(_getNoteHighByte(_currentNote+PITCHBENDRANGE),_getNoteLowByte(_currentNote+PITCHBENDRANGE)); 105 | // int bendDetune = (int)((((float)(currentBendDifference))/(float)8193.0) * ( (float)_wavelength*0.06*6 )); 106 | // if(_notesPressed > 0 ) { 107 | // _setWavelength(_wavelength - bendDetune); 108 | // } 109 | // _currentBend = bend; 110 | } 111 | 112 | 113 | 114 | 115 | 116 | uint8_t WaveGen::_getNoteLowByte(uint8_t noteTableIdx){ 117 | return _noteTableLo[noteTableIdx]; 118 | } 119 | uint8_t WaveGen::_getNoteHighByte(uint8_t noteTableIdx){ 120 | return _noteTableHi[noteTableIdx]; 121 | } 122 | 123 | 124 | /* 125 | * Functions to maintain the note queue 126 | */ 127 | void bubble_sort(uint8_t list[], uint8_t n) 128 | { 129 | uint8_t c, d, t; 130 | 131 | for (c = 0 ; c < ( n - 1 ); c++) 132 | { 133 | for (d = 0 ; d < n - c - 1; d++) 134 | { 135 | if (list[d] > list[d+1]) 136 | { 137 | /* Swapping */ 138 | 139 | t = list[d]; 140 | list[d] = list[d+1]; 141 | list[d+1] = t; 142 | } 143 | } 144 | } 145 | } 146 | void WaveGen::createSortedQueues(){ 147 | for(int i=0;i<_notesPressed;i++){ 148 | _noteQueue_UP[i] = _noteQueue[i]; 149 | } 150 | bubble_sort(_noteQueue_UP,_notesPressed); 151 | } 152 | 153 | void WaveGen::_pushNoteOnQueue(uint8_t note){ 154 | _noteQueue[_notesPressed] = note; 155 | 156 | _notesPressed++; 157 | 158 | createSortedQueues(); 159 | } 160 | void WaveGen::_removeNoteFromQueue(uint8_t pitch){ 161 | boolean found=false; 162 | for(int i=0; i<_notesPressed-1; i++){ 163 | if(_noteQueue[i] == pitch){ 164 | found=true; 165 | } 166 | if(found){ 167 | _noteQueue[i] = _noteQueue[i+1]; 168 | } 169 | } 170 | _noteQueue[_notesPressed-1] = 0; 171 | _notesPressed--; 172 | 173 | createSortedQueues(); 174 | } 175 | uint8_t WaveGen::_getLastNoteInQueue(){ 176 | return _noteQueue[_notesPressed-1]; 177 | } 178 | 179 | void WaveGen::_playNote(uint8_t note){ 180 | if(_noteState == NOTESTATE_OFF || _noteState == NOTESTATE_RELEASE){ 181 | _noteState = NOTESTATE_ATTACK; 182 | } 183 | _setWavelength(word(_getNoteHighByte(note+_getNoteOffset()),_getNoteLowByte(note+_getNoteOffset())),false); 184 | _currentNote = note; 185 | } 186 | 187 | void WaveGen::_setNoteOffset(int offset){ 188 | if(_notesPressed > 0){ 189 | _setWavelength(word(_getNoteHighByte(_currentNote+_getNoteOffset()),_getNoteLowByte(_currentNote+_getNoteOffset())),false); 190 | } 191 | _noteOffset = offset; 192 | } 193 | int WaveGen::_getNoteOffset(){ 194 | return _noteOffset; 195 | } 196 | 197 | // 0 2/14 198 | // 1 4/12 199 | // 2 8/ 8 200 | // 3 12/ 4 201 | void WaveGen::_setDutyCycle(uint8_t dc){ 202 | if(dc > 3) dc = 3; 203 | if(dc != _dutyCycleValue){ 204 | _dutyCycleValue = dc; 205 | if(_notesPressed > 0) _sendWaveDataMessage(); 206 | } 207 | } 208 | 209 | uint8_t WaveGen::_getDutyCycle(){ 210 | return _dutyCycleValue; 211 | } 212 | 213 | //Set Volume 0000-1111 (0-15) 214 | void WaveGen::_setVolume(uint8_t v){ 215 | //uint8_t vol = map(v,0,127,0,15); 216 | //if(vol != _volume){ 217 | // _volume = vol; 218 | if(v > 15) v=15; 219 | _volume = v; 220 | if(_notesPressed > 0) _sendWaveDataMessage(); 221 | //} 222 | } 223 | 224 | uint8_t WaveGen::_getVolume(){ 225 | //return _volume; 226 | return _currentVolume; 227 | } 228 | 229 | void WaveGen::_setAttack(uint8_t a){ 230 | unsigned long atk = 0; 231 | if(a > 0) atk = map(a,0,127,0,100000); 232 | //if(vol != _volume){ 233 | _cycle_applyAttack = atk; 234 | // if(_notesPressed > 0) _sendWaveDataMessage(); 235 | //} 236 | } 237 | 238 | void WaveGen::_setRelease(uint8_t r){ 239 | unsigned long rls = 0; 240 | if(r > 0) rls = map(r,0,127,0,100000); 241 | //if(vol != _volume){ 242 | _cycle_applyRelease = rls; 243 | // if(_notesPressed > 0) _sendWaveDataMessage(); 244 | //} 245 | } 246 | 247 | /* 248 | void WaveGen::_setFineDetune(uint8_t fd){ 249 | int mapped_fd = fd;//map(fd,0,127,0,100); 250 | if(mapped_fd != _fineDetune){ 251 | _fineDetune = mapped_fd; 252 | if(_notesPressed > 0) _setWavelength(_wavelength); 253 | } 254 | } 255 | 256 | uint8_t WaveGen::_getFineDetune(){ 257 | return _fineDetune; 258 | } 259 | 260 | uint16_t WaveGen::_getFineDetuneAmount(uint16_t wl){ 261 | if(_getFineDetune() == 0) return 0; 262 | //uint16_t currentInterval = word(_getNoteHighByte(_currentNote),_getNoteLowByte(_currentNote)) - word(_getNoteHighByte(_currentNote+1),_getNoteLowByte(_currentNote+1)); 263 | //int d = (int)((((float)(_getFineDetune()))/(float)100.0) * (float)currentInterval); 264 | uint16_t d = (uint16_t)((((float)(_getFineDetune()))/(float)127.0) * ( (float)_wavelength*0.06*12 )); 265 | return d; 266 | } 267 | */ 268 | 269 | 270 | // Only applies to rect gens, until we implement glide/arp/vibrato 271 | void WaveGen::_handleNoteStates(){ 272 | 273 | switch (_noteState) { 274 | //case NOTESTATE_TRANSITION_DECAY: 275 | //applyTransDecay(); 276 | // break; 277 | //case NOTESTATE_TRANSITION_ATTACK: 278 | //applyTransAttack(); 279 | // break; 280 | case NOTESTATE_RELEASE: 281 | _applyRelease(); 282 | break; 283 | case NOTESTATE_ATTACK: 284 | _applyAttack(); 285 | break; 286 | } 287 | 288 | if(_LFOMode == LFOMODE_MOD){ 289 | _applyMod(); 290 | } 291 | 292 | } 293 | 294 | 295 | 296 | 297 | void WaveGen::_runLFO(){ 298 | 299 | if(_LFOMode == LFOMODE_ARP){ 300 | if(_notesPressed > 1){ 301 | if(_notesPressed == 2 || _arpStyle == ARPSTYLE_ASPLAYED){ 302 | // if there are only 2 notes, we just oscillate between them.. 303 | _arpNoteQueuePosition = (_arpNoteQueuePosition+1)%(_notesPressed); 304 | _playNote(_noteQueue[_arpNoteQueuePosition]); 305 | }else{ 306 | 307 | 308 | if(_arpStyle == ARPSTYLE_UP || _arpStyle == ARPSTYLE_CONVERGE){ 309 | _arpNoteQueuePosition = (_arpNoteQueuePosition+1)%(_notesPressed); 310 | if(_arpStyle == ARPSTYLE_UP) _playNote(_noteQueue_UP[_arpNoteQueuePosition]); 311 | //else _playNote(_noteQueue_CONVERGE[_arpNoteQueuePosition]); 312 | }else if(_arpStyle == ARPSTYLE_DOWN || _arpStyle == ARPSTYLE_DIVERGE){ 313 | _arpNoteQueuePosition = (_arpNoteQueuePosition+1)%(_notesPressed); 314 | if(_arpStyle == ARPSTYLE_DOWN) _playNote(_noteQueue_UP[_notesPressed-_arpNoteQueuePosition-1]); 315 | //else _playNote(_noteQueue_CONVERGE[_arpNoteQueuePosition]); 316 | }else if(_arpStyle == ARPSTYLE_UPDOWN || _arpStyle == ARPSTYLE_CONVERGEDIVERGE){ 317 | if(_arpNoteQueuePosition >= _notesPressed-1){ 318 | _arpDirectionAscend = false; 319 | }else if(_arpNoteQueuePosition <= 0){ 320 | _arpDirectionAscend = true; 321 | } 322 | 323 | if(_arpDirectionAscend) 324 | _arpNoteQueuePosition++; 325 | else 326 | _arpNoteQueuePosition--; 327 | 328 | if(_arpStyle == ARPSTYLE_UPDOWN) _playNote(_noteQueue_UP[_arpNoteQueuePosition]); 329 | //else _playNote(_noteQueue_CONVERGE[_arpNoteQueuePosition]); 330 | } 331 | } 332 | } 333 | }else if(_LFOMode == LFOMODE_MOD){ 334 | if(_arpDirectionAscend){ 335 | //_sendAddrData(0x01,B11110001); //0x87 sweep enabled, shift = 7 (1/128) 336 | //_sendAddrData(0x17,0xC0); //clock sweep immediately 337 | _arpDirectionAscend = false; //flip 338 | _risingEdgeMicros = micros(); 339 | _risingEdgeModFlag = true; 340 | }else{ 341 | //_sendAddrData(0x01,B11111001); //sweep enabled, shift = 7 (1/128) 342 | //_sendAddrData(0x17,0xC0); 343 | _arpDirectionAscend = true; //flip 344 | _fallingEdgeModFlag = true; 345 | } 346 | } 347 | 348 | } 349 | 350 | void WaveGen::setLFOMillis(unsigned long lfoMillis){ 351 | _LFOMillis = lfoMillis; 352 | } 353 | 354 | unsigned long WaveGen::getModCycle(){ 355 | // uint16_t currentWavelength = word(_getNoteHighByte(_currentNote),_getNoteLowByte(_currentNote)); 356 | // uint16_t nextWavelength = word(_getNoteHighByte(_currentNote+1),_getNoteLowByte(_currentNote+1)); 357 | // uint16_t prevWavelength = word(_getNoteHighByte(_currentNote-1),_getNoteLowByte(_currentNote-1)); 358 | 359 | // uint16_t long diff = currentWavelength - nextWavelength; 360 | // if(!_arpDirectionAscend){ 361 | // diff = prevWavelength - currentWavelength; 362 | // } 363 | // return (unsigned long)(((double)_LFOMillis*(double)1000)/((double)1*(((double)diff)/(double)_tremaloDepth))); 364 | 365 | return 1; 366 | } 367 | 368 | void WaveGen::_applyMod(){ 369 | unsigned long cycle = getModCycle(); 370 | if(_cycleCheck(&_timer_applyMod, cycle)){ 371 | if(_noteState != NOTESTATE_OFF && _modDepth > 0){ 372 | 373 | uint16_t currentWavelength = word(_getNoteHighByte(_currentNote),_getNoteLowByte(_currentNote)); 374 | //uint16_t nextWavelength = word(_getNoteHighByte(_currentNote+2),_getNoteLowByte(_currentNote+2)); 375 | uint16_t prevWavelength = word(_getNoteHighByte(_currentNote-10),_getNoteLowByte(_currentNote-10)); 376 | 377 | uint16_t w = currentWavelength; 378 | 379 | 380 | 381 | if(_modWaveForm == MODWAVEFORM_SINE){ 382 | 383 | 384 | 385 | unsigned long x = micros() - _risingEdgeMicros; 386 | unsigned long period_micros = (unsigned long)2*(_LFOMillis*(unsigned long)1000); 387 | double multiplier = sin(((double)x/(double)period_micros)*(double)6.28318); 388 | 389 | if(_modMode == MOD_VIBRATO){ 390 | // float delta = (float)_volume; // for now, should be _volume, i believe.. 391 | // delta *= (float)_tremaloDepth/(float)100; 392 | // double offset = multiplier*(double)delta; 393 | 394 | // // oscillate _currentVolume from _volume-offset to _volume 395 | // uint8_t v = _volume; 396 | // v = (uint8_t)((double)_volume-offset); 397 | 398 | 399 | // if(v!=_currentVolume){ 400 | // _currentVolume = v; 401 | // if(_notesPressed > 0) 402 | // _sendWaveDataMessage(); 403 | // } 404 | }else{ 405 | float delta = (float)(prevWavelength - currentWavelength); 406 | delta *= (float)_modDepth/(float)100; 407 | double offset = multiplier*(double)delta; 408 | w = (uint16_t)((double)currentWavelength+offset); 409 | if(w!=_wavelength) _setWavelength(w,false); 410 | } 411 | 412 | 413 | 414 | } 415 | else if(_modWaveForm == MODWAVEFORM_SQUARE){ 416 | 417 | if(_modMode == MOD_VIBRATO){ 418 | float delta = (float)15; // for now, should be _volume, i believe.. 419 | delta *= (float)_modDepth/(float)100; 420 | 421 | uint8_t v = _volume; 422 | 423 | if(!_arpDirectionAscend){ 424 | v = (uint8_t)((float)_volume-delta); 425 | } 426 | //otherwise set volume back to normal 427 | 428 | 429 | if(v!=_currentVolume){ 430 | _currentVolume = v; 431 | if(_noteState != NOTESTATE_OFF) 432 | _sendWaveDataMessage(); 433 | } 434 | }else{ 435 | if(!_arpDirectionAscend){ 436 | float delta = (float)(prevWavelength - currentWavelength); 437 | delta *= (float)_modDepth/(float)100; 438 | w = (uint16_t)((float)currentWavelength+delta); 439 | } 440 | //otherwise wavelength stays at root note 441 | if(w!=_wavelength) _setWavelength(w,false); 442 | } 443 | 444 | }else if(_modWaveForm == MODWAVEFORM_SAW){ 445 | // do something 446 | }else if(_modWaveForm == MODWAVEFORM_NOISE){ 447 | 448 | float delta = (float)(prevWavelength - currentWavelength); 449 | float adjustedDelta = delta * (float)_modDepth/(float)100; 450 | float randomizedDelta = ((float)random(adjustedDelta*(float)100))/((float)100); 451 | 452 | w = (uint16_t)((float)currentWavelength)+randomizedDelta; 453 | 454 | if(_risingEdgeModFlag){ 455 | _risingEdgeModFlag = false; 456 | if(w!=_wavelength) 457 | _setWavelength(w,false); 458 | } 459 | } 460 | 461 | 462 | 463 | } 464 | } 465 | } 466 | 467 | 468 | 469 | bool WaveGen::_cycleCheck(unsigned long *lastMicros, unsigned long cycle) { 470 | unsigned long currentMicros = micros(); 471 | if(*lastMicros == 0 || currentMicros - *lastMicros >= cycle) { 472 | *lastMicros = currentMicros; 473 | return true; 474 | } 475 | else 476 | return false; 477 | } 478 | 479 | 480 | 481 | -------------------------------------------------------------------------------- /WaveGen.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_WAVEGEN_H_ 2 | #define LIB_WAVEGEN_H_ 3 | 4 | #include 5 | 6 | 7 | #define PIN_INTERRUPT 18 //pin 46 8 | 9 | //#define PIN_INTERRUPT 2 //Interrupt pin 10 | //#define PIN_LATCH 10 //SPI Latch Pin 11 | 12 | 13 | #define QUEUE_SIZE 10 14 | 15 | #define PITCHBENDRANGE 1 16 | 17 | #define NOTESTATE_OFF 0 18 | #define NOTESTATE_ATTACK 1 19 | #define NOTESTATE_SUSTAIN 2 20 | #define NOTESTATE_RELEASE 3 21 | 22 | #define ARPSTYLE_ASPLAYED 0 23 | #define ARPSTYLE_UP 1 24 | #define ARPSTYLE_DOWN 2 25 | #define ARPSTYLE_UPDOWN 3 26 | #define ARPSTYLE_CONVERGE 4 27 | #define ARPSTYLE_DIVERGE 5 28 | #define ARPSTYLE_CONVERGEDIVERGE 6 29 | #define ARPSTYLE_RANDOM 7 30 | 31 | #define LFOMODE_DISABLE 0 32 | #define LFOMODE_ARP 1 33 | #define LFOMODE_MOD 2 34 | 35 | #define MODWAVEFORM_SINE 1 36 | #define MODWAVEFORM_SQUARE 2 37 | #define MODWAVEFORM_SAW 3 38 | #define MODWAVEFORM_NOISE 4 39 | 40 | #define MOD_TREMALO 0 41 | #define MOD_VIBRATO 1 42 | 43 | class WaveGen { 44 | public: 45 | void handleNoteOn(byte,byte,byte); 46 | void handleNoteOff(byte,byte,byte); 47 | void handlePitchBend(byte,int); 48 | //protected: 49 | uint16_t _wavelength; 50 | uint8_t _currentNote; 51 | int _currentBend; 52 | int _notesPressed; 53 | uint8_t _noteQueue[QUEUE_SIZE]; 54 | 55 | uint8_t _noteQueue_UP[QUEUE_SIZE]; 56 | 57 | 58 | int _noteOffset; 59 | 60 | uint8_t _dutyCycleValue; //0-3 61 | uint8_t _volume; //0-15 62 | uint8_t _currentVolume; //0-15 63 | int _fineDetune; 64 | 65 | uint8_t _noteState; 66 | 67 | unsigned long _timer_applyAttack; 68 | unsigned long _cycle_applyAttack; 69 | 70 | unsigned long _timer_applyRelease; 71 | unsigned long _cycle_applyRelease; 72 | 73 | unsigned long _timer_applyMod; 74 | unsigned long _cycle_applyMod; 75 | 76 | uint8_t _LFOMode; 77 | unsigned long _LFOMillis; 78 | unsigned long _risingEdgeMicros; 79 | 80 | uint8_t _modDepth; 81 | uint8_t _modWaveForm; 82 | 83 | uint8_t _arpNoteQueuePosition; 84 | boolean _arpDirectionAscend; 85 | uint8_t _arpStyle; 86 | boolean _risingEdgeModFlag; 87 | boolean _fallingEdgeModFlag; 88 | 89 | uint8_t _modMode; 90 | 91 | void init(); 92 | 93 | void _sendAddrData(uint8_t,uint8_t); 94 | void _pushNoteOnQueue(uint8_t); 95 | void _removeNoteFromQueue(uint8_t); 96 | uint8_t _getLastNoteInQueue(); 97 | 98 | uint8_t _getNoteLowByte(uint8_t); 99 | uint8_t _getNoteHighByte(uint8_t); 100 | 101 | void _playNote(uint8_t); 102 | 103 | void _setDutyCycle(uint8_t); 104 | uint8_t _getDutyCycle(); 105 | void _setVolume(uint8_t); 106 | uint8_t _getVolume(); 107 | 108 | void _setAttack(uint8_t); 109 | void _setRelease(uint8_t); 110 | 111 | void _setNoteOffset(int); 112 | int _getNoteOffset(); 113 | //void _setFineDetune(uint8_t); 114 | //uint8_t _getFineDetune(); 115 | //uint16_t _getFineDetuneAmount(uint16_t); 116 | 117 | virtual void _setWavelength(uint16_t,bool); 118 | virtual void _stop(); 119 | virtual uint8_t _getWaveDataMessage(); 120 | virtual void _sendWaveDataMessage(); 121 | virtual void _applyAttack(); 122 | virtual void _applyRelease(); 123 | 124 | void _handleNoteStates(); 125 | 126 | 127 | void _applyMod(); 128 | 129 | //unsigned long _cycleCheck(unsigned long, unsigned long); 130 | bool _cycleCheck(unsigned long *, unsigned long); 131 | bool _cycleCheck_millis(unsigned long *, unsigned long); 132 | 133 | void _runLFO(); 134 | void setLFOMillis(unsigned long); 135 | unsigned long getModCycle(); 136 | void createSortedQueues(); 137 | }; 138 | 139 | #endif -------------------------------------------------------------------------------- /examples/NES_SynthShield_MIDI/NES_SynthShield_MIDI.ino: -------------------------------------------------------------------------------- 1 | #include "NES2A03.h" 2 | #include 3 | #include 4 | 5 | // The MIDI channel that will control each wave gen. 6 | // Default: all set to channel 1, all wave gens play in unison 7 | #define CHANNEL_REC1 1 8 | #define CHANNEL_REC2 1 9 | #define CHANNEL_TRI 1 10 | 11 | // Here is where you can configure which knob controls which parameter. 12 | #define CC_RECT1_DUTYCYCLE 0x4B //75 13 | #define CC_RECT2_DUTYCYCLE 0x4C //76 14 | #define CC_RECT1_VOLUME 0x5C //92 15 | #define CC_RECT2_VOLUME 0x5F //95 16 | #define CC_RECT1_ATTACK 0x0C //12 17 | #define CC_RECT2_ATTACK 0x0D //13 18 | 19 | #define MIDI_NOTE_OFFSET 21 20 | 21 | NES2A03 nes; 22 | 23 | void handleNoteOn(byte c,byte n,byte v){ 24 | if(n < MIDI_NOTE_OFFSET) return; 25 | 26 | if(v == 0){ 27 | handleNoteOff(c,n,v); //direct midi keyboard sends 0 velocity rather than note off command 28 | return; 29 | } 30 | 31 | //Use a series of ifs rather than a switch to support controlling multiple wave gens with the same midi channel 32 | if(c == CHANNEL_REC1) nes.rectangle1.handleNoteOn(c,n-MIDI_NOTE_OFFSET,v); 33 | if(c == CHANNEL_REC2) nes.rectangle2.handleNoteOn(c,n-MIDI_NOTE_OFFSET,v); 34 | if(c == CHANNEL_TRI) nes.triangle.handleNoteOn(c,n-MIDI_NOTE_OFFSET,v); 35 | 36 | } 37 | 38 | void handleNoteOff(byte c,byte n,byte v){ 39 | if(n < MIDI_NOTE_OFFSET) return; 40 | 41 | if(c == CHANNEL_REC1) nes.rectangle1.handleNoteOff(c,n-MIDI_NOTE_OFFSET,v); 42 | if(c == CHANNEL_REC2) nes.rectangle2.handleNoteOff(c,n-MIDI_NOTE_OFFSET,v); 43 | if(c == CHANNEL_TRI) nes.triangle.handleNoteOff(c,n-MIDI_NOTE_OFFSET,v); 44 | 45 | } 46 | 47 | void handlePitchBend(byte c,int b){ 48 | 49 | if(c == CHANNEL_REC1) nes.rectangle1.handlePitchBend(c,b); 50 | if(c == CHANNEL_REC2) nes.rectangle2.handlePitchBend(c,b); 51 | if(c == CHANNEL_TRI) nes.triangle.handlePitchBend(c,b); 52 | 53 | } 54 | 55 | void handleCC(byte channel, byte number, byte value){ 56 | switch(number){ 57 | case CC_RECT1_DUTYCYCLE: 58 | nes.rectangle1._setDutyCycle(value); 59 | break; 60 | case CC_RECT2_DUTYCYCLE: 61 | nes.rectangle2._setDutyCycle(value); 62 | break; 63 | case CC_RECT1_VOLUME: 64 | nes.rectangle1._setVolume(value); 65 | break; 66 | case CC_RECT2_VOLUME: 67 | nes.rectangle2._setVolume(value); 68 | break; 69 | case CC_RECT1_ATTACK: 70 | nes.rectangle1._setAttack(value); 71 | break; 72 | case CC_RECT2_ATTACK: 73 | nes.rectangle2._setAttack(value); 74 | break; 75 | default: 76 | break; 77 | } 78 | } 79 | 80 | 81 | // 82 | // Initiate MIDI communications 83 | // 84 | void MIDISetup(){ 85 | MIDI.begin(MIDI_CHANNEL_OMNI); // listen to any MIDI channel 86 | MIDI.turnThruOff(); // turn this off or else! 87 | MIDI.setHandleNoteOn(handleNoteOn); // attach handler for key presses 88 | MIDI.setHandleNoteOff(handleNoteOff); // attach handler for key releases 89 | MIDI.setHandlePitchBend(handlePitchBend); 90 | MIDI.setHandleControlChange(handleCC); 91 | } 92 | 93 | void setup() { 94 | nes.init(); 95 | MIDISetup(); 96 | 97 | //set some default values so we can hear something.. 98 | nes.rectangle1._setVolume(127); 99 | nes.rectangle2._setVolume(127); 100 | nes.triangle._setVolume(127); 101 | } 102 | void loop() { 103 | MIDI.read(); // Call MIDI.read the fastest you can for real-time performance. 104 | nes.run(); 105 | } 106 | 107 | 108 | 109 | --------------------------------------------------------------------------------