Teensy3.6 UsbHost MIDI Performance Issues

Van

Well-known member
Hi there;

I'm trying to control a 64 led matrix (on my push2) that is controlled simply via midi noteOn() massages.

I have this code :
Code:
#include <Arduino.h>
#include "USBHost_t36.h"

USBHost myusb;
USBHub hub1(myusb);
USBHub hub2(myusb);
MIDIDevice_BigBuffer midi1(myusb);


bool led_state = false;
int row;
int column;

void performanceTest(){

  elapsedMillis took_time;

  led_state = !led_state;

  row = 0;
  while(row<8){

    column = 0;
    while(column<8){
      midi1.sendNoteOn(36 + (row * 8) + column, led_state, 1);
      column++;
    }
    row++;
  }

  Serial.print("took time_: ");
  Serial.println(took_time);

}

void setup() {

  while(!Serial);
  Serial.begin(115200);

  delay(1500);
  Serial.println("USB Host InputFunctions example");
  delay(10);
  myusb.begin();
}

void loop() {

  performanceTest();

}

this gives me pretty much exact 320ms per function call, or 5ms for a midi noteOn() call.

Can this be improved?

thanks and greets!

PS. a interesting readout about latency in MIDI: https://www.soundonsound.com/techniques/truth-about-latency-part-2
 
Last edited:
I've tested it against a "normal" midi usb with my computer and there is no problem regarding the speed ( much higher speed that 5ms per NoteOn)...

Does this have something to do with the limitation I'm experiencing:

in function
Code:
write_packed(uint32_t data){
...
// TODO: start a timer, rather than sending the buffer
// before it's full, to make best use of bandwidth
 
Last edited:
So I'm talking to myself here, but it seems to be USB Host speed issue I can't resolve by myself.. :/

This is one of the debug outputs I'm getting :
Code:
MIDIDevice transmit complete
  MIDI Data: 09 90 5D 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

So a bunch of nulls..


Edit1: Aha, so I'm sending one(!) midi noteOn per buffer(512)???
 
Last edited:
Is here someone wo can tell what the above transmission means and why my USB Host speed is so slow?
 
Last edited:
Code:
// TODO: start a timer, rather than sending the buffer
// before it's full, to make best use of bandwidth
This means that, at the moment, it sends a message immediately in which case having a very large buffer is counter-productive, as you've noticed. Just use "normal" midi.

Pete
 
hey Pete,
thanks for replaying!!
I have to use the BigBuffer because the MIDI device I'm interfacing (Push2) requires a 480 Mbit/sec protocol.
Is there a way to optimize the transfer?
Greets!
 
I don't think the buffer size has anything to do with the 480Mbit/sec transfer rate.
I may be wrong, but I think the larger buffer size just allows you to send larger SYSEX messages in one large chunk.

Pete
 
if I don't assign it to a BugBuffer, the device want work at all. it doesn't get recognized by the teensy..
I've had a quetsion about this in antoher tread and Paul answered:
"Are you using the "BigBuffer" version of the MIDI device driver? This is a 480 MBit speed device, so the normal small buffer driver isn't enough."

Perhaps the core usbHost code needs some love?

Otherwise i can't explain the odd behavior..

cheers! :)
 
I've been working on a more efficient implementation of the USB host MIDI driver. It uses alternating transmit buffers and disables interrupts only for a minimal amount of time, which allows for high throughput. It also implements a timeout that's started whenever an empty transmit buffer is started to be filled. If more data is written before the timeout fires, it's all sent in a single USB packet. When the timeout fires, the data is sent even if the buffer wasn't full yet.

You can find the code here: https://github.com/tttapa/Control-Surface/blob/new-input/src/MIDI_Interfaces/USBMIDI/Teensy-host/

I tried the following test code based on the code in the OP:
Code:
#define NEW_IMPL // Comment out to try original USBHost_t36 implementation

#include <Arduino.h>
#include "USBHost_t36.h"

#ifdef NEW_IMPL
#include <Control_Surface.h>
#include <MIDI_Interfaces/USBHostMIDI_Interface.hpp>
#endif

USBHost myusb;
USBHub hub1(myusb);
USBHub hub2(myusb);

#ifdef NEW_IMPL
USBHostMIDI_Interface_BigBuffer midi1{myusb};
#else
MIDIDevice_BigBuffer midi1(myusb);
#endif

bool led_state = false;
int row;
int column;

#ifdef NEW_IMPL
void noteOn(uint8_t note, uint8_t velocity, uint8_t channel) {
  midi1.sendNoteOn({note, Channel::createChannel(channel)}, velocity);
}
#else
void noteOn(uint8_t note, uint8_t velocity, uint8_t channel) {
  midi1.sendNoteOn(note, velocity, channel);
}
#endif

void performanceTest(){
  elapsedMicros took_time;

  led_state = !led_state;

  row = 0;
  while(row<8){

    column = 0;
    while(column<8){
      noteOn(36 + (row * 8) + column, led_state, 1);
      column++;
    }
    row++;
  }

  Serial.print("took time: ");
  Serial.println(took_time);
}

void setup() {
  while(!Serial);
  Serial.begin(115200);

  delay(1500);
  Serial.println("USB Host InputFunctions example");
  delay(10);
  myusb.begin();
  delay(1000); // wait for connection
}

void loop() {
  performanceTest(); // sends 64 note messages at a time
  delay(1000); // slow down!
}

Running on a T4.1 with a T4.0 connected to its host port, I get 31,000 µs using the USBHost_t36 MIDIDevice_BigBuffer driver, and 17 µs using my own driver.
This is somewhat misleading, though, because this is just the time it takes to fill the buffer with 64 note messages. If you remove the delay, you'll get alternating results of 17 µs and 982 µs: after writing 256 note messages, both buffers are full, and you have to actually wait for them to be sent, which seems to take approximately 980 µs. Then filling the free buffer takes 128 messages before you have to wait 980 µs again.

Code:
took time: 17
took time: 17
took time: 17
took time: 17
took time: 945
took time: 17
took time: 981
took time: 17
took time: 981
took time: 17
took time: 981
took time: 17
took time: 981
took time: 17
took time: 981
took time: 17

With large buffers (8 KiB IIRC), I've seen throughput of over 30 Mbps. This is of course much lower than the 480 Mbps of the USB connection, but improving that would involve optimizing large parts of the low-level USB code for both USBHost_t36 and the USB device stack for the second Teensy, which I don't think is useful (at least not for MIDI).
Setting a pin high whenever I enqueue a data transfer and setting it low again when I get the callback for it reveals that pretty much all time is spent in the low-level USB code, not in my driver.

I've paid attention to atomic accesses and memory order in my MIDI driver, which has been verified using the ThreadSanitizer. However, I've noticed that this is not the case for some parts of the USBHost_t36 library, none of the variables shared between the main code and the USB ISRs are atomic (reading and writing aligned integers and pointers on Teensy is atomic, but without any memory ordering guarantees), and some aren't even volatile (e.g. rx_head and rx_tail in the MIDI driver). This will most likely lead to data races, which is an issue. It might work fine at low speeds, but chances are they'll manifest themselves when the rate of interrupts increases.
Unfortunately, I don't have enough experience with the low-level USB code to fix this.

A question to Paul or anyone more knowledgeable about USB than I am: how are the packet sizes “negotiated” between host and device? Let's say I have a device with buffers of 64 bytes and a host with buffers of 512 bytes, what happens when I write “queue_Data_Transfer(txpipe, buffer, 512, this);” on the host? Similarly, what happens if the device has larger buffers than the host?

Pieter
 
Running on a T4.1 with a T4.0 connected to its host port, I get 31,000 µs using the USBHost_t36 MIDIDevice_BigBuffer driver, and 17 µs using my own driver.

Can you give me the exact code you're running on the Teensy 4.0, so I can exactly recreate your test results?

Even if the other code on Teensy 4.0 is trivial, I always ask this because over and over when people have reported problems, usually I fail to recreate the same problem if I have to guess and write some of the code.

Please, give me exactly the code to run on both sides, and any other necessary details, so I quickly get to the exact same test you're running.


... and to specifically answer your question:

A question to Paul or anyone more knowledgeable about USB than I am: how are the packet sizes “negotiated” between host and device?

During enumeration the USB host reads an "interface descriptor" from the USB device. It is actually many smaller descriptors concatenated together. Among those is an "endpoint descriptor" for each pipe / endpoint. Inside each of those descriptors is a 16 bit "wMaxPacketSize" field. It's documented on page 271 of the USB 2.0 spec. Here's a direct link to just the PDF, so you can quickly turn to page 271...

https://www.pjrc.com/teensy/beta/usb20.pdf

You'll see is says "Refer to Chapter 5 for more information", but doesn't tell you where in the lengthy chapter 5 to look. For bulk transfer type, the restrictions on sizes are near the top of page 53.

(these are the page numbers as printed inside the PDF - not the page index your PDF reader uses, due to many pre-1 pages for the table of contents and other fluff at the beginning of the document)
 
@PeterP Hey, I've already found your repo ( you've made a pull request on the USBHost_t36, just about few days, right? :) )
I've tested your version and there is a difference like night and day!
All 64 LED's from my Push2 device (with a large buffer) are blinking simultaneously with no noticeable delay!!!
Can this make it into the official USBHost_t36 ?

Unfortunately I do not have any C/C++ skills, besides those I need for some simple audio-interface-workflow-arduino-modular-diy-stuff.
I hope someone with more knowledge will chime in soon and help!

hope you all are well; cheers!

my code so far (You'll need this branch https://github.com/tttapa/Control-Surface/tree/new-input):

Code:
#include <Arduino.h>
#include "USBHost_t36.h"

#include <Control_Surface.h>
#include <MIDI_Interfaces/USBHostMIDI_Interface.hpp>

USBHost myusb;
USBHub hub1(myusb);
USBHub hub2(myusb);
USBHostMIDI_Interface_BigBuffer midi1(myusb);


bool led_state = false;
int row;
int column;


volatile int count = 0;

void performanceTest(){

    elapsedMillis took_time;

    led_state = !led_state;

    row = 0;
    while(row<8){

    column = 0;
    while(column<8){

      midi1.sendNoteOn(36 + (row * 8) + column, led_state ? column+1 : 0);
      
      column++;
    }
    row++;

    }

    Serial.print("took time_: ");
    Serial.println(took_time);
  
  

}

void setup() {

  while(!Serial);
  Serial.begin(115200);

  delay(1500);
  Serial.println("USB Host InputFunctions example");
  delay(10);
  myusb.begin();
}

void loop() {

  // count
  performanceTest();
  delay(1000);

}
 
Can you give me the exact code you're running on the Teensy 4.0, so I can exactly recreate your test results?
Even if the other code on Teensy 4.0 is trivial, I always ask this because over and over when people have reported problems, usually I fail to recreate the same problem if I have to guess and write some of the code.
Please, give me exactly the code to run on both sides, and any other necessary details, so I quickly get to the exact same test you're running.
Sure, the code for the T4.0 simply discards any incoming MIDI USB data (compiled with USB type “MIDI”, of course):
Code:
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  delay(100);
  digitalWrite(LED_BUILTIN, LOW);
}

void loop() {
  (void)usb_midi_read_message();
}

During enumeration the USB host reads an "interface descriptor" from the USB device. It is actually many smaller descriptors concatenated together. Among those is an "endpoint descriptor" for each pipe / endpoint. Inside each of those descriptors is a 16 bit "wMaxPacketSize" field. It's documented on page 271 of the USB 2.0 spec. Here's a direct link to just the PDF, so you can quickly turn to page 271...

https://www.pjrc.com/teensy/beta/usb20.pdf

You'll see is says "Refer to Chapter 5 for more information", but doesn't tell you where in the lengthy chapter 5 to look. For bulk transfer type, the restrictions on sizes are near the top of page 53.

(these are the page numbers as printed inside the PDF - not the page index your PDF reader uses, due to many pre-1 pages for the table of contents and other fluff at the beginning of the document)
Thanks a lot, I think it's clear now.
If I understand correctly, this wMaxPacketSize is stored in the “tx_size” variable here https://github.com/PaulStoffregen/U...3d8dbcd9501e354e777d90f6ac88037/midi.cpp#L169.
I'll have to fix my driver tomorrow, it's always sending up to the full buffer size 512 bytes, completely ignoring tx_size :D

@PeterP Hey, I've already found your repo ( you've made a pull request on the USBHost_t36, just about few days, right? :) )
I've tested your version and there is a difference like night and day!
All 64 LED's from my Push2 device (with a large buffer) are blinking simultaneously with no noticeable delay!!!
Indeed, that pull request is mine :)

I'm glad to hear it's working! Do keep in mind that I haven't been able to test this extensively yet, I've just tested it with Teensies connected to Teensies, no “real” MIDI hardware yet, and I definitely have to fix the tx_size and rx_size bugs first.
Edit: this refers to the driver posted above, not the pull request, the changes in the pull request are fine
 
Last edited:
@paul any chance this driver is going to get into the official lib?

@PieterP how can I set up midi callbacks like in the USBHost_t36 lib ?

i.e.:
Code:
midi1.setHandleNoteOn(myNoteOn);
 
You would inherit from the MIDI_Callback class and implement its onChannelMessage method. Then attach the callback to the MIDI interface using setCallbacks.
For example:
Code:
#include <Control_Surface.h>
#include <MIDI_Interfaces/USBHostMIDI_Interface.hpp>

USBHost myusb;
USBHub hub1(myusb);
USBHub hub2(myusb);

USBHostMIDI_Interface_BigBuffer midi1{myusb};

struct MyCallbacks : MIDI_Callbacks {
  // Callback for channel messages (notes, control change, pitch bend, etc.).
  void onChannelMessage(MIDI_Interface &, ChannelMessage cm) override {
    if (cm.getMessageType() == MIDIMessageType::NOTE_ON) { // similar for NOTE_OFF
      Serial << "Note On, note: " << cm.getData1()
             << ", velocity: " << cm.getData2() 
             << ", channel: " << cm.getChannel().getOneBased() << endl;
    }
  }
} callbacks;

void setup() {
  while(!Serial);
  Serial.begin(115200);

  delay(1500);
  Serial.println("USB Host callback example");
  delay(10);
  myusb.begin();
  midi1.setCallbacks(callbacks);
  delay(1000); // wait for connection
}

void loop() {
  midi1.update();
}
This example might be useful as well: https://tttapa.github.io/Control-Surface-doc/new-input/Doxygen/d1/d5d/MIDI-Input_8ino-example.html
 
I've reproduced the problem here. Working on it now.

Part of the reason for a single MIDI message per packet is lingering bugs in the USBDriverTimer class....
 
What exactly is the problem with the USBDriverTimer class? Is there anything you could use help with?
 
Ok, I've committed a fix on github.

https://github.com/PaulStoffregen/USBHost_t36/commit/cd4c2772096d49a134114a68e954350fc9430db4

This should allow transmitting about 800K MIDI messages/sec on Teensy 3.6 or about 3.3M on Teensy 4.0 & 4.1. That's still only about 1/4 of the theoretical 480 Mbit bandwidth, but to go faster would require faster receiving by Teensy 4.0 and more buffering on the transmitting side.


What exactly is the problem with the USBDriverTimer class?

I wasn't able to reproduce it today. Maybe I'm just remembering the painful early days of struggling to get EHCI working? Long ago, it would have trouble when multiple drivers were making heavy use of differing timeouts.
 
Please let me know when/if you have tried the new code. How is the speed for your project?

I'm keeping this on my list of known issues for a few more days. A confirmation (or clear report of any trouble) would be nice, otherwise later this week I'll assume "no news is good news". ;)
 
Please let me know when/if you have tried the new code. How is the speed for your project?

yes, I've testet this version in my project. The transfer is much faster!

I can't say what and if it is a difference with PeterP's lib. ()

Seems to work fine!

Here again is my test code:

Code:
//#define NEW_IMPL // Comment out to try original USBHost_t36 implementation

#include <Arduino.h>
#include "USBHost_t36.h"

#ifdef NEW_IMPL
#include <Control_Surface.h>
#include <MIDI_Interfaces/USBHostMIDI_Interface.hpp>
#endif


USBHost myusb;
USBHub hub1(myusb);
USBHub hub2(myusb);


#ifdef NEW_IMPL
USBHostMIDI_Interface_BigBuffer midi1{myusb};
#else
MIDIDevice_BigBuffer midi1(myusb);
#endif


#ifdef NEW_IMPL
void noteOn(uint8_t note, uint8_t velocity, uint8_t channel) {
  midi1.sendNoteOn({note, Channel::createChannel(channel)}, velocity);
}
#else
void noteOn(uint8_t note, uint8_t velocity, uint8_t channel) {
  midi1.sendNoteOn(note, velocity, channel);
}
#endif

bool led_state = false;
int row;
int column;

void performanceTest(){

  elapsedMillis took_time;

  led_state = !led_state;

  row = 0;
  while(row<8){

    column = 0;
    while(column<8){
      int note = 36 + (row * 8) + column;
      int color = led_state ? 122 : 0;
      int channel = 1;
      noteOn(note, color, channel);
      column++;
    }
    row++;
  }

  Serial.print("took time: ");
  Serial.println(took_time);

}

void setup() {

  while(!Serial);
  Serial.begin(115200);

  delay(1500);
  Serial.println("USB Host InputFunctions example");
  delay(10);
  myusb.begin();
}

void loop() {

  performanceTest();
  delay(1000);

}

Thank you for your time!!! :)
 
Changing the Interrupt Threshold Control value seems to alter the timing and might expose some data races.
Occasionally, packets seem to be dropped when sending very long SysEx messages using my driver. I added some print statements to make sure it wasn't my code that was dropping them, and I can see that I'm calling queue_Data_Transfer with the correct data, and I do get a callback for it, but the packet never arrives on the other side.

I don't have too much time to look into it right now, I'll have to study the datasheet to understand what's going on in ehci.cpp, but it might be good to be aware of it when other drivers might fail as well, since it's very timing sensitive, even adding a Serial.print() of a single character in certain places causes the problem to disappear.
This might be related to the issues you were seeing with the USBDriverTimer class?

I'll see if I can write a simpler reproducible example.



Ok, after writing that, I did some further testing, and the problem might be the receiving Teensy 4.0, not necessarily the USB Host code on the T4.1.

I've tried the following code on a Teensy 4.0 with USB mode MIDI, connected to my laptop (Dell XPS15 9560, Ubuntu 20.04 5.4.0-65-lowlatency #73-Ubuntu SMP PREEMPT), using Teensyduino 1.53:
Code:
#define BLINK 0

void setup() {
#if BLINK == 1
  pinMode(LED_BUILTIN, OUTPUT);
#endif
}

void loop() {
#if BLINK == 1
  static elapsedMillis m;
  if (m >= 500) {
    m = 0;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
#endif
  if (uint32_t r = usb_midi_read_message())
    usb_midi_write_packed(r);
}
After uploading the code, use “amidi -l” to list the MIDI devices and find the port number of the Teensy, if it's the only MIDI device connected to your computer, it will be "hw:1,0,0".
Then run the following shell script:
Code:
#!/usr/bin/env bash

cd "$(dirname "${BASH_SOURCE}")"

oport=${1:-hw:1,0,0}
iport=${2:-${oport}}

chg="${3:-MIDI-long-challenge.syx}"
rsp="/tmp/response.syx"

amidi -p "${iport}" -d -t 1 > /dev/null # flush buffer before starting test
amidi -p "${iport}" -r "$rsp" -t 1 & # receive response
/usr/bin/time -f "Duration: %E" amidi -p "${oport}" -s "$chg" # send challenge
wait
wc -c "${chg}"
wc -c "${rsp}"
sha256sum -b "${chg}"
sha256sum -b "${rsp}"
cmp "${chg}" "${rsp}"
It sends a long file with MIDI messages to the Teensy, reads it back from the Teensy and compares the two. I've pushed all code and the MIDI file to GitHub because it was too large to upload to the forum:
https://github.com/tttapa/Teensy-MIDI-stresstest
Run the script using: ./MIDI-stresstest.sh hw:1,0,0 hw:1,0,0 MIDI-long-challenge.syx, replacing the port number if necessary.

With #define BLINK 1, I get output similar to
Code:
./MIDI-stresstest.sh hw:1,0,0 hw:1,0,0 MIDI-long-challenge.syx
Duration: 0:00.57

2535844 bytes read
2617684 MIDI-long-challenge.syx
2535844 /tmp/response.syx
14558f9f8340ee40c05862ca78f1004c98a2f7b9bd98ad077b6b441f9495a284 *MIDI-long-challenge.syx
4d79866a4cdffe53f51075840ffd241180341df8e365bf8fa35e428715343e72 */tmp/response.syx
MIDI-long-challenge.syx /tmp/response.syx differ: byte 38919, line 336
This means that some packets or bytes are dropped, but most of them seem to arrive eventually.

However, with #define BLINK 0, it's even worse, and I get:
Code:
./MIDI-stresstest.sh hw:1,0,0 hw:1,0,0 MIDI-long-challenge.syx
Duration: 0:00.32

36515 bytes read
2617684 MIDI-long-challenge.syx
36515 /tmp/response.syx
14558f9f8340ee40c05862ca78f1004c98a2f7b9bd98ad077b6b441f9495a284 *MIDI-long-challenge.syx
80a356e00e50b03c0e7ca08a33e25f53b5842e2edc587b065d4373c608c90a7a */tmp/response.syx
MIDI-long-challenge.syx /tmp/response.syx differ: byte 22388, line 196
It drops 99% of the data! After the first test with BLINK 0, the Teensy seems to lock up, and it no longer sends any MIDI data back.

If I add a delayMicroseconds(10) in the loop, all data arrives without issues (but much, much slower, of course):
Code:
./MIDI-stresstest.sh hw:1,0,0 hw:1,0,0 MIDI-long-challenge.syx
Duration: 0:09.13

2617684 bytes read
2617684 MIDI-long-challenge.syx
2617684 /tmp/response.syx
14558f9f8340ee40c05862ca78f1004c98a2f7b9bd98ad077b6b441f9495a284 *MIDI-long-challenge.syx
14558f9f8340ee40c05862ca78f1004c98a2f7b9bd98ad077b6b441f9495a284 */tmp/response.syx

Given the fact that adding the blinking LED or the delay changes the behavior dramatically, I'm afraid it might be a data race in the (MIDI) USB device code. Any idea what could cause this?
 
Back
Top