More uniform APIs between MIDI, USB MIDI and USBHost_t36

I'm working right now to add the many callbacks the MIDI 4.3.1 has which Teensyduino's usbMIDI lacks.

Teensy has 2 callbacks which MIDI 4.3.1 doesn't.

We do a different sysex callback that provides partial results when the buffer fills, with a 3rd parameter telling you whether this is the last part of the message. MIDI 4.3.1 uses a 2 param callback and just truncates the message if larger than the buffer.

We have a single setHandleRealTimeSystem() handler for all 6 realtime messages, contributed some time ago by Sebastian Tomczak. While it consumes 24 bytes of RAM and a little more code space, I'm going to add MIDI's 6 individual handlers too. Seems a bit redundant, but at least it should allow programs written for MIDI 4.3.1 to "just work" with Teensy's usbMIDI.

My plan is to call the more specific realtime handlers, if they've been set, and otherwise fall back to the catch-all one.

Likewise for sysex, if the partial handler is set, it will be used. The MIDI 4.3.1 compatible one that truncates will be used only if the partial handler isn't defined.
 
My plan is to call the more specific realtime handlers, if they've been set, and otherwise fall back to the catch-all one.

Likewise for sysex, if the partial handler is set, it will be used. The MIDI 4.3.1 compatible one that truncates will be used only if the partial handler isn't defined.

I saw that in the commit, and think these were a good choices.
 
Can anyone recommend a simple program, ideally Linux-based (alsa, not jack), for sending all/any possible MIDI messages for testing purpose?
 
Are the new sysex partial and complete functions expected to be called directly? Or do you still call setHandleSysEx and it does the appropriate thing?

I put together a test program and those two sysex handlers don't have syntax hilighting; plus I'm getting an error

Code:
TestUSBDeviceMIDI: In function 'void setup()':
TestUSBDeviceMIDI:26: error: 'class usb_midi_class' has no member named 'setHandleSysExPartial'
   usbMIDI.setHandleSysExPartial(OnSysExPartial);

This is the test program. Test environment was a fresh Arduino 1.8.5, Teensyduino 1.4.1beta 2, and the GitHub cores checked out today copied on top. If the two SysEx lines are commented out it compiles.

Code:
// TestUSBDeviceMIDI.ino
// Test of USB Device receiving MIDI, prints to terminal
// Set USB type to Serial + MIDI
//
// This example is in the public domain


void setup()
{
	while (!Serial) ; // wait for Arduino Serial Monitor
	Serial.println("USB Device MIDI Testing");
  
  // Channel Voice Messages, complete
	usbMIDI.setHandleNoteOff(OnNoteOff);
	usbMIDI.setHandleNoteOn(OnNoteOn);
  usbMIDI.setHandleVelocityChange(OnPolyPressure); // Polyphonic Aftertouch
  usbMIDI.setHandleControlChange(OnControlChange);
  usbMIDI.setHandleProgramChange(OnProgramChange);
  usbMIDI.setHandleAfterTouch(OnAfterTouch);      // Channel Aftertouch
  usbMIDI.setHandlePitchChange(OnPitchChange);
  
  // Channel Mode Messages, not specially handled
  // use Control Change handler
  
  // System Common Messages, complete 
  usbMIDI.setHandleSysExPartial(OnSysExPartial);
//  usbMIDI.setHandleSysExComplete(OnSysExComplete);
  usbMIDI.setHandleTimeCodeQuarterFrame(OnTimeCodeQuarterFrame);
  usbMIDI.setHandleSongPosition(OnSongPosition);
  usbMIDI.setHandleSongSelect(OnSongSelect);
  usbMIDI.setHandleTuneRequest(OnTuneRequest);

  // System Real-Time Messages, complete 
  usbMIDI.setHandleClock(OnClock);
  usbMIDI.setHandleStart(OnStart);
  usbMIDI.setHandleContinue(OnContinue);
  usbMIDI.setHandleStop(OnStop);
  usbMIDI.setHandleActiveSensing(OnActiveSense);
  usbMIDI.setHandleSystemReset(OnSystemReset);
  // This one won't be called as all the more specific handlers are used
  usbMIDI.setHandleRealTimeSystem(OnRealTimeSystem);
}

void loop()
{
	usbMIDI.read();
}

void OnNoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
{
  if (velocity==0) {
    // Looks like this will never get called, as library
    // already checks for zero velocity Note On and dispatches Note Off
    Serial.print("Running Status Note Off, ch=");
    Serial.print(channel);
    Serial.print(", note=");
    Serial.print(note);
    Serial.println();
    }
  else {
	  Serial.print("Note On, ch=");
	  Serial.print(channel);
	  Serial.print(", note=");
	  Serial.print(note);
	  Serial.print(", velocity=");
	  Serial.print(velocity);
	  Serial.println();
  }
}

void OnNoteOff(uint8_t channel, uint8_t note, uint8_t velocity)
{
	Serial.print("Note Off, ch=");
	Serial.print(channel);
	Serial.print(", note=");
	Serial.print(note);
	Serial.print(", release velocity=");
	Serial.print(velocity);
	Serial.println();
}

void OnPolyPressure(uint8_t channel, uint8_t note, uint8_t pressure)
{
  Serial.print("Poly Aftertouch, ch=");
  Serial.print(channel);
  Serial.print(", note=");
  Serial.print(note);
  Serial.print(", pressure=");
  Serial.print(pressure);
  Serial.println();
}

void OnControlChange(uint8_t channel, uint8_t control, uint8_t value)
{
	Serial.print("Control Change, ch=");
	Serial.print(channel);
	Serial.print(", control=");
	Serial.print(control);
	Serial.print(", value=");
	Serial.print(value);
	Serial.println();
}

void OnProgramChange(uint8_t channel, uint8_t patchno)
{
  Serial.print("Program Change, ch=");
  Serial.print(channel);
  Serial.print(", patch number=");
  Serial.print(patchno);
  Serial.println();
}

void OnAfterTouch(uint8_t channel, uint8_t pressure)
{
  Serial.print("Channel Aftertouch, ch=");
  Serial.print(channel);
  Serial.print(", pressure=");
  Serial.print(pressure);
  Serial.println();
}

void OnPitchChange(uint8_t channel, int pitchbend) // int, sigh
{
  Serial.print("Pitchbend, ch=");
  Serial.print(channel);
  Serial.print(", pitchbend=");
  Serial.print(pitchbend - 0x2000); // positive and negative bends
  Serial.println();
}

void OnSysExPartial(const uint8_t *data, uint16_t length, bool complete)
{
  Serial.print("Sysex, of length=");
  Serial.print(length);
  (complete)? (Serial.print(" now complete")) : (Serial.print(" still going"));
  Serial.println();
}

void OnSysExComplete(uint8_t *data, unsigned int size)
{
  Serial.print("Sysex, of size=");
  Serial.print(size);
  Serial.println();
}

void OnTimeCodeQuarterFrame(uint8_t data)
{
  // Should split into first three bits (type) and last 4 (values)
  Serial.print("Time Code, data=");
  Serial.print(data);
  Serial.println();
}

void OnSongPosition(uint16_t beats)
{
  Serial.print("Song Position, beat=");
  Serial.print(beats);
  Serial.println();
}

void OnSongSelect(uint8_t songnumber)
{
  Serial.print("Song Select, song number=");
  Serial.print(songnumber);
  Serial.println();
}

void OnTuneRequest()
{
  Serial.print("Get your oscillators in tune, please!");
  Serial.println();
}

void OnClock()
{
  Serial.print("MIDI clock pulse. ");
  Serial.print("(Expecting 24 of these per quarter note)");
  Serial.println();
}

void OnStart()
{
  Serial.print("Start syncing to clock pulses.");
  Serial.println();
}

void OnContinue()
{
  Serial.print("Carry on syncing, pause is over");
  Serial.println();
}

void OnStop()
{
  Serial.print("Stop syncing to clock pulses (for now).");
  Serial.println();
}

void OnActiveSense()
{
  Serial.print("Active Sensing detected. ");
  Serial.print("(Expecting more of these, max 300ms apart)");
  Serial.println();
}

void OnSystemReset()
{
  Serial.print("Have you tried turning it off, and back on again?");
  Serial.println();
}

void OnRealTimeSystem(uint8_t rtb)
// only called if one of the more specific handlers is not defined
{
  Serial.print("Realtime, data=");
  Serial.print(rtb);
  Serial.println();
}

// MIDI Specification at
// https://www.midi.org/specifications/item/table-1-summary-of-midi-message
// https://www.midi.org/specifications/item/table-2-expanded-messages-list-status-bytes
// https://www.midi.org/specifications/item/table-4-universal-system-exclusive-messages
 
About the 'pain' from getType....

What about retaining the current values in getType but adding a separate function that returns the status offset (the status values minus the channel).

Maybe getTypeOffset or whatever...

The very slight divergence would only matter to those processing midi between the two libraries.

If not... how long until the change would migrate to the current download?
 
I thought quite a lot about adding another getType function. I guess the question is whether we want the drag out the pain slowly with Teensy's API not matching MIDI 4.3.1 (and presumably future versions), or just take it all at once.
 
Code:
TestUSBDeviceMIDI: In function 'void setup()':
TestUSBDeviceMIDI:26: error: 'class usb_midi_class' has no member named 'setHandleSysExPartial'
   usbMIDI.setHandleSysExPartial(OnSysExPartial);

It's setHandleSystemExclusive(function), where the partial vs complete/truncated version is selected by function overloading (whether you give it a 2 or 3 param function).

I'm working today on updated examples and documentation. Here's a work-in-progress example to demonstrate all the new callbacks. Still filling in code, but it does compile as at least it should be useful for resolving the name & syntax.

Code:
void setup() {
  Serial.begin(115200);
  usbMIDI.setHandleNoteOff(myNoteOff);
  usbMIDI.setHandleNoteOn(myNoteOn);
  usbMIDI.setHandleAfterTouchPoly(myAfterTouchPoly);
  usbMIDI.setHandleControlChange(myControlChange);
  usbMIDI.setHandleProgramChange(myProgramChange);
  usbMIDI.setHandleAfterTouchChannel(myAfterTouchChannel);
  usbMIDI.setHandlePitchChange(myPitchChange);
  usbMIDI.setHandleSystemExclusive(mySystemExclusiveChunk);
  usbMIDI.setHandleSystemExclusive(mySystemExclusive);
  usbMIDI.setHandleTimeCodeQuarterFrame(myTimeCodeQuarterFrame);
  usbMIDI.setHandleSongPosition(mySongPosition);
  usbMIDI.setHandleSongSelect(mySongSelect);
  usbMIDI.setHandleTuneRequest(myTuneRequest);
  usbMIDI.setHandleClock(myClock);
  usbMIDI.setHandleStart(myStart);
  usbMIDI.setHandleContinue(myContinue);
  usbMIDI.setHandleStop(myStop);
  usbMIDI.setHandleActiveSensing(myActiveSensing);
  usbMIDI.setHandleSystemReset(mySystemReset);
  usbMIDI.setHandleRealTimeSystem(myRealTimeSystem);
}

void loop() {
  usbMIDI.read(); // USB MIDI receive
}


void myNoteOn(byte channel, byte note, byte velocity) {
  Serial.print("Note On, ch=");
  Serial.print(channel, DEC);
  Serial.print(", note=");
  Serial.print(note, DEC);
  Serial.print(", velocity=");
  Serial.print(velocity, DEC);
  Serial.println();
}

void myNoteOff(byte channel, byte note, byte velocity) {
  Serial.print("Note Off, ch=");
  Serial.print(channel, DEC);
  Serial.print(", note=");
  Serial.print(note, DEC);
  Serial.print(", velocity=");
  Serial.print(velocity, DEC);
  Serial.println();
}

void myAfterTouchPoly(byte channel, byte note, byte velocity) {
  Serial.print("AfterTouch Change, ch=");
  Serial.print(channel, DEC);
  Serial.print(", note=");
  Serial.print(note, DEC);
  Serial.print(", velocity=");
  Serial.print(velocity, DEC);
  Serial.println();
}

void myControlChange(byte channel, byte control, byte value) {
  Serial.print("Control Change, ch=");
  Serial.print(channel, DEC);
  Serial.print(", control=");
  Serial.print(control, DEC);
  Serial.print(", value=");
  Serial.print(value, DEC);
  Serial.println();
}

void myProgramChange(byte channel, byte program) {
  Serial.print("Program Change, ch=");
  Serial.print(channel, DEC);
  Serial.print(", program=");
  Serial.print(program, DEC);
  Serial.println();
}

void myAfterTouchChannel(byte channel, byte pressure) {
  Serial.print("After Touch, ch=");
  Serial.print(channel, DEC);
  Serial.print(", pressure=");
  Serial.print(pressure, DEC);
  Serial.println();
}

void myPitchChange(byte channel, int pitch) {
  Serial.print("Pitch Change, ch=");
  Serial.print(channel, DEC);
  Serial.print(", pitch=");
  Serial.print(pitch, DEC);
  Serial.println();
}

void mySystemExclusiveChunk(const byte *data, uint16_t length, bool last) {
  Serial.print("SysEx Message: ");
  printBytes(data, length);
  if (last) {
    Serial.println(" (end)");
  } else {
    Serial.println(" (to be continued)");
  }
}

void mySystemExclusive(byte *data, unsigned int length) {
  Serial.print("SysEx Message: ");
  printBytes(data, length);
  Serial.println();
}

void myTimeCodeQuarterFrame(byte data) {
}
void mySongPosition(uint16_t beats) {
}
void mySongSelect(byte songNumber) {
}
void myTuneRequest() {
}
void myClock() {
}
void myStart() {
}
void myContinue() {
}
void myStop() {
}
void myActiveSensing() {
}
void mySystemReset() {
}
void myRealTimeSystem(uint8_t realtimebyte) {
}



void printBytes(const byte *data, unsigned int size) {
  while (size > 0) {
    byte b = *data++;
    if (b < 16) Serial.print('0');
    Serial.print(b, HEX);
    if (size > 1) Serial.print(' ');
    size = size - 1;
  }
}
 
It's setHandleSystemExclusive(function), where the partial vs complete/truncated version is selected by function overloading (whether you give it a 2 or 3 param function).

I'm working today on updated examples and documentation. Here's a work-in-progress example to demonstrate all the new callbacks.

Ahah, thanks for that.

The example I posted was also intended as an example, and deals with all of the callbacks.
 
The inline comments about how to choose between the two sysex calls will be helpful, and the splitting out of clock data into SMPTE codes will also be helpful. In my example I pointed out the consequence of seeing an active sensing (you should see more, maximum interval 300ms, and if they stop you do an all notes off per spec). In my example I tried to illustrate that Stop is actually Pause (because Continue un-pauses it).

The comparison examples show the callback style vs case statement on MIDI type well.

I don't see anything missing.

Once you get into working out what the different received control changes mean, including interactions like MSB LSB and higher level messages like RPN and NRPN, that is a higher level that can be built on top in my opinion.

An example which looks for a particular RPN could be useful as self-contained example. I was thinking of Pitch Bend Sensitivity (0, 0) which lets you turn the arbitrary 0-16k pitch bend number into an actual number of semitones and cents shift. I'm back at work now but could put together such an example if you would like.

However, for sending, a couple of utility functions to construct an RPN or NRPN (like the Francois Best MIDI library) would be nice to have and increase compatibility. But lack of that shouldn't delay release of these great improvements in the next Teensyduino beta.
 
I found & fixed a few bugs last night. Now I'm working to bring all this new stuff over to Teensy 2.0. Well, except 8 bit Teensy is only going to support single cable, with getCable() always returning zero.
 
Hi Guys,
Thanks for all the hard work on these drivers, and the due diligence on transport-specific driver consistency.
Steve
Conductive Labs
 
Thanks for the awesome work. I have already used the usbMIDI implementation and really appreciate the effort for a single API.

With the latest MIDI 5.0 release, there are new transports to support USB.
I understand that full implementation might be quite an involved effort.

In the meantime there are a few changes that would help for compatibility in the short term:
* Function setHandlePitchChange is now setHandlePitchBend on MIDI library (Maybe create an alias so that backward compatibility is maintained?)
* MidiType and other definitions from midi_Defs.h might benefit from access in the global scope (define in global scope or integrate the midi_Defs.h in the current implementation?)

Regards,
Sergio
 
Maybe start a new thread about the specific MIDI 5.0 library changes. Links to details and example code would really help.
 
Back
Top