/*
* =====================================================
* MIDI Controller — ESP32-S3 + CD74HC4067 MUX
* =====================================================
* MUX PADS A (pads 1–16) : SIG → GPIO 10 digital
* MUX PADS B (pads 17–32) : SIG → GPIO 11 digital
* MUX KNOBS (knobs 1–8) : SIG → GPIO 1 analogique
* MUX FADERS (faders 1–8) : SIG → GPIO 2 analogique
* S0–S3 partagés : GPIO 4, 5, 6, 7
* PAD LEDs NeoPixel 4x8 : GPIO 17
* PROFILE LEDs NeoPixel 1x3: GPIO 18
* PROFILE BUTTONS : GPIO 19, 20, 21
* =====================================================
*/
#include <Adafruit_NeoPixel.h>
// ── MUX pins ───────────────────────────────────────
#define MUX_S0 4
#define MUX_S1 5
#define MUX_S2 6
#define MUX_S3 7
#define MUX_PADS_A_SIG 10
#define MUX_PADS_B_SIG 11
#define MUX_KNOBS_SIG 1
#define MUX_FADERS_SIG 2
const int PROFILE_PINS[3] = {19, 20, 21};
#define PAD_LED_PIN 17
#define PROFILE_LED_PIN 18
#define NUM_PAD_LEDS 32
#define NUM_PROFILE_LEDS 3
Adafruit_NeoPixel padLeds(NUM_PAD_LEDS, PAD_LED_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel profileLeds(NUM_PROFILE_LEDS, PROFILE_LED_PIN, NEO_GRB + NEO_KHZ800);
const uint32_t PROFILE_COLORS[3] = {
0x00FFFF, 0xFF8800, 0xFF00FF
};
// ── État ───────────────────────────────────────────
bool padLastState[32] = {};
bool profileLastState[3] = {};
int currentProfile = 0;
// ── Filtre analogique ──────────────────────────────
#define AVG_SAMPLES 32
#define DEADBAND 8
#define DIR_CONFIRM 6
int knobBuf[8][AVG_SAMPLES] = {};
int faderBuf[8][AVG_SAMPLES] = {};
int knobIdx[8] = {};
int faderIdx[8] = {};
int knobMidiOut[8] = {};
int faderMidiOut[8] = {};
int knobDirCount[8] = {};
int faderDirCount[8] = {};
int knobPrevMidi[8] = {};
int faderPrevMidi[8] = {};
// ── Debug : compteur de loops ──────────────────────
unsigned long loopCount = 0;
// ── Sélection canal MUX ────────────────────────────
void selectMuxChannel(int ch) {
digitalWrite(MUX_S0, (ch >> 0) & 1);
digitalWrite(MUX_S1, (ch >> 1) & 1);
digitalWrite(MUX_S2, (ch >> 2) & 1);
digitalWrite(MUX_S3, (ch >> 3) & 1);
delayMicroseconds(50); // délai plus long pour stabilisation MUX
}
// ── Moyenne glissante ──────────────────────────────
int updateAvg(int buf[], int &idx, int newVal) {
buf[idx] = newVal;
idx = (idx + 1) % AVG_SAMPLES;
long sum = 0;
for (int i = 0; i < AVG_SAMPLES; i++) sum += buf[i];
return (int)(sum / AVG_SAMPLES);
}
// ── Filtre directionnel ────────────────────────────
int directionalFilter(int newMidi, int &lastOut,
int &prevMidi, int &dirCount) {
int delta = newMidi - prevMidi;
prevMidi = newMidi;
if (delta > 0) dirCount = max(dirCount + 1, 1);
else if (delta < 0) dirCount = min(dirCount - 1, -1);
else dirCount = 0;
bool confirmed = (abs(dirCount) >= DIR_CONFIRM)
&& (abs(newMidi - lastOut) > DEADBAND);
if (confirmed) {
dirCount = 0;
lastOut = newMidi;
return newMidi;
}
return -1;
}
// ── LEDs ───────────────────────────────────────────
void refreshPadLeds() {
for (int i = 0; i < NUM_PAD_LEDS; i++) {
padLeds.setPixelColor(i, padLastState[i] ? 0x00FFFF : 0x111111);
}
padLeds.show();
}
void refreshProfileLeds() {
for (int i = 0; i < NUM_PROFILE_LEDS; i++) {
profileLeds.setPixelColor(i,
(i == currentProfile) ? PROFILE_COLORS[i] : 0x000000);
}
delayMicroseconds(300);
profileLeds.show();
}
// ── Setup ──────────────────────────────────────────
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("=== MIDI Controller MUX ===");
pinMode(MUX_S0, OUTPUT);
pinMode(MUX_S1, OUTPUT);
pinMode(MUX_S2, OUTPUT);
pinMode(MUX_S3, OUTPUT);
// !! Dans Wokwi avec digitalMux=1, le CD74HC4067 laisse
// le SIG flottant quand aucun bouton n'est pressé.
// On utilise INPUT_PULLUP sur l'ESP pour que l'état repos = HIGH.
// Le bouton tire vers GND → LOW = pressé. C'est correct.
pinMode(MUX_PADS_A_SIG, INPUT_PULLUP);
pinMode(MUX_PADS_B_SIG, INPUT_PULLUP);
for (int p = 0; p < 3; p++) pinMode(PROFILE_PINS[p], INPUT_PULLUP);
padLeds.begin(); padLeds.setBrightness(80);
padLeds.clear(); padLeds.show(); delay(10);
profileLeds.begin(); profileLeds.setBrightness(120);
profileLeds.clear(); profileLeds.show(); delay(10);
// Pré-remplissage buffers analogiques
for (int ch = 0; ch < 8; ch++) {
selectMuxChannel(ch);
delay(5);
long kSum = 0, fSum = 0;
for (int s = 0; s < 8; s++) {
kSum += analogRead(MUX_KNOBS_SIG);
fSum += analogRead(MUX_FADERS_SIG);
delay(2);
}
int kInit = (int)(kSum / 8);
int fInit = (int)(fSum / 8);
for (int s = 0; s < AVG_SAMPLES; s++) {
knobBuf[ch][s] = kInit;
faderBuf[ch][s] = fInit;
}
knobMidiOut[ch] = map(kInit, 0, 4095, 0, 127);
faderMidiOut[ch] = map(fInit, 0, 4095, 0, 127);
knobPrevMidi[ch] = knobMidiOut[ch];
faderPrevMidi[ch] = faderMidiOut[ch];
}
currentProfile = 0;
refreshPadLeds();
delay(10);
refreshProfileLeds();
// ── DEBUG : lecture brute de tous les canaux au démarrage ──
Serial.println("--- DEBUG PADS au demarrage ---");
for (int ch = 0; ch < 16; ch++) {
selectMuxChannel(ch);
int a = digitalRead(MUX_PADS_A_SIG);
int b = digitalRead(MUX_PADS_B_SIG);
Serial.printf(" CH%02d : A=%d B=%d\n", ch, a, b);
}
Serial.println("--- DEBUG KNOBS brut (ch0) ---");
selectMuxChannel(0);
for (int i = 0; i < 5; i++) {
Serial.printf(" K_raw=%d F_raw=%d\n",
analogRead(MUX_KNOBS_SIG), analogRead(MUX_FADERS_SIG));
delay(10);
}
Serial.println("--- FIN DEBUG ---");
Serial.println("[INIT] Pret !");
}
// ── Scan pads ──────────────────────────────────────
void scanPads() {
bool changed = false;
for (int ch = 0; ch < 16; ch++) {
selectMuxChannel(ch);
bool stateA = (digitalRead(MUX_PADS_A_SIG) == LOW);
if (stateA != padLastState[ch]) {
padLastState[ch] = stateA;
changed = true;
Serial.printf("[PAD %d] %s\n", ch + 1, stateA ? "ON" : "OFF");
}
bool stateB = (digitalRead(MUX_PADS_B_SIG) == LOW);
if (stateB != padLastState[ch + 16]) {
padLastState[ch + 16] = stateB;
changed = true;
Serial.printf("[PAD %d] %s\n", ch + 17, stateB ? "ON" : "OFF");
}
}
if (changed) refreshPadLeds();
}
// ── Scan profils ───────────────────────────────────
void scanProfiles() {
for (int p = 0; p < 3; p++) {
bool pressed = (digitalRead(PROFILE_PINS[p]) == LOW);
if (pressed && !profileLastState[p]) {
currentProfile = p;
Serial.printf("[PROFILE] P%d\n", p + 1);
refreshProfileLeds();
}
profileLastState[p] = pressed;
}
}
// ── Scan knobs ─────────────────────────────────────
void scanKnobs() {
for (int ch = 0; ch < 8; ch++) {
selectMuxChannel(ch);
int raw = analogRead(MUX_KNOBS_SIG);
int avg = updateAvg(knobBuf[ch], knobIdx[ch], raw);
int midi = map(avg, 0, 4095, 0, 127);
int res = directionalFilter(midi, knobMidiOut[ch],
knobPrevMidi[ch], knobDirCount[ch]);
if (res >= 0) {
Serial.printf("[KNOB %d] MIDI=%d\n", ch + 1, res);
}
}
}
// ── Scan faders ────────────────────────────────────
void scanFaders() {
for (int ch = 0; ch < 8; ch++) {
selectMuxChannel(ch);
int raw = analogRead(MUX_FADERS_SIG);
int avg = updateAvg(faderBuf[ch], faderIdx[ch], raw);
int midi = map(avg, 0, 4095, 0, 127);
int res = directionalFilter(midi, faderMidiOut[ch],
faderPrevMidi[ch], faderDirCount[ch]);
if (res >= 0) {
Serial.printf("[FADER %d] MIDI=%d\n", ch + 1, res);
}
}
}
// ── Loop ───────────────────────────────────────────
void loop() {
scanPads();
scanProfiles();
scanKnobs();
scanFaders();
// Debug périodique toutes les 200 loops : affiche l'état brut
// de MuxPadsA canal 0 pour vérifier si le SIG répond
loopCount++;
if (loopCount % 200 == 0) {
selectMuxChannel(0);
Serial.printf("[DBG] CH0 A=%d B=%d | K_raw=%d F_raw=%d\n",
digitalRead(MUX_PADS_A_SIG),
digitalRead(MUX_PADS_B_SIG),
analogRead(MUX_KNOBS_SIG),
analogRead(MUX_FADERS_SIG));
}
delay(10);
}Loading
cd74hc4067
cd74hc4067
Loading
cd74hc4067
cd74hc4067
Loading
cd74hc4067
cd74hc4067
Loading
cd74hc4067
cd74hc4067