More uniform APIs between MIDI, USB MIDI and USBHost_t36

PaulStoffregen

Well-known member
Can anyone (maybe @oddson) remind me of what still needs to be done on each of these MIDI APIs?

Teensy's USB MIDI functions were modeled on an older version (then the most recent) of the MIDI library. I've tried to replicate it on USBHost_t36, though only just last night did I finally add the ability to actually send messages on USB Host.

Please understand I have very little MIDI gear. I'm not a musician. I'm depending on feedback from MIDI experts to improve the MIDI experience for everyone. If there are low-risk changes that can make things better long-term, I'd really like to get them in before the upcoming 1.41 release.
 
Yikes... called on by teacher by name.

To be honest I'm a bit lost as I took some comments on the form to mean work was already done to start to align with standard Arduino MIDI library... but I guess not.

From memory:

usbMIDI'S midi-type internal values need to change to the bit-shifted version used in MIDI (with a conditional that shifts user supplied values below 0x80 up to the expected range? )

We need a generic usbMIDI.send() to simplify non functional programming (i.e. passing arbitrary MIDI messages without care of type for functions like MIDI thru).

There may still be issues with sysex message lengths and support in host

(While I don't believe realtime midi has explict support I don't see this as a major issue as those needing it should be able to handle as a special case of sysex - which is all they are.)

Perhaps what's really needed is to step back to let MIDI handle MIDI as the new library has tried to abstract the midi support from the transport layer. Then the usbMIDI library would (in theory) just need to handle the USB side and pass the midi on as data to the main library.

I have been hoping to take a more active role in development or at least in understanding the libraries suffiencently that I could comment more knowledgeably but life keeps intruding on my hobby time. So far I haven't even loaded the current beta.:(
 
NRPN support might be important as without your sending two messages without assurance they will process together.

Max sysex is set to default to 128 but I think that's a chunk size limit for processing rather than a hard limit on sysex size whereas the Teensy limit was absolute at least at some point.
 
usbMIDI'S midi-type internal values need to change to the bit-shifted version used in MIDI (with a conditional that shifts user supplied values below 0x80 up to the expected range? )

Ok, this is going to be the painful change. Currently usbMIDI.getType() is documented as returning these (which were the numbers from MIDI 3.2):

Code:
The types are:
0 = Note Off,
1 = Note On,
2 = Velocity Change,
3 = Control Change,
4 = Program Change,
5 = After Touch,
6 = Pitch Bend,
7 = System Exclusive

Looking at MIDI 4.3.1 (the latest version), I see Francois Best now defines these:

Code:
    InvalidType           = 0x00,    ///< For notifying errors
    NoteOff               = 0x80,    ///< Note Off
    NoteOn                = 0x90,    ///< Note On
    AfterTouchPoly        = 0xA0,    ///< Polyphonic AfterTouch
    ControlChange         = 0xB0,    ///< Control Change / Channel Mode
    ProgramChange         = 0xC0,    ///< Program Change
    AfterTouchChannel     = 0xD0,    ///< Channel (monophonic) AfterTouch
    PitchBend             = 0xE0,    ///< Pitch Bend
    SystemExclusive       = 0xF0,    ///< System Exclusive
    TimeCodeQuarterFrame  = 0xF1,    ///< System Common - MIDI Time Code Quarter Frame
    SongPosition          = 0xF2,    ///< System Common - Song Position Pointer
    SongSelect            = 0xF3,    ///< System Common - Song Select
    TuneRequest           = 0xF6,    ///< System Common - Tune Request
    Clock                 = 0xF8,    ///< System Real Time - Timing Clock
    Start                 = 0xFA,    ///< System Real Time - Start
    Continue              = 0xFB,    ///< System Real Time - Continue
    Stop                  = 0xFC,    ///< System Real Time - Stop
    ActiveSensing         = 0xFE,    ///< System Real Time - Active Sensing
    SystemReset           = 0xFF,    ///< System Real Time - System Reset

As far as I can tell, usbMIDI doesn't have any place for users to input these numbers for sending. They're only used for receiving, and only if using the non-callback read approach.

I see MIDI 4.3.1 now has this public function, which does take those codes as its first input.

Code:
    void send(MidiType inType,
              DataByte inData1,
              DataByte inData2,
              Channel inChannel);



We need a generic usbMIDI.send() to simplify non functional programming (i.e. passing arbitrary MIDI messages without care of type for functions like MIDI thru).

Will implementing that 4-input send() function meet this need?


There may still be issues with sysex message lengths and support in host

Yes, sysex on USBHost_t36 is pretty much untested. I haven't used it. As far as I know, nobody has (yet). I'd be surprised if it actually works.

usbMIDI isn't handling sysex at all with the read/getType interface, and for the callback it's making callbacks for each tiny chunk.

MIDI 4.3.1 appears to have a 128 byte size limit, but gives a way to create class instances with whatever buffer size you like.


Perhaps what's really needed is to step back to let MIDI handle MIDI as the new library has tried to abstract the midi support from the transport layer. Then the usbMIDI library would (in theory) just need to handle the USB side and pass the midi on as data to the main library.

I'm looking at midi_UsbTransport.h and midi_UsbTransport.hpp. It seems to be designed to use midiEventPacket_t from this MIDIUSB lib. Maybe I should try to implement that API as well?

I have been hoping to take a more active role in development or at least in understanding the libraries suffiencently that I could comment more knowledgeably but life keeps intruding on my hobby time. So far I haven't even loaded the current beta.:(

Yeah, I certainly know about the lack of time. That's pretty much why Teensyduino had the old MIDI 3.2 for so long, and why I've not managed to update usbMIDI.

I'd really like to update Teensyduino's MIDI support. The main problem is I'm not a musician and I have only a small amount of MIDI gear that I barely know how to use. I'm really depending on feedback from people who actually know and use MIDI.
 
Ok, as a first step I've made a first attempt at a MIDIUSB.h for compatibility with Arduino's new API.

https://github.com/PaulStoffregen/cores/commit/ff5f62f798a36d9e4e8d7ddd75d9b91a5b934913

It compiles without error for the MIDI library's midi_UsbTransport.h. My Linux desktop isn't set up for MIDI, so I have no quick way to test if this actually works.



Code:
#include <MIDI.h>
#include <midi_UsbTransport.h>

static const unsigned sUsbTransportBufferSize = 16;
typedef midi::UsbTransport<sUsbTransportBufferSize> UsbTransport;

UsbTransport sUsbTransport;

MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI);


void handleNoteOn(byte inChannel, byte inNumber, byte inVelocity)
{
    Serial.print("NoteOn  ");
    Serial.print(inNumber);
    Serial.print("\tvelocity: ");
    Serial.println(inVelocity);
}
void handleNoteOff(byte inChannel, byte inNumber, byte inVelocity)
{
    Serial.print("NoteOff ");
    Serial.print(inNumber);
    Serial.print("\tvelocity: ");
    Serial.println(inVelocity);
}

void setup() {
    Serial.begin(115200);
    while (!Serial);
    MIDI.begin();
    MIDI.setHandleNoteOn(handleNoteOn);
    MIDI.setHandleNoteOff(handleNoteOff);
    Serial.println("Arduino ready.");
}

void loop() {
    MIDI.read();
}
 
Four byte send is fine with me.

On the type value thing... is the "pain" from supporting old code that uses the old values?

Can't you just transform values by shifting 4 bits and adding eight for values 0x00 to 0x07?

I don't think you should worry about the sysytem common and system real time mesages getting explicit support in you library as a major priority provided users can read and send three or four byte data easily.

Maybe there's a better way to get to the required result... we need to be able to read a midi message and send it's equivalent via serial midi.... currently the user has to translate the type info.
 
On the type value thing... is the "pain" from supporting old code that uses the old values?

The painful part is anyone who's already written a program which calls usbMIDI.getType(), which uses the old numbers, will have it break when they upgrade Teensyduino.
 
Can anyone (maybe @oddson) remind me of what still needs to be done on each of these MIDI APIs?

Teensy's USB MIDI functions were modeled on an older version (then the most recent) of the MIDI library. I've tried to replicate it on USBHost_t36, though only just last night did I finally add the ability to actually send messages on USB Host.

Please understand I have very little MIDI gear. I'm not a musician. I'm depending on feedback from MIDI experts to improve the MIDI experience for everyone. If there are low-risk changes that can make things better long-term, I'd really like to get them in before the upcoming 1.41 release.

The type codes used for Teensy 3.x USB MIDI, Teensy 2.x MIDI, and T3.6 USB Host MIDI are all single-digit numbers which are derived from the MIDI type codes. In many cases, the MIDI type code uses the channel number in 4 bits of the value, so the Teensy type codes simply drop that. However, not all message types are constructed like that, in particular the Real-Time messages. This causes problems when the list is extended (for example this thread which tried to add Song Position Pointer support with msg_type = 9):
https://forum.pjrc.com/threads/4609...-Pointer-(SPP)-to-core-library?highlight=MIDI

So yes, it would be good to align them with the MIDI standard (and the Arduino MIDI library). To my mind it is unfortunate that the Francois Best MIDI library seems to use a rather baroque coding style compared to the clearer and more straightforward MIDI code Paul writes.

In terms of missing stuff, the code I posted in this thread:
https://forum.pjrc.com/threads/48355-Teensy-3-6-USB-Host-MIDI
groups the messages in the order of the MIDI specification and notes in comments where there is partial or no support. Perhaps the following list is clearer:

Channel Voice Messages are all supported.
Channel Mode Messages are not supported, they are treated like any other control change. Support can be added on top.
System Common Messages have some support: SysEx, End of Exclusive, are supported. The PR to add Song Position Pointer was mentioned already. I don't remember offhand if MIDI Time Code Quarter Frame was added along with the clock stuff? Song Select and Tune Request are unsupported.
System Real-Time Messages were added with the 24-per-quarter-note clock stuff, if I remember correctly.

Not a uniformity issue, but for Teensy 3.x and USB Host the sysex size could usefully be increased.

That covers all of the low-level support. There is a lot missing in terms of MIDI state and higher-level support, but that can be implemented in the sketch or with a library on top. And when making such a library, consistency between USB (device) MIDI, USB Host MIDI and DIN MIDI is helpful so the sketch or library can handle all three uniformly.

(My current project is a Eurorack implementation of Multidimensional Polyphonic Expression (MPE) so I have started planning such a higher-level library that implements MIDI state, 14-bit support, RPN and NRPN, and so on).
 
NRPN support might be important as without your sending two messages without assurance they will process together.

Yes, but that is also true for RPN and for the LSB/MSB pairs of CCS and for CC88 High Resolution Velocity Prefix. All of that can be implemented on top as a higher-level abstraction.
 
I don't think you should worry about the sysytem common and system real time mesages getting explicit support in you library as a major priority provided users can read and send three or four byte data easily.

I strongly disagree, these are missing features from the lower level support and make things harder to code and to understand by their absence.
 
Ok, this is going to be the painful change. Currently usbMIDI.getType() is documented as returning these (which were the numbers from MIDI 3.2):

Code:
The types are:
0 = Note Off,
1 = Note On,
2 = Velocity Change,
3 = Control Change,
4 = Program Change,
5 = After Touch,
6 = Pitch Bend,
7 = System Exclusive

Isn't there an 8 for Clock? And 9 was proposed for Song Position Pointer.

Looking at MIDI 4.3.1 (the latest version), I see Francois Best now defines these:

Code:
    InvalidType           = 0x00,    ///< For notifying errors
    NoteOff               = 0x80,    ///< Note Off
    NoteOn                = 0x90,    ///< Note On
    AfterTouchPoly        = 0xA0,    ///< Polyphonic AfterTouch
    ControlChange         = 0xB0,    ///< Control Change / Channel Mode
    ProgramChange         = 0xC0,    ///< Program Change
    AfterTouchChannel     = 0xD0,    ///< Channel (monophonic) AfterTouch
    PitchBend             = 0xE0,    ///< Pitch Bend
    SystemExclusive       = 0xF0,    ///< System Exclusive
    TimeCodeQuarterFrame  = 0xF1,    ///< System Common - MIDI Time Code Quarter Frame
    SongPosition          = 0xF2,    ///< System Common - Song Position Pointer
    SongSelect            = 0xF3,    ///< System Common - Song Select
    TuneRequest           = 0xF6,    ///< System Common - Tune Request
    Clock                 = 0xF8,    ///< System Real Time - Timing Clock
    Start                 = 0xFA,    ///< System Real Time - Start
    Continue              = 0xFB,    ///< System Real Time - Continue
    Stop                  = 0xFC,    ///< System Real Time - Stop
    ActiveSensing         = 0xFE,    ///< System Real Time - Active Sensing
    SystemReset           = 0xFF,    ///< System Real Time - System Reset
Yes, these are the codes from the MIDI spec, except that messages which take channel numbers have the channels set to zero. Plus the 00 error code, which seems like a good idea.


Yes, sysex on USBHost_t36 is pretty much untested. I haven't used it. As far as I know, nobody has (yet). I'd be surprised if it actually works.

It does work; I briefly tested it here
https://forum.pjrc.com/threads/48355-Teensy-3-6-USB-Host-MIDI
and reported seeing several short SysEx flying by from the two devices I tested.
None of the devices I tested sent long SysEx, though.
 
I strongly disagree, these are missing features from the lower level support and make things harder to code and to understand by their absence.

Low level support? If something is stopping users from accessing the data then yes... if you mean adding callback functions I just think they are not the highest priority.

I'm really starting to feel I'm not qualified to comment on how Paul should support MIDI. (I'm not a programmer or a musician!)

But you should be able to send MIDI between the two libraries without resorting to handling each type separately.

In my limited understanding of things it seems Paul can either move to supporting FB's library directly with a few API calls like those in the MIDIUSB library (but then there is the 'pain') or he can add any missing features and keep the two libraries functionally separate with a feature or two for communicating between them.

I would assume he'd rather do the former as he must have said 'I'm not a musician' 20 times on this forum ;)


...then there's BLE-MIDI support.
 
I'm comparing usbMIDI to MIDI 4.3.1 send functions.

MIDI's sendPitchBend takes a signed integer from -8192 to +8191. It also has a version taking a double from -1.0 to +1.0. usbMIDI takes unsigned 0 to 16383.

MIDI has sendAfterTouch with 2 and 3 params. usbMIDI has only the 2 param version.

MIDI's sendSysEx takes an optional 3rd param, indicating whether the array contains the start & stop codes.

MIDI has these functions which aren't in usbMIDI at all.

Code:
    inline void sendSongPosition(unsigned inBeats);
    inline void sendSongSelect(DataByte inSongNumber);
    inline void sendTuneRequest();

    inline void beginRpn(unsigned inNumber, Channel inChannel);
    inline void sendRpnValue(unsigned inValue, Channel inChannel);
    inline void sendRpnValue(byte inMsb, byte inLsb, Channel inChannel);
    inline void sendRpnIncrement(byte inAmount, Channel inChannel);
    inline void sendRpnDecrement(byte inAmount, Channel inChannel);
    inline void endRpn(Channel inChannel);

    inline void beginNrpn(unsigned inNumber, Channel inChannel);
    inline void sendNrpnValue(unsigned inValue, Channel inChannel);
    inline void sendNrpnValue(byte inMsb, byte inLsb, Channel inChannel);
    inline void sendNrpnIncrement(byte inAmount, Channel inChannel);
    inline void sendNrpnDecrement(byte inAmount, Channel inChannel);
    inline void endNrpn(Channel inChannel);

    void send(MidiType inType, DataByte inData1, DataByte inData2, Channel inChannel);

Any thoughts on these?
 
I've added the missing send functions, at least on usbMIDI for 32 bit Teensy. Will do 8 bit Teensy and USBHost_t36 later. Looking for feedback (and ideally help testing) before replicating this to the others.

I also added an optional "cable" parameter, which is intended to lay the groundwork for supporting multi-cable USB MIDI.

https://github.com/PaulStoffregen/cores/commit/648342f973b0af4d52bf74080a3fae6238782c49

This should be very close to all the send functions in MIDI 4.3.1. As far as I know, there are still 2 differences remaining.

MIDI 4.3.1 uses a signed integer from -8192 to +8191 for sendPitchBend. Should I go with that, or stay with unsigned 0 to 16383?

MIDI 4.3.1 has 2 functions for sendTimeCodeQuarterFrame(). The single input version can't be used with the added optional cable parameter, since it create ambiguous function overloading.
 
I'm comparing usbMIDI to MIDI 4.3.1 send functions.

MIDI's sendPitchBend takes a signed integer from -8192 to +8191. It also has a version taking a double from -1.0 to +1.0. usbMIDI takes unsigned 0 to 16383.

The signed versions are slightly more convenient (negative values are a drop in pitch, positive are a raise in pitch) but the unsigned one is not than much less usable.
MIDI has the problem that, until MPE, the actual number of semitones change that would be produced was undefined. In my planned higher-level library I was intending to add a function to send pitchbend whose value was a float number of semitones.

Given the different parameter types, i guess all three functions can be supported with overloading? That would maximize backwards compat, perhaps.

MIDI has sendAfterTouch with 2 and 3 params. usbMIDI has only the 2 param version.
What is the third param, a note? (Maybe it is somehow combining chanel and polyphonic aftertouches into one function?)

MIDI's sendSysEx takes an optional 3rd param, indicating whether the array contains the start & stop codes.
I guess that could be added for compat, it seems easier if the function adds them for you but I don't have a strong opinion here.

MIDI has these functions which aren't in usbMIDI at all.

Code:
    inline void sendSongPosition(unsigned inBeats);
    inline void sendSongSelect(DataByte inSongNumber);
    inline void sendTuneRequest();

Yes, those are the three missing ones I mentioned earlier. For code that uses callbacks, currently you have to hack the library to add those so it is much better to have them.

Code:
    inline void beginRpn(unsigned inNumber, Channel inChannel);
    inline void sendRpnValue(unsigned inValue, Channel inChannel);
    inline void sendRpnValue(byte inMsb, byte inLsb, Channel inChannel);
    inline void sendRpnIncrement(byte inAmount, Channel inChannel);
    inline void sendRpnDecrement(byte inAmount, Channel inChannel);
    inline void endRpn(Channel inChannel);

    inline void beginNrpn(unsigned inNumber, Channel inChannel);
    inline void sendNrpnValue(unsigned inValue, Channel inChannel);
    inline void sendNrpnValue(byte inMsb, byte inLsb, Channel inChannel);
    inline void sendNrpnIncrement(byte inAmount, Channel inChannel);
    inline void sendNrpnDecrement(byte inAmount, Channel inChannel);
    inline void endNrpn(Channel inChannel);

Excellent; much easier for sending RPN and NRPN. (Receiving still requires these to be built per-sketch on top of the onControlChange callback).

Code:
    void send(MidiType inType, DataByte inData1, DataByte inData2, Channel inChannel);

Any thoughts on these?

I guess the last one would let you send anything, whether it was standard or not, and might be useful for people using MIDI as a handy transport for unrelated things like lighting control.
 
I'm going to work on the input functions later today and tomorrow. While I'm not excited about breaking backwards compatibility, I agree the return from getType() needs to sync with MIDI 4.3.1 and hopefully will remain stable in the future.

This is the list of callbacks for MIDI 4.3.1. Is this a complete set, or is anything else needed? Now's the time to tell me!

Code:
    inline void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity));
    inline void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity));
    inline void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure));
    inline void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value));
    inline void setHandleProgramChange(void (*fptr)(byte channel, byte number));
    inline void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure));
    inline void setHandlePitchBend(void (*fptr)(byte channel, int bend));
    inline void setHandleSystemExclusive(void (*fptr)(byte * array, unsigned size));
    inline void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data));
    inline void setHandleSongPosition(void (*fptr)(unsigned beats));
    inline void setHandleSongSelect(void (*fptr)(byte songnumber));
    inline void setHandleTuneRequest(void (*fptr)(void));
    inline void setHandleClock(void (*fptr)(void));
    inline void setHandleStart(void (*fptr)(void));
    inline void setHandleContinue(void (*fptr)(void));
    inline void setHandleStop(void (*fptr)(void));
    inline void setHandleActiveSensing(void (*fptr)(void));
    inline void setHandleSystemReset(void (*fptr)(void));
 
...While I'm not excited about breaking backwards compatibility, I agree the return from getType() needs to sync with MIDI 4.3.1 and hopefully will remain stable in the future...
Is there no way to support the old type numbers by shifting them to the new range? The only conflict is with invalidType and noteOn and no one is sending invalidType.
 
Since we are doing MIDI consistency it is worth mentioning that in addition to the DIN current-loop transport and the USB transport, there is also RTP-MIDI which uses an Ethernet connection and UDP transport.

This is defined by RFC6295 and implemented in Apple CoreMIDI on iOS and OSX; there are apparently compatible implementations on Windows, Linux and Android. I have never used this protocol mainly because I don't own any Macs or iOS devices.

https://en.wikipedia.org/wiki/RTP-MIDI
https://www.midi.org/articles/rtp-midi-or-midi-over-networks
http://john-lazzaro.github.io//rtpmidi/


I see that there is an Arduino library for it called Arduino-AppleMIDI-Library which defines the following

Code:
class IRtpMidi
{
public:
	virtual bool PassesFilter(void* sender, DataByte, DataByte) = 0;

	virtual void OnNoteOn(void* sender, DataByte, DataByte, DataByte) = 0;
	virtual void OnNoteOff(void* sender, DataByte, DataByte, DataByte) = 0;
	virtual void OnPolyPressure(void* sender, DataByte, DataByte, DataByte) = 0;
	virtual void OnChannelPressure(void* sender, DataByte, DataByte) = 0;
	virtual void OnPitchBendChange(void* sender, DataByte, int) = 0;
	virtual void OnProgramChange(void* sender, DataByte, DataByte) = 0;
	virtual void OnControlChange(void* sender, DataByte, DataByte, DataByte) = 0;
	virtual void OnTimeCodeQuarterFrame(void* sender, DataByte) = 0;
	virtual void OnSongSelect(void* sender, DataByte) = 0;
	virtual void OnSongPosition(void* sender, unsigned short) = 0;
	virtual void OnTuneRequest(void* sender) = 0;
	virtual void OnClock(void* sender) = 0;
	virtual void OnStart(void* sender) = 0;
	virtual void OnContinue(void* sender) = 0;
	virtual void OnStop(void* sender) = 0;
	virtual void OnActiveSensing(void* sender) = 0;
	virtual void OnReset(void* sender) = 0;
	virtual void OnSysEx(void* sender, const byte* data, uint16_t size) = 0;
};

This is the same list (slightly different names) as the Francois best MIDI library, in the same order, because that is the order in the MIDI 1.0 Specification.
 
Sorry, I've been imprecise with abbreviated language. In most of this thread, "MIDI" actually means the Arduino MIDI library by Francois Best, which is currently at version 4.3.1. When you write "#include <MIDI.h>" in Arduino, this is the library which gets included.

The MIDI specification also uses version numbers. That's not really the subject here. This conversation is about the versions of several different USB libraries, not the MIDI standard itself (though some of this necessarily involves talking about newer features or conventions of MIDI as implemented in the newer libraries).

The USB MIDI features in Teensyduino were originally crafted for compatibility with version 2.5 of Francios's library (at the time Teensy first got USB MIDI, that was the latest version), and also for semi-compatibility with another MIDI library (which is now defunct and seems to no longer exist anywhere on the internet) which used callbacks via C++ subclass. Years later, Francois Best incorporated the callbacks into his MIDI library. He also many many chances and improvements since those old days. I put some of them into Teensyduino, some by my own work, others contributed from users.

But this piecemeal approach over the years hasn't kept things complete or consistent. I'm trying to update everything on the Teensyduino side.

I've also seen some comments from Francois Best about a possible major version 5 redesign of his MIDI library. Ah, progress....
 
Last edited:
Is there no way to support the old type numbers by shifting them to the new range?

Sure there is. The problem is those changes to deal with the new numbers need to happen in end user sketches.

I don't see any way we can update usbMIDI.getType() and preserve things for programs that check for numbers 0 to 7. Program people have written using usbMIDI.getType() WILL BREAK if this change is made.

Rarely do I allow API changes that break backwards compatibility. But this is one of the unfortunate cases where I just don't see any way around it that doesn't permanently hobble Teensyduino's USB MIDI from supporting all MIDI messages.
 
Of course.... I use getType to test for sysex and that will break. Wasn't thinking it through.
 
Back
Top