// Scan Tipple organ keyboard with Arduino Leonardo board // Using MIDIUSB library v.1.0.5 from Gary Grewal (install from the Arduino IDE Library Manaager) // John Harvey 25 November 2021 // 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 = 1; // The channel assigned to this keyboard; channels are zero-based in code, so channel 1 is 0 in code etc. // Remember key state, true = key on, false = key off boolean KeyState[64]; // Only 61 keys in full 5 octave keyboard (and some have fewer keys) but zero-based row/column scan of 8x8 keyboard matrix goes from 0 to 63 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 keyNumber = 0; keyNumber <= 63; keyNumber ++) { KeyState[keyNumber] = false; // All notes off } } void loop() { 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 key 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(100); byte keyNumber; boolean PreviousKeyState; boolean CurrentKeyState; for (byte col = 1; col <= 8; col ++) { keyNumber = ((row - 1) * 8) + (col - 1); PreviousKeyState = KeyState[keyNumber]; CurrentKeyState = !digitalRead(col - 1); // Low = key on, so invert // Note that "keyNumber + 0x24" produces an int in compiler so reconvert to byte value otherwise compiler outputs a narrowing conversion warning if ((PreviousKeyState == false) && (CurrentKeyState == true)) { // Note has just gone on KeyState[keyNumber] = true; midiEventPacket_t noteOn = {0x09, byte(MidiChannel - 1 + 0x90), byte(keyNumber + 0x24), 0x40}; MidiUSB.sendMIDI(noteOn); MidiUSB.flush(); if (keyNumber == 0) { digitalWrite(LED_BUILTIN, HIGH); // Turn Arduino onboard LED on (Bottom C visual indication) } } if ((PreviousKeyState == true) && (CurrentKeyState == false)) { // Note has just gone off KeyState[keyNumber] = false; midiEventPacket_t noteOff = {0x08, byte(MidiChannel - 1 + 0x80), byte(keyNumber + 0x24), 0x40}; MidiUSB.sendMIDI(noteOff); MidiUSB.flush(); if (keyNumber == 0) { digitalWrite(LED_BUILTIN, LOW); // Turn Arduino onboard LED off (Bottom C visual indication) } } } } delayMicroseconds(100); }