forked from copych/AcidBox
-
Notifications
You must be signed in to change notification settings - Fork 0
/
AcidBox.ino
407 lines (325 loc) · 12.3 KB
/
AcidBox.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/*
AcidBox
ESP32 acid combo of 303 + 303 + 808 like synths. MIDI driven. I2S output to DAC. No indication. Uses both cores of ESP32.
To build the thing
You will need an ESP32 with PSRAM (ESP32 WROVER module). Preferrable an external DAC, like PCM5102. In ArduinoIDE Tools menu select:
* * Board: "ESP32 Dev Module" or "ESP32S3 Dev Module"
* * Partition scheme: No OTA (1MB APP/ 3MB SPIFFS)
* * PSRAM: "enabled" or "OPI PSRAM" or what type you have
!!!!!!!! ATTENTION !!!!!!!!!
You will need to upload samples from /data folder to the ESP32 flash, otherwise you'll only have 40kB samples from samples.h.
To upload samples follow the instructions:
https://github.com/lorol/LITTLEFS#arduino-esp32-littlefs-filesystem-upload-tool
And then use Tools -> ESP32 Sketch Data Upload
*/
#include "config.h"
#include "fx_delay.h"
#ifndef NO_PSRAM
#include "fx_reverb.h"
#endif
#include "compressor.h"
#include "synthvoice.h"
#include "sampler.h"
#include <Wire.h>
#if defined MIDI_VIA_SERIAL2 || defined MIDI_VIA_SERIAL
#include <MIDI.h>
#endif
#ifdef MIDI_VIA_SERIAL
// default settings for Hairless midi is 115200 8-N-1
struct CustomBaudRateSettings : public MIDI_NAMESPACE::DefaultSerialSettings {
static const long BaudRate = 115200;
};
struct SerialMIDISettings : public midi::DefaultSettings {
static const long BaudRate = 115200;
static const bool Use1ByteParsing = false;
};
MIDI_NAMESPACE::SerialMIDI<HardwareSerial, CustomBaudRateSettings> serialMIDI(Serial);
MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<HardwareSerial, SerialMIDISettings>> MIDI((MIDI_NAMESPACE::SerialMIDI<HardwareSerial, SerialMIDISettings>&)serialMIDI);
#endif
#ifdef MIDI_VIA_SERIAL2
// MIDI port on UART2, pins 16 (RX) and 17 (TX) prohibited, as they are used for PSRAM
struct Serial2MIDISettings : public midi::DefaultSettings {
static const long BaudRate = 31250;
static const int8_t RxPin = MIDIRX_PIN;
static const int8_t TxPin = MIDITX_PIN;
static const bool Use1ByteParsing = false;
};
MIDI_NAMESPACE::SerialMIDI<HardwareSerial> Serial2MIDI2(Serial2);
MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<HardwareSerial, Serial2MIDISettings>> MIDI2((MIDI_NAMESPACE::SerialMIDI<HardwareSerial, Serial2MIDISettings>&)Serial2MIDI2);
#endif
// lookuptables
static float midi_pitches[128];
static float midi_phase_steps[128];
static float midi_tbl_steps[128];
static float exp_square_tbl[TABLE_SIZE+1];
//static float square_tbl[TABLE_SIZE+1];
static float saw_tbl[TABLE_SIZE+1];
static float exp_tbl[TABLE_SIZE+1];
static float knob_tbl[TABLE_SIZE+1]; // exp-like curve
static float shaper_tbl[TABLE_SIZE+1]; // illinear tanh()-like curve
static float sin_tbl[TABLE_SIZE+1];
static float norm1_tbl[16][16]; // cutoff-reso pair gain compensation
static float norm2_tbl[16][16]; // wavefolder-overdrive gain compensation
//static float (*tables[])[TABLE_SIZE+1] = {&exp_square_tbl, &square_tbl, &saw_tbl, &exp_tbl};
// service variables and arrays
volatile uint32_t s1t, s2t, drt, fxt, s1T, s2T, drT, fxT, art, arT, c0t, c0T, c1t, c1T; // debug timing: if we use less vars, compiler optimizes them
volatile uint32_t prescaler;
static uint32_t last_reset = 0;
static float param[POT_NUM];
static uint8_t ctrl_hold_notes;
// Audio buffers of all kinds
volatile uint8_t current_gen_buf = 0; // set of buffers for generation
volatile uint8_t current_out_buf = 1 - 0; // set of buffers for output
static float synth1_buf[2][DMA_BUF_LEN]; // synth1 mono
static float synth2_buf[2][DMA_BUF_LEN]; // synth2 mono
static float drums_buf_l[2][DMA_BUF_LEN]; // drums L
static float drums_buf_r[2][DMA_BUF_LEN]; // drums R
static float mix_buf_l[2][DMA_BUF_LEN]; // mix L channel
static float mix_buf_r[2][DMA_BUF_LEN]; // mix R channel
static union { // a dirty trick, instead of true converting
int16_t _signed[DMA_BUF_LEN * 2];
uint16_t _unsigned[DMA_BUF_LEN * 2];
} out_buf[2]; // i2s L+R output buffer
size_t bytes_written; // i2s result
volatile boolean processing = false;
#ifndef NO_PSRAM
volatile float rvb_k1, rvb_k2, rvb_k3;
#endif
volatile float dly_k1, dly_k2, dly_k3;
// tasks for Core0 and Core1
TaskHandle_t SynthTask1;
TaskHandle_t SynthTask2;
// 303-like synths
SynthVoice Synth1(0); // instance 0 to recognize from the inside
SynthVoice Synth2(1); // instance 1 to recognize from the inside
// 808-like drums
Sampler Drums( DEFAULT_DRUMKIT ); // argument: starting drumset [0 .. total-1]
// Global effects
FxDelay Delay;
#ifndef NO_PSRAM
FxReverb Reverb;
#endif
Compressor Comp;
hw_timer_t * timer1 = NULL; // Timer variables
hw_timer_t * timer2 = NULL; // Timer variables
portMUX_TYPE timer1Mux = portMUX_INITIALIZER_UNLOCKED;
portMUX_TYPE timer2Mux = portMUX_INITIALIZER_UNLOCKED;
volatile boolean timer1_fired = false; // Update battery icon flag
volatile boolean timer2_fired = false; // Update battery icon flag
/*
* Timer interrupt handler **********************************************************************************************************************************
*/
void IRAM_ATTR onTimer1() {
portENTER_CRITICAL_ISR(&timer1Mux);
timer1_fired = true;
portEXIT_CRITICAL_ISR(&timer1Mux);
}
void IRAM_ATTR onTimer2() {
portENTER_CRITICAL_ISR(&timer2Mux);
timer2_fired = true;
portEXIT_CRITICAL_ISR(&timer2Mux);
}
/*
* Core Tasks ************************************************************************************************************************
*/
// forward declaration
static void IRAM_ATTR mixer() ;
// Core0 task
// static void audio_task1(void *userData) {
static void IRAM_ATTR audio_task1(void *userData) {
while (true) {
taskYIELD();
if (ulTaskNotifyTake(pdTRUE, portMAX_DELAY)) { // we need all the generators to fill the buffers here, so we wait
c0t = micros();
// taskYIELD();
current_gen_buf = current_out_buf; // swap buffers
current_out_buf = 1 - current_gen_buf;
xTaskNotifyGive(SynthTask2); // if we are here, then we've already received a notification from task2
s1t = micros();
synth1_generate();
s1T = micros() - s1t;
// taskYIELD();
drt = micros();
drums_generate();
drT = micros() - drt;
}
// taskYIELD();
taskYIELD();
c0T = micros() - c0t;
}
}
// task for Core1, which tipically runs user's code on ESP32
// static void IRAM_ATTR audio_task2(void *userData) {
static void IRAM_ATTR audio_task2(void *userData) {
while (true) {
taskYIELD();
if (ulTaskNotifyTake(pdTRUE, portMAX_DELAY)) { // wait for the notification from the SynthTask1
c1t = micros();
fxt = micros();
mixer();
i2s_output();
fxT = micros() - fxt;
taskYIELD();
s2t = micros();
synth2_generate();
s2T = micros() - s2t;
xTaskNotifyGive(SynthTask1);
}
c1T = micros() - c1t;
art = micros();
if (timer2_fired) {
timer2_fired = false;
#ifdef TEST_POTS
readPots();
#endif
#ifdef DEBUG_TIMING
DEBF ("synt1=%dus synt2=%dus drums=%dus mixer=%dus DMA_BUF=%dus\r\n" , s1T, s2T, drT, fxT, DMA_BUF_TIME);
// DEBF ("TaskCore0=%dus TaskCore1=%dus DMA_BUF=%dus\r\n" , c0T , c1T , DMA_BUF_TIME);
// DEBF ("AllTheRestCore1=%dus\r\n" , arT);
#endif
}
// taskYIELD();
arT = micros() - art;
}
}
/*
* Quite an ordinary SETUP() *******************************************************************************************************************************
*/
void setup(void) {
#ifdef DEBUG_ON
#ifndef MIDI_VIA_SERIAL
Serial.begin(115200);
#endif
#endif
btStop(); // we don't want bluetooth to consume our precious cpu time
MidiInit(); // init midi input and handling of midi events
/*
for (int i = 0; i < GPIO_BUTTONS; i++) {
pinMode(buttonGPIOs[i], INPUT_PULLDOWN);
}
*/
buildTables();
for (int i = 0; i < POT_NUM; i++) pinMode( POT_PINS[i] , INPUT);
Synth1.Init();
Synth2.Init();
Drums.Init();
#ifndef NO_PSRAM
Reverb.Init();
#endif
Delay.Init();
Comp.Init(SAMPLE_RATE);
#ifdef JUKEBOX
init_midi(); // AcidBanger function
#endif
// silence while we haven't loaded anything reasonable
for (int i = 0; i < DMA_BUF_LEN; i++) {
drums_buf_l[current_gen_buf][i] = 0.0f ;
drums_buf_r[current_gen_buf][i] = 0.0f ;
synth1_buf[current_gen_buf][i] = 0.0f ;
synth2_buf[current_gen_buf][i] = 0.0f ;
out_buf[current_out_buf]._signed[i * 2] = 0 ;
out_buf[current_out_buf]._signed[i * 2 + 1] = 0 ;
mix_buf_l[current_out_buf][i] = 0.0f;
mix_buf_r[current_out_buf][i] = 0.0f;
}
i2sInit();
// i2s_write(i2s_num, out_buf[current_out_buf]._signed, sizeof(out_buf[current_out_buf]._signed), &bytes_written, portMAX_DELAY);
//xTaskCreatePinnedToCore( audio_task1, "SynthTask1", 8000, NULL, (1 | portPRIVILEGE_BIT), &SynthTask1, 0 );
//xTaskCreatePinnedToCore( audio_task2, "SynthTask2", 8000, NULL, (1 | portPRIVILEGE_BIT), &SynthTask2, 1 );
xTaskCreatePinnedToCore( audio_task1, "SynthTask1", 5000, NULL, 1, &SynthTask1, 0 );
xTaskCreatePinnedToCore( audio_task2, "SynthTask2", 5000, NULL, 1, &SynthTask2, 1 );
// somehow we should allow tasks to run
xTaskNotifyGive(SynthTask1);
// xTaskNotifyGive(SynthTask2);
processing = true;
#if ESP_ARDUINO_VERSION_MAJOR < 3
// timer interrupt
/*
timer1 = timerBegin(0, 80, true); // Setup timer for midi
timerAttachInterrupt(timer1, &onTimer1, true); // Attach callback
timerAlarmWrite(timer1, 4000, true); // 4000us, autoreload
timerAlarmEnable(timer1);
*/
timer2 = timerBegin(1, 80, true); // Setup general purpose timer
timerAttachInterrupt(timer2, &onTimer2, true); // Attach callback
timerAlarmWrite(timer2, 200000, true); // 200ms, autoreload
timerAlarmEnable(timer2);
#else
timer2 = timerBegin(1000000); // Setup general purpose timer
timerAttachInterrupt(timer2, &onTimer2); // Attach callback
timerAlarm(timer2, 200000, true, 0); // 200ms, autoreload
#endif
}
static uint32_t last_ms = micros();
/*
* Finally, the LOOP () ***********************************************************************************************************
*/
void loop() { // default loopTask running on the Core1
// you can still place some of your code here
// or vTaskDelete(NULL);
// processButtons();
regular_checks();
taskYIELD(); // this can wait
}
/*
* Some debug and service routines *****************************************************************************************************************************
*/
void readPots() {
static const float snap = 0.003f;
static uint8_t i = 0;
static float tmp;
static const float NORMALIZE_ADC = 1.0f / 4096.0f;
//read one pot per call
tmp = (float)analogRead(POT_PINS[i]) * NORMALIZE_ADC;
if (fabs(tmp - param[i]) > snap) {
param[i] = tmp;
paramChange(i, tmp);
}
i++;
// if (i >= POT_NUM) i=0;
i %= POT_NUM;
}
void paramChange(uint8_t paramNum, float paramVal) {
// paramVal === param[paramNum];
DEBF ("param %d val %0.4f\r\n" , paramNum, paramVal);
paramVal *= 127.0;
switch (paramNum) {
case 0:
//set_bpm( 40.0f + (paramVal * 160.0f));
Synth2.ParseCC(CC_303_CUTOFF, paramVal);
break;
case 1:
Synth2.ParseCC(CC_303_RESO, paramVal);
break;
case 2:
Synth2.ParseCC(CC_303_OVERDRIVE, paramVal);
Synth2.ParseCC(CC_303_DISTORTION, paramVal);
break;
case 3:
Synth2.ParseCC(CC_303_ENVMOD_LVL, paramVal);
break;
case 4:
Synth2.ParseCC(CC_303_ACCENT_LVL, paramVal);
break;
default:
{}
}
}
#ifdef JUKEBOX
void jukebox_tick() {
run_tick();
myRandomAddEntropy((uint16_t)(micros() & 0x0000FFFF));
}
#endif
void regular_checks() {
timer1_fired = false;
#ifdef MIDI_VIA_SERIAL
MIDI.read();
#endif
#ifdef MIDI_VIA_SERIAL2
MIDI2.read();
#endif
#ifdef JUKEBOX
jukebox_tick();
#endif
}