Spotting a semantic issue in a debounce

Let me try to rephrase that, and see whether I've understood you correctly.

You have a set of inputs - four, in this example. The states of the inputs change over time. The state of all the inputs at a given time constitutes a chord, and the state of the inputs over time represents a sequence of chords.

Because the inputs don't necessarily all change state at the same instant, when the inputs are changing you wait until they have settled into their new state before you determine that a new chord has occurred.

In a sense what you're doing is not debouncing the inputs individually - you are debouncing their combined state.

In that case I'd implement a function to return the instantaneous states of the inputs as a number (each input corresponding to one bit of the number) and use an asynchronous debouncing algorithm along the following lines:

int getDebouncedState()
{
    static int oldState = 0;
    static int debouncedState = 0;
    static unsigned long lastChangeTime = millis();

    int newState = readRawState();
    if(newState != oldState)
    {
        // one of the inputs has changed state
        lastChangeTime = millis();
        oldState = newState;
    }
    else
    {
        // the inputs have not changed
        if((oldState != debouncedState) && (millis() - lastChangeTime > SETTLING_TIME))
        {
            // the inputs are stable and in a new state
            debouncedState = newState;
        }
    }
    return debouncedState;
}

You'd call that periodically like this:

int newChord = getDebouncedState()

if(newChord != oldChord)
{
    handleChordChange(oldChord, newChord);
    oldChord = newChord;
}