/ 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()