Sonicake pocket master midi control using ESP32 CYD

The Sonicake pocket master is a great cheap guitar effects you can buy. But it lacks control

The code

#include <Arduino.h>
#include <Control_Surface.h>
#include <MIDI_Interfaces/BluetoothMIDI_Interface.hpp>
#include <SPI.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>

// Backlight and Display Pins
#define CYD_BACKLIGHT_PIN 21
TFT_eSPI tft = TFT_eSPI();

// CYD Dedicated Touch SPI Pins
#define XPT2046_IRQ 36
#define XPT2046_MOSI 32
#define XPT2046_MISO 39
#define XPT2046_CLK 25
#define XPT2046_CS 33

SPIClass touchSpi = SPIClass(VSPI);
XPT2046_Touchscreen ts(XPT2046_CS, XPT2046_IRQ);

// Bluetooth MIDI Instance
BluetoothMIDI_Interface midi_ble;

unsigned long lastTouchTime = 0;
int activePreset = 0;   // 0 means no preset active, 1-4 for buttons
bool isOutOfSync = false; // True when bank changes while a preset was active
int bank = 0;           // Bank variable from 0 to 4
int volume = 70;        // Volume variable initialized to 70 (Range: 20 to 100)

// Bank & Preset Arrays
const char* presets_name[] = {"rock", "ballad", "pop", "jazz", "blues"};
const int button1_presets[] = {1, 2, 3, 4, 5};
const int button2_presets[] = {6, 7, 8, 9, 10};
const int button3_presets[] = {11, 12, 13, 14, 15};
const int button4_presets[] = {16, 17, 18, 19, 20};

// UI Button Locations & Dimensions (Landscape: 320x240)
#define BTN_Y 145
#define BTN_W 68
#define BTN_H 65

#define BTN_1_X 15
#define BTN_2_X 91
#define BTN_3_X 167
#define BTN_4_X 243

// Volume Top Bar Controls
#define VOL_DEC_X 10
#define VOL_DEC_Y 10
#define VOL_DEC_W 60
#define VOL_DEC_H 35

#define VOL_BOX_X 80
#define VOL_BOX_Y 10
#define VOL_BOX_W 160
#define VOL_BOX_H 35

#define VOL_INC_X 250
#define VOL_INC_Y 10
#define VOL_INC_W 60
#define VOL_INC_H 35

// Bank Nav Buttons & Display Box
#define BANK_BOX_X 90
#define BANK_BOX_Y 65
#define BANK_BOX_W 140
#define BANK_BOX_H 50

#define BANK_DEC_X 20
#define BANK_DEC_Y 65
#define BANK_DEC_W 65
#define BANK_DEC_H 50

#define BANK_INC_X 235
#define BANK_INC_Y 65
#define BANK_INC_W 65
#define BANK_INC_H 50

void drawButton(int btnNum, int x, int y, int w, int h) {
  bool isActive = (activePreset == btnNum);
  uint16_t fillColor = TFT_BLUE;
  uint16_t textColor = TFT_WHITE;

  if (isActive) {
    if (isOutOfSync) {
      fillColor = TFT_RED;
      textColor = TFT_WHITE;
    } else {
      fillColor = TFT_YELLOW;
      textColor = TFT_BLACK;
    }
  }

  tft.fillRoundRect(x, y, w, h, 10, fillColor);
  tft.drawRoundRect(x, y, w, h, 10, TFT_WHITE);
  tft.setTextColor(textColor, fillColor);
  tft.setTextDatum(MC_DATUM);
  tft.drawString(String(btnNum), x + (w / 2), y + (h / 2), 4);
}

void drawBankDisplay() {
  tft.fillRoundRect(BANK_BOX_X, BANK_BOX_Y, BANK_BOX_W, BANK_BOX_H, 8, TFT_DARKGREY);
  tft.drawRoundRect(BANK_BOX_X, BANK_BOX_Y, BANK_BOX_W, BANK_BOX_H, 8, TFT_WHITE);
  tft.setTextColor(TFT_WHITE, TFT_DARKGREY);
  tft.setTextDatum(MC_DATUM);
  
  // Format bank number next to preset name (e.g., "1 - rock")
  String bankStr = String(bank + 1) + " - " + presets_name[bank];
  tft.drawString(bankStr, BANK_BOX_X + (BANK_BOX_W / 2), BANK_BOX_Y + (BANK_BOX_H / 2), 4);
}

void drawVolumeDisplay() {
  tft.fillRoundRect(VOL_BOX_X, VOL_BOX_Y, VOL_BOX_W, VOL_BOX_H, 6, TFT_NAVY);
  tft.drawRoundRect(VOL_BOX_X, VOL_BOX_Y, VOL_BOX_W, VOL_BOX_H, 6, TFT_WHITE);
  tft.setTextColor(TFT_WHITE, TFT_NAVY);
  tft.setTextDatum(MC_DATUM);
  tft.drawString("VOL: " + String(volume), VOL_BOX_X + (VOL_BOX_W / 2), VOL_BOX_Y + (VOL_BOX_H / 2), 2);
}

void drawUI() {
  tft.fillScreen(TFT_BLACK);

  // Volume - Button
  tft.fillRoundRect(VOL_DEC_X, VOL_DEC_Y, VOL_DEC_W, VOL_DEC_H, 6, TFT_DARKGREY);
  tft.drawRoundRect(VOL_DEC_X, VOL_DEC_Y, VOL_DEC_W, VOL_DEC_H, 6, TFT_WHITE);
  tft.setTextColor(TFT_WHITE, TFT_DARKGREY);
  tft.setTextDatum(MC_DATUM);
  tft.drawString("VOL-", VOL_DEC_X + (VOL_DEC_W / 2), VOL_DEC_Y + (VOL_DEC_H / 2), 2);

  // Volume + Button
  tft.fillRoundRect(VOL_INC_X, VOL_INC_Y, VOL_INC_W, VOL_INC_H, 6, TFT_DARKGREY);
  tft.drawRoundRect(VOL_INC_X, VOL_INC_Y, VOL_INC_W, VOL_INC_H, 6, TFT_WHITE);
  tft.setTextColor(TFT_WHITE, TFT_DARKGREY);
  tft.setTextDatum(MC_DATUM);
  tft.drawString("VOL+", VOL_INC_X + (VOL_INC_W / 2), VOL_INC_Y + (VOL_INC_H / 2), 2);

  // Draw Volume Display Box
  drawVolumeDisplay();

  // Bank - Button
  tft.fillRoundRect(BANK_DEC_X, BANK_DEC_Y, BANK_DEC_W, BANK_DEC_H, 8, TFT_MAROON);
  tft.drawRoundRect(BANK_DEC_X, BANK_DEC_Y, BANK_DEC_W, BANK_DEC_H, 8, TFT_WHITE);
  tft.setTextColor(TFT_WHITE, TFT_MAROON);
  tft.setTextDatum(MC_DATUM);
  tft.drawString("BANK-", BANK_DEC_X + (BANK_DEC_W / 2), BANK_DEC_Y + (BANK_DEC_H / 2), 2);

  // Bank + Button
  tft.fillRoundRect(BANK_INC_X, BANK_INC_Y, BANK_INC_W, BANK_INC_H, 8, TFT_DARKGREEN);
  tft.drawRoundRect(BANK_INC_X, BANK_INC_Y, BANK_INC_W, BANK_INC_H, 8, TFT_WHITE);
  tft.setTextColor(TFT_WHITE, TFT_DARKGREEN);
  tft.setTextDatum(MC_DATUM);
  tft.drawString("BANK+", BANK_INC_X + (BANK_INC_W / 2), BANK_INC_Y + (BANK_INC_H / 2), 2);

  // Draw Bank Name Box
  drawBankDisplay();

  // Draw 4 Preset Buttons
  drawButton(1, BTN_1_X, BTN_Y, BTN_W, BTN_H);
  drawButton(2, BTN_2_X, BTN_Y, BTN_W, BTN_H);
  drawButton(3, BTN_3_X, BTN_Y, BTN_W, BTN_H);
  drawButton(4, BTN_4_X, BTN_Y, BTN_W, BTN_H);
}

void handlePresetPress(int btnNum, const int* presetArray) {
  activePreset = btnNum;
  isOutOfSync = false;
  int val = presetArray[bank];
  Control_Surface.sendCC({1, CHANNEL_1}, val);
  drawUI();
  Serial.print("Status: Touch Button ");
  Serial.print(btnNum);
  Serial.print(" -> Sent CC {1, 1} value: ");
  Serial.println(val);
}

void setup() {
  Serial.begin(115200);
  delay(500);

  // Enable Backlight
  pinMode(CYD_BACKLIGHT_PIN, OUTPUT);
  digitalWrite(CYD_BACKLIGHT_PIN, HIGH);

  // Initialize Display
  tft.init();
  tft.setRotation(1); // Landscape mode
  drawUI();

  // Initialize Touch Bus
  touchSpi.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
  ts.begin(touchSpi);
  ts.setRotation(1);

  // Initialize MIDI
  Control_Surface.begin();
  Serial.println("Status: CYD Ready & BLE Server Active");
}

void loop() {
  // Continuously service BLE MIDI processing
  Control_Surface.loop();

  // Debounced Touch Scanner
  if (millis() - lastTouchTime > 250) {
    if (ts.touched()) {
      TS_Point p = ts.getPoint();

      // Map touch coordinates for landscape (320x240)
      int touchX = map(p.x, 200, 3700, 0, 320);
      int touchY = map(p.y, 240, 3800, 0, 240);

      // VOL - Button Pressed
      if (touchX >= VOL_DEC_X && touchX <= (VOL_DEC_X + VOL_DEC_W) && touchY >= VOL_DEC_Y && touchY <= (VOL_DEC_Y + VOL_DEC_H)) {
        if (volume > 20) {
          volume--;
          drawVolumeDisplay();
          Control_Surface.sendCC({6, CHANNEL_1}, volume);
          Serial.print("Status: VOL- -> Sent CC {6, 1} value: ");
          Serial.println(volume);
          lastTouchTime = millis();
        }
      }

      // VOL + Button Pressed
      if (touchX >= VOL_INC_X && touchX <= (VOL_INC_X + VOL_INC_W) && touchY >= VOL_INC_Y && touchY <= (VOL_INC_Y + VOL_INC_H)) {
        if (volume < 100) {
          volume++;
          drawVolumeDisplay();
          Control_Surface.sendCC({6, CHANNEL_1}, volume);
          Serial.print("Status: VOL+ -> Sent CC {6, 1} value: ");
          Serial.println(volume);
          lastTouchTime = millis();
        }
      }

      // BANK - Button Pressed
      if (touchX >= BANK_DEC_X && touchX <= (BANK_DEC_X + BANK_DEC_W) && touchY >= BANK_DEC_Y && touchY <= (BANK_DEC_Y + BANK_DEC_H)) {
        if (bank > 0) {
          bank--;
          if (activePreset != 0) {
            isOutOfSync = true;
          }
          drawUI();
          Serial.print("Status: Bank Decremented -> ");
          Serial.println(presets_name[bank]);
          lastTouchTime = millis();
        }
      }

      // BANK + Button Pressed
      if (touchX >= BANK_INC_X && touchX <= (BANK_INC_X + BANK_INC_W) && touchY >= BANK_INC_Y && touchY <= (BANK_INC_Y + BANK_INC_H)) {
        if (bank < 4) {
          bank++;
          if (activePreset != 0) {
            isOutOfSync = true;
          }
          drawUI();
          Serial.print("Status: Bank Incremented -> ");
          Serial.println(presets_name[bank]);
          lastTouchTime = millis();
        }
      }

      // Button 1 Pressed
      if (touchX >= BTN_1_X && touchX <= (BTN_1_X + BTN_W) && touchY >= BTN_Y && touchY <= (BTN_Y + BTN_H)) {
        handlePresetPress(1, button1_presets);
        lastTouchTime = millis();
      }

      // Button 2 Pressed
      if (touchX >= BTN_2_X && touchX <= (BTN_2_X + BTN_W) && touchY >= BTN_Y && touchY <= (BTN_Y + BTN_H)) {
        handlePresetPress(2, button2_presets);
        lastTouchTime = millis();
      }

      // Button 3 Pressed
      if (touchX >= BTN_3_X && touchX <= (BTN_3_X + BTN_W) && touchY >= BTN_Y && touchY <= (BTN_Y + BTN_H)) {
        handlePresetPress(3, button3_presets);
        lastTouchTime = millis();
      }

      // Button 4 Pressed
      if (touchX >= BTN_4_X && touchX <= (BTN_4_X + BTN_W) && touchY >= BTN_Y && touchY <= (BTN_Y + BTN_H)) {
        handlePresetPress(4, button4_presets);
        lastTouchTime = millis();
      }
    }
  }
}