Categories
Blog Code Music

Reading MIDI

I have a few ideas I’d like to prototype for live performance using MIDI loopers rather than audio loopers. I have sketched early ideas but I’d like to experiment with the project in earnest. I’m tinkering with tools before mocking up some early designs.

In the past I’ve written MIDI code on Windows using the old multimedia headers and real time clocks, and on AVR/Arduino using direct serial communication. My current thinking is to write a cross-platform mock-up in C++ or Python, and port to C++ on AVR when a hardware build is viable.

This tinker is on my ‘new’ Linux machine, a 2008 ThinkPad X230 running Ubuntu 18. I switched over because OSX and Windows drive me mad their constant updates and weird release policy. Long story short: now I’m able to experiment with /dev/midi.

#include <iostream>
#include <fstream>

namespace midi {

// https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message

enum MsgType {
    NoteOff,
    NoteOn,
    KeyPressure,
    ControlChange,
    ProgramChange,
    ChannelPressure,
    PitchBend,
    Unknown,
};

const char* g_msg_names[] = {
    "note off",
    "note on",
    "key pressure",
    "control change",
    "program change",
    "channel pressure",
    "pitch bend"
};

struct MsgInfo {
    MsgType type;
    int channel;
    size_t extra_bytes;
};

MsgInfo msg_info(unsigned char header_byte) {
    int hi_nibble = header_byte >> 4;
    int lo_nibble = header_byte & 0b1111;
    switch (hi_nibble) {
    case 0b1000:
        return {MsgType::NoteOff, lo_nibble, 2};
    case 0b1001:
        return {MsgType::NoteOn, lo_nibble, 2};
    case 0b1010:
        return {MsgType::KeyPressure, lo_nibble, 2};
    case 0b1011:
        return {MsgType::ControlChange, lo_nibble, 2};
    case 0b1100:
        return {MsgType::ProgramChange, lo_nibble, 1};
    case 0b1101:
        return {MsgType::ChannelPressure, lo_nibble, 1};
    case 0b1110:
        return {MsgType::PitchBend, lo_nibble, 2};
    }
    return {MsgType::Unknown, -1, 0};
}

} // namespace midi

int main() {
    std::fstream fs("/dev/midi1", std::ios_base::binary | std::ios_base::in);
    while (fs.is_open()) {
        unsigned char header_byte;
        fs.read(reinterpret_cast<char*>(&header_byte), 1);

        midi::MsgInfo info = midi::msg_info(header_byte);
        if (info.type != midi::MsgType::Unknown) {
            std::cout << midi::g_msg_names[(size_t)info.type] << '\n';
            std::cout << info.channel << '\n';

            for (size_t i = 0; i < info.extra_bytes; ++i) {
                unsigned char data_byte;
                fs.read(reinterpret_cast<char*>(&data_byte), 1);
                std::cout << (int)data_byte << '\n';
            }
        }

        std::cout.flush();
    }
}

When the keyboard is plugged in this reports any non-sysex messages like this:

note on
0
60
66
note on
0
60
0
control change
0
1
6
control change
0
1
8
control change
0
71
33
control change
0
71
37

Note: installed xclip. Very handy for moving text from command line into blog. Added alias xclip="xclip -selection c" to .bash_profile and copied with xclip < main.cpp

Leave a Reply

Your email address will not be published. Required fields are marked *