Featurewish: MidiMessage::getStatus()


#1

I am using this all the time:

uint8 msgStatus = *msg.getRawData() & 0xF0;
switch (msgStatus){
case NoteOn:
    (...)
case NoteOff:
    (...)
case PitchBend:
    (...)
}

Any chance to include it with the MidiMessage class?
Like that for example

enum MidiStatus : uint8
{
NoteOn = 0x80,
NoteOff = 0x90,
AfterTouch = 0xA0,
ControlChange = 0xB0,
ProgramChange = 0xC0,
ChannelPressure = 0xD0,
PitchBend = 0xE0,
SystemMessage = 0xF0
}
(…)
uint8 MidiMessage::getStatus() { return *msg.getRawData() & 0xF0; }


#2

What’s wrong with MidiMessage::isNoteOn(), MidiMessage::isNoteOff() et al ?


#3

The switch statement is faster and to me it just looks cleaner than a cascade of ifs .
Here is the call stack that is run within each of the if branches:
msg.isWhatever() -> getRawData() -> getData() -> isHeapAllocated()
I would prefer to fetch the status only once at the start of the branching statement.


#4

After some time you’ll probably find out that a noteOff might be disguised as noteOn w/ velocity 0 or you’ll find it wise to also check for isAllSoundOff or isAllNotesOff messages or endoftrack messages or isSustainPedalOn or any other message that you need to check additional bytes from getRawData() and then your switch statement won’t no longer look cleaner than cascaded if/else statements. Not to mention the increased chance for errors…

And bearing in mind that audio processing is done at 44 100 times per second (or even more), you’ll have to play really, really fast midi to notice any benefits from your switch optimisation. If there are any at all that is, after the compiler have optimised the code.


#5

Style issues aside, have you measured that this is faster?

For example, try this at https://godbolt.org/

#include <stdlib.h>
#include <stdio.h>

class Thing
{
public:
    Thing()
    {
        data[0] = rand() % 128;
        data[1] = rand() % 128;
        data[2] = rand() % 128;
    }

    const unsigned char* getData() const { return &data[0]; }

    bool isA() const { return (data[0] & 0xF0) == 0xA0; }
    bool isB() const { return (data[0] & 0xF0) == 0xB0; }
    bool isC() const { return (data[0] & 0xF0) == 0xC0; }

private:
    unsigned char data[3];
};

void usingSwitch()
{
    Thing thing;

    const unsigned  status = thing.getData()[0] & 0xF0;

    switch (status)
    {
        case 0xA0: printf ("it's A"); break;
        case 0xB0: printf ("it's B"); break;
        case 0xC0: printf ("it's C"); break;
    }  
}

void usingIf()
{
    Thing thing;

    if (thing.isA())
        printf ("it's A");
    else if (thing.isB())
        printf ("it's B");
    else if (thing.isC())
        printf ("it's C");
}

Both the if and switch functions produce identical code, at least at -O3, using clang and gcc. Of course, the above example is all inlined, but the MidiMessage code should be optimised away by modern compilers. Even isHeapAllocated() should be a non-issue unless you have significant sysex data interleaving with your note and controller data since the branch predictor should corectly predict that isHeapAllocated() is almost always false.