Skip to content

Commit d932097

Browse files
authored
Merge pull request #1984 from jedgarpark/arcade-synth-controller
Arcade synth controller
2 parents d5c0ce4 + e68b293 commit d932097

File tree

5 files changed

+525
-0
lines changed

5 files changed

+525
-0
lines changed

Arcade_Synth_Controller/.feather_m4_express_tinyusb.test.only

Whitespace-only changes.

Arcade_Synth_Controller/ADT.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// GUItool: begin automatically generated code
2+
AudioSynthWaveform wave0; //xy=502.74795150756836,82.7552137374878
3+
AudioSynthWaveform wave1; //xy=504.28649139404297,117.86524295806885
4+
AudioSynthWaveform wave2; //xy=503.2865982055664,153.0081024169922
5+
AudioSynthWaveform wave3; //xy=502.8580284118653,188.86524295806885
6+
AudioMixer4 mixer0; //xy=633.7151184082031,100.00811004638672
7+
AudioEffectEnvelope env0; //xy=758.612813949585,54.04482841491699
8+
AudioFilterStateVariable filter0; //xy=888.6010780334473,60.850419998168945
9+
AudioMixer4 mixerA; //xy=1010.7359161376953,171.30673599243164
10+
AudioMixer4 mixerL; //xy=1196.8192749023438,210.86235809326172
11+
AudioMixer4 mixerR; //xy=1198.2637329101562,277.8345947265625
12+
AudioOutputAnalogStereo audioOut; //xy=1360.3193969726562,250.61236572265625
13+
AudioConnection patchCord1(wave0, 0, mixer0, 0);
14+
AudioConnection patchCord2(wave3, 0, mixer0, 3);
15+
AudioConnection patchCord3(wave2, 0, mixer0, 2);
16+
AudioConnection patchCord4(wave1, 0, mixer0, 1);
17+
AudioConnection patchCord5(mixer0, env0);
18+
AudioConnection patchCord6(env0, 0, filter0, 0);
19+
AudioConnection patchCord7(filter0, 0, mixerA, 0);
20+
AudioConnection patchCord8(mixerA, 0, mixerL, 0);
21+
AudioConnection patchCord9(mixerA, 0, mixerR, 0);
22+
AudioConnection patchCord10(mixerL, 0, audioOut, 0);
23+
AudioConnection patchCord11(mixerR, 0, audioOut, 1);
24+
// GUItool: end automatically generated code
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
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

Comments
 (0)