///////////////////////// Feb 2023 ////////////////////////////////////////////////////////////////////////////// // This is an example by dansfing.uk that uses the MIDI.h Library // This should make it easier to add some other nice features like a MIDI through socket. // Better realtime control of input on the front panel // I have abandoned sample playback for the moment. // Cheers Dan 9th Feb 2023 /////////////////////////////////////////////////////////////////////////////////////////////////////// // Arduino YM2149 MIDI Synth // by yukimizake // video: https://youtu.be/hUPs2pv5d_g // schematics: http://electronicfields.wordpress.com/?attachment_id=128 (Vcc/GND not shown) // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include #include //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE); U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE); // Buttons on Analogue Pin A3 #define buttonPin A3 // analog input pin to use as a digital input // Potentiometer Pin A0 #define potPin A0 // analog input pin to use as a 0 to 127 variable int A3val=0; int A0val=0; MIDI_CREATE_DEFAULT_INSTANCE(); //Port settings const int ad0 = 8; const int ad1 = 9; const int ad2 = 2; const int ad3 = 3; const int ad4 = 4; const int ad5 = 5; const int ad6 = 6; const int ad7 = 7; const int pinBC1 = 10; const int pinBDIR = 11; //BC2 to +5V const int pinYMReset = 12; //voicing byte noteA = 0; byte noteB = 0; byte noteC = 0; int periodA = 0; int periodB = 0; int periodC = 0; //envelope byte AmaxVolume = 0; byte BmaxVolume = 0; byte CmaxVolume = 0; //unison detune int detuneValue = 1; //arpeggio settings byte arpeggio[] = {0,7,12}; byte arpeggioLength = 3; byte arpeggioCounter = 0; boolean arpeggioFlipMe = false; byte defaultLevel = 10; //Fast pin switching macros #define CLR(x,y) (x&=(~(1< 0) { periodB = tp[noteB + arpeggio[arpeggioCounter]]; byte LSB = ( periodB & 0x00FF); byte MSB = ((periodB >> 8) & 0x000F); send_data(0x02, LSB); send_data(0x03, MSB); arpeggioCounter++; if (arpeggioCounter == arpeggioLength) arpeggioCounter = 0; } } } //Current Channel int currentC=1; //MIDI MUTES int mm0=0; int mm1=0; int mm2=0; int mm3=0; int mm4=0; int mm5=0; int mm6=0; int mm7=0; //Button status - pressed or not //MUTE Button int muteP=0; //STORE Button int storeP=0; //Button debounce timers unsigned long startMillis; //some global variables available anywhere in the program unsigned long currentMillis; const unsigned long period = 175; // MUTE debounce timer unsigned long startMillisM; //some global variables available anywhere in the program unsigned long currentMillisM; const unsigned long periodM = 500; //the value is a number of milliseconds void setup(){ startMillisM = millis(); //initial start time for MUTE debounce //init pins pinMode(ad0, OUTPUT); pinMode(ad1, OUTPUT); pinMode(ad2, OUTPUT); pinMode(ad3, OUTPUT); pinMode(ad4, OUTPUT); pinMode(ad5, OUTPUT); pinMode(ad6, OUTPUT); pinMode(ad7, OUTPUT); pinMode(pinBC1, OUTPUT); pinMode(pinBDIR, OUTPUT); pinMode(pinYMReset, OUTPUT); pinMode(ledPin, OUTPUT); __RGBLEDDDR__ |= ( 1 << __RLED__ | 1 << __GLED__ | 1 << __BLED__); //led pins as output resetYM(); AmaxVolume = defaultLevel; BmaxVolume = defaultLevel; CmaxVolume = defaultLevel; //serial init //timer1 : sample player cli(); TCCR1A = 0; //timer reset TCCR1B = 0; //timer reset OCR1A = 1450; //period for 11025 kHz at 16Mhz TCCR1B |= (1 << WGM12); //CTC mode TCCR1B |= (1 << CS10); // timer ticks = clock ticks TIMSK1 |= (1 << OCIE1A); // enable compare sei(); // Connect the handleNoteOn function to the library, // so it is called upon reception of a NoteOn. MIDI.setHandleNoteOn(handleNoteOn); // Put only the name of the function // Do the same for NoteOffs MIDI.setHandleNoteOff(handleNoteOff); // Initiate MIDI communications, listen to all channels MIDI.begin(MIDI_CHANNEL_OMNI); ////////read stored mutes from eeprom //// If nothing is stored in eeprom we make it unmuted. // byte mmS0b; byte mmS0b = EEPROM.read(1); mm0 = int(mmS0b); if ( mm0==0 || mm0==1 ) {} else {EEPROM.write(1, 0); mm0=0;} byte mmS1b = EEPROM.read(2); mm1 = int(mmS1b); if ( mm1==0 || mm1==1 ) {} else {EEPROM.write(2, 0); mm1=0;} byte mmS2b = EEPROM.read(3); mm2 = int(mmS2b); if ( mm2==0 || mm2==1 ) {} else {EEPROM.write(3, 0); mm2=0;} byte mmS3b = EEPROM.read(4); mm3 = int(mmS3b); if ( mm3==0 || mm3==1 ) {} else {EEPROM.write(4, 0); mm3=0;} byte mmS4b = EEPROM.read(5); mm4 = int(mmS4b); if ( mm4==0 || mm4==1 ) {} else {EEPROM.write(5, 0); mm4=0;} byte mmS5b = EEPROM.read(6); mm5 = int(mmS5b); if ( mm5==0 || mm5==1 ) {} else {EEPROM.write(6, 0); mm5=0;} byte mmS6b = EEPROM.read(7); mm6 = int(mmS6b); if ( mm6==0 || mm6==1 ) {} else {EEPROM.write(7, 0); mm6=0;} byte mmS7b = EEPROM.read(8); mm7 = int(mmS7b); if ( mm7==0 || mm7==1 ) {} else {EEPROM.write(8, 0); mm7=0;} //say hello playNote(60, 127, 5); delay(40); playNote(64, 127, 5); delay(40); playNote(67, 127, 5); delay(40); playNote(72, 127, 5); delay(40); stopNote(72, 5); playDigidrum(52, 127); u8x8.begin(); u8x8.setPowerSave(0); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.drawString(4,0,"YM2149F"); u8x8.drawString(4,2,"MIDI.h"); u8x8.drawString(1,4,"v2.1b Feb 2023"); u8x8.drawString(3,6,"dansfing.uk"); pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT); pinMode(potPin, INPUT); delay(4000); u8x8.clearDisplay(); showMutes(); } void handleNoteOn(byte channel, byte pitch, byte velocity) { // Do whatever you want when a note is pressed. if(channel!=10){ playNote(pitch, 127, channel);} if(channel==1){ u8x8.setCursor(4,0); u8x8.print(pitch); u8x8.setCursor(7,0); u8x8.print("A"); } if(channel==2){ u8x8.setCursor(4,1); u8x8.print(pitch); u8x8.setCursor(7,1); u8x8.print("B"); } if(channel==3){ u8x8.setCursor(4,2); u8x8.print(pitch); u8x8.setCursor(7,2); u8x8.print("C"); } if(channel==4){ u8x8.setCursor(4,3); u8x8.print(pitch); u8x8.setCursor(7,3); u8x8.print("AB");} if(channel==5){ u8x8.setCursor(4,4); u8x8.print(pitch); u8x8.setCursor(7,4); u8x8.print("ABC");} if(channel==6){ u8x8.setCursor(4,5); u8x8.print(pitch); u8x8.setCursor(7,5); u8x8.print("A");} if(channel==7){ u8x8.setCursor(4,6); u8x8.print(pitch); u8x8.setCursor(7,6); u8x8.print("A");} if(channel==8){ u8x8.setCursor(4,7); u8x8.print(pitch); u8x8.setCursor(7,7); u8x8.print("AB");} // Sample playpack on channel 10 if(channel==10){ playDigidrum(pitch, 127); } // Try to keep your callbacks short (no delays ect) // otherwise it would slow down the loop() and have a bad impact // on real-time performance. } void handleNoteOff(byte channel, byte pitch, byte velocity) { stopNote(pitch, channel); // Do something when the note is released. // Note that NoteOn messages with 0 velocity are interpreted as NoteOffs. } void showMutes(){ u8x8.setCursor(9,0); if(mm0==1){u8x8.print(" M");}else{u8x8.print(" _");} u8x8.setCursor(9,1); if(mm1==1){u8x8.print(" M");}else{u8x8.print(" _");} u8x8.setCursor(9,2); if(mm2==1){u8x8.print(" M");}else{u8x8.print(" _");} u8x8.setCursor(9,3); if(mm3==1){u8x8.print(" M");}else{u8x8.print(" _");} u8x8.setCursor(9,4); if(mm4==1){u8x8.print(" M");}else{u8x8.print(" _");} u8x8.setCursor(9,5); if(mm5==1){u8x8.print(" M");}else{u8x8.print(" _");} u8x8.setCursor(9,6); if(mm6==1){u8x8.print(" M");}else{u8x8.print(" _");} u8x8.setCursor(9,7); if(mm7==1){u8x8.print(" M");}else{u8x8.print(" _");} u8x8.setCursor(0,0); u8x8.print("[1]"); u8x8.setCursor(0,1); u8x8.print("[2]"); u8x8.setCursor(0,2); u8x8.print("[3]"); u8x8.setCursor(0,3); u8x8.print("[4]"); u8x8.setCursor(0,4); u8x8.print("[5]"); u8x8.setCursor(0,5); u8x8.print("[6]"); u8x8.setCursor(0,6); u8x8.print("[7]"); u8x8.setCursor(0,7); u8x8.print("[8]"); u8x8.setCursor(13,7); u8x8.print('('); u8x8.print(currentC); u8x8.print(')'); } int CHs=0; int detuneKnob=0; int detuneKnobL=0; int detune10=0; void loop() { currentMillisM = millis(); //get the current "time" for the MUTE debounce // Call MIDI.read the fastest you can for real-time performance. MIDI.read(); //////////////// TIMERS///////////////////////////////////////////////////////////////////////// currentMillis = millis(); //get the current "time" (actually the number of milliseconds since the program started) if (currentMillis - startMillis >= period) //test whether the period has elapsed { CHs=0; muteP=0; storeP=0; detuneKnob=analogRead(A0); detune10=detuneKnob/8; ///Maybe alter detune with knob if (detune10!=detuneKnobL){detuneKnobL=detune10;} else { //detune10=detuneKnob/100; u8x8.setCursor(13,5); u8x8.print(" "); u8x8.setCursor(13,5); u8x8.print(detune10); setChannelVolume(detune10 , currentC); } startMillis = currentMillis; //IMPORTANT reset timer for screen. } //Check CH Button if (analogRead(A3)>=929 && analogRead(A3)<=936 && CHs==0 && currentMillisM - startMillisM >= periodM){ if(currentC>=8){ currentC=0; } else { currentC++; CHs=1; u8x8.setCursor(13,7); u8x8.print('('); u8x8.print(currentC); u8x8.print(')'); } startMillisM = currentMillisM; } //Check Store Button if (analogRead(A3)>=990 && analogRead(A3)<=996){ storeP=1; } else {storeP=0;} //Check Mute Button if (analogRead(A3)>=1011 && analogRead(A3)<=1016 && muteP==0){ muteP=1; } else {} if(currentC==1 && muteP==1 && currentMillisM - startMillisM >= periodM){ if( mm0==0){mm0=1; showMutes(); muteP=0; } else {mm0=0; showMutes(); muteP=0; } startMillisM = currentMillisM; } if(currentC==2 && muteP==1 && currentMillisM - startMillisM >= periodM){ if( mm1==0){mm1=1; showMutes(); } else {mm1=0; showMutes(); } startMillisM = currentMillisM; } if(currentC==3 && muteP==1 && currentMillisM - startMillisM >= periodM){ if( mm2==0){mm2=1; showMutes(); } else {mm2=0; showMutes();} startMillisM = currentMillisM; } if(currentC==4 && muteP==1 && currentMillisM - startMillisM >= periodM){ if( mm3==0){mm3=1; showMutes(); } else {mm3=0; showMutes(); } startMillisM = currentMillisM; } if(currentC==5 && muteP==1 && currentMillisM - startMillisM >= periodM){ if( mm4==0){mm4=1; showMutes(); } else {mm4=0; showMutes(); } startMillisM = currentMillisM; } if(currentC==6 && muteP==1 && currentMillisM - startMillisM >= periodM){ if( mm5==0){mm5=1; showMutes(); } else {mm5=0; showMutes(); } startMillisM = currentMillisM; } if(currentC==7 && muteP==1 && currentMillisM - startMillisM >= periodM){ if( mm6==0){mm6=1; showMutes(); } else {mm6=0; showMutes(); } startMillisM = currentMillisM; } if(currentC==8 && muteP==1 && currentMillisM - startMillisM >= periodM){ if( mm7==0){mm7=1; showMutes(); } else {mm7=0; showMutes();} startMillisM = currentMillisM; } if(currentC==1 && storeP==1 && currentMillisM - startMillisM >= periodM){ EEPROM.write(currentC, mm0); startMillisM = currentMillisM; } if(currentC==2 && storeP==1 && currentMillisM - startMillisM >= periodM){ EEPROM.write(currentC, mm1); startMillisM = currentMillisM; } if(currentC==3 && storeP==1 && currentMillisM - startMillisM >= periodM){ EEPROM.write(currentC, mm2); startMillisM = currentMillisM; } if(currentC==4 && storeP==1 && currentMillisM - startMillisM >= periodM){ EEPROM.write(currentC, mm3); startMillisM = currentMillisM; } if(currentC==5 && storeP==1 && currentMillisM - startMillisM >= periodM){ EEPROM.write(currentC, mm4); startMillisM = currentMillisM; } if(currentC==6 && storeP==1 && currentMillisM - startMillisM >= periodM){ EEPROM.write(currentC, mm5); startMillisM = currentMillisM; } if(currentC==7 && storeP==1 && currentMillisM - startMillisM >= periodM){ EEPROM.write(currentC, mm6); startMillisM = currentMillisM; } if(currentC==8 && storeP==1 && currentMillisM - startMillisM >= periodM){ EEPROM.write(currentC, mm7); startMillisM = currentMillisM; } } void setDetune(byte value) { detuneValue = (value >> 3) & 0x0F; //downscaling to 4 bits } void setChannelVolume(byte value, byte chan) { value = (value >> 3) & 0x0F; //downscaling to 4 bits if (chan == 1) { AmaxVolume = value; send_data(0x08, value); } else if (chan == 2) { BmaxVolume = value; send_data(0x09, value); } else if (chan == 3) { CmaxVolume = value; send_data(0x0A, value); } } void playNote(byte note, byte velo, byte chan) { if (note < 24) return; if (chan == 1 && mm0 == 0) { SET(__RGBLEDPORT__,__RLED__); noteA = note; periodA = tp[note]; byte LSB = ( periodA & 0x00FF); byte MSB = ((periodA & 0x0F00) >> 8); cli(); send_data(0x00, LSB); send_data(0x01, MSB); send_data(0x08, AmaxVolume); //can be set to 0 by envelope mode note off sei(); } else if (chan == 2 && mm1 == 0) { SET(__RGBLEDPORT__,__GLED__); noteB = note; periodB = tp[note]; byte LSB = ( periodB & 0x00FF); byte MSB = ((periodB >> 8) & 0x000F); cli(); arpeggioCounter = 0; //arpeggio reset send_data(0x02, LSB); send_data(0x03, MSB); sei(); } else if (chan == 3 && mm2 == 0) { SET(__RGBLEDPORT__,__BLED__); noteC = note; periodC = tp[note]; byte LSB = ( periodC & 0x00FF); byte MSB = ((periodC >> 8) & 0x000F); cli(); send_data(0x04, LSB); send_data(0x05, MSB); sei(); } else if (chan == 4 && mm3 == 0) { SET(__RGBLEDPORT__,__RLED__); SET(__RGBLEDPORT__,__GLED__); noteA = note; noteB = note; periodA = tp[note]; periodB = tp[note] + detuneValue; byte ALSB = ( periodA & 0x00FF); byte AMSB = ((periodA >> 8) & 0x000F); byte BLSB = ( periodB & 0x00FF); byte BMSB = ((periodB >> 8) & 0x000F); cli(); send_data(0x00, ALSB); send_data(0x01, AMSB); send_data(0x02, BLSB); send_data(0x03, BMSB); sei(); } else if (chan == 5 && mm4 == 0) { SET(__RGBLEDPORT__,__RLED__); SET(__RGBLEDPORT__,__GLED__); SET(__RGBLEDPORT__,__BLED__); noteA = note; noteB = note; noteC = note; periodA = tp[note - 12]; periodB = tp[note] + detuneValue; periodC = tp[note] - detuneValue; byte ALSB = ( periodA & 0x00FF); byte AMSB = ((periodA >> 8) & 0x000F); byte BLSB = ( periodB & 0x00FF); byte BMSB = ((periodB >> 8) & 0x000F); byte CLSB = ( periodC & 0x00FF); byte CMSB = ((periodC >> 8) & 0x000F); cli(); send_data(0x00, ALSB); send_data(0x01, AMSB); send_data(0x02, BLSB); send_data(0x03, BMSB); send_data(0x04, CLSB); send_data(0x05, CMSB); sei(); } else if (chan == 6 && mm5 == 0) { SET(__RGBLEDPORT__,__RLED__); noteA = note; periodA = envTp[note]; byte LSB = ( periodA & 0x00FF); byte MSB = ((periodA >> 8) & 0x000F); cli(); send_data(0x08, 0x10); send_data(0x0B, LSB); send_data(0x0C, MSB); send_data(0x0D, 0b00001110); sei(); } else if (chan == 7 && mm6 == 0) { SET(__RGBLEDPORT__,__RLED__); noteA = note; periodA = envTp[note]; byte LSB = ( periodA & 0x00FF); byte MSB = ((periodA >> 8) & 0x000F); cli(); send_data(0x08, 0x10); send_data(0x0B, LSB); send_data(0x0C, MSB); send_data(0x0D, 0b00001110); sei(); } else if (chan == 8 && mm7 == 0) { SET(__RGBLEDPORT__,__RLED__); SET(__RGBLEDPORT__,__GLED__); noteA = note; noteB = note; periodA = envTp[note]; periodB = (tp[note - 12] + detuneValue) << 1; byte LSB = ( periodA & 0x00FF); byte MSB = ((periodA >> 8) & 0x000F); byte BLSB = ( periodB & 0x00FF); byte BMSB = ((periodB >> 8) & 0x000F); cli(); send_data(0x02, BLSB); send_data(0x03, BMSB); send_data(0x08, 0x10); //envelope mode on send_data(0x0B, LSB); send_data(0x0C, MSB); send_data(0x0D, 0b00001000); sei(); } } void stopNote(byte note, byte chan) { if (chan == 1 && note == noteA) { CLR(__RGBLEDPORT__,__RLED__); noteA = periodA = 0; cli(); send_data(0x00, 0); send_data(0x01, 0); sei(); } else if (chan == 2 && note == noteB) { CLR(__RGBLEDPORT__,__GLED__); noteB = periodB = 0; cli(); send_data(0x02, 0); send_data(0x03, 0); sei(); } else if (chan == 3 && note == noteC) { CLR(__RGBLEDPORT__,__BLED__); noteC = periodC = 0; cli(); send_data(0x04, 0); send_data(0x05, 0); sei(); } else if (chan == 4 && note == noteA) { CLR(__RGBLEDPORT__,__RLED__); CLR(__RGBLEDPORT__,__GLED__); noteA = periodA = 0; noteA = periodB = 0; cli(); send_data(0x00, 0); send_data(0x01, 0); send_data(0x02, 0); send_data(0x03, 0); sei(); } else if (chan == 5 && note == noteA) { CLR(__RGBLEDPORT__,__RLED__); CLR(__RGBLEDPORT__,__GLED__); CLR(__RGBLEDPORT__,__BLED__); noteA = periodA = 0; noteB = periodB = 0; noteC = periodC = 0; cli(); send_data(0x00, 0); send_data(0x01, 0); send_data(0x02, 0); send_data(0x03, 0); send_data(0x04, 0); send_data(0x05, 0); sei(); } else if (chan == 6 && note == noteA) { CLR(__RGBLEDPORT__,__RLED__); noteA = periodA = 0; cli(); send_data(0x0D, 0); send_data(0x08, AmaxVolume); sei(); } else if (chan == 7 && note == noteA) { CLR(__RGBLEDPORT__,__RLED__); noteA = periodA = 0; cli(); send_data(0x0D, 0); send_data(0x08, AmaxVolume); sei(); } else if (chan == 8 && note == noteA) { CLR(__RGBLEDPORT__,__RLED__); CLR(__RGBLEDPORT__,__GLED__); noteA = periodA = 0; noteB = periodB = 0; cli(); send_data(0x02, 0); send_data(0x03, 0); send_data(0x0D, 0); send_data(0x08, AmaxVolume); sei(); } } void playDigidrum(byte index, byte velo) { if (index == 60) { cli(); SET(__RGBLEDPORT__,__BLED__); sampleOffset = s0; sampleLength = s0Length; sampleCounter = 0; sei(); } else if (index == 62) { cli(); SET(__RGBLEDPORT__,__BLED__); sampleOffset = s1; sampleLength = s1Length; sampleCounter = 0; sei(); } else if (index == 64) { cli(); SET(__RGBLEDPORT__,__BLED__); sampleOffset = s2; sampleLength = s2Length; sampleCounter = 0; sei(); } else if (index == 65) { cli(); SET(__RGBLEDPORT__,__BLED__); sampleOffset = s3; sampleLength = s3Length; sampleCounter = 0; sei(); } else if (index == 67) { cli(); SET(__RGBLEDPORT__,__BLED__); sampleOffset = s4; sampleLength = s4Length; sampleCounter = 0; sei(); } } void resetYM() { digitalWrite(pinYMReset, LOW); digitalWrite(pinYMReset, HIGH); delay(1); send_data(0x07, 0b00111000); for (byte i=0; i <= defaultLevel; i++) { send_data(0x08, i); send_data(0x09, i); send_data(0x0A, i); delay(1); } } void send_data(unsigned char address, unsigned char data) { SET(__LEDPORT__,__LED__); boolean value[8]; //put address in a 8-bit array for (int i; i < 8; i++) { value[i] = ((0x01 & address) == 1); address = address >> 1; } //write address to pins outputToYM(value); //validate addess __BCPORT__ |= (1 << __BDIR__) | (1 << __BC1__); delayMicroseconds(1); __BCPORT__ &= ~((1 << __BDIR__) | (1 << __BC1__)); //put data in a 8-bit array for (int i; i < 8; i++) { value[i] = ((0x01 & data) == 1); data = data >> 1; } //write data to pins outputToYM(value); //validate data SET(__BCPORT__,__BDIR__); delayMicroseconds(1); CLR(__BCPORT__,__BDIR__); CLR(__LEDPORT__,__LED__); } void outputToYM(boolean value[]) { value[0] ? SET(PORTB, 0) : CLR(PORTB, 0); value[1] ? SET(PORTB, 1) : CLR(PORTB, 1); value[2] ? SET(PORTD, 2) : CLR(PORTD, 2); value[3] ? SET(PORTD, 3) : CLR(PORTD, 3); value[4] ? SET(PORTD, 4) : CLR(PORTD, 4); value[5] ? SET(PORTD, 5) : CLR(PORTD, 5); value[6] ? SET(PORTD, 6) : CLR(PORTD, 6); value[7] ? SET(PORTD, 7) : CLR(PORTD, 7); } byte getSerialByte() { while(Serial.available() < 1) __asm__("nop\n\t"); return Serial.read(); }