Easy Code Upload
You can get the code onto your Feather M4 Express as easy as drag-and-drop! Simply plug in the Feather to your computer with a known good USB data cable (not power only!) and then double-click the reset button.
The board will show up on your computer as a USB drive named FEATHERBOOT. Download the Arcade_Synth_Controller.UF2 file linked below and then drag it onto the FEATHERBOOT drive.
The board will automatically reset and run the code.
Source Code Hacking
If you want to dig in deeper, you can download the source code here. This will require some knowledge of the Arduino IDE and how to upload code to your board.
// SPDX-FileCopyrightText: 2022 John Park and Tod Kurt for Adafruit Industries // // SPDX-License-Identifier: MIT // Arcade Synth Controller II: Son of Pianocade -- The Enriffening // written by John Park and Tod Kurt // Synthesizer/MIDI arpeggiator for with multiple LED Arcade boards & joystick input // Arpy library: https://github.com/todbot/mozzi_experiments/blob/main/eighties_arp/Arpy.h // midi_to_freq and ADT patch: https://github.com/todbot/tal_experiments/tree/main/arpy_test // - to do: when arp is off it acts as a normal keyboard. #include <Arduino.h> #include <Adafruit_TinyUSB.h> #include <MIDI.h> #include <Audio.h> #include <Bounce2.h> #include "Adafruit_seesaw.h" #include "ADT.h" #include "midi_to_freq.h" #include "Arpy.h" // ----- LED Arcade 1x4 STEMMA QT board pins----- // pin definitions on each LED Arcade 1x4 #define SWITCH1 18 // PA01 #define SWITCH2 19 // PA02 #define SWITCH3 20 // PA03 #define SWITCH4 2 // PA04 #define PWM1 12 // PC00 #define PWM2 13 // PC01 #define PWM3 0 // PA04 #define PWM4 1 // PA05 #define I2C_BASE_ADDR 0x3A // boards are in order, 0x3A, 0x3B, 0x3C, 0x3D #define NUM_BOARDS 4 Adafruit_seesaw ledArcades[ NUM_BOARDS ]; //----- board variables int boardNum = 0; //used to read each board int switchNum = 0; //used to read each switch int boardSwitchNum = 0; //unique button ID accross all boards/buttons int led_low = 10; //min pwm brightness int led_med = 60; int led_high = 220; // max brightness bool lastButtonState[16] ; bool currentButtonState[16] ; //-----joystick pins----- const int joyDownPin = 11; //down const int joyUpPin = 12; // up const int joyLeftPin = 9; // left const int joyRightPin = 10; //right const int joyGroundPin = 6; //"fake" ground pin //-----joystick debouncer Bounce joyDown = Bounce(); Bounce joyUp = Bounce(); Bounce joyLeft = Bounce(); Bounce joyRight = Bounce(); //-----MIDI instances----- Adafruit_USBD_MIDI usb_midi; MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDIusb); // USB MIDI MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDIclassic); // classic midi over RX/TX //-----Audio Library Syth parameters #define NUM_VOICES 4 AudioSynthWaveform *waves[] = { &wave0, &wave1, &wave2, &wave3 }; int filterf_max = 6000; int filterf = filterf_max; uint32_t lastControlMillis=0; uint8_t arp_octaves = 1; uint8_t root_note = 0; //----- create arpy arpeggiator Arpy arp = Arpy(); int bpm = 160; int octave_offset = 3; // initially starts on MIDI note 36 with the offset of 3 octaves from zero bool arp_on_off_state; void setup() { Wire.setClock(400000); //----- MIDI and Serial setup----- // MIDIusb.begin(MIDI_CHANNEL_OMNI); MIDIclassic.begin(MIDI_CHANNEL_OMNI); Serial.begin(115200); MIDIusb.turnThruOff(); delay(2000); // it's hard getting started in the morning Serial.println("[.::.:::.] Welcome to Arcade Synth Controller II: Son of Pianocade -- The Enriffening [.::.:::.]"); Serial.println("MIDI USB/Classic and Serial have begun"); //----- end MIDI and Serial setup----- //----- joystick pins setup----- // pinMode( joyDownPin, INPUT); pinMode( joyUpPin, INPUT); pinMode( joyLeftPin, INPUT); pinMode( joyRightPin, INPUT); pinMode( joyGroundPin, OUTPUT); joyDown.attach( joyDownPin, INPUT_PULLUP); joyUp.attach( joyUpPin, INPUT_PULLUP); joyLeft.attach( joyLeftPin, INPUT_PULLUP); joyRight.attach( joyRightPin, INPUT_PULLUP); digitalWrite(joyGroundPin, LOW); //----- end joystick pins setup----- //----- LED Arcade 1x4 setup----- // for ( int i = 0; i < NUM_BOARDS; i++ ) { if ( !ledArcades[i].begin( I2C_BASE_ADDR + i ) ) { Serial.println(F("LED Arcade not found!")); while (1) delay(10); } } Serial.println(F("LED Arcade boards started")); for ( int i = 0; i < NUM_BOARDS; i++ ) { ledArcades[i].pinMode(SWITCH1, INPUT_PULLUP); ledArcades[i].pinMode(SWITCH2, INPUT_PULLUP); ledArcades[i].pinMode(SWITCH3, INPUT_PULLUP); ledArcades[i].pinMode(SWITCH4, INPUT_PULLUP); ledArcades[i].analogWrite(PWM1, led_low); ledArcades[i].analogWrite(PWM2, led_low); ledArcades[i].analogWrite(PWM3, led_low); ledArcades[i].analogWrite(PWM4, led_low); } // brighten default root note ledArcades[0].analogWrite(PWM1, led_high); // turn down brightness of the function buttons ledArcades[3].analogWrite(PWM2, 0); ledArcades[3].analogWrite(PWM3, led_low); ledArcades[3].analogWrite(PWM4, led_low); //----- end LED Arcade 1x4 setup----- //-----Arpy setup----- // arp.setNoteOnHandler(noteOn); arp.setNoteOffHandler(noteOff); arp.setRootNote( root_note ); arp.setOctaveOffset(octave_offset); arp.setBPM( bpm ); arp.setGateTime( 0.75 ); // percentage of bpm arp.off(); //----- Audio Library Synth setup----- // (patch is saved in ADT.h file) AudioMemory(120); filter0.frequency(filterf_max); filter0.resonance(0.5); env0.attack(10); env0.hold(2); env0.decay(100); env0.sustain(0.5); env0.release(100); // Initialize processor and memory measurements AudioProcessorUsageMaxReset(); AudioMemoryUsageMaxReset(); Serial.println("Arpy setup done"); } // end setup() int waveform = WAVEFORM_SQUARE; void noteOn(uint8_t note) { waves[0]->begin( 0.9, tune_frequencies2_PGM[note], waveform); waves[1]->begin( 0.9, tune_frequencies2_PGM[note] * 1.01, waveform); // detune waves[2]->begin( 0.9, tune_frequencies2_PGM[note] * 1.005, waveform); // detune waves[3]->begin( 0.9, tune_frequencies2_PGM[note] * 1.025, waveform); // detune filterf = filterf_max; filter0.frequency(filterf); env0.noteOn(); MIDIusb.sendNoteOn(note, 127, 1); MIDIclassic.sendNoteOn(note, 127, 1); } void noteOff(uint8_t note) { env0.noteOff(); MIDIusb.sendNoteOn(note, 0, 1); MIDIclassic.sendNoteOn(note, 0, 1); } void midiPanic(){ for( uint8_t m = 0; m < 128; m++ ){ MIDIusb.sendNoteOn(m, 0, 1) ; MIDIclassic.sendNoteOn(m, 0, 1) ; yield(); // keep usb midi from flooding } } void lightLED(uint8_t buttonLED) { uint8_t pwms[4] = {PWM1, PWM2, PWM3, PWM4}; boardNum = map(buttonLED, 0, 12, 0, 3); // first dim all buttons on first three boards for( int b = 0; b < 3; b++) { for( int p = 0; p < 4; p ++) { ledArcades[b].analogWrite(pwms[p], led_low); } } // dim first button on fourth board (the other two are function buttons) ledArcades[3].analogWrite(PWM1, led_low); // then brighten the selected one ledArcades[boardNum].analogWrite(pwms[buttonLED % 4], led_high); } #define SWITCHMASK ((1 << SWITCH1) | (1 << SWITCH2) | (1 << SWITCH3) | (1 << SWITCH4)) void arcadeButtonCheck() { for ( boardNum = 0; boardNum < NUM_BOARDS; boardNum++) { // check all boards, all switches int pos = boardNum*4; uint32_t switches = ledArcades[boardNum].digitalReadBulk(SWITCHMASK); currentButtonState[pos+0] = ! (switches & (1<<SWITCH1)); currentButtonState[pos+1] = ! (switches & (1<<SWITCH2)); currentButtonState[pos+2] = ! (switches & (1<<SWITCH3)); currentButtonState[pos+3] = ! (switches & (1<<SWITCH4)); } for( int i = 0; i < 4*NUM_BOARDS; i++ ) { bool state = currentButtonState[i]; if(state != lastButtonState[i]) { if( state == HIGH ) { //pressed // ---button functions--- // --root notes-- if (i < 13){ // these are the piano keys for picking root notes root_note = 0 + i ; // MIDI note lightLED(i); } //-- start/stop toggle button-- if (i == 13) { // arp pattern button on front panel if( !arp_on_off_state) { arp.on(); ledArcades[3].analogWrite(PWM2, led_med); arp_on_off_state = true; } else { arp.off(); midiPanic(); // just to be on the safe side... ledArcades[3].analogWrite(PWM2, 0); arp_on_off_state = false; } } //-- arp octave range button-- if (i == 14) { // arp range button on front panel ledArcades[3].analogWrite(PWM3, led_high); arp_octaves = arp_octaves + 1; if( arp_octaves==4) { arp_octaves=1; } arp.setTransposeSteps( arp_octaves ); //Serial.printf("arp steps:%d\n",arp_octaves); ledArcades[3].analogWrite(PWM3, led_low); } //-- pattern button-- if (i == 15) { // arp pattern button on front panel ledArcades[3].analogWrite(PWM4, led_high); arp.nextArpId(); ledArcades[3].analogWrite(PWM4, led_low); } } } } for( int i=0; i<4*NUM_BOARDS; i++ ) { lastButtonState[i] = currentButtonState[i]; } } //----- end arcade button check void loop(){ arcadeButtonCheck(); // see if any buttons are pressed, send notes or adjust parameters joyDown.update(); joyUp.update(); joyLeft.update(); joyRight.update(); if ( joyUp.fell() ) { // read a joystick single tap ledArcades[3].analogWrite(PWM3, led_high); // feedback on front panel button octave_offset = octave_offset + 1; if( octave_offset>7) { octave_offset=7; } arp.setOctaveOffset(octave_offset); ledArcades[3].analogWrite(PWM3, led_low); } if ( joyDown.fell() ) { ledArcades[3].analogWrite(PWM3, led_high); // feedback on front panel button octave_offset = octave_offset - 1; if( octave_offset<0) { octave_offset=0; } arp.setOctaveOffset(octave_offset); ledArcades[3].analogWrite(PWM3, led_low); } int joyLeftVal = joyLeft.read(); // read a held joystick (autorepeat) instead of single tap if( joyLeftVal == LOW ) { bpm = bpm - 1; if(bpm < 100) { bpm = 100; } ledArcades[3].analogWrite(PWM4, led_high); arp.setBPM( bpm ); ledArcades[3].analogWrite(PWM4, led_low); } int joyRightVal = joyRight.read(); // for a held joystick instead of single tap if( joyRightVal == LOW ) { bpm = bpm + 1; if(bpm > 3000) { bpm = 3000; } ledArcades[3].analogWrite(PWM4, led_high); arp.setBPM( bpm ); ledArcades[3].analogWrite(PWM4, led_low); } arp.update(root_note); // if( millis() - lastControlMillis > 20 ) { lastControlMillis = millis(); } } //end loop()
MIDI
The Arcade_Synth_Controller.ino code plays notes via the Audio Library synthesizer, as well as sending out MIDI via USB and Classic DIN-5 connection. To adjust which channels are used, change the MIDIusb.sendNoteOn
or Off
lines here from channel 1 to whichever you need.
void noteOn(uint8_t note) { waves[0]->begin( 0.9, tune_frequencies2_PGM[note], waveform); waves[1]->begin( 0.9, tune_frequencies2_PGM[note] * 1.01, waveform); // detune waves[2]->begin( 0.9, tune_frequencies2_PGM[note] * 1.005, waveform); // detune waves[3]->begin( 0.9, tune_frequencies2_PGM[note] * 1.025, waveform); // detune filterf = filterf_max; filter0.frequency(filterf); env0.noteOn(); MIDIusb.sendNoteOn(note, 127, 1); MIDIclassic.sendNoteOn(note, 127, 1); } void noteOff(uint8_t note) { env0.noteOff(); MIDIusb.sendNoteOn(note, 0, 1); MIDIclassic.sendNoteOn(note, 0, 1); } void midiPanic(){ for( uint8_t m = 0; m < 128; m++ ){ MIDIusb.sendNoteOn(m, 0, 1) ; MIDIclassic.sendNoteOn(m, 0, 1) ; yield(); // keep usb midi from flooding } }
Arpeggios
The Arpy.h class, design by awesome guy Tod Kurt, handles the playing of arpeggio patterns based on the root note played on the keyboard.
You may want to edit the existing patterns or create your own. This is where to make those changes in the Arpy.h code:
int8_t arps[arp_count][arp_len] = { {0, 4, 7, 12}, // major {0, 3, 7, 10}, // minor 7th {0, 3, 6, 3}, // Diminished {0, 5, 7, 12}, // Suspended 4th {0, 12, 0, -12}, // octaves {0, 12, 24, -12}, // octaves 2 {0, -12, -12, 0}, // octaves 3 (bass) {0, 0, 0, 0}, // root };
Synthesizer Design
The Audio System Design Tool can be used to design your synth modules and patch connections. In the example used here, the patch is contained in the ADT.h file.
AudioSynthWaveform wave0; //xy=502.74795150756836,82.7552137374878 AudioSynthWaveform wave1; //xy=504.28649139404297,117.86524295806885 AudioSynthWaveform wave2; //xy=503.2865982055664,153.0081024169922 AudioSynthWaveform wave3; //xy=502.8580284118653,188.86524295806885 AudioMixer4 mixer0; //xy=633.7151184082031,100.00811004638672 AudioEffectEnvelope env0; //xy=758.612813949585,54.04482841491699 AudioFilterStateVariable filter0; //xy=888.6010780334473,60.850419998168945 AudioMixer4 mixerA; //xy=1010.7359161376953,171.30673599243164 AudioMixer4 mixerL; //xy=1196.8192749023438,210.86235809326172 AudioMixer4 mixerR; //xy=1198.2637329101562,277.8345947265625 AudioOutputAnalogStereo audioOut; //xy=1360.3193969726562,250.61236572265625 AudioConnection patchCord1(wave0, 0, mixer0, 0); AudioConnection patchCord2(wave3, 0, mixer0, 3); AudioConnection patchCord3(wave2, 0, mixer0, 2); AudioConnection patchCord4(wave1, 0, mixer0, 1); AudioConnection patchCord5(mixer0, env0); AudioConnection patchCord6(env0, 0, filter0, 0); AudioConnection patchCord7(filter0, 0, mixerA, 0); AudioConnection patchCord8(mixerA, 0, mixerL, 0); AudioConnection patchCord9(mixerA, 0, mixerR, 0); AudioConnection patchCord10(mixerL, 0, audioOut, 0); AudioConnection patchCord11(mixerR, 0, audioOut, 1);
Page last edited January 21, 2025
Text editor powered by tinymce.