pot reading is too slow via ADC
I have been testing the readPot function but the pots are read every 200ms which means the pot movement is very noticeable, especially with filters. I changed a few lines and added a button to switch between synth 1 and synth 2. | Now the pots are read smoothly!
/* 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). Preferably 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>
// =================== BUTTON SELECTOR ADDITIONS =================== #define BUTTON_PIN 42 // <-- Change this to your button GPIO as needed! bool synth_select = false; // false: Synth1, true: Synth2
unsigned long lastButtonCheck = 0; unsigned long lastButtonToggle = 0; bool lastButtonState = HIGH; const unsigned long debounceDelay = 40;
void checkSynthButton() { unsigned long now = millis(); if (now - lastButtonCheck > debounceDelay) { lastButtonCheck = now; bool reading = digitalRead(BUTTON_PIN); if (reading != lastButtonState) { lastButtonState = reading; if (reading == LOW && (now - lastButtonToggle > 200)) { // pressed synth_select = !synth_select; lastButtonToggle = now; } } } } // =============================================================== MIDI interfaces ===============================================================
#if defined MIDI_VIA_SERIAL2 || defined MIDI_VIA_SERIAL #include <MIDI.h> #endif
#ifdef MIDI_VIA_SERIAL
struct CustomBaudRateSettings : public MIDI_NAMESPACE::DefaultSettings { static const long BaudRate = 115200; static const bool Use1ByteParsing = false; };
MIDI_NAMESPACE::SerialMIDI<MIDI_PORT_TYPE, CustomBaudRateSettings> serialMIDI(MIDI_PORT); MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<MIDI_PORT_TYPE, CustomBaudRateSettings>> MIDI((MIDI_NAMESPACE::SerialMIDI<MIDI_PORT_TYPE, CustomBaudRateSettings>&)serialMIDI);
#endif
#ifdef MIDI_VIA_SERIAL2 // MIDI port on UART2, pins 16 (RX) and 17 (TX) prohibited on ESP32, 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 lim_tbl[TABLE_SIZE+1]; // diode soft clipping at about 1.0 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 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(); 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; drt = micros(); drums_generate(); drT = micros() - drt; } taskYIELD(); c0T = micros() - c0t; } }
// task for Core1, which tipically runs user's code on ESP32
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;
readPots(); // Scan all pots every timer tick, for smooth CC
#ifdef DEBUG_TIMING
DEBF ("synt1=%dus synt2=%dus drums=%dus mixer=%dus DMA_BUF=%dus\r\n" , s1T, s2T, drT, fxT, DMA_BUF_TIME);
#endif
}
arT = micros() - art;
}
}
/*
- Quite an ordinary SETUP() ******************************************************************************************************************************* */
void setup(void) {
#ifdef DEBUG_ON #ifndef MIDI_VIA_SERIAL DEBUG_PORT.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
buildTables();
for (int i = 0; i < POT_NUM; i++) pinMode( POT_PINS[i] , INPUT);
// ========== Button pin setup ========== pinMode(BUTTON_PIN, INPUT_PULLUP); // assumes button pulls pin LOW when pressed
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(); xTaskCreatePinnedToCore( audio_task1, "SynthTask1", 5000, NULL, 1, &SynthTask1, 0 ); xTaskCreatePinnedToCore( audio_task2, "SynthTask2", 5000, NULL, 1, &SynthTask2, 1 );
// allow tasks to run xTaskNotifyGive(SynthTask1); processing = true;
#if ESP_ARDUINO_VERSION_MAJOR < 3
// timer interrupt
timer2 = timerBegin(1, 80, true); // Setup general purpose timer
timerAttachInterrupt(timer2, &onTimer2, true); // Attach callback
timerAlarmWrite(timer2, 10000, true); // 10ms, autoreload (100 Hz for smooth pots)
timerAlarmEnable(timer2);
#else
timer2 = timerBegin(1000000);
timerAttachInterrupt(timer2, &onTimer2);
timerAlarm(timer2, 10000, true, 0); // 10ms, 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);
regular_checks();
taskYIELD(); // this can wait
}
/*
- Some debug and service routines ***************************************************************************************************************************** */
// Reads ALL pots every call, sends CC only if value changed, for smooth control void readPots() { static int lastCCVal[POT_NUM] = {0}; for (int i = 0; i < POT_NUM; ++i) { float tmp = (float)analogRead(POT_PINS[i]) / 4095.0f; int ccVal = (int)(tmp * 127.0f + 0.5f); if (ccVal != lastCCVal[i]) { lastCCVal[i] = ccVal; param[i] = tmp; paramChange(i, ccVal); } } }
void paramChange(uint8_t paramNum, int ccVal) { SynthVoice* synth = synth_select ? &Synth2 : &Synth1; switch (paramNum) { case 0: synth->ParseCC(CC_303_CUTOFF, ccVal); break; case 1: synth->ParseCC(CC_303_RESO, ccVal); break; case 2: synth->ParseCC(CC_303_OVERDRIVE, ccVal); synth->ParseCC(CC_303_DISTORTION, ccVal); break; case 3: synth->ParseCC(CC_303_ENVMOD_LVL, ccVal); break; case 4: synth->ParseCC(CC_303_ACCENT_LVL, ccVal); 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
// ========== Button check ========== checkSynthButton(); }