MIDI Nibble Mystery: Decoding USB Data Duplication
Hey everyone! 👋 I'm diving into a fascinating issue I encountered while adapting Embassy's USB MIDI examples for an STM32F7 microcontroller. I was trying to receive MIDI data over USB, but I ran into a snag: the high nibble of the MIDI status byte was being duplicated in the received data. Let's break down what happened, how I investigated it, and the questions I have for the Embassy community.
The Curious Case of the Duplicated Nibble
So, I adapted a couple of Embassy examples to get MIDI data flowing over USB on my STM32F7. When I sent a Middle C note from my laptop, I saw this output:
118.646118 [INFO ] data: [9, 90, 3c, 40] (miditest miditest/src/main.rs:158)
118.731353 [INFO ] data: [8, 80, 3c, 40] (miditest miditest/src/main.rs:158)
I repeated this a bunch of times, using different MIDI sources like Ardour (a DAW), a virtual keyboard, and even manually entering hex values. I'm pretty confident that the issue isn't with my MIDI sources because they work perfectly with my other commercial synths.
Looking at the last three bytes of the messages, everything seemed normal:
Status byte (hex, dec) | Note (hex, dec) | Velocity (hex, dec) |
---|---|---|
Channel 1 Note On (0x90, 144) | C4 (0x3c, 60) | (0x40, 64) |
Channel 1 Note Off (0x80, 128) | C4 (0x3c, 60) | (0x40, 64) |
But the first byte in data
was a head-scratcher. After digging deeper, I realized that this first value is always the four most significant bits of the status byte duplicated. In MIDI, the status byte is the first byte of a message, indicating the message type. It's the only byte in a MIDI message that can have a 1 in the MSB. The subsequent data bytes (like note and velocity) have a 0 in the MSB.
Now, I'm not an Embassy USB code expert, but from what I can tell, MidiClass
seems more focused on defining the device interfaces. Its read_packet
method hands off the real data handling to the USB driver, which in my case is embassy-usb-synopsys-otg
.
Diving Deeper into the MIDI Mystery
Let's discuss some aspects of this issue in more detail:
When working with MIDI over USB, the status byte is crucial. This byte, identifiable by its Most Significant Bit (MSB) being set to 1, dictates the type of MIDI message being transmitted. Think of it as the instruction manual for the following data bytes. For example, a status byte might indicate a Note On message (a key being pressed) or a Note Off message (a key being released). The subsequent bytes, which have their MSB set to 0, provide the specifics, such as the note's pitch and the velocity (how hard the key was struck).
In my case, the duplication of the high nibble of the status byte threw a wrench in the works. Instead of receiving the expected status byte directly, I was getting its most significant four bits prepended to the message. This unexpected behavior makes interpreting the MIDI messages correctly quite difficult.
This is where understanding the flow of data within the Embassy framework becomes essential. The MidiClass
, as I understand it, primarily sets up the USB interfaces for MIDI communication. It's responsible for declaring and describing these interfaces to the operating system, allowing the device to be recognized as a MIDI device. However, the actual reading of data packets is delegated to the USB driver. In my setup, this driver is embassy-usb-synopsys-otg
.
The read_packet
method within MidiClass
is the bridge between the MIDI interface and the USB driver. When a MIDI message arrives, this method calls upon the driver to fetch the raw data. This is where the mystery deepens: is the duplication occurring within the driver itself, or somewhere even lower in the USB stack?
To further investigate, I started tracing the data flow. The USB driver, in this case, embassy-usb-synopsys-otg
, is responsible for interacting directly with the USB hardware. It handles the low-level details of receiving USB packets and making them available to the higher-level MIDI class. If the nibble duplication is happening at this stage, it suggests an issue with how the driver is reading the data from the USB endpoint.
However, the problem could also lie deeper within the USB stack. There might be an issue with how the endpoint is configured, or perhaps a misunderstanding in how the data is being interpreted at a lower level. This is where having a solid grasp of USB protocols and the specific hardware being used becomes invaluable.
To get to the bottom of this, I've been diving into the datasheets for the STM32F7 microcontroller and the Synopsys OTG controller it uses. Understanding the intricacies of the USB hardware and its interaction with the driver is crucial for pinpointing the source of the problem.
Questions for the Embassy Community
This leads me to a few questions for the Embassy community:
-
Is this behavior intentional? It seems unlikely, but maybe there's some detail I'm missing about how generalized On-The-Go USB drivers work. If it is intentional, then the fix would likely involve adding logic to
MidiClass
to process the data further, rather than modifying the driver itself. But this seems counterintuitive. Guys, what do you think? -
Where's the bug? I'm not sure if the bug (if it's not in my own code, haha) is in
embassy-usb-synopsys-otg
or somewhere deeper in the USB stack. I've had a hard time tracing the code beyond that driver. Any tips on debugging this further?- Would it be helpful if I posted the code I adapted? It's mostly the same as the examples, but it might help. However, it might only be useful for someone with a similar chip. 😅
- Assuming the bug isn't in my code, how is the
out_size
in the endpoint state getting set to 4? And how is theout_buffer
populated? I'm struggling to follow that part of the code flow. 🤯
-
What's the plan for
MidiClass
? Because theNote On
andNote Off
messages were showing up separately, I initially thoughtMidiClass
was doing more data processing – like grouping bytes into complete MIDI messages. But then I sent two simultaneous notes, and all the bytes arrived together:[9, 90, 3c, 40, 9, 90, 3a, 40]
. See the duplicated nibble in bothNote On
events? This makes it even harder to figure out what to do with that extra0x9
!So, is grouping bytes into messages something that should be handled in
MidiClass
, or should that happen downstream? I'm curious about the intended scope here. 🤔
The Bigger Picture: MIDI Message Grouping and Parsing
Let's talk more about grouping MIDI messages. My initial assumption was that MidiClass
might be involved in this process. The reason? The separate arrival of Note On
and Note Off
messages suggested that the class was identifying and grouping the bytes belonging to a single MIDI message. I envisioned it working something like this: the class would detect the Note On
status byte, recognize that it's the start of a new message, and then collect the subsequent data bytes (note and velocity) until it encounters the next status byte (in this case, Note Off
).
However, the behavior I observed when sending simultaneous notes threw a wrench in this theory. The fact that all the bytes for both notes arrived in a single chunk indicated that the grouping wasn't happening at the MidiClass
level. Instead, it seemed more like the timing of the MIDI events was the primary factor in how the bytes were being delivered.
This raised the question: where should MIDI message grouping occur? Is it the responsibility of the MidiClass
, or should this be handled further downstream in the application logic? This is where the concept of MIDI parsing comes into play. Parsing involves taking the raw stream of MIDI bytes and organizing them into meaningful messages. This includes identifying status bytes, extracting data bytes, and constructing complete MIDI events.
There are a few ways to approach MIDI parsing. One option is to do it within the MidiClass
itself. This would keep the parsing logic close to the USB driver and provide a higher-level interface for the application to consume. However, this could also make the MidiClass
more complex and less focused on its core responsibility of managing the USB interface.
Another option is to handle parsing downstream in the application. This would keep the MidiClass
leaner and allow for more flexibility in how the MIDI messages are processed. The application could implement its own parsing logic, or it could use a dedicated MIDI parsing library.
The decision of where to handle MIDI parsing depends on several factors, including the complexity of the application, the desired level of abstraction, and the performance requirements. In my case, I'm leaning towards handling parsing downstream, as it seems to offer more flexibility and control.
Next Steps: Debugging and Community Collaboration
So, where do I go from here? Well, the first step is to continue digging into the code and trying to pinpoint the source of the nibble duplication. I'll be focusing on the embassy-usb-synopsys-otg
driver and the lower levels of the USB stack. I'll also be experimenting with different USB configurations and trying to isolate the issue.
I'm also eager to hear from the Embassy community. Your insights and experiences could be invaluable in solving this puzzle. If you've worked with USB MIDI in Embassy before, or if you have any thoughts on the nibble duplication issue, please chime in! Let's work together to get this sorted out.
Thanks for reading, and I look forward to your thoughts and suggestions! 🙏