// Arduino YM2149 MIDI Synth v2.1a // Original Code by yukimizake // video: https://youtu.be/hUPs2pv5d_g // schematics: http://electronicfields.wordpress.com/?attachment_id=128 (Vcc/GND not shown) // New PCB and re-jig for 2022 by Obakegaku and Dan Suter. // Code re-factored in May 2022 as it would no longer compile in the latest version of Arduino IDE // OLED Added Oct 2022 // MIDI Channel 7 now works - Oct 2022 //Working on buttons and input interupts.... watch this space for v2.1b (b) for buttons... // contact webmidi@dansfing.uk https://dansfing.uk for help. // 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 #include //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE); U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE); //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; // Buttons on Analogue Pin A1 #define buttonPin A1 // analog input pin to use as a digital input //voicing byte noteA = 0; byte noteB = 0; byte noteC = 0; int periodA = 0; int periodB = 0; int periodC = 0; //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; //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; } } } int buttonPress; int last_buttonPress; // Stuff to blank the screen so it does not burn out. unsigned long startMillis; //some global variables available anywhere in the program unsigned long currentMillis; const unsigned long period = 120000; //2 minutes - the value is a number of milliseconds before screen blanks // Stuff to clear the sample play notication. unsigned long startMillisS; //some global variables available anywhere in the program unsigned long currentMillisS; const unsigned long periodS = 250; //1 second - the value is a number of milliseconds before sample notification blanks ////////////////////////////////////////////////////////// // I want to add another timer to listen for button presses or knob movement ////////////////////////////////////////////////////////// void setup(){ startMillis = millis(); //initial start time for screen blank startMillisS = millis(); //initial start time for sample blank u8x8.begin(); u8x8.setPowerSave(0); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.drawString(0,0,"v2.1a Oct 22"); u8x8.setFont(u8x8_font_px437wyse700a_2x2_r); //u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.drawString(1,2,"YM2149F"); //u8x8.drawString(4,4,"MIDI"); u8x8.drawString(3,6,"Synth"); //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); pinMode(buttonPin, INPUT); //digitalWrite(buttonPin, HIGH ); // __RGBLEDDDR__ |= ( 1 << __RLED__ | 1 << __GLED__ | 1 << __BLED__); //led pins as output resetYM(); AmaxVolume = defaultLevel; BmaxVolume = defaultLevel; CmaxVolume = defaultLevel; //serial init Serial.begin(31250); //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(); //say hello //This is the bleep played on power on. ////note/velo/channel playNote(60, 127, 4); delay(40); playNote(64, 127, 4); delay(40); playNote(67, 127, 4); delay(40); playNote(72, 127, 4); delay(40); playNote(73, 127, 4); delay(40); playNote(74, 127, 4); delay(40); playNote(75, 127, 4); delay(40); playNote(76, 127, 4); delay(40); playNote(77, 127, 4); delay(40); playNote(79, 127, 4); delay(40); playNote(80, 127, 4); delay(40); stopNote(80, 4); playDigidrum(60, 127);//Go Sample delay(4000); u8x8.clearDisplay(); // u8x8.clearLine(7); // u8x8.setCursor(0,7); // u8x8.print(analogRead(A1)); }//END OF SETUP // Main Loop void loop() { //u8x8.setCursor(0,7); //u8x8.print(analogRead(A1)); 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 { u8x8.clearDisplay(); //if so, change the state of the LED. Uses a neat trick to change the state startMillis = currentMillis; //IMPORTANT reset timer for screen. } currentMillisS = millis(); //get the current "time" (actually the number of milliseconds since the program started) if (currentMillisS - startMillisS >= periodS) //test whether the period has elapsed { u8x8.setCursor(13,0); u8x8.print(" "); u8x8.setCursor(13,1); u8x8.print(" "); u8x8.setCursor(13,2); u8x8.print(" "); u8x8.setCursor(13,3); u8x8.print(" "); u8x8.setCursor(13,4); u8x8.print(" "); startMillisS = currentMillisS; //IMPORTANT reset timer for screen. } byte command = getSerialByte(); byte commandMSB = command & 0xF0; byte midiChannel = command & 0x0F; if (commandMSB == 0x80) //Note off { byte note = getSerialByte(); getSerialByte(); //discard 3rd byte stopNote(note, midiChannel); } else if (commandMSB == 0x90) //Note on { byte note = getSerialByte(); byte velo = getSerialByte(); // Ifi ts Midi channel 10 we trigger samples using the playDigidrum(); function. if (velo != 0 && midiChannel == 0x09) playDigidrum(note, velo); else if (velo != 0) playNote(note, velo, midiChannel); else if (velo == 0) stopNote(note, midiChannel); } else if (commandMSB == 0xA0) // Key pressure { getSerialByte(); getSerialByte(); } else if (commandMSB == 0xB0) // Control change { byte controller = getSerialByte(); byte value = getSerialByte(); if (controller == 0x01) setDetune(value); if (controller == 0x07) setChannelVolume(value, midiChannel); } else if (commandMSB == 0xC0) // Program change { byte program = getSerialByte(); } else if (commandMSB == 0xD0) // Channel pressure { byte pressure = getSerialByte(); } else if (commandMSB == 0xE0) // Pitch bend { byte pitchBendLSB = getSerialByte(); byte pitchBendMSB = getSerialByte(); } } 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 == 0) { AmaxVolume = value; send_data(0x08, value); } else if (chan == 1) { BmaxVolume = value; send_data(0x09, value); } else if (chan == 2) { CmaxVolume = value; send_data(0x0A, value); } } void playNote(byte note, byte velo, byte chan) { if (note < 24) return; if (chan == 0)//MIDI Channel 1 { 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(); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.drawString(0,0,"[1]A"); u8x8.setCursor(7,0); u8x8.print(noteA); u8x8.setCursor(10,0); u8x8.print(AmaxVolume); startMillis = currentMillis;//reset screen timout } else if (chan == 1)//MIDI Channel 2 { 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(); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.drawString(0,1,"[2] B"); u8x8.setCursor(7,1); u8x8.print(noteB); u8x8.setCursor(10,1); u8x8.print(AmaxVolume); startMillis = currentMillis;//reset screen timout } else if (chan == 2)//MIDI Channel 3 { noteC = note; periodC = tp[note]; byte LSB = ( periodC & 0x00FF); byte MSB = ((periodC >> 8) & 0x000F); cli(); send_data(0x04, LSB); send_data(0x05, MSB); sei(); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.drawString(0,2,"[3] C"); u8x8.setCursor(7,2); u8x8.print(noteC); u8x8.setCursor(10,2); u8x8.print(AmaxVolume); startMillis = currentMillis;//reset screen timout } else if (chan == 3)//MIDI Channel 4 { if(mm3==0){ 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(); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.clearLine(3); u8x8.drawString(0,3,"[4]AB"); u8x8.setCursor(7,3); u8x8.print(noteA); u8x8.setCursor(10,3); u8x8.print(AmaxVolume); startMillis = currentMillis;//reset screen timout } } else if (chan == 4)//MIDI Channel 5 { 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(); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.clearLine(4); u8x8.drawString(0,4,"[5]ABC"); u8x8.setCursor(7,4); u8x8.print(noteA); startMillis = currentMillis;//reset screen timout } else if (chan == 5)//MIDI Channel 6 { 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, 0b00001000); sei(); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.clearLine(5); u8x8.drawString(0,5,"[6]A"); u8x8.setCursor(7,5); u8x8.print(noteA); startMillis = currentMillis;//reset screen timout } else if (chan == 6)//MIDI Channel 7 { 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, 0b00001000); sei(); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.clearLine(6); u8x8.drawString(0,6,"[7]A"); u8x8.setCursor(7,6); u8x8.print(noteA); startMillis = currentMillis;//reset screen timout } else if (chan == 7)//MIDI Channel 8 { 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, 0b00001100); sei(); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.clearLine(7); u8x8.drawString(0,7,"[8]AB"); u8x8.setCursor(7,7); u8x8.print(noteA); startMillis = currentMillis;//reset screen timout } } void stopNote(byte note, byte chan) { if (chan == 0 && note == noteA) { noteA = periodA = 0; cli(); send_data(0x00, 0); send_data(0x01, 0); sei(); } else if (chan == 1 && note == noteB) { noteB = periodB = 0; cli(); send_data(0x02, 0); send_data(0x03, 0); sei(); } else if (chan == 2 && note == noteC) { noteC = periodC = 0; cli(); send_data(0x04, 0); send_data(0x05, 0); sei(); } else if (chan == 3 && note == noteA) { 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 == 4 && note == noteA) { 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 == 5 && note == noteA) { noteA = periodA = 0; cli(); send_data(0x0D, 0); send_data(0x08, AmaxVolume); sei(); } else if (chan == 6 && note == noteA) { noteA = periodA = 0; cli(); send_data(0x0D, 0); send_data(0x08, AmaxVolume); sei(); } else if (chan == 7 && note == noteA) { 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 == 64) { cli(); sampleOffset = s0; sampleLength = s0Length; sampleCounter = 0; sei(); u8x8.setCursor(13,0); u8x8.print("[1]"); startMillisS = currentMillisS; //IMPORTANT reset timer for screen. } else if (index == 63) { cli(); sampleOffset = s1; sampleLength = s1Length; sampleCounter = 0; sei(); u8x8.setCursor(13,1); u8x8.print("[2]"); startMillisS = currentMillisS; //IMPORTANT reset timer for screen. } else if (index == 62) { cli(); sampleOffset = s2; sampleLength = s2Length; sampleCounter = 0; sei(); u8x8.setCursor(13,2); u8x8.print("[3]"); startMillisS = currentMillisS; //IMPORTANT reset timer for screen. } else if (index == 61) { cli(); sampleOffset = s3; sampleLength = s3Length; sampleCounter = 0; sei(); u8x8.setCursor(13,3); u8x8.print("[4]"); startMillisS = currentMillisS; //IMPORTANT reset timer for screen. } else if (index == 60) { cli(); sampleOffset = s4; sampleLength = s4Length; sampleCounter = 0; sei(); u8x8.setCursor(13,4); u8x8.print("[5]"); startMillisS = currentMillisS; //IMPORTANT reset timer for screen. } } 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) { 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(); }