Hello all!
I am a fresh beginner on Arduino. No previous experience coding, but I know a thing or two about midi hex. Aside from the 20+ hours I've spent racking by brain trying to get this to work, I have no other experience. Arduino isn't supposed to be this frustrating, is it?
I need to use (12x) CD4021BE shiftIn registers to send button press information (from 96 buttons) to an Arduino Leonardo, then have the Arduino turn each buttonPress & buttonRelease event into midi noteOn & noteOff commands using the USBMIDI library. I don't want it to keep playing noteOn & noteOff commands at a set interval (like a loop), just only when a button state change happens.
I know you might want to suggest that I try to matrix them instead of daisy chain, but I'm just burnt at this point. I'm sorry, I understand CD4021 daisy chaining and that's about it haha.
So... for my masterpiece (in progress). I have set my circuit up exactly like it is set up here... except mine will have 12 CD4021's. Here is the code so far (mostly patched here and there from different sources, so I apologize for how scatterbrained it is). I'll explain the trouble I'm having after the code...
#include "MIDIUSB.h"
void noteOn(byte channel, byte note, byte velocity) {
midiEventPacket_t noteOn = {0x09, 0x90 | channel, note, velocity};
MidiUSB.sendMIDI(noteOn);
}
void noteOff(byte channel, byte note, byte velocity) {
midiEventPacket_t noteOff = {0x08, 0x80 | channel, note, velocity};
MidiUSB.sendMIDI(noteOff);
}
// #define DEBUG
// General settings
#define NUM_INPUTS 24 // 32 switches for AGO Pedalboard C2-G4
#define BTN_DEBOUNCE 75 // Delay to prevent switch bouncing
// MIDI Settings
#define MIDI_CHANNEL 1
#define MIDI_NOTE_OFFSET 36 // MIDI note 36 = C2
#define MIDI_NOTE_ON_VELOCITY 127
// Arduino Pins
#define CLOCKPIN 7 // To CD4012 pin 10 (CLOCK)
#define LATCHPIN 8 // To CD4012 pin 9 (P/S C)
#define DATAPIN 9 // To CD4012 pin 3 (Q8)
#define NUM_REGISTERS (NUM_INPUTS / 8)
byte midiState[NUM_REGISTERS]; // Hold last register state HIGH=OFF
byte switchState[NUM_REGISTERS]; // Hold shift register state HIGH=OFF
unsigned long lastSwitch = 0; // Holds last switch read time for debouncing switches
#if defined(DEBUG)
//MIDI_CREATE_DEFAULT_INSTANCE();
#endif
void setup() {
// Initialize last switch state array
for (byte i = 0; i < NUM_REGISTERS; i++) {
midiState[i] = 0; // 11111111
}
Serial.begin(9600);
// Initialize MIDI
// Define pin modes
pinMode(CLOCKPIN, OUTPUT);
pinMode(LATCHPIN, OUTPUT);
pinMode(DATAPIN, INPUT);
}
void loop() {
// Pulse the latch pin: set it to 1 to collect parallel data, then wait
digitalWrite(LATCHPIN, 1);
delayMicroseconds(20);
// Set latch pin to 0 to transmit data serially
digitalWrite(LATCHPIN, 0);
readShiftRegisters(); // Read current switch positions
processSwitches(); // Process switches & send MIDI
#if defined(DEBUG)
// Add delay to keep up with print statements
//delay(500);
#endif
}
// Read the shift registers
void readShiftRegisters() {
#if defined(DEBUG)
// Serial.println("-------------------");
// Serial.println("Reading registers");
#endif
// While the shift register is in serial mode collect each shift register
// into a byte. NOTE: the register attached to the chip comes in first.
for (byte i = 0; i < NUM_REGISTERS; i++) {
switchState[i] = shiftIn(DATAPIN, CLOCKPIN);
#if defined(DEBUG)
// Serial.print("Register #");
// Serial.print(i);
// Serial.print(": ");
// Serial.print(switchState[i]);
// Serial.print("-");
// Serial.println(switchState[i], BIN);
#endif
}
}
void processSwitches() {
if (micros() - lastSwitch > BTN_DEBOUNCE) { // Ignore if we're in the debounce time
lastSwitch = micros();
// Process all registers
for (byte i = 0; i < NUM_REGISTERS; i++) {
if (switchState[i] != midiState[i]) { // Don't process if no switches changed
#if defined(DEBUG)
// Serial.print("Register #");
// Serial.print(i);
// Serial.print(": ");
// Serial.print(switchState[i]);
// Serial.print("-");
// Serial.println(midiState[i]);
#endif
for (byte j = 0; j < 8; j++ ) {
// Read old & new switch states
byte switchStateValue = bitRead(switchState[i], j);
byte midiStateValue = bitRead(midiState[i], j);
delay(5);
// Switch has changed, send MIDI command
if (switchStateValue != midiStateValue) {
byte note = (i * 8) + j; // MIDI note for current switch
if (switchStateValue == 1) { // HIGH = switch open
noteOn(note, MIDI_NOTE_ON_VELOCITY, MIDI_CHANNEL);
} else { // LOW = switch closed
#if defined(DEBUG)
//
#else
noteOff(note, MIDI_NOTE_ON_VELOCITY, MIDI_CHANNEL);
#endif
}
#if defined(DEBUG)
//Serial.print(i);
//Serial.print("-");
//Serial.print(j);
//Serial.print(": ");
//Serial.println(note);
#endif
}
}
midiState[i] = switchState[i]; // Save current switch state
}
}
}
}
// Returns a byte with each bit in the byte corresponding
// to a pin on the shift register. leftBit 7 = Pin 7 / Bit 0= Pin 0
// From: http://arduino.cc/en/Tutorial/ShftIn21
byte shiftIn(int myDataPin, int myClockPin) {
int i;
int temp = 0;
int pinState;
byte myDataIn = 0;
pinMode(myClockPin, OUTPUT);
pinMode(myDataPin, INPUT);
// Hold the clock pin high 8 times (0,..,7) at the end of each time through
// the for loop. At the beginning of each loop when the clock is set low,
// it will be doing the necessary low to high drop to cause the shift
// register's DataPin to change state based on the value of the next bit in
// its serial information flow. The register transmits the information about
// the pins from pin 7 to pin 0 so that is why our function counts down.
for (i=7; i>=0; i--) {
digitalWrite(myClockPin, 0); // Set clock low
delayMicroseconds(2);
temp = digitalRead(myDataPin);
if (temp) {
pinState = 1;
// Set the bit to 0 no matter what
myDataIn = myDataIn | (1 << i);
} else {
// Turn it off -- only necessary for debuging
// print statement since myDataIn starts as 0
pinState = 0;
}
digitalWrite(myClockPin, 1);
}
return myDataIn;
}
So... after you've had a good chuckle at the frankenstein code I've created, hopefully you know how easy (or hard) of a problem I have on my hands.
When I run the code on my Leonardo, I am able to send midi commands to my computer. The button sends the correct information for noteOn/noteOff, note, velocity, and channel. My problem, I believe, is with how the code handles the shift registers. I think it's the timing. I have to press a button 8 times (for 16 state changes) before the shift register releases the information into the Arduino and the Arduino sends the midi messages. The result is I get 16 midi messages all at once for all of the last 8 noteOn and 8 noteOff events.
So, given my circuit and my code, what do I need to do to make it so the moment a button is pressed, a noteOn (with proper note, channel, velocity) is played, and noteOff when that button is released?
If it'd be considerably (and I mean considerably) easier to re-configure my circuit given the parts I have (based on the schematic at the link above), let me know! Thank you so much in advance!