|
| 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() |
0 commit comments