Skip to content

pot reading is too slow via ADC #60

@carlostau

Description

@carlostau

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 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();
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions