arduino-libraries / MIDIUSB

A MIDI library over USB, based on PluggableUSB
GNU Lesser General Public License v2.1
482 stars 89 forks source link

Sending arbitrary length MIDI messages #19

Open vockleya opened 8 years ago

vockleya commented 8 years ago

I'm working on a project that requires me to send a different length of MIDI message than the standard 4-byte note on/off, and program/control change messages. It seems like this should be trivial, but I'm having trouble getting it to work. It looks like I should be able to use the MidiUSB.write() function to send arbitrary data to the computer, but I'm getting very odd messages on the computer.

Here's the snippet of code that is actually sending the data.

uint8_t data[7] = {0xF0, 0x7F, 0x00, 0x02, 0x7F, 0x01, 0xF7}; MidiUSB.write(data, 7); MidiUSB.flush();

I'm using Snoize MIDI Monitor on OS X 10.11.5 to view the messages. Depending on the content of the message, I get anywhere from nothing, to a single byte, to something random that wasn't even in the original message. Given the example above, MIDI Monitor shows a message 1 byte long, with the contents of whatever is in the second to last byte, and in this case 0x01.

The message I'm trying to send is a SysEx MIDI show control "go" command (and various others once I get this working). According to the SysEx/MSC spec, I'm using the correct byte sequence.

As far as I can tell, all the sendMIDI() function does is create a data buffer from the midiEventPacket_t passed to it, and then calls write(). I'm just creating my own buffer and directly calling write(). It's interesting to note that when I use a standard 4-byte note on message in my code above, it works correctly.

Edit: After a bit more playing around, I've discovered some even weirder behavior. The bit of code above is run by a button press. There's no issue with the button push detection/debouncing code. If I comment out the MIDIUSB.flush() line, and spam push the button, sometimes the complete message makes it through, and sometimes it's garbled, truncated, or otherwise messed up as before. So I know the data is getting to the computer properly, but it seems like it's being misinterpreted by the computer. Am I just running into a limitation of the library, or is there something I'm missing?

monteslu commented 8 years ago

Just hit this same thing. Trying to use https://github.com/firmata/protocol/blob/master/protocol.md on top of MIDIUSB but need variable length messages as well.

oqibidipo commented 8 years ago

USB MIDI event packets are always 4 bytes long with a header byte and up to 3 bytes of MIDI data (padded with zeros if necessary). You have to split the SysEx message into several packets.

Untested code:

void MidiUSB_sendSysEx(const uint8_t *data, size_t size)
{
    midiEventPacket_t event;
    const uint8_t *d = data;
    size_t bytesRemaining = size;

    while (bytesRemaining > 0) {
        switch (bytesRemaining) {
        case 1:
            event.header = 5;   // SysEx ends with following single byte
            event.byte1 = *d;
            event.byte2 = 0;
            event.byte3 = 0;
            bytesRemaining = 0;
            break;
        case 2:
            event.header = 6;   // SysEx ends with following two bytes
            event.byte1 = *d++;
            event.byte2 = *d;
            event.byte3 = 0;
            bytesRemaining = 0;
            break;
        case 3:
            event.header = 7;   // SysEx ends with following three bytes
            event.byte1 = *d++;
            event.byte2 = *d++;
            event.byte3 = *d;
            bytesRemaining = 0;
            break;
        default:
            event.header = 4;   // SysEx starts or continues
            event.byte1 = *d++;
            event.byte2 = *d++;
            event.byte3 = *d++;
            bytesRemaining -= 3;
            break;
        }
        MidiUSB.sendMIDI(event);
    }
}
oqibidipo commented 8 years ago

Results of some testing with MIDI Monitor & SysEx Librarian on OS X 10.11.6:

        MidiUSB.sendMIDI(event);
        if (bytesRemaining > 0) delay(1);
oqibidipo commented 8 years ago

This version sends all packets at once, no delay needed.

void MidiUSB_sendSysEx(const uint8_t *data, size_t size)
{
    if (data == NULL || size == 0) return;

    size_t midiDataSize = (size+2)/3*4;
    uint8_t midiData[midiDataSize];
    const uint8_t *d = data;
    uint8_t *p = midiData;
    size_t bytesRemaining = size;

    while (bytesRemaining > 0) {
        switch (bytesRemaining) {
        case 1:
            *p++ = 5;   // SysEx ends with following single byte
            *p++ = *d;
            *p++ = 0;
            *p = 0;
            bytesRemaining = 0;
            break;
        case 2:
            *p++ = 6;   // SysEx ends with following two bytes
            *p++ = *d++;
            *p++ = *d;
            *p = 0;
            bytesRemaining = 0;
            break;
        case 3:
            *p++ = 7;   // SysEx ends with following three bytes
            *p++ = *d++;
            *p++ = *d++;
            *p = *d;
            bytesRemaining = 0;
            break;
        default:
            *p++ = 4;   // SysEx starts or continues
            *p++ = *d++;
            *p++ = *d++;
            *p++ = *d++;
            bytesRemaining -= 3;
            break;
        }
    }
    MidiUSB.write(midiData, midiDataSize);
}
monteslu commented 8 years ago

This is great @oqibidipo !

Any idea how'd we go about reading SYSEX messages into the device?

thenetimp commented 6 years ago

@monteslu did you ever figure out how to read sysex messages on your arduino?

monteslu commented 6 years ago

@thenetimp I wish :) I'd really love to get firmata running directly over midi to an arduino device. I'm just not good enough at c++ to fix this library myself.

thenetimp commented 6 years ago

I am working on a usb stomp box, I'd love to be able to send sysex messages to map buttons to channels, but my c++ is very much shit. le sigh

sanotronics commented 6 years ago

Hi!

Anyone got this working?

I also need to be able to receive sysex on a SAMD21.

I used @oqibidipo's function to send sysex and it seems to work fine.

Maybe it is a matter of porting the parse() function of the Arduino MIDI Library to this library, what do you think?

franky47 commented 5 years ago

Conversation about integration with the MIDI Library (which now includes an interface with MIDIUSB and allows sending and receiving "large" SysEx messages) is here: https://github.com/arduino-libraries/MIDIUSB/issues/21#issuecomment-435800047

lathoub commented 4 years ago

This issue is resolved in https://github.com/lathoub/Arduino-USBMIDI (fully supporting receiving and sending SysEx) that uses the underlaying FortySevenEffects Arduino MIDI Library (USB-MIDI is one of the transport mechanisms ). See also #21