#include <MIDI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
const int switchPins[12] = {12, 13, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; // Schalter-Pins
const int ledPins[10] = {23, 25, 27, 29, 31, 33, 35, 37, 39, 41}; // LED-Pins
const int ccValues[10] = {35, 36, 37, 38, 44, 39, 40, 41, 42, 45};
const char* ccNames[12] = {
// Funktions-Namen für Schalter 3-11
"FUZZ", "CHORUS", "DLY LEAD", "DLY AMBIENT", "TAP-TEMPO",
"DIST", "MX FLANGER", "DRIVE", "MX VIBE", "TUNE",
// Funktions-Namen für Schalter 1&2
"LOADED", "STORED"
};
const int MAX_PRESETS = 99;
const int SWITCHES_TO_SAVE = 8;
const int switchesToSaveIndices[SWITCHES_TO_SAVE] = {2, 3, 4, 5, 7, 8, 9, 10}; // Indizes der zu speichernden Schalter
struct Preset {
bool switchStates[SWITCHES_TO_SAVE];
};
bool switchStates[12] = {false}; // Aktueller Zustand der Schalter
bool lastButtonStates[12] = {false}; // Letzter physischer Zustand der Schalter
bool lastSwitchState = false;
int currentPreset = 0;
int lastProgramChange = 0;
int lastCCValue = -1; // -1 bedeutet, dass noch kein CC-Wert empfangen wurde
String lastCCName = ""; // Variable für den letzten CC-Namen
String formatTwoDigit(int number) {
return (number < 10 ? "0" : "") + String(number);
}
// LED-Blinkzeit
unsigned long lastBlinkTime = 0;
const unsigned long blinkInterval = 200; // 200ms Blinkintervall
bool blinkState = false;
// Auto-OFF nach 1 sekunde
unsigned long pressStartTime[12] = {0};
const unsigned long longPressTime = 1000; // 1 Sekunde für LONGPRESS
bool isLongPress[12] = {false};
// Neue Variablen für das Ausblenden des CC-Namens und CC-Werts
const unsigned long ccValueDisplayTime = 300; // 300 Millisekunden für CC-Wert
const unsigned long ccNameDisplayTime = 2000; // 2 Sekunden für CC-Name
unsigned long lastCCValueTime = 0;
unsigned long lastCCNameTime = 0;
//Tap-Tempo definition
const int tapTempoSwitch = 4; // Index des Tap-Tempo-Schalters (Pin 6)
unsigned long lastTapTime = 0;
unsigned long tapInterval = 0;
int currentBPM = 0;
// Neue Variablen für die Durchschnittsberechnung der Tempo-Taps
const int MAX_TAPS = 8;
unsigned long tapTimes[MAX_TAPS] = {0};
int tapIndex = 0;
MIDI_CREATE_DEFAULT_INSTANCE();
void setup() {
MIDI.begin(MIDI_CHANNEL_OMNI);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
for(;;);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
for (int i = 0; i < 12; i++) {
pinMode(switchPins[i], INPUT_PULLUP);
}
for (int i = 0; i < 10; i++) {
pinMode(ledPins[i], OUTPUT);
digitalWrite(ledPins[i], LOW);
}
}
void loop() {
MIDI.read();
checkSwitches();
updateLEDs();
displayInfo();
}
void checkSwitches() {
for (int i = 0; i < 12; i++) {
bool currentButtonState = digitalRead(switchPins[i]) == LOW;
if (currentButtonState && !lastButtonStates[i]) {
// Button wurde gerade gedrückt
pressStartTime[i] = millis();
isLongPress[i] = false;
if (i >= 2) { // Nur für Schalter 3-12 (Pins 2-10)
handleSwitch(i);
}
} else if (currentButtonState && lastButtonStates[i]) {
// Button wird gehalten
if (!isLongPress[i] && millis() - pressStartTime[i] >= longPressTime) {
// LONGPRESS erkannt
isLongPress[i] = true;
if (i < 2) { // Für Schalter 1-2 (Pins 12 und 13)
handleSwitch(i);
}
}
} else if (!currentButtonState && lastButtonStates[i]) {
// Button wurde gerade losgelassen
if (i >= 2) { // Nur für Schalter 3-12 (Pins 2-10)
if (isLongPress[i]) {
switchStates[i] = false;
sendCCMessage(i - 2, 0);
updateLEDs();
}
}
}
lastButtonStates[i] = currentButtonState;
}
}
void handleSwitch(int index) {
if (index == 0) { // Load preset
loadPreset();
} else if (index == 1) { // Save preset
savePreset();
} else if (index - 2 == tapTempoSwitch) { // Tap Tempo
unsigned long now = millis();
// Überprüfe, ob es sich um einen neuen Tap-Zyklus handelt
if (now - lastTapTime > 2000) {
// Wenn mehr als 2 Sekunden vergangen sind, starte einen neuen Zyklus
tapIndex = 0;
for (int i = 0; i < MAX_TAPS; i++) {
tapTimes[i] = 0;
}
}
tapTimes[tapIndex] = now;
tapIndex = (tapIndex + 1) % MAX_TAPS;
if (tapIndex >= 2) { // Wir beginnen die Berechnung nach dem zweiten Tap
// Berechne den Durchschnitt der verfügbaren Intervalle
unsigned long totalInterval = 0;
int validIntervals = 0;
int startIndex = (tapIndex >= MAX_TAPS) ? tapIndex % MAX_TAPS : 0;
for (int i = 0; i < MAX_TAPS - 1; i++) {
int currentIndex = (startIndex + i) % MAX_TAPS;
int nextIndex = (startIndex + i + 1) % MAX_TAPS;
if (tapTimes[nextIndex] > tapTimes[currentIndex]) {
totalInterval += tapTimes[nextIndex] - tapTimes[currentIndex];
validIntervals++;
}
}
if (validIntervals > 0) {
unsigned long avgInterval = totalInterval / validIntervals;
if (avgInterval > 200 && avgInterval < 2000) {
currentBPM = 60000 / avgInterval;
sendCCMessage(tapTempoSwitch, currentBPM);
}
}
}
lastTapTime = now;
} else { // Andere Switches (3-12)
switchStates[index] = !switchStates[index];
sendCCMessage(index - 2, switchStates[index] ? 127 : 0);
}
updateLEDs();
}
void savePreset() {
if (currentPreset >= MAX_PRESETS) return; // Sicherheitscheck
Preset currentPresetData;
// Speichere den Zustand der ausgewählten Schalter
for (int i = 0; i < SWITCHES_TO_SAVE; i++) {
currentPresetData.switchStates[i] = switchStates[switchesToSaveIndices[i]];
}
// Speichere das Preset im EEPROM
EEPROM.put(currentPreset * sizeof(Preset), currentPresetData);
// Aktualisiere die Anzeige
lastCCName = ccNames[11]; // "STORED"
lastCCNameTime = millis();
lastSwitchState = true;
}
void loadPreset() {
if (currentPreset >= MAX_PRESETS) return; // Sicherheitscheck
Preset loadedPreset;
EEPROM.get(currentPreset * sizeof(Preset), loadedPreset);
// Lade den Zustand der ausgewählten Schalter und sende CC-Nachrichten bei Änderungen
for (int i = 0; i < SWITCHES_TO_SAVE; i++) {
int switchIndex = switchesToSaveIndices[i];
if (switchStates[switchIndex] != loadedPreset.switchStates[i]) {
switchStates[switchIndex] = loadedPreset.switchStates[i];
sendCCMessage(i, switchStates[switchIndex] ? 127 : 0); // Geändert von 'switchIndex - 2' zu 'i'
}
}
// Aktualisiere die Anzeige
lastCCName = ccNames[10]; // "LOADED"
lastCCNameTime = millis();
lastSwitchState = true;
updateLEDs();
}
void sendCCMessage(int index, int value) {
int ccValue = ccValues[index];
MIDI.sendControlChange(ccValue, value, 1);
lastCCValue = ccValue; // Speichere den letzten CC-Wert
lastCCValueTime = millis(); // Aktualisiere die Zeit des letzten CC-Werts
if (value == 127) { // Nur wenn der Schalter auf AN geschaltet wird
lastCCName = ccNames[index]; // Speichere den letzten CC-Namen
lastCCNameTime = millis(); // Aktualisiere die Zeit des letzten CC-Namens
lastSwitchState = true;
} else {
lastSwitchState = false;
}
}
void updateLEDs() {
unsigned long currentTime = millis();
if (currentTime - lastBlinkTime >= blinkInterval) {
lastBlinkTime = currentTime;
blinkState = !blinkState;
}
for (int i = 0; i < 10; i++) {
if (i == tapTempoSwitch) {
// Tap Tempo LED blinkt im Takt
digitalWrite(ledPins[i], (currentBPM > 0 && (currentTime % (60000 / currentBPM)) < 100) ? HIGH : LOW);
} else if (ledPins[i] == 41) { // Nur LED an PIN 41 soll blinken
digitalWrite(ledPins[i], (switchStates[i + 2] && blinkState) ? HIGH : LOW);
} else {
digitalWrite(ledPins[i], switchStates[i + 2] ? HIGH : LOW);
}
}
}
String formatBPM(int bpm) {
if (bpm == 0) return " "; // Drei Leerzeichen für kein Tempo
return bpm < 100 ? " " + String(bpm) : String(bpm);
}
void displayInfo() {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print("RX-PC:");
display.print(formatTwoDigit(lastProgramChange));
display.print(" / TX-CC:");
if (lastCCValue != -1 && (millis() - lastCCValueTime < ccValueDisplayTime)) {
display.println(formatTwoDigit(lastCCValue));
} else {
display.println();
}
display.println();
display.setTextSize(2);
display.print("Preset:");
display.println(formatTwoDigit(currentPreset));
display.print("Tempo:");
display.println(formatBPM(currentBPM));
if (lastSwitchState && millis() - lastCCNameTime < ccNameDisplayTime) {
display.println(lastCCName);
}
display.display();
}
void handleProgramChange(byte channel, byte program) {
if (program < MAX_PRESETS) {
lastProgramChange = program;
currentPreset = program;
loadPreset();
}
}
void handleControlChange(byte channel, byte number, byte value) {
// Hier kannst du die Control Change-Nachrichten verarbeiten, falls nötig
}