// Scan Tipple organ pedalboard with Arduino Leonardo board // Uses old Kimber-Allen pedal jacks modified to operate with separate on and off contacts on each pedal to avoid contact bounce problems with worn contacts // Using MIDIUSB library v.1.0.5 from Gary Grewal (install from the Arduino IDE Library Manaager) // John Harvey 12 April 2022 // www.johnharvey.uk // See these for more information: // www.arduino.cc/en/Reference/MIDIUSB // www.arduino.cc/en/Tutorial/MidiDevice // github.com/arduino-libraries/MIDIUSB // github.com/arduino/tutorials/blob/master/ArduinoZeroMidi/ArduinoZeroMidi.ino #include // Arduino output pins to drive keyboard matrix rows const byte Row1 = 8; const byte Row2 = 9; const byte Row3 = 10; const byte Row4 = 11; const byte Row5 = A0; // Analog pins used as digital outputs const byte Row6 = A1; const byte Row7 = A2; const byte Row8 = A3; // Arduino input pins to read keyboard matrix columns const byte Column1 = 0; const byte Column2 = 1; const byte Column3 = 2; const byte Column4 = 3; const byte Column5 = 4; const byte Column6 = 5; const byte Column7 = 6; const byte Column8 = 7; const byte ScopeSync = 12; // Trigger oscilloscope at start of loop // MIDI channels allocated as follows: // Swell Channel 01 (0x00) // Great Channel 02 (0x01) // Pedals Channel 03 (0x02) // Thumb Pistons Channel 04 (0x03) // Swell Pedal Channel 05 (0x04) const byte MidiChannel = 3; // The channel assigned to this device; channels are zero-based in code, so channel 1 is 0 in code etc. boolean PedalState[32]; // Remember pedal state, true = pedal on, false = pedal off (only 30 pedals in Tipple console) void setup() { pinMode (Column1, INPUT_PULLUP); pinMode (Column2, INPUT_PULLUP); pinMode (Column3, INPUT_PULLUP); pinMode (Column4, INPUT_PULLUP); pinMode (Column5, INPUT_PULLUP); pinMode (Column6, INPUT_PULLUP); pinMode (Column7, INPUT_PULLUP); pinMode (Column8, INPUT_PULLUP); pinMode (Row1, OUTPUT); pinMode (Row2, OUTPUT); pinMode (Row3, OUTPUT); pinMode (Row4, OUTPUT); pinMode (Row5, OUTPUT); pinMode (Row6, OUTPUT); pinMode (Row7, OUTPUT); pinMode (Row8, OUTPUT); pinMode (A4, OUTPUT); // Set unused analog inputs as outputs so that stray noise does not trigger ADCs pinMode (A5, OUTPUT); digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); pinMode(LED_BUILTIN, OUTPUT); // Pin 13 is LED_BUILTIN, use for bottom C visual indication digitalWrite(LED_BUILTIN, LOW); // Set LED to off pinMode (ScopeSync, OUTPUT); digitalWrite(ScopeSync, LOW); for (byte Pedal = 0; Pedal <= 31; Pedal ++) { PedalState[Pedal] = false; // Initialise all notes off } } void loop() { // Loop period = 9.0 milliseconds (110 scans per second) digitalWrite(ScopeSync, HIGH); // Generate oscilloscope sync pulse at start of each keyboard scan for test purposes delayMicroseconds(50); digitalWrite(ScopeSync, LOW); for (byte row = 1; row <= 8; row ++) { // Rows are normally high, set one low at a time and see which columns are pulled low by pedal presses if (row == 1) { digitalWrite(Row1, LOW); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 2) { digitalWrite(Row1, HIGH); digitalWrite(Row2, LOW); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 3) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, LOW); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 4) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, LOW); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 5) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, LOW); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 6) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, LOW); digitalWrite(Row7, HIGH); digitalWrite(Row8, HIGH); } if (row == 7) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, LOW); digitalWrite(Row8, HIGH); } if (row == 8) { digitalWrite(Row1, HIGH); digitalWrite(Row2, HIGH); digitalWrite(Row3, HIGH); digitalWrite(Row4, HIGH); digitalWrite(Row5, HIGH); digitalWrite(Row6, HIGH); digitalWrite(Row7, HIGH); digitalWrite(Row8, LOW); } delayMicroseconds(200); byte PedalNumber; byte ContactNumber; boolean OnContactState; boolean OffContactState; boolean PreviousPedalState; for (byte col = 1; col <= 8; col += 2) { // Step through contact pairs (alt code: col = col +2) ContactNumber = ((row - 1) * 8) + (col - 1); // 0 to 63 PedalNumber = (ContactNumber + 2) / 2; // 1 to 32 (integer division) OnContactState = !digitalRead(col - 1); // Low = contact closed, so invert; Arduino digital input numbering is 0 - 7 OffContactState = !digitalRead(col); if ((OnContactState == true) && (OffContactState == true)) { // This should not happen - means both contacts grounded continue; // at the same time, pedal jack contacts need adjusting; } // skip to next iteration of for loop PreviousPedalState = PedalState[PedalNumber - 1]; // Note that "PedalNumber + 0x24" produces an int in compiler so reconvert to byte value otherwise compiler outputs a narrowing conversion warning if ((OnContactState == true) && (PreviousPedalState == false)) { // Note has just gone on, after first contact any subsequent contact bounce ignored PedalState[PedalNumber - 1] = true; midiEventPacket_t noteOn = {0x09, byte(MidiChannel - 1 + 0x90), byte(PedalNumber - 1 + 0x24), 0x40}; MidiUSB.sendMIDI(noteOn); MidiUSB.flush(); if (PedalNumber == 1) { digitalWrite(LED_BUILTIN, HIGH); // Turn Arduino onboard LED on (Bottom C visual indication) } } if ((OffContactState == true) && (PreviousPedalState == true)) { // Note has just gone off, after first contact any subsequent contact bounce ignored PedalState[PedalNumber - 1] = false; midiEventPacket_t noteOff = {0x08, byte(MidiChannel - 1 + 0x80), byte(PedalNumber - 1 + 0x24), 0x40}; MidiUSB.sendMIDI(noteOff); MidiUSB.flush(); if (PedalNumber == 1) { digitalWrite(LED_BUILTIN, LOW); // Turn Arduino onboard LED off (Bottom C visual indication) } } delayMicroseconds(200); } } delayMicroseconds(200); }