How to optimize this code + setting pins to LOW INPUT before OUTPUT

Hi all;

This is code I’m working on which should read a 3x3 button matrix and output various midi notes based on which buttons were pressed:

// Required libraries
#include <MIDI.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Constants and variables
const byte arLength = 9;
byte notesOnOld[arLength] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; // Arrays which track the state of the current and previous button presses
byte notesOnNew[arLength] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; 
byte arIndex = 0;
const byte noteShift = 60;
const byte noteVel = 90;
const byte numRows = 3;
const byte numCols = 3;
const byte rowPins[numRows] = {2, 3, 4};
const byte colPins[numCols] = {5, 6, 7};

// Pre-setup initialization for MIDI and lcd display will go here
MIDI_CREATE_DEFAULT_INSTANCE();

void setup() {
  // Activate row pins as INPUT (later toggled to LOW OUTPUT). unsure if initial LOW declaration works like this
  for (byte i = 0; i < numRows; i++) {
    pinMode(rowPins[i], INPUT);
    digitalWrite(rowPins[i], LOW);
  }

  // Activate column pins as INPUT_PULLUP
  for (byte j = 0; j < numCols; j++) {
    pinMode(colPins[j], INPUT_PULLUP);
  }

  // Setup initialization MIDI and lcd display will go here
  MIDI.begin(MIDI_CHANNEL_OMNI);
}

void loop() {
  // Outside loop goes through rows and sets them to LOW OUTPUT one-by-one
  for (byte i = 0; i < numRows; i++) {
    pinMode(rowPins[i], OUTPUT);

    // Inside loop goes through cols and reads their state (saved as byte to save space)
    for (byte j = 0; j < numCols; j++) {
      notesOnNew[arIndex] = (byte) digitalRead(colPins[j]);
      arIndex++;
    }
    pinMode(rowPins[i], INPUT);
  }
  arIndex = 0;

  // Second loop: output button press changes as either noteOn or noteOff and write notesOnOld array
  for (byte i = 0; i < arLength; i++) {
    if (notesOnNew[i] < notesOnOld[i]) {
      
      // Since we're using pullup, a 0 means the switch is pressed, and 1 means it's open 
      MIDI.sendNoteOn(i+noteShift, noteVel, 1);
    } else if (notesOnNew[i] > notesOnOld[i]) {
      MIDI.sendNoteOff(i+noteShift, 0, 1);
    }
    notesOnOld[i] = notesOnNew[i];
  }

}

I have 2 questions. The first is: where do you see any areas within which I could optimize, either via reducing the number of loops required or saving dynamic memory? My plan is to expand this same code to a 9x15 matrix for 135 keys which will require substantially more resources, thankfully I’m using an Arduino Mega. Also, I’m not even sure if the code works persay, but I think it’s close to what it needs to do. I noticed that as soon as I included the initialization for the midi library, my dynamic variable usage jumped from 10% to 25%, so maybe I can write the same functionality by hand and save some space.

Secondly: I am using diodes in my scan matrix in order to prevent ghosting and allow for multikey. However, I still want to ensure that the row pins which I’m each setting to GND don’t jump to OUTPUT HIGH before going to OUTPUT LOW. Is my code for this operation valid? For reference, here’s what I’m doing:

// in setup:
pinMode(rowPins, INPUT);
digitalWrite(rowPins, LOW);

// Then, before loop
pinMode(rowPins, OUTPUT);
// after loop
pinMode(rowPins, INPUT);

Have you tried using the Keypad library ?

UKHeliBob:
Have you tried using the Keypad library ?

Everyone keeps suggesting the keypad library, but I'm not a fan of how they use chars to represent keys when that's just an extra step for something like a midi controller. I'd rather do it by hand and be able to troubleshoot it without diving through a library which I may not be using in its entirety.

You're using an entire byte for each key state, which is binary. If you pack the bits and access them individually, you can make your key state arrays size 8 times smaller.

As for the jumping HIGH before LOW thing, I have no idea what you're talking about. Normally, scan rows can be left as low impedance outputs. I've never heard of, or seen any case where they had to be in a high impedance state for any reason.

aarg:
You're using an entire byte for each key state, which is binary. If you pack the bits and access them individually, you can make your key state arrays size 8 times smaller.

That sounds promising. I know that, for some reason, digitalRead outputs HIGH or LOW as int rather than bool, or even byte, so I was trying to reduce the size of the arrays by using bytes instead.

Your suggestion for 'packing the bits' sounds good, but I have no idea where I'd start with doing that. I'm a civil engineer who is such a beginner to this entire world of circuits and code that it's not even funny! How would I go about doing that?

aarg:
As for the jumping HIGH before LOW thing, I have no idea what you're talking about. Normally, scan rows can be left as low impedance outputs. I've never heard of, or seen any case where they had to be in a high impedance state for any reason.

It's just that I need to be able to only enable 1 row at a time. So the row that i'm currently scanning should be OUTPUT LOW, but the other rows need to be HIGH-Z... at least, that's what I've been told.

Everyone keeps suggesting the keypad library, but I'm not a fan of how they use chars to represent keys

There is no requirement that the values in the array used by the Keypad library to define what is returned when a key is pressed be chars, it works perfectly well with bytes instead.

The only limitation that I am aware of being that you cannot sensibly define one of the keys to return zero as that is the value assigned to the NO_KEY constant by the library which prevents the use of the getKey() function of the library as you cannot distinguish between the key that returns zero and NO_KEY, but that is easily dealt with. So you can have up to 255 different key values if you want. The value returned can then be used as an index to an array to convert the key number into another value if required.

As to your program as it stands, why the extensive use of Strings ?