Skip to content

bugfix: midi thru from udp to usb triggers an assert()#998

Merged
probonopd merged 3 commits intoprobonopd:mainfrom
penfold42:Thru-udp-usb
Dec 4, 2025
Merged

bugfix: midi thru from udp to usb triggers an assert()#998
probonopd merged 3 commits intoprobonopd:mainfrom
penfold42:Thru-udp-usb

Conversation

@penfold42
Copy link
Contributor

@penfold42 penfold42 commented Nov 25, 2025

usbmidi.cpp(91): assertion failed: Cable <= 15

usbmidi supports 16 virtual cables (nCable in the code).
Midi message received from udp sets this to 24 which is invalid.
This replaces 24 with 0 but perhaps something configurable or dynamic could be done.

Summary by Sourcery

Bug Fixes:

  • Normalize UDP-originated MIDI messages that use an out-of-range virtual cable number so they map to a valid USB MIDI cable and avoid assertion failures.

usbmidi supports 16 virtual cables (nCable in the code)
any midi message received from udp sets this to 24 which is invalid.
This replaces 24 with 0
@sourcery-ai
Copy link

sourcery-ai bot commented Nov 25, 2025

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Fixes an assertion failure when forwarding MIDI messages received over UDP to USB by normalizing an out-of-range virtual cable number to a valid USB MIDI cable index.

Sequence diagram for UDP to USB MIDI forwarding with cable normalization

sequenceDiagram
    participant UdpSource
    participant MidiRouter
    participant CMIDIKeyboard
    participant UsbMidiDriver

    UdpSource->>MidiRouter: MIDI message (nCable 24)
    MidiRouter->>CMIDIKeyboard: Send(pMessage, nLength, nCable 24)
    CMIDIKeyboard->>CMIDIKeyboard: Check nCable
    alt nCable == 24
        CMIDIKeyboard->>CMIDIKeyboard: Set Entry.nCable = 0
    else nCable != 24
        CMIDIKeyboard->>CMIDIKeyboard: Set Entry.nCable = nCable
    end
    CMIDIKeyboard->>UsbMidiDriver: Enqueue USB MIDI message (Entry.nCable <= 15)
    UsbMidiDriver->>UsbMidiDriver: Assert Cable <= 15 (no failure)
    UsbMidiDriver-->>UdpSource: MIDI successfully forwarded via USB
Loading

Class diagram for CMIDIKeyboard Send logic with cable normalization

classDiagram
    class CMIDIKeyboard {
        +void Send(u8* pMessage, size_t nLength, unsigned nCable)
    }

    class TSendQueueEntry {
        +u8* pMessage
        +size_t nLength
        +unsigned nCable
    }

    CMIDIKeyboard "1" --> "many" TSendQueueEntry : enqueues

    class SendImplementation {
        +void Send(u8* pMessage, size_t nLength, unsigned nCable)
        +void NormalizeCable(unsigned nCable)
    }

    CMIDIKeyboard ..> SendImplementation : implements logic

    class NormalizeCableLogic {
        +unsigned NormalizeCable(unsigned nCable)
    }

    SendImplementation ..> NormalizeCableLogic : uses

    %% Pseudocode representation of the updated logic
    class NormalizeCableLogicDetails {
        +unsigned NormalizeCable(unsigned nCable)
        unsigned nCableParam
        unsigned resultCable
    }

    NormalizeCableLogicDetails : NormalizeCable(unsigned nCable)
    NormalizeCableLogicDetails : if nCable == 24 then resultCable = 0
    NormalizeCableLogicDetails : else resultCable = nCable

    NormalizeCableLogic <|-- NormalizeCableLogicDetails
Loading

File-Level Changes

Change Details Files
Normalize invalid UDP-originated MIDI cable index to a valid USB MIDI cable value to prevent assertion failures.
  • Intercept the cable index when queuing MIDI messages to send
  • Detect the special UDP-originated cable value (24) and map it to cable 0
  • Fallback to using the original cable index when it is not the special UDP value
src/midikeyboard.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Instead of hard-coding 24, consider defining a named constant or reusing an existing enum/value for the UDP virtual cable so the intent is clearer and less error-prone if it changes elsewhere.
  • You might want to clamp or validate nCable more generally (e.g., if nCable > 15, map to 0 or a default) rather than only handling the special case of 24, to avoid similar asserts from other invalid values.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Instead of hard-coding 24, consider defining a named constant or reusing an existing enum/value for the UDP virtual cable so the intent is clearer and less error-prone if it changes elsewhere.
- You might want to clamp or validate nCable more generally (e.g., if nCable > 15, map to 0 or a default) rather than only handling the special case of 24, to avoid similar asserts from other invalid values.

## Individual Comments

### Comment 1
<location> `src/midikeyboard.cpp:79-91` </location>
<code_context>
 	Entry.pMessage = new u8[nLength];
 	Entry.nLength = nLength;
-	Entry.nCable = nCable;
+	// If the midi message originated from udp, nCable is VIRTUALCABLE (24)
+	// This violates usb midi as its > 15 and will trigger an assert
+	// maybe make this configurable or use the 1st usb rx message nCable
</code_context>

<issue_to_address>
**suggestion:** Avoid hardcoding the magic value 24; rely on a named constant instead.

Since the code and comment both rely on the value 24, it would be clearer and safer to use the named constant (e.g., VIRTUALCABLE) instead of the literal. That way, if the underlying value changes, this comparison stays correct and the intent remains obvious.

```suggestion
	Entry.pMessage = new u8[nLength];
	Entry.nLength = nLength;
	// If the midi message originated from udp, nCable is VIRTUALCABLE
	// This violates usb midi as its > 15 and will trigger an assert
	// maybe make this configurable or use the 1st usb rx message nCable
	if (nCable == VIRTUALCABLE)
	{
		Entry.nCable = 0;
	}
	else
	{
		Entry.nCable = nCable;
	}
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +79 to +91
Entry.pMessage = new u8[nLength];
Entry.nLength = nLength;
Entry.nCable = nCable;
// If the midi message originated from udp, nCable is VIRTUALCABLE (24)
// This violates usb midi as its > 15 and will trigger an assert
// maybe make this configurable or use the 1st usb rx message nCable
if (nCable == 24)
{
Entry.nCable = 0;
}
else
{
Entry.nCable = nCable;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Avoid hardcoding the magic value 24; rely on a named constant instead.

Since the code and comment both rely on the value 24, it would be clearer and safer to use the named constant (e.g., VIRTUALCABLE) instead of the literal. That way, if the underlying value changes, this comparison stays correct and the intent remains obvious.

Suggested change
Entry.pMessage = new u8[nLength];
Entry.nLength = nLength;
Entry.nCable = nCable;
// If the midi message originated from udp, nCable is VIRTUALCABLE (24)
// This violates usb midi as its > 15 and will trigger an assert
// maybe make this configurable or use the 1st usb rx message nCable
if (nCable == 24)
{
Entry.nCable = 0;
}
else
{
Entry.nCable = nCable;
}
Entry.pMessage = new u8[nLength];
Entry.nLength = nLength;
// If the midi message originated from udp, nCable is VIRTUALCABLE
// This violates usb midi as its > 15 and will trigger an assert
// maybe make this configurable or use the 1st usb rx message nCable
if (nCable == VIRTUALCABLE)
{
Entry.nCable = 0;
}
else
{
Entry.nCable = nCable;
}

@github-actions
Copy link

Build for testing:
MiniDexed_1305_2025-11-25-290313d_32bit
MiniDexed_1305_2025-11-25-290313d_64bit
Use at your own risk.

@soyersoyer
Copy link
Contributor

I think you should simply change the VIRTUALCABLE define to 0 in udpmididevice.cpp.
If more cables are needed, I think more UDPMidiDevices should be created with a cable parameter.

This reverts commit d300d2e.

For a much simpler solution...
@penfold42
Copy link
Contributor Author

I think you should simply change the VIRTUALCABLE define to 0 in udpmididevice.cpp. If more cables are needed, I think more UDPMidiDevices should be created with a cable parameter.

much simpler :)
will push an update after testing (tomorrow)

    usbmidi supports 16 virtual cables (nCable in the code)
    any midi message received from udp sets this to 24 which is invalid.
    This replaces 24 with 0
@github-actions
Copy link

Build for testing:
MiniDexed_1306_2025-11-26-6f13fc0_32bit
MiniDexed_1306_2025-11-26-6f13fc0_64bit
Use at your own risk.

@probonopd
Copy link
Owner

@penfold42 thank you very much for this bugfix. Does the latest build work for you as intended? Do you think it is ready for being merged?
@soyersoyer thank you very much for your review.

@penfold42
Copy link
Contributor Author

@penfold42 thank you very much for this bugfix. Does the latest build work for you as intended?

The build from my dev environment does - do you think I should also test the github actions build?

@probonopd
Copy link
Owner

Yes, that'd be the their purpose. Thanks!

@penfold42
Copy link
Contributor Author

Yes, that'd be the their purpose. Thanks!

Tested that build on my pi3.
VMPK->rtpmidid->minidexed->Launchkey mini mk3
No crashes

VMPK->rtpmidid->minidexed->Arduino USB midi cable->homebrew WiFi midi box->fluidsynth
No crashes and midi data makes its way around.

@probonopd probonopd merged commit bc7803b into probonopd:main Dec 4, 2025
4 checks passed
@probonopd
Copy link
Owner

Thanks @penfold42

@penfold42 penfold42 deleted the Thru-udp-usb branch December 4, 2025 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants