/ Arcade_Synth_Controller / Arcade_Synth_Controller.ino
Arcade_Synth_Controller.ino
1 // SPDX-FileCopyrightText: 2022 John Park and Tod Kurt for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 // Arcade Synth Controller II: Son of Pianocade -- The Enriffening 6 // written by John Park and Tod Kurt 7 // Synthesizer/MIDI arpeggiator for with multiple LED Arcade boards & joystick input 8 9 // Arpy library: https://github.com/todbot/mozzi_experiments/blob/main/eighties_arp/Arpy.h 10 // midi_to_freq and ADT patch: https://github.com/todbot/tal_experiments/tree/main/arpy_test 11 12 // - to do: when arp is off it acts as a normal keyboard. 13 14 #include <Arduino.h> 15 #include <Adafruit_TinyUSB.h> 16 #include <MIDI.h> 17 #include <Audio.h> 18 #include <Bounce2.h> 19 #include "Adafruit_seesaw.h" 20 21 #include "ADT.h" 22 #include "midi_to_freq.h" 23 #include "Arpy.h" 24 25 // ----- LED Arcade 1x4 STEMMA QT board pins----- 26 // pin definitions on each LED Arcade 1x4 27 #define SWITCH1 18 // PA01 28 #define SWITCH2 19 // PA02 29 #define SWITCH3 20 // PA03 30 #define SWITCH4 2 // PA04 31 #define PWM1 12 // PC00 32 #define PWM2 13 // PC01 33 #define PWM3 0 // PA04 34 #define PWM4 1 // PA05 35 36 #define I2C_BASE_ADDR 0x3A // boards are in order, 0x3A, 0x3B, 0x3C, 0x3D 37 #define NUM_BOARDS 4 38 39 Adafruit_seesaw ledArcades[ NUM_BOARDS ]; 40 41 //----- board variables 42 int boardNum = 0; //used to read each board 43 int switchNum = 0; //used to read each switch 44 int boardSwitchNum = 0; //unique button ID accross all boards/buttons 45 int led_low = 10; //min pwm brightness 46 int led_med = 60; 47 int led_high = 220; // max brightness 48 49 bool lastButtonState[16] ; 50 bool currentButtonState[16] ; 51 52 //-----joystick pins----- 53 const int joyDownPin = 11; //down 54 const int joyUpPin = 12; // up 55 const int joyLeftPin = 9; // left 56 const int joyRightPin = 10; //right 57 const int joyGroundPin = 6; //"fake" ground pin 58 59 //-----joystick debouncer 60 Bounce joyDown = Bounce(); 61 Bounce joyUp = Bounce(); 62 Bounce joyLeft = Bounce(); 63 Bounce joyRight = Bounce(); 64 65 66 //-----MIDI instances----- 67 Adafruit_USBD_MIDI usb_midi; 68 MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDIusb); // USB MIDI 69 MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDIclassic); // classic midi over RX/TX 70 71 //-----Audio Library Syth parameters 72 #define NUM_VOICES 4 73 74 AudioSynthWaveform *waves[] = { 75 &wave0, &wave1, &wave2, &wave3 76 }; 77 78 int filterf_max = 6000; 79 int filterf = filterf_max; 80 81 uint32_t lastControlMillis=0; 82 83 uint8_t arp_octaves = 1; 84 uint8_t root_note = 0; 85 86 //----- create arpy arpeggiator 87 Arpy arp = Arpy(); 88 89 int bpm = 160; 90 int octave_offset = 3; // initially starts on MIDI note 36 with the offset of 3 octaves from zero 91 bool arp_on_off_state; 92 93 void setup() { 94 Wire.setClock(400000); 95 //----- MIDI and Serial setup----- 96 // 97 MIDIusb.begin(MIDI_CHANNEL_OMNI); 98 MIDIclassic.begin(MIDI_CHANNEL_OMNI); 99 Serial.begin(115200); 100 MIDIusb.turnThruOff(); 101 delay(2000); // it's hard getting started in the morning 102 Serial.println("[.::.:::.] Welcome to Arcade Synth Controller II: Son of Pianocade -- The Enriffening [.::.:::.]"); 103 Serial.println("MIDI USB/Classic and Serial have begun"); 104 //----- end MIDI and Serial setup----- 105 106 //----- joystick pins setup----- 107 // 108 pinMode( joyDownPin, INPUT); 109 pinMode( joyUpPin, INPUT); 110 pinMode( joyLeftPin, INPUT); 111 pinMode( joyRightPin, INPUT); 112 pinMode( joyGroundPin, OUTPUT); 113 114 joyDown.attach( joyDownPin, INPUT_PULLUP); 115 joyUp.attach( joyUpPin, INPUT_PULLUP); 116 joyLeft.attach( joyLeftPin, INPUT_PULLUP); 117 joyRight.attach( joyRightPin, INPUT_PULLUP); 118 digitalWrite(joyGroundPin, LOW); 119 //----- end joystick pins setup----- 120 121 //----- LED Arcade 1x4 setup----- 122 // 123 for ( int i = 0; i < NUM_BOARDS; i++ ) { 124 if ( !ledArcades[i].begin( I2C_BASE_ADDR + i ) ) { 125 Serial.println(F("LED Arcade not found!")); 126 while (1) delay(10); 127 } 128 } 129 Serial.println(F("LED Arcade boards started")); 130 131 for ( int i = 0; i < NUM_BOARDS; i++ ) { 132 ledArcades[i].pinMode(SWITCH1, INPUT_PULLUP); 133 ledArcades[i].pinMode(SWITCH2, INPUT_PULLUP); 134 ledArcades[i].pinMode(SWITCH3, INPUT_PULLUP); 135 ledArcades[i].pinMode(SWITCH4, INPUT_PULLUP); 136 ledArcades[i].analogWrite(PWM1, led_low); 137 ledArcades[i].analogWrite(PWM2, led_low); 138 ledArcades[i].analogWrite(PWM3, led_low); 139 ledArcades[i].analogWrite(PWM4, led_low); 140 } 141 // brighten default root note 142 ledArcades[0].analogWrite(PWM1, led_high); 143 // turn down brightness of the function buttons 144 ledArcades[3].analogWrite(PWM2, 0); 145 ledArcades[3].analogWrite(PWM3, led_low); 146 ledArcades[3].analogWrite(PWM4, led_low); 147 //----- end LED Arcade 1x4 setup----- 148 149 150 //-----Arpy setup----- 151 // 152 arp.setNoteOnHandler(noteOn); 153 arp.setNoteOffHandler(noteOff); 154 arp.setRootNote( root_note ); 155 arp.setOctaveOffset(octave_offset); 156 arp.setBPM( bpm ); 157 arp.setGateTime( 0.75 ); // percentage of bpm 158 arp.off(); 159 160 //----- Audio Library Synth setup----- 161 // (patch is saved in ADT.h file) 162 AudioMemory(120); 163 164 filter0.frequency(filterf_max); 165 filter0.resonance(0.5); 166 167 env0.attack(10); 168 env0.hold(2); 169 env0.decay(100); 170 env0.sustain(0.5); 171 env0.release(100); 172 173 // Initialize processor and memory measurements 174 AudioProcessorUsageMaxReset(); 175 AudioMemoryUsageMaxReset(); 176 177 Serial.println("Arpy setup done"); 178 179 } // end setup() 180 181 182 int waveform = WAVEFORM_SQUARE; 183 184 185 void noteOn(uint8_t note) { 186 waves[0]->begin( 0.9, tune_frequencies2_PGM[note], waveform); 187 waves[1]->begin( 0.9, tune_frequencies2_PGM[note] * 1.01, waveform); // detune 188 waves[2]->begin( 0.9, tune_frequencies2_PGM[note] * 1.005, waveform); // detune 189 waves[3]->begin( 0.9, tune_frequencies2_PGM[note] * 1.025, waveform); // detune 190 filterf = filterf_max; 191 filter0.frequency(filterf); 192 env0.noteOn(); 193 MIDIusb.sendNoteOn(note, 127, 1); 194 MIDIclassic.sendNoteOn(note, 127, 1); 195 } 196 197 198 void noteOff(uint8_t note) { 199 env0.noteOff(); 200 MIDIusb.sendNoteOn(note, 0, 1); 201 MIDIclassic.sendNoteOn(note, 0, 1); 202 } 203 204 void midiPanic(){ 205 for( uint8_t m = 0; m < 128; m++ ){ 206 MIDIusb.sendNoteOn(m, 0, 1) ; 207 MIDIclassic.sendNoteOn(m, 0, 1) ; 208 yield(); // keep usb midi from flooding 209 } 210 } 211 212 void lightLED(uint8_t buttonLED) { 213 uint8_t pwms[4] = {PWM1, PWM2, PWM3, PWM4}; 214 boardNum = map(buttonLED, 0, 12, 0, 3); 215 // first dim all buttons on first three boards 216 for( int b = 0; b < 3; b++) { 217 for( int p = 0; p < 4; p ++) { 218 ledArcades[b].analogWrite(pwms[p], led_low); 219 } 220 } 221 // dim first button on fourth board (the other two are function buttons) 222 ledArcades[3].analogWrite(PWM1, led_low); 223 // then brighten the selected one 224 ledArcades[boardNum].analogWrite(pwms[buttonLED % 4], led_high); 225 } 226 227 228 #define SWITCHMASK ((1 << SWITCH1) | (1 << SWITCH2) | (1 << SWITCH3) | (1 << SWITCH4)) 229 230 void arcadeButtonCheck() { 231 for ( boardNum = 0; boardNum < NUM_BOARDS; boardNum++) { // check all boards, all switches 232 int pos = boardNum*4; 233 uint32_t switches = ledArcades[boardNum].digitalReadBulk(SWITCHMASK); 234 currentButtonState[pos+0] = ! (switches & (1<<SWITCH1)); 235 currentButtonState[pos+1] = ! (switches & (1<<SWITCH2)); 236 currentButtonState[pos+2] = ! (switches & (1<<SWITCH3)); 237 currentButtonState[pos+3] = ! (switches & (1<<SWITCH4)); 238 } 239 for( int i = 0; i < 4*NUM_BOARDS; i++ ) { 240 bool state = currentButtonState[i]; 241 if(state != lastButtonState[i]) { 242 243 if( state == HIGH ) { //pressed 244 // ---button functions--- 245 // --root notes-- 246 if (i < 13){ // these are the piano keys for picking root notes 247 root_note = 0 + i ; // MIDI note 248 lightLED(i); 249 } 250 251 252 //-- start/stop toggle button-- 253 if (i == 13) { // arp pattern button on front panel 254 if( !arp_on_off_state) { 255 arp.on(); 256 ledArcades[3].analogWrite(PWM2, led_med); 257 arp_on_off_state = true; 258 } 259 else { 260 arp.off(); 261 midiPanic(); // just to be on the safe side... 262 ledArcades[3].analogWrite(PWM2, 0); 263 arp_on_off_state = false; 264 } 265 } 266 //-- arp octave range button-- 267 if (i == 14) { // arp range button on front panel 268 ledArcades[3].analogWrite(PWM3, led_high); 269 arp_octaves = arp_octaves + 1; if( arp_octaves==4) { arp_octaves=1; } 270 arp.setTransposeSteps( arp_octaves ); 271 //Serial.printf("arp steps:%d\n",arp_octaves); 272 ledArcades[3].analogWrite(PWM3, led_low); 273 } 274 //-- pattern button-- 275 if (i == 15) { // arp pattern button on front panel 276 ledArcades[3].analogWrite(PWM4, led_high); 277 arp.nextArpId(); 278 ledArcades[3].analogWrite(PWM4, led_low); 279 } 280 } 281 } 282 } 283 for( int i=0; i<4*NUM_BOARDS; i++ ) { 284 lastButtonState[i] = currentButtonState[i]; 285 } 286 } 287 //----- end arcade button check 288 289 290 void loop(){ 291 arcadeButtonCheck(); // see if any buttons are pressed, send notes or adjust parameters 292 293 joyDown.update(); 294 joyUp.update(); 295 joyLeft.update(); 296 joyRight.update(); 297 298 if ( joyUp.fell() ) { // read a joystick single tap 299 ledArcades[3].analogWrite(PWM3, led_high); // feedback on front panel button 300 octave_offset = octave_offset + 1; if( octave_offset>7) { octave_offset=7; } 301 arp.setOctaveOffset(octave_offset); 302 ledArcades[3].analogWrite(PWM3, led_low); 303 } 304 305 if ( joyDown.fell() ) { 306 ledArcades[3].analogWrite(PWM3, led_high); // feedback on front panel button 307 octave_offset = octave_offset - 1; if( octave_offset<0) { octave_offset=0; } 308 arp.setOctaveOffset(octave_offset); 309 ledArcades[3].analogWrite(PWM3, led_low); 310 } 311 312 int joyLeftVal = joyLeft.read(); // read a held joystick (autorepeat) instead of single tap 313 if( joyLeftVal == LOW ) { 314 bpm = bpm - 1; if(bpm < 100) { bpm = 100; } 315 ledArcades[3].analogWrite(PWM4, led_high); 316 arp.setBPM( bpm ); 317 ledArcades[3].analogWrite(PWM4, led_low); 318 } 319 320 int joyRightVal = joyRight.read(); // for a held joystick instead of single tap 321 if( joyRightVal == LOW ) { 322 bpm = bpm + 1; if(bpm > 3000) { bpm = 3000; } 323 ledArcades[3].analogWrite(PWM4, led_high); 324 arp.setBPM( bpm ); 325 ledArcades[3].analogWrite(PWM4, led_low); 326 } 327 328 arp.update(root_note); // 329 330 if( millis() - lastControlMillis > 20 ) { 331 lastControlMillis = millis(); 332 } 333 } 334 //end loop()