Receiving, Sending Sysex Messages with Control Surface Library

Hello everyone!

I'm trying make this sketch working.
Basically, whenever I receive a Sysex message from MIDI usb,
I return automatically a Sysex message.

I used SysEx-Send-Receive as a starting point.

#include <Control_Surface.h>

USBMIDI_Interface midi;

// Custom MIDI callback that prints incoming SysEx messages.
struct MyMIDI_Callbacks : MIDI_Callbacks {

  // This callback function is called when a SysEx message is received.
  void onSysExMessage(MIDI_Interface &, SysExMessage sysex) override {
    // Print the message
    Serial << F("Received SysEx message: ")          //
           << AH::HexDump(sysex.data, sysex.length)  //
           << F(" on cable ") << sysex.cable.getOneBased() << endl;
  }
}
callback{};

void setup() {

  Serial.begin(115200);
  Control_Surface.begin();

  midi.begin();
  midi.setCallbacks(callback);
}

void loop() {
  Control_Surface.loop();

  uint8_t sysex[] {0xF0, 0x11, 0x22, 0x33, 0xF7};

    if (sysex.data() == 0xF0) { // 0xF0 is the Sysex start byte
    midi.sendSysEx(sysex);
    }

  // SysEx message has been received.
  midi.update();
}

This sketch, of course, doesn't get compiled.
Could you please help?
Thank you!

One more thing:

When I use SysEx-Send-Receive for sending a SysEx message with a button, I get an unexpected behaviour; the sketch get uploaded and runs perfectly, through MIDI Monitor I can see the SysEx message being sent normally.
However if I monitor the activity between Midi Device and Midi Host, I don't see any SysEx message from the host.
I can see any other kind of messages (PB, CC, Notes) from the Host, except SysEx.
If I plug the host directly to the computer I can see the SysEx messages.

What am I doing wrong?
Thanks

What's the intention of these lines?
sysex is a C-style array, it doesn't have a .data() method. Even if it did, data() would be a pointer, so you couldn't compare it to an integer.
Finally, the check is unnecessary, because you just set the first element to 0xF0.

Thanks for answering @PieterP.

The intention is to detect when a SysEx message is received.
I guess it could be done whenever a callback function is called or 0xf0 is received.

The code I've highlighted does not deal with received messages at all, so it's not quite clear what it tries to achieve. The "detection" of incoming SysEx messages is already handled by the library, you only get a callback when a SysEx message is received, and it will always start with 0xF0.

Ok got it.
Would it be possible then to send a SysEx message rather than printing to the serial (see tentative sketch below)?

#include <Control_Surface.h>

USBMIDI_Interface midi;

uint8_t sysex[]{ 0xF0, 0x11, 0x22, 0x33, 0xF7 };

// Custom MIDI callback that prints incoming SysEx messages.
struct MyMIDI_Callbacks : MIDI_Callbacks {

  // This callback function is called when a SysEx message is received.
  void onSysExMessage(MIDI_Interface &, SysExMessage sysex) override {
    // Send the message
    midi.sendSysEx(sysex);
  }
} callback{};

void setup() {

  Serial.begin(115200);
  Control_Surface.begin();

  midi.begin();
  midi.setCallbacks(callback);
}

void loop() {
  Control_Surface.loop();

  // SysEx message has been received.
  midi.update();
}

Sure, but your global sysex array is shadowed by the sysex argument of the onSysExMessage function. Give them different names.

Great! Thanks a lot @PieterP.
It gets compiled. I'll test it asap.

For the records:

#include <Control_Surface.h>

USBMIDI_Interface midi;

uint8_t sysexout[]{ 0xF0, 0x11, 0x22, 0x33, 0xF7 };

// Custom MIDI callback that prints incoming SysEx messages.
struct MyMIDI_Callbacks : MIDI_Callbacks {

  // This callback function is called when a SysEx message is received.
  void onSysExMessage(MIDI_Interface &, SysExMessage sysex) override {
    // Send the message
    midi.sendSysEx(sysexout);
  }
} callback{};

void setup() {

  Serial.begin(115200);
  Control_Surface.begin();

  midi.begin();
  midi.setCallbacks(callback);
}

void loop() {
  Control_Surface.loop();

  // SysEx message has been received.
  midi.update();
}

Hi again,
yes it works BUT there is still something that doesn't look right:

  • actually I don't need the sketch to send the same SysEx message every time it receives a SysEx. Shall I get rid of midi.update to send my SysEx just once?
  • unfortunately the Teensy passthrough sketch doesn't let SysEx messages to pass through (sorry for the word play) from the host to the device. It runs but IDE gives me the following warning, which seems SysEx related: cast between incompatible function types from 'void ()(const uint8_t, uint16_t, bool)' {aka 'void ()(const unsigned char, short unsigned int, bool)'} to 'void ()(const uint8_t, uint16_t, uint8_t)' {aka 'void ()(const unsigned char, short unsigned int, unsigned char)'} [-Wcast-function-type]
    347 | usb_midi_handleSysExPartial = (void (*)(const uint8_t *, uint16_t, uint8_t))fptr;

Thank you!

No, that wouldn't work. Besides, you need to regularly update the MIDI interface to clear the USB buffer, otherwise you'll stall the MIDI software on the computer. Simply create a global counter or flag, increment or set it whenever you send a SysEx message, and then make the sending conditional using e.g. if (num_messages_sent < 10) to limit the number of responses you send.

As a side note, Control_Surface.loop() will already call midi.update() for you: Control Surface: Purpose of the Control_Surface singleton.

This is a warning inside of the Teensy core, it is not used by Control Surface.
I've opened an issue about this over two years ago, but Paul never fixed it: New (valid) warnings raised by GCC 11 in v1.58 beta 2 · Issue #660 · PaulStoffregen/cores · GitHub

Please post the exact sketch you're using and a description of the full setup with the connections between different components.

Hi @PieterP and thanks for your reply.

Yes, I though about the global counter. But can I move the whole MyMIDI_Callbacks inside the Loop section if I want to make it conditional?
Something like this:

#include <Control_Surface.h>

USBMIDI_Interface midi;

int passFlag = 0;

void setup() {

  Serial.begin(115200);
  Control_Surface.begin();

  midi.begin();
  midi.setCallbacks(callback);
}

void loop() {
  Control_Surface.loop();

  if(passFlag == 0) {

    uint8_t sysexout[]{ 0xF0, 0x11, 0x22, 0x33, 0xF7 };
// Custom MIDI callback that prints incoming SysEx messages.
struct MyMIDI_Callbacks : MIDI_Callbacks {

  // This callback function is called when a SysEx message is received.
  void onSysExMessage(MIDI_Interface &, SysExMessage sysex) override {
    // Send the message
    midi.sendSysEx(sysexout);
  }
} callback{};
    passFlag++;                     // Increment passFlag
  }

  // SysEx message has been received.
  midi.update();
}

Re-Midipass-through, I could create a different topic for this issue for avoiding confusion.

No, that's not possible. Just add an if statement inside of the MyMIDI_Callbacks::onSysExMessage function.

Thanks @PieterP

It worked!

Below the sketch:

#include <Control_Surface.h>

USBMIDI_Interface midi;

int passFlag = 0;

uint8_t sysexout[]{ 0xF0, 0x11, 0x22, 0x33, 0xF7 };

// Custom MIDI callback that prints incoming SysEx messages.
struct MyMIDI_Callbacks : MIDI_Callbacks {

    // This callback function is called when a SysEx message is received.
    void onSysExMessage(MIDI_Interface &, SysExMessage sysex) override {
        if (passFlag == 0) 
      // Send the message
      midi.sendSysEx(sysexout);
      passFlag++;
    
  }
} callback{};

void setup() {

  Serial.begin(115200);
  Control_Surface.begin();

  midi.begin();
  midi.setCallbacks(callback);
}

void loop() {
  Control_Surface.loop();

  // SysEx message has been received.
  midi.update();
}

Thanks a lot!

This may overflow, you should move it inside of the body of the if-statement.

At least in the code you posted, passFlag is only accessed in the onSysExMessage() function of the myMIDI_Callbacks struct. So, there's no need for it to be global. Why not make it a data member of the struct?

Done, much appreciated @PieterP .

if (passFlag == 0, passFlag++)

If I knew how to do it @gfvalvo

https://www.learncpp.com/cpp-tutorial/introduction-to-structs-members-and-member-selection/

1 Like

This doesn't do what you expect. You want the following:

if (passFlag == 0) {
   midi.sendSysEx(sysexout);
   passFlag++;
}

Although it doesn't need to be an integer if you just send the message once, it can be a boolean flag.

To get the syntax of the if statement etc. correct, I'd also recommend studying learncpp.

Hey @PieterP, thanks a lot for fixing my sketch.
It works properly now.