for reference, here is the complete code that I now use with the keyboard.
//This code is working with the 49 note midi controller. The heart of the code, reading the keyboard matrix using a shift register was taken from the Youtube tutorial
//https://www.youtube.com/watch?v=TeOvE9mf0BA&t=1s . Parts of code were added using ChatGPT. Somewhere in the proces the 'note on' and 'note off' commands were switched
//in addition to reading a 49 note keyboard, this code contains parts for dealing with:
//ten pots associated with undefined midi CC messages for assigning through midi learn
//two buttons for choosing between three note arrays. Here these arrays are one octave up and down, but they can be changed according to liking
// a joystick for pitch bend and modulation
// a pedal for sustaining notes. pedal is working though notes can't be retriggered while pedal is pushed down
// the pitch bend is not functioning on PC but works properly on Ipad
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
void sendMidiMessage(int cmd, int channel, int note, int velocity);
void scanColumn(int colNum);
void sendPotMessage(int cmd, int channel, int lsb, int msb);
// MIDI channel (use 0 for Channel 1)
const int MIDI_CHANNEL = 0;
const int MIDI_CHANNEL1 = 1;
const int MIDI_CHANNEL2 = 2;
// MIDI baud rate
const int SERIAL_RATE = 31250; // needs to be 31250 when going through midi port, can be 9600 for hairless
//-------------------------------pedal---------------------------------------------
const int PedalPin = 3;
int SwitchState = 0;
//-------------------------------joystick -------------------------------------------
//pitch bend
const int PITCHBEND_CMD = 224;
const int PB_LSB = 0;
int PB_VALUE = 64;
bool PBisOn = false;
//control change
const int CONTROLCHANGE1_CMD = 176;
const int CC_LSB = 1;
int CC_VALUE = 64;
bool CCisOn = false;
// joystick analog pins
const int X_pin = A14;
const int Y_pin = A15;
//--------------------------midi notes and keyboard matrix --------------------------------------
const int NUM_ROWS = 7;
const int NUM_COLS = 7;
const int NOTE_ON_CMD = 144; // in the original code these two numbers were the other way round, with 128 for note on. somehow I had to swap these
const int NOTE_OFF_CMD = 128;
const int NOTE_VELOCITY = 127;
// Define an array of MIDI note numbers
int noteArray1[] = {26, 27,28, 29, 30, 31, 32, 33, 34, 35, 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, };
int noteArray2 []= {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,};
int noteArray3 [] = {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};
int* activeArray = noteArray2; // Start with the second array
boolean keyPressed[NUM_ROWS][NUM_COLS];
uint8_t keyToMidiMap[NUM_ROWS][NUM_COLS];
// 74HC595 pins
const int dataPin = 28;
const int latchPin = 26;
const int clockPin = 24;
// Row pins
const int rowPins[NUM_ROWS] = {32,34, 36, 38, 40, 42, 44};
//---------------------------------------------------- octave switches -------------------------------------------------
const int switchOnePin = 8;
const int switchTwoPin = 9;
const int ledRedPin = 10;
const int ledGreenPin = 11;
// Variables to track octave switching buttons state
int switchOneState = HIGH; // Assuming the button is LOW when pressed
int lastSwitchOneState = HIGH;
int switchTwoState = HIGH;
int lastSwitchTwoState = HIGH;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 10; // Adjust this delay as needed
//-----------------------------------------Pots-----------------------------------------
const int potPin1 = A0;
const int potPin2 = A1;
const int potPin3 = A2;
const int potPin4 = A3;
const int potPin5 = A4;
const int potPin6 = A5;
const int potPin7 = A8;
const int potPin8 = A9;
const int potPin9 = A10;
const int potPin10 = A11;
const int numPots = 10;
int potPins [numPots] = {potPin1, potPin2, potPin3, potPin4, potPin5, potPin6, potPin7, potPin8, potPin9, potPin10};
int prevPotValues [numPots] = {0};
const int ccNumberPot1 = 7; // 7 is volume
const int ccNumberPot2 = 13;
const int ccNumberPot3 = 14;
const int ccNumberPot4 = 15;
const int ccNumberPot5 = 20;
const int ccNumberPot6 = 21;
const int ccNumberPot7 = 102;
const int ccNumberPot8 = 103;
const int ccNumberPot9 = 104;
const int ccNumberPot10 = 105;
// Define threshold values for each potentiometer
const int threshold = 10; // Adjust this threshold as needed
// Variables to store the previous potentiometer values
int prevPotValue1 = 0;
int prevPotValue2 = 0;
int prevPotValue3 = 0;
int prevPotValue4 = 0;
int prevPotValue5 = 0;
int prevPotValue6 = 0;
int prevPotValue7 = 0;
int prevPotValue8 = 0;
int prevPotValue9 = 0;
int prevPotValue10 = 0;
//-----------------------------------------einde pots----------------------------
//////////////////////////////setup////////////////////////////////////setup/////////////////////////////////////////////////////////////////////
void setup() {
//----------------------------buttons and LEDS-------------------------------------------------------------------
pinMode(switchOnePin, INPUT_PULLUP); // Assuming the button is connected to ground when pressed
pinMode(switchTwoPin, INPUT_PULLUP);
pinMode(ledGreenPin, OUTPUT);
pinMode(ledRedPin, OUTPUT);
//-----------------------------pedal-------------------------------------------------------------
pinMode(PedalPin, INPUT_PULLUP);
//--------------------------setup joystick input-------------------------------------
pinMode (X_pin, INPUT); // Joystick X-axis pin
pinMode (Y_pin , INPUT); //
//-------------------------------pots setup------------------------------------------
for (int i = 0; i < numPots; i++) {
pinMode(potPins[i], INPUT);
}
// Initialize previous potentiometer values
for (int i = 0; i < numPots; i++) {
prevPotValues[i] = analogRead(potPins[i]);
}
//-------------------------------keyboard matrix setup ----------------------------------------
int note = 0;
for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr) {
for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr) {
keyPressed[rowCtr][colCtr] = false;
keyToMidiMap[rowCtr][colCtr] = note;
note++;
}
}
pinMode(dataPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(latchPin, OUTPUT);
for (int i = 0; i < NUM_ROWS; i++) {
pinMode(rowPins[i], INPUT_PULLUP); // Use internal pull-ups
}
Serial.begin(SERIAL_RATE);
}
//////////////////////////////////////////////////// loop //////////// loop //////////////// loop ////////////////////////////////////////////////////////
void loop() {
//----------------------------loop pots--------------------------------------------
// code generated by GPT
for (int i = 0; i < numPots; i++) {
int potValue = analogRead(potPins[i]);
// Check if the potentiometer value has changed and exceeds the threshold
if (abs(potValue - prevPotValues[i]) > threshold) {
int mappedValue = map(potValue, 0, 1023, 0, 127);
int ccNumber = 100 + i; // Adjust this as needed
MIDI.sendControlChange(ccNumber, mappedValue, MIDI_CHANNEL1);
prevPotValues[i] = potValue;
}
}
//-----------------------------------------------------------switch octaves ---------------------------------------------------------
// code generated by GPT
// Read the state of the switches with debounce
int switchOneReading = digitalRead(switchOnePin);
int switchTwoReading = digitalRead(switchTwoPin);
// Check if switchOne has been pressed and released
if (switchOneReading != lastSwitchOneState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (switchOneReading != switchOneState) {
switchOneState = switchOneReading;
if (switchOneState == LOW) { // Button is pressed
// Check the current activeArray and update accordingly
if (activeArray == noteArray2) {
activeArray = noteArray1;
} else if (activeArray == noteArray3) {
activeArray = noteArray2;
}
}
}
}
// Check if switchTwo has been pressed and released
if (switchTwoReading != lastSwitchTwoState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (switchTwoReading != switchTwoState) {
switchTwoState = switchTwoReading;
if (switchTwoState == LOW) { // Button is pressed
// Check the current activeArray and update accordingly
if (activeArray == noteArray2) {
activeArray = noteArray3;
} else if (activeArray == noteArray1) {
activeArray = noteArray2;
}
}
}
}
// Update the state of switches for the next iteration
lastSwitchOneState = switchOneReading;
lastSwitchTwoState = switchTwoReading;
//-------------------------------------------------leds------------------------------------------
if (activeArray == noteArray2) {
digitalWrite (ledRedPin, LOW);
digitalWrite (ledGreenPin, LOW);
}
if (activeArray == noteArray1) {
digitalWrite (ledRedPin, HIGH);
digitalWrite (ledGreenPin, LOW);
}
if (activeArray == noteArray3) {
digitalWrite (ledRedPin, LOW);
digitalWrite (ledGreenPin, HIGH);
}
// --------------------------------------joystick -----------------------------------------
{
// joystick X-axis - pitch bend
int newPB_VALUE = map(analogRead(X_pin), 0, 1023, 0, 127); //this line works with Ipad, not with pc
//int newPB_VALUE = map(analogRead(X_pin), -8192, 8192, 0, 127); this line contains proper pitchbend values but is not working
newPB_VALUE = 127 - newPB_VALUE; // Reverse the direction because joystick is mounted upside down
if (abs(newPB_VALUE - PB_VALUE) > 2) {
sendPotMessage(PITCHBEND_CMD, MIDI_CHANNEL2, PB_LSB, newPB_VALUE);
PB_VALUE = newPB_VALUE;
}
// joystick Y-axis - control change
int newCC_VALUE = map(analogRead(Y_pin), 0, 1023, 0, 127);
newCC_VALUE = 127 - newCC_VALUE; // Reverse the direction because joystick is mounted upside down
if (abs(newCC_VALUE - CC_VALUE) > 2) {
sendPotMessage(CONTROLCHANGE1_CMD, MIDI_CHANNEL2, CC_LSB, newCC_VALUE);
CC_VALUE = newCC_VALUE;
}
}
// ---------------------------------------------------midi notes --------------------------------------------------
SwitchState = digitalRead (PedalPin);
for (int colCtr = 0; colCtr < NUM_COLS; ++colCtr) {
scanColumn(colCtr);
int rowValue[NUM_ROWS];
for (int rowCtr = 0; rowCtr < NUM_ROWS; ++rowCtr) {
rowValue[rowCtr] = digitalRead(rowPins[rowCtr]);
if (rowValue[rowCtr] == LOW) {
if (!keyPressed[rowCtr][colCtr]) {
// Button pressed
if ( keyPressed[rowCtr][colCtr] = true && SwitchState == HIGH){
sendMidiMessage(NOTE_OFF_CMD, MIDI_CHANNEL1, activeArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY); // in the original code note off here was note on
}
}
} else {
if (keyPressed[rowCtr][colCtr]) {
// Button released
keyPressed[rowCtr][colCtr] = false ;
//Serial.println(SwitchState);
//delay (600); //Serial monitor shows that switch is functioning properly
sendMidiMessage(NOTE_ON_CMD, MIDI_CHANNEL1, activeArray[rowCtr * NUM_COLS + colCtr], NOTE_VELOCITY);// in the original code note on here was note off
}
}
}
}
}
void sendMidiMessage(int cmd, int channel, int note, int velocity) {
Serial.write(cmd | channel);
Serial.write(note);
Serial.write(velocity);
}
//-------------------------------------------------- functions -----------------------------------------------------------
void scanColumn(int colNum) {
digitalWrite(latchPin, LOW);
if (0 <= colNum && colNum <= 6) {
shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
shiftOut(dataPin, clockPin, MSBFIRST, 1 << colNum);
} else {
shiftOut(dataPin, clockPin, MSBFIRST, 1 << (colNum - 7));
shiftOut(dataPin, clockPin, MSBFIRST, B00000000);
}
digitalWrite(latchPin, HIGH);
}
void sendPotMessage(int cmd, int channel, int lsb, int msb) {
Serial.write(cmd + channel); // send command plus the channel number
Serial.write(lsb); // least significant bit
Serial.write(msb); // most significant bit
}