/* ******************************************************************************
* X Y L U I N O v0.81 - https://intuitiv.de/xyluino
*
* Last change: 20.02.2025
*
* History:
* --------
*
* 2020/10/03 Erster Code-Entwurf
*
* 2020/11/24 Aktivierung für 3x 8-fach AD-Multiplexer
* Code-Eergänzung für schnellere AD-Wandlung
*
* 2020/12/02 Test-Routine Output zur Kalibrierung der Anschlagdynamik
*
* 2020/12/06 TFT-Display und Keypad
*
* 2026/02/03 Arduino Nano Version [Gucky]
* - Klaviaturbreite = 133cm
* - MIDI OUT Led gebaut
* - NEO Pixel LEDs eingebaut
* - int Variablen in Byte geändert, wo möglich (spart Speicher)
* - Prog Change einbgebaut (auch mit Piezo)
* - Chan Changeeingebaut (auch mit Piezo)
* - Transpose + und - eingebaut (auch mit Piezo)
*
* 2026/02/18 Erweiterungen mit ChatGPT
* - MIDI Kanal (0–16)
* - Instrument (0–7)
* - Transpose (-2 / -1 / 0 / +1 / +2)
* - LED Helligkeit (0–255)
* - Velocity an/aus
*
* ******************************************************************************
*
* Pin-Belegung für Multiplexer
* Multiplex1 D2
* Multiolex2 D3
* Multiplex3 D4
* Multiplex4 D5
*
* Analog Input der Multiplexer
* AnalogIn1 A0
* AnalogIn2 A1
* AnalogIn3 A2
*
* Taster-Belegung an den IO-Ports
* Taster1 D6
* Taster2 D7
* Taster4 A3
*
* Display (Pin) - HW-221 - Arduino (Nano)
* GND (1) <--> GND <--> GND
* VCC (2) <--> VB <--> +5V
* SCK (3) <--> B1 <--> D13
* RES (5) <--> B4 <--> D8
* RS (6) <--> B3 <--> D9
* CS (7) <--> B2 <--> D10
* LEDA (8) <--> VA <--> 3.3V
*
* A4 = Sustain Pedal
* A5 = Programmwechel +
* A6 = MIDI Kanal +
* A7 =
*
* Zu freien Verfügung
* Multiplexer 3, Kanäle 11,12,13,14,15
*
*/
//*******************************************************************************************************************
// Definitions
//*******************************************************************************************************************
#include <TFT.h> // TFT Library
#include <SPI.h> // SPI für die Kommunikation
#include <Adafruit_NeoPixel.h> // LED Stripe (WS2812B) ansteuern
//#include <SimpleRotary.h> // Rotary Encoder einbinden
//#include <EEPROM.h> // EEprom Zugriff ermöglichen
//#include <SoftwareSerial.h>
// TFT Farbwerte R G B
#define BLACK 0, 0, 0
#define WHITE 255, 255, 255
#define RED_1 255, 0, 0
#define RED_2 210, 0, 0
#define RED_3 150, 0, 0
#define RED_4 100, 0, 0
#define RED_5 60, 0, 0
#define RED_6 30, 0, 0
#define GREEN_1 0, 255, 0
#define GREEN_2 0, 200, 0
#define GREEN_3 0, 120, 0
#define GREEN_4 0, 80, 0
#define GREEN_5 0, 50, 0
#define GREEN_6 0, 20, 0
#define BLUE_1 0, 0, 255
#define BLUE_2 0, 0, 200
#define BLUE_3 0, 0, 150
#define BLUE_4 0, 0, 110
#define BLUE_5 0, 0, 60
#define BLUE_6 0, 0, 30
#define CYAN_1 0, 255, 255
#define CYAN_2 0, 200, 200
#define CYAN_3 0, 150, 150
#define CYAN_4 0, 110, 110
#define CYAN_5 0, 60, 60
#define CYAN_6 0, 30, 30
#define ORANGE_1 250, 100, 0
#define ORANGE_2 240, 80, 0
#define ORANGE_3 200, 60, 0
#define ORANGE_4 120, 25, 0
#define ORANGE_5 90, 15, 0
#define ORANGE_6 80, 10, 0
#define YELLOW_1 255, 255, 0
#define YELLOW_2 255, 255, 20
#define YELLOW_3 255, 255, 40
#define YELLOW_4 255, 255, 60
#define YELLOW_5 255, 255, 80
#define YELLOW_6 255, 255, 130
#define GREY_1 220, 220, 220
#define GREY_2 180, 180, 180
#define GREY_3 150, 150, 150
#define GREY_4 100, 100, 100
#define GREY_5 60, 60, 60
#define GREY_6 20, 20, 20
#define MAGENTA_1 255, 30, 200
#define MAGENTA_2 150, 0, 70
#define muxAddress0 2 // Multiplexer Adressbit 0
#define muxAddress1 3 // Multiplexer Adressbit 1 [PWM]
#define muxAddress2 4 // Multiplexer Adressbit 2
#define muxAddress3 5 // Multiplexer Adressbit 3 [PWM]
#define pinPwmDisplayLight 6 // Steuert mit PWM die Displayhelligkeit [PWM]
#define pinLedStripeData 7 // WS2128B Steuerpin
#define TFTrst 8 // Display RST Signal
#define TFTdc 9 // Display DC Signal [PWM]
#define TFTcs 10 // Display CS Signal [PWM]
#define MOSI 11 // Display MOSI/SDA [PWM]
#define pinMidiOutLed 12 // Ausgabepin für MIDI Out Led
#define SCK 13 // Display SCK/SCL
#define pinProgramChange A3
#define pinMidiChannelChange A4
#define pinTransposeChangeDown A5
#define pinTransposeChangeUp A6 // RGB LED Steuerpin D5 [5]
#define pinSustainPedal A7 // Anschluss Sustainpedal
/*
* Midi-Noten
* C2: 36
* C4: 60
* A4 (440Hz) : 69
* C7: 96 (nicht mehr spielbar)
*
* normal: C3, C4, C5, C6 (->H7)
* tiefer: C2, C3, C4, C5 (->H6)
*/
// Midi-Notenwerte
const char* version = "0.81 by Gucky 15.02.2026";
unsigned char PadNote[72] = {36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107};
// Threshold Werte, Anschlagsempfindlichkeit
// Tuning-Bedarf ! Ch 10
int padCutOff[48] = {300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300,300};
// time each note remains on after being hit
int maxPlayTime[48] = {50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50};
// array of flags of pad currently playing
boolean activePad[48] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
// counter since pad started to play
int pinPlayTime[48] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // Spieldauer
boolean velocityFlag = false; // Velocity ein/aus
unsigned long midiOutLedTime;
int hitavg = 0;
byte pad = 0;
unsigned char status; // byte? Wird nu für MIDI Message benutzt
boolean sustainStatus = false; // Sustain-Pedal
boolean lastSustainStatus = false;
// instrumente
boolean instrumentStatus = false;
boolean lastInstrumentStatus = false;
byte instrumentNumber = 0;
byte midiChannel = 1; // 0 = aus | 1 ...16
int instrumentProg[8] = { 127, 100, 10, 3, 102, 103, 104, 105 };
char* instrumentName[8] = { "Marimba", "Vibraphon", "Xylophon", "Glockenspiel", "Davinci Code", "Kalimba", "Wood Mallet", "Yellow" };
byte byte1;
byte byte2;
byte byte3;
byte r0 = 0; // value of select pin at the 4051 (s0)
byte r1 = 0; // value of select pin at the 4051 (s1)
byte r2 = 0; // value of select pin at the 4051 (s2)
byte r3 = 0; // value of select pin at the 4051 (s3)
byte count = 0; // Aktueller Multiplxer Eingang (0-15)
int multiplex[48]; // Multiplexer Array für die gelesenen Analogwerte
byte midiOctave = 12; // Oktave in Oktavenschritten, steuerbar über Taster/Pad. Standard = 12
byte lastmidiOctave = 24;
bool midiOctaveStatus = false;
bool lastMidiOctaveStatus = false;
bool channelChangeStatus = false;
bool lastChannelChangeStatus= false;
bool progChangeStatus = false;
bool lastProgChangeStatus = false;
byte numLed = 49; // Anzahl der RGB LEDs (Pro Pas 2 LEDs)
int rgbHueOff = 170; // Werte für nicht gedrückte Tasten (H, S, V)
byte rgbSatOff = 255;
byte rgbValOff = 30;
int rgbHueOn = 235; // LED Werte für gedrückte Tasten (H, S, V)
byte rgbSatOn = 255;
byte rgbValOn = 30;
uint32_t rgbColOff, rgbColOn;
// ---------------- MENU SYSTEM ----------------
bool menuActive = false;
bool lastMenuButtonState = false;
unsigned long menuButtonPressTime = 0;
byte menuIndex = 0;
const byte menuItems = 5;
byte ledBrightness = 255;
int transposeSteps = 0; // -2 ... +2
bool lastBtnA3 = false;
bool lastBtnA4 = false;
bool lastBtnA5 = false;
bool lastBtnA6 = false;
// Analoges Lesen beschleunigen. Drumrolls funktionierenmit diesem Code.
#define FASTADC 1
// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
TFT TFTscreen = TFT(TFTcs, TFTdc, TFTrst);
Adafruit_NeoPixel rgbStripe(numLed, pinLedStripeData, NEO_GRB + NEO_KHZ800); // LED Stripe initialiseren
// ##################################################################################################################
// ############################################ SETUP ###############################################################
// ##################################################################################################################
void setup()
{
/*
* ST7735-Chip initialisieren (INITR_BLACKTAB / INITR_REDTAB / INITR_GREENTAB)
* Muss bei AZ-Delivery 1.77'' 160x128px RGB TFT INITR_GREENTAB sein ansonsten Pixelfehler rechts und unten.
* Hinweis: https://github.com/adafruit/Adafruit-ST7735-Library/blob/master/examples/soft_spitftbitmap/soft_spitftbitmap.ino#L52
* Zeile 52-65
*/
rgbColOff = rgbStripe.ColorHSV(rgbHueOff * 256, rgbSatOff, rgbValOff); // Farbwert für ausgeschaltete LEDs
rgbColOn = rgbStripe.ColorHSV(rgbHueOn * 256, rgbSatOn, rgbValOn); // Farbwert für eingeschaltete LEDs
rgbStripe.begin();
TFTscreen.begin(); // Display initialisieren und Startbild zeichnen
for(byte y = 0; y <= numLed / 2; y ++)
{
rgbStripe.setPixelColor(numLed - y, rgbColOn);
rgbStripe.setPixelColor(y , rgbColOn);
delay(100);
rgbStripe.show();
}
rgbStripe.show();
TFTscreen.initR(INITR_BLACKTAB);
TFTscreen.setRotation(1);
TFTscreen.background(0x0); // Hintergrundfarbe schwarz
TFTscreen.stroke(0xFFFF); // Zeichenfarbe
TFTscreen.setTextSize(2); // Klaviatur zeichnen
TFTscreen.fillRoundRect( 7, 0, 18, 47, 3, TFTscreen.Color565(CYAN_4));
TFTscreen.drawRoundRect( 7, 0, 18, 47, 3, TFTscreen.Color565(CYAN_1));
TFTscreen.fillRoundRect( 27, 0, 18, 47, 3, TFTscreen.Color565(ORANGE_3));
TFTscreen.drawRoundRect( 27, 0, 18, 47, 3, TFTscreen.Color565(ORANGE_1));
TFTscreen.fillRoundRect( 51, 0, 18, 47, 3, TFTscreen.Color565(RED_3));
TFTscreen.drawRoundRect( 51, 0, 18, 47, 3, TFTscreen.Color565(RED_1));
TFTscreen.fillRoundRect( 71, 0, 18, 47, 3, TFTscreen.Color565(YELLOW_1));
TFTscreen.drawRoundRect( 71, 0, 18, 47, 3, TFTscreen.Color565(YELLOW_4));
TFTscreen.fillRoundRect( 91, 0, 18, 47, 3, TFTscreen.Color565(BLUE_4));
TFTscreen.drawRoundRect( 91, 0, 18, 47, 3, TFTscreen.Color565(BLUE_1));
TFTscreen.fillRoundRect( 115, 0, 18, 47, 3, TFTscreen.Color565(MAGENTA_2));
TFTscreen.drawRoundRect( 115, 0, 18, 47, 3, TFTscreen.Color565(MAGENTA_1));
TFTscreen.fillRoundRect( 135, 0, 18, 47, 3, TFTscreen.Color565(GREEN_3));
TFTscreen.drawRoundRect( 135, 0, 18, 47, 3, TFTscreen.Color565(GREEN_2));
TFTscreen.fillRoundRect( 40, 0, 16, 27, 3, 0x0); // Schwarze Taste 1
TFTscreen.fillRoundRect( 41, 0, 14, 26, 3, TFTscreen.Color565(GREEN_3));
TFTscreen.drawRoundRect( 41, 0, 14, 26, 3, TFTscreen.Color565(GREEN_1));
TFTscreen.fillRoundRect( 104, 0, 16, 27, 3, 0x0); // Schwarze Taste 2
TFTscreen.fillRoundRect( 105, 0, 14, 26, 3, TFTscreen.Color565(CYAN_4));
TFTscreen.drawRoundRect( 105, 0, 14, 26, 3, TFTscreen.Color565(CYAN_1));
TFTscreen.fillRect(0, 0, 160, 2, 0x0); // Tasten oben abschneiden
TFTscreen.drawRoundRect(0, 0, 160, 127, 4, 0xFFFF);
TFTscreen.drawRoundRect(1, 1, 158, 125, 3, 0xFFFF);
TFTscreen.stroke(TFTscreen.Color565(RED_3));
TFTscreen.text("N", 11, 27);
TFTscreen.stroke(BLACK);
TFTscreen.text("a", 31, 29);
TFTscreen.text("n", 55, 29);
TFTscreen.text("o", 75, 29);
TFTscreen.stroke(TFTscreen.Color565(GREEN_1));
TFTscreen.text("X", 95, 29);
TFTscreen.stroke(BLACK);
TFTscreen.text("y", 119, 29);
TFTscreen.text("l", 139, 29);
//TFTscreen.fillRect(2, 60, 156, 64, TFTscreen.Color565(GREY_5)); // Untere Bildhälfte löschen
TFTscreen.setTextSize(1); // Copyrightmeldung
TFTscreen.stroke(0xFFFF);
TFTscreen.text(version, 8, 51);
// -------------------------------------------------
TFTscreen.fillRect(2, 62, 156, 50, 0x0); // Instrumentenbereich komplett löschen
TFTscreen.drawRoundRect( 5, 62, 150, 48, 3, 0xFFFF); // Abgerundetes Rechteck um alles zeichnen
TFTscreen.setTextSize(1); // Textgröße auf klein setzen
TFTscreen.stroke(GREEN_2); // Schriftfarbe setzen
TFTscreen.text("INSTRUMENT:", 7, 66); // Schriftfarbe setzen
TFTscreen.drawFastVLine(110, 62, 14, 0xFFFF); // Vertikale Linie zeichnen
TFTscreen.drawFastHLine( 6, 76, 148, 0xFFFF);
updateMidiChannel(); // MIDI Kanal aktualisieren
updateInstrument(); // Instrument aktualisieren
updateTranspose(); // Trnspose aktualisieren
// Control Multiplexer
pinMode(muxAddress0, OUTPUT); // Multiplexer Adresse Bit 0
pinMode(muxAddress1, OUTPUT); // Multiplexer Adresse Bit 1
pinMode(muxAddress2, OUTPUT); // Multiplexer Adresse Bit 2
pinMode(muxAddress3, OUTPUT); // Multiplexer Adresse Bit 3
// Analog Input von den 3 Multiplexern
// A0, A1, A2
// Taster
pinMode(pinProgramChange, INPUT); // Programmwechsel + (A3)
pinMode(pinMidiChannelChange, INPUT); // Taster/Pad MIDI Channel + (A4)
pinMode(pinTransposeChangeDown, INPUT); // Taster/Pad Trasnspose - (A6)
pinMode(pinTransposeChangeUp, INPUT); // Taster/Pad Trasnspose + (A6)
pinMode(pinSustainPedal, INPUT); // Sustainpedal (A7)
// LEDs
pinMode(pinMidiOutLed, OUTPUT); // MIDI Out LED
pinMode(pinPwmDisplayLight, OUTPUT); // Steuert die Displaybeleuchtung (max. 3.3 Volt/5mA) [0 (= 0V) ... 255 (= 5V)]
pinMode(pinLedStripeData, OUTPUT); // RGB LED Stripe
#if FASTADC // AD-Wandler beschleunigen
// set prescale to 16
sbi(ADCSRA,ADPS2) ;
cbi(ADCSRA,ADPS1) ;
cbi(ADCSRA,ADPS0) ;
#endif
MIDI_PC(instrumentProg[instrumentNumber]); // Standard Program Nummer: Marimba
analogWrite(pinPwmDisplayLight, 255); // Bei 5Volt sind 169 3.2Volt
for(byte y = 0; y <= numLed / 2; y ++)
{
rgbStripe.setPixelColor(numLed - y, rgbColOff);
rgbStripe.setPixelColor(y , rgbColOff);
delay(50);
rgbStripe.show();
}
Serial.begin(31250); // Seriellen Port für MIDI öffnen
// Selbsttest (1 x beim Einschalten)
/*
for(byte y = 0; y <= numLed; y ++)
{
//rgbStripe.setPixelColor(0 + y, rgbStripe.ColorHSV( rgbHueOff * 256, rgbSatOff, rgbValOff)); // Alle Tasten von links mit Farbe 1...
//rgbStripe.setPixelColor(numLed - y, rgbStripe.ColorHSV( rgbHueOn * 256, rgbSatOn , rgbValOn)); // und von rechts mit Farbe 2 füllen.
rgbStripe.setPixelColor(0 + y, rgbValueOff); // Alle Tasten von links mit Farbe 1...
rgbStripe.setPixelColor(numLed - y, rgbValueOn); // und von rechts mit Farbe 2 füllen.
rgbStripe.show();
delay(1000);
}
delay(500);
for(byte y = 0; y < 33; y++)
{
rgbStripe.setBrightness(map(y, 0, 32, 255, 0)); // Helligeit aller LED von 64 bis 0 dimmen.
rgbStripe.show();
delay(1000);
}
delay(500);
rgbStripe.setBrightness(V1); // Alle LEDs auf Helligkeit 1 (NoteOff) setzen
for(byte x = 0; x < numLed; x++)
{
//rgbStripe.setPixelColor((numLed / 2) - x / 2, rgbStripe.ColorHSV( H1 * 256, S1, V1)); // Alle Tasten von links und rechts...
//rgbStripe.setPixelColor((numLed / 2) + x / 2, rgbStripe.ColorHSV( H1 * 256, S1, V1)); // mit Farbe 1 füllen.
rgbStripe.setPixelColor((numLed / 2) - x / 2, RGB1); // Alle Tasten von links und rechts...
rgbStripe.setPixelColor((numLed / 2) + x / 2, RGB1); // mit Farbe RGB1 füllen.
rgbStripe.show();
delay(1000);
}
*/
}
// ##################################################################################################################
// ############################################ LOOP ################################################################
// ##################################################################################################################
void loop()
{
if(millis() >= midiOutLedTime + 100) // Wenn die Einschaltzeit der MIDI Out LED 100mS überschreitet, ...
{
digitalWrite(pinMidiOutLed, LOW); // LED ausschalten (damit die LED nicht wild flackert, sondern leuchtet)
midiOutLedTime = millis();
}
checkMenuButton(); // Menüsystem
if(menuActive)
{
handleMenu();
return; // Sensoren pausieren solange Menü offen
}
// midiLoopback();
readSensors(); // Die Werte aller Multiplexer (der PADs) einlesen und in Array speichern.
checkSensors(); // Werte aus Array auslesen und Noten verarbeiten.
updateLedStripe();
checkPedal();
checkInstrumentChange(); // Wurde das Instrument gewechselt?
checkChannelChange(); // Wurde der Kanal geändert?
checkMidiOctave(); // Wurde die Transponierung geädert?
//updateMeasure();
}
void updateLedStripe()
{
for(byte x = 0;x < 15; x++)
{
if(activePad[x])
Serial.println(x);
rgbStripe.setPixelColor(x, activePad[x] == true? rgbColOn:rgbColOff);
//rgbStripe.setPixelColor(activePad[x * 2 + 1] , activePad[x] == true? rgbColOn:rgbColOff);
}
rgbStripe.show();
}
// ##################################################################################################################
// ###################################### SENSOREN EINLESEN #########################################################
// ##################################################################################################################
void readSensors ()
{
//byte count = 0;
for (int count = 0; count <= 15; count++)
{
// select the bit for digital out sent to multiplexer to cycle through 16 inputs
r0 = bitRead(count, 0); // Adressen der Multiplexer generieren (0 ... 15)
r1 = bitRead(count, 1);
r2 = bitRead(count, 2);
r3 = bitRead(count, 3);
digitalWrite(muxAddress0, r0); // Adressen an die Multiplexer ausgaben
digitalWrite(muxAddress1, r1);
digitalWrite(muxAddress2, r2);
digitalWrite(muxAddress3, r3);
multiplex[count] = analogRead(A0); // Werte von Multiplexer 0 einlesen und in Array ab Adr. 0 sichern
multiplex[count + 16] = analogRead(A1); // Werte von Multiplexer 1 einlesen und in Array ab Adr. 16 sichern
multiplex[count + 32] = analogRead(A2); // Werte von Multiplexer 2 einlesen und in Array ab Adr. 32 sichern
}
}
void checkSensors() // function to get values of each piezo; only checking a single analog input pin
{
//Serial.println("check");
for(byte senseCount = 0; senseCount <= 2; senseCount++)
{
//senseCount = 0;
for(byte pin = 0; pin <= 15; pin++)
{
pad = pin + (senseCount * 16);
hitavg = multiplex[pad]; // variable hitavg equals the voltage (0-1023) of the piezo
if(hitavg > padCutOff[pad]) // if the voltage of the piezo is higher than the value of the
{ // "threshold" element in array padCutOff, then:
if(activePad[pad] == false) // and if the pad wasn't already on or "active"
{
if(velocityFlag == true)
{
//hitavg = 127 / ((1023 - padCutOff[pin]) / (hitavg - padCutOff[pin])); // With full range (Too sensitive ?)
hitavg = (hitavg / 8) -1; // and if you want force to correspond to volume (velocityFlag=true),
} // set voltage of piezo into volume (or "velocity") range of MIDI note (0-127)
else
hitavg = 127; // if you don't care, set velocity to max value (127)
MIDI_TX(144, PadNote[pad + midiOctave], hitavg); // MIDI Message generieren und ausgeben
rgbStripe.setPixelColor(pad, rgbColOn);
/*
Serial.print(pad); //senseCount * 16 + pin);
Serial.print(" - ");
Serial.print("Value: ");
Serial.print(PadNote[pad]);
Serial.print(" - ");
int avgvolt;
avgvolt = (hitavg / 8) -1;
Serial.print(hitavg);
Serial.print(" : ");
Serial.println(avgvolt);
Serial.println();
Serial.println();
*/
updateMeasure();
pinPlayTime[pad] = 0; // reset the pin play time
activePad[pad] = true; // make the pin active (was inactive)
}
else // if the pad was already active when it was hit, increment its play time
pinPlayTime[pad] = pinPlayTime[pad] + 1;
}
else if(activePad[pad] == true) // the pad is active, but it is not greater than cutoff, increment play time
{
pinPlayTime[pad] = pinPlayTime[pad] + 1;
if(pinPlayTime[pad] > maxPlayTime[pad]) // but if it's already been on for the amount set in the maxPlayTime array,
{
activePad[pad] = false; // turn it off
MIDI_TX(128, PadNote[pad + midiOctave], 127); // velocity 0: OFF state
rgbStripe.setPixelColor(pad, rgbColOff);
}
}
}
rgbStripe.show();
}
}
// ##################################################################################################################
// ######################################## PEDAL ABFRAGEN ##########################################################
// ##################################################################################################################
void checkPedal() // Sustain-Pedal abfragen und ControlChange senden
{
if(digitalRead(pinSustainPedal) == HIGH)
sustainStatus = true;
else
sustainStatus = false;
if (sustainStatus == true && lastSustainStatus == false)
{
MIDI_CC(127);
lastSustainStatus = true;
}
if (sustainStatus == false && lastSustainStatus == true)
{
MIDI_CC(0);
lastSustainStatus = false;
}
}
// ##################################################################################################################
// ###################################### instrument WECHSELN #######################################################
// ##################################################################################################################
void checkInstrumentChange()
{
if(digitalRead(pinProgramChange) == HIGH)
instrumentStatus = true;
else
instrumentStatus = false;
if (instrumentStatus == true && lastInstrumentStatus == false)
{
instrumentNumber += 1; // Instrument Nr. +1
if (instrumentNumber > 7) // Ist Instrument > 7? Ja, ...
instrumentNumber = 0; // ... dann wieder bei 0 anfangem
updateInstrument();
lastInstrumentStatus = true;
}
if (instrumentStatus == false && lastInstrumentStatus == true)
lastInstrumentStatus = false;
}
// ##################################################################################################################
// ##################################### MIDI KANAL ÄNDERN ##########################################################
// ##################################################################################################################
void checkChannelChange()
{
if(digitalRead(pinMidiChannelChange) == HIGH)
channelChangeStatus = true;
else
channelChangeStatus = false;
if (channelChangeStatus == true && lastChannelChangeStatus == false)
{
if (midiChannel < 16) // Wenn MIDI Kanal < 16 ist, ...
midiChannel += 1; // ... um 1 erhöhen, ...
else
midiChannel = 0; // ... sonst auf 0 setzen (Dann erfolgt keine MIDI Ausgabe mehr).
updateMidiChannel();
lastChannelChangeStatus = true;
}
if (channelChangeStatus == false && lastChannelChangeStatus == true)
lastChannelChangeStatus = false;
}
// ##################################################################################################################
// ######################################### OKTAVE ÄNDERN ##########################################################
// ##################################################################################################################
void checkMidiOctave()
{
if(digitalRead(pinTransposeChangeUp) == HIGH)
lastMidiOctaveStatus = true;
else
lastMidiOctaveStatus = false;
if (lastMidiOctaveStatus == true && lastMidiOctaveStatus == false)
{
if (midiOctave == 0)
{
midiOctave = 12;
lastmidiOctave = 0;
}
else if (midiOctave == 24)
{
midiOctave = 12;
lastmidiOctave = 24;
}
else if ((midiOctave == 12) && (lastmidiOctave == 0))
{
midiOctave = 24;
lastmidiOctave = 12;
}
else if ((midiOctave == 12) && (lastmidiOctave == 24))
{
midiOctave = 0;
lastmidiOctave = 12;
}
lastMidiOctaveStatus = true;
updateTranspose();
}
if (midiOctaveStatus == false && lastMidiOctaveStatus == true)
lastMidiOctaveStatus = false;
}
// ##################################################################################################################
// ########################################## MIDI SENDEN ###########################################################
// ##################################################################################################################
void MIDI_TX(unsigned char MESSAGE, unsigned char PITCH, unsigned char VELOCITY) // Schickt MIDI Daten zur seriellen Schnittstelle.
{
digitalWrite(pinMidiOutLed, HIGH); // MIDI Out LED einschalten.
status = MESSAGE + midiChannel; // Nachricht generieren und ausgeben.
Serial.write(status);
Serial.write(PITCH);
Serial.write(VELOCITY);
midiOutLedTime = millis(); // Zeit für MIDI Out Leuchtdauer zurücksetzen.
}
void MIDI_PC(unsigned char PROGRAMNUMBER) // MIDI Program Change senden
{
digitalWrite(pinMidiOutLed, HIGH); // MIDI Out LED einschalten.
status = 192 + midiChannel; // Nachricht generieren und an die serielle Schnittstelle senden.
Serial.write(status);
Serial.write(PROGRAMNUMBER);
midiOutLedTime = millis(); // Zeit für MIDI Out Leuchtdauer zurücksetzen.
}
void MIDI_CC(unsigned char State) // MIDI Control Change senden
{
digitalWrite(pinMidiOutLed, HIGH); // MIDI Out LED einschalten.
status = 176 + midiChannel; // Nachricht generieren und an die serielle Schnittstelle senden.
Serial.write(status);
Serial.write(64);
Serial.write(State);
midiOutLedTime = millis(); // Zeit für MIDI Out Leuchtdauer zurücksetzen.
}
void midiLoopback() // #################### MIDI THRU ####################
{
if(Serial.available() > 0) // Sind Datewn am Seriellen Eingang vorhanden?
{
byte1 = Serial.read(); // Ja, dann alle Daten einlesen ...
byte2 = Serial.read();
byte3 = Serial.read();
MIDI_TX(byte1, byte2, byte3); // ... und an serielle Schnittstelle ausgaben.
}
}
void updateInstrument() // Instrumentenabzeige auf dem TFT Display aktualisieren.
{
//TFTscreen.fillRect(2, 62, 156, 50, 0x0); // Instrumentenbereich komplett löschen
//TFTscreen.drawRoundRect( 5, 62, 150, 48, 3, 0xFFFF); // Abgerundetes Rechteck um alles zeichnen
TFTscreen.setTextSize(1); // Textgröße auf klein setzen
TFTscreen.stroke(GREEN_2); // Schriftfarbe setzen
//TFTscreen.text("INSTRUMENT:", 7, 66); // Text "Instrument: " ausgeben
TFTscreen.fillRect(75, 66, 6, 7, 0x0);
TFTscreen.setCursor(75, 66); // Cursor setzen
TFTscreen.print(instrumentNumber + 1); // Aktuelle Instrumentennummer + 1 ausgeben (1 ... 8)
//TFTscreen.drawFastVLine(110, 62, 14, 0xFFFF); // Vertikale Linie zeichnen
//TFTscreen.drawFastHLine( 6, 76, 148, 0xFFFF); // Horizontale Linie zeichnen
TFTscreen.fillRect(111, 63, 43, 13, 0xFFFF); // Bereich der Programmnummer köschen
TFTscreen.stroke(BLACK); // Textfarbe schwarz
TFTscreen.text("PR:", 114, 66); // Programmnunner (MID Nummer) anzeigen
TFTscreen.setCursor(134, 66); // Cursor setzen
TFTscreen.print(instrumentProg[instrumentNumber] < 100? "0":""); // Ist die Programmnummer kleiner als 100? Wenn ja, eine "0" voranstellen.
TFTscreen.print(instrumentProg[instrumentNumber] < 10 ? "0":""); // Ist die Programmnummer kleiner als 10? Wenn ja, noch eine "0" voranstellen.
TFTscreen.print(instrumentProg[instrumentNumber]); // Instrumentennummer ausgeben
//TFTscreen.fillRect(7, 77, 146, 30, 0x0);
TFTscreen.fillRect(6, 86, 148, 17, 0x0);
TFTscreen.setTextSize(2); // Schriftgröße mittel
TFTscreen.stroke(CYAN_1); // Schriftfarben setzen (Schriftfarbe)
TFTscreen.text(instrumentName[instrumentNumber], 10, 86);
MIDI_PC(instrumentProg[instrumentNumber]); // Programmwechel über MIDI senden
}
void updateMidiChannel() // MIDI Kanal aktualisieren
{
TFTscreen.setTextSize(1); // Schriftgröße setzen
TFTscreen.stroke(midiChannel > 0? BLACK:YELLOW_1); // Wenn MIDI Kanal > 0, Schriftfarbe schwarz, sonst gelb
TFTscreen.fillRect(5, 113, 48, 11, TFTscreen.Color565(midiChannel > 0? WHITE:RED_1)); // Wenn MIDI Kanal = 0, Hintergrund weiss, sonst rot
TFTscreen.text("CH:", 10, 115); // Wenn MIDI Kanal > 0 Kanalmummer ausgeben, ...
TFTscreen.setCursor(30, 115);
if(!midiChannel) // ... andernfalls "AUS".
TFTscreen.print("AUS");
else
{
TFTscreen.print(midiChannel < 10? "0":""); // Wenn MIDI Kanal < 10, eine "0" voranstellen.
TFTscreen.print(midiChannel);
// Ggf. einfpgen: Nach Kanalwechsel Instrument senden ja/nein
//MIDI_PC(instrumentProg[instrumentNumber]);
}
//TFTscreen.setTextSize(3); // Wenn MIDI IN, roten Punkt hinter Channel
//TFTscreen.stroke(BLACK);
//TFTscreen.text(".", 35, 115);
}
void updateTranspose()
{
midiOctave = 0;
char midiOctaveText[3];
String tempTxt = String((midiOctave - 12) / 12);
tempTxt.toCharArray(midiOctaveText, 3);
TFTscreen.setTextSize(1);
TFTscreen.stroke(BLACK);
TFTscreen.fillRect(55, 113, 46, 11, 0xFFFF);
TFTscreen.text("TR: ", 59, 115);
TFTscreen.setCursor(78, 115);
//if(midiOctave == 12)
// TFTscreen.text(" ", 69, 115);
//else if(midiOctave > 12)
// TFTscreen.text("+", 69, 115);
//else
// TFTscreen.text("-", 69, 119);
TFTscreen.print("+2");
}
void updateMeasure()
{
TFTscreen.fillRect(103, 113, 52, 11, TFTscreen.Color565(WHITE));
int v = analogRead(A0);
byte length = map(v, 0, 1023, 0, 50);
TFTscreen.setCursor(111, 115);
TFTscreen.setTextSize(1);
TFTscreen.stroke(BLACK);
if(v <= 500)
TFTscreen.fillRect(104, 114, length, 9, TFTscreen.Color565(GREEN_1));
else
TFTscreen.fillRect(104, 114, length, 9, TFTscreen.Color565(RED_1));
TFTscreen.print(v);
//if(v > 300)
//delay(100);
}
// ##################################################################################################################
// ############################################### MENU #############################################################
// ##################################################################################################################
void checkMenuButton()
{
bool state = digitalRead(pinProgramChange);
if(state && !lastMenuButtonState)
menuButtonPressTime = millis();
if(state && (millis() - menuButtonPressTime > 2000))
{
menuActive = true;
drawMenu();
}
lastMenuButtonState = state;
}
void drawMenu()
{
TFTscreen.fillRect(0, 0, 160, 127, TFTscreen.Color565(GREY_6));
TFTscreen.drawRoundRect(5, 5, 150, 117, 5, 0xFFFF);
TFTscreen.setTextSize(2);
TFTscreen.stroke(WHITE);
TFTscreen.text("EINSTELLUNGEN", 40, 10);
updateMenu();
}
void updateMenu()
{
TFTscreen.fillRect(10, 40, 140, 70, TFTscreen.Color565(GREY_6));
TFTscreen.setTextSize(2);
TFTscreen.stroke(CYAN_1);
switch(menuIndex)
{
case 0:
TFTscreen.text("CH:", 10, 50);
TFTscreen.print(midiChannel);
break;
case 1:
TFTscreen.text("INST:", 10, 50);
TFTscreen.print(instrumentNumber + 1);
break;
case 2:
TFTscreen.text("TR:", 10, 50);
TFTscreen.print(transposeSteps);
break;
case 3:
TFTscreen.text("LED:", 10, 50);
TFTscreen.print(ledBrightness);
break;
case 4:
TFTscreen.text("VELO:", 10, 50);
TFTscreen.print(velocityFlag ? "EIN" : "AUS");
break;
}
}
void handleMenu()
{
bool btnA3 = digitalRead(pinProgramChange);
bool btnA4 = digitalRead(pinMidiChannelChange);
bool btnA5 = digitalRead(pinTransposeChangeDown);
bool btnA6 = digitalRead(pinTransposeChangeUp);
// nächster Menüpunkt
if(btnA3 && !lastBtnA3)
{
menuIndex++;
if(menuIndex >= menuItems)
menuIndex = 0;
updateMenu();
}
// Wert erhöhen
if(btnA4 && !lastBtnA4)
{
changeValue(+1);
updateMenu();
}
// Wert verringern
if(btnA5 && !lastBtnA5)
{
changeValue(-1);
updateMenu();
}
// Menü verlassen
if(btnA6 && !lastBtnA6)
{
menuActive = false;
TFTscreen.background(0x0);
setup(); // Startscreen neu zeichnen
}
lastBtnA3 = btnA3;
lastBtnA4 = btnA4;
lastBtnA5 = btnA5;
lastBtnA6 = btnA6;
}
void changeValue(int delta)
{
switch(menuIndex)
{
case 0: // MIDI Channel
midiChannel += delta;
if(midiChannel > 16) midiChannel = 0;
if(midiChannel < 0) midiChannel = 16;
updateMidiChannel();
break;
case 1: // Instrument
instrumentNumber += delta;
if(instrumentNumber > 7) instrumentNumber = 0;
if(instrumentNumber < 0) instrumentNumber = 7;
updateInstrument();
break;
case 2: // Transpose
transposeSteps += delta;
if(transposeSteps > 2) transposeSteps = 2;
if(transposeSteps < -2) transposeSteps = -2;
midiOctave = 12 + (transposeSteps * 12);
updateTranspose();
break;
case 3: // LED Brightness
ledBrightness += delta * 10;
if(ledBrightness > 255) ledBrightness = 255;
if(ledBrightness < 0) ledBrightness = 0;
rgbStripe.setBrightness(ledBrightness);
rgbStripe.show();
break;
case 4: // Velocity
velocityFlag = !velocityFlag;
break;
}
}Loading
cd74hc4067
cd74hc4067
PROG
P
CHAN
C
SUSTAIN
S
TR -
D
TR +
U
PAD 1
PAD 2
PAD 3
PAD 4
PAD 5
48 x Piezo PAD mit je 5.1V ZDiode gegen GND
3 x Multiplexer