Skip to content

oscmix: fix hotplug reliability bugs and improve oscmix-gtk.sh#20

Draft
rhaist wants to merge 4 commits into
huddx01:mainfrom
rhaist:hotplug-fixes
Draft

oscmix: fix hotplug reliability bugs and improve oscmix-gtk.sh#20
rhaist wants to merge 4 commits into
huddx01:mainfrom
rhaist:hotplug-fixes

Conversation

@rhaist
Copy link
Copy Markdown

@rhaist rhaist commented Apr 14, 2026

Follow-up to #18 with reliability fixes found during testing.

Changes

main.c

  • writeosc(): retry once on ECONNREFUSED — on Linux, a connected UDP socket stores the ICMP port-unreachable from the initial /device/id send (before the GTK frontend has bound its socket) and returns it on the next write, dropping the frontend's /refresh response and leaving the UI stuck on the scanning page.
  • midiread(): lift the SysEx reassembly buffer to file scope (midibuf/midireset()) so disconnect transitions can clear any partial packet in flight — previously stale bytes were concatenated with fresh bytes on reconnect, producing corrupt SysEx.
  • openmidi(): change inner-loop error paths from return -1 to break so a failure on one card doesn't abort the whole scan; guard close(ctlfd) against double-close after the MIDI fd path.
  • Promote logged_race to file scope and reset it on each online transition so the driver-not-yet-bound warning fires again after a fast replug cycle.

coremidiio.c

  • Guard reconnect endpoint matching with g_src_name[0]/g_dst_name[0] checks so unrelated device events don't trigger a spurious reconnect in virtual-port mode (no -p flag).
  • Zero-initialise ctx[2] before MIDIClientCreate so an early hotplug notification can't compare garbage endpoint values and trigger a spurious disconnect.

oscmix.c

  • Remove accidental debug fprintf left in setoutputloopback().
  • Remove two stray debug lines from oscsendenum().

oscmix-gtk.sh

  • Kill any running oscmix whose on-disk binary has been replaced (i.e. /proc/<pid>/exe ends with (deleted)) before starting the backend, so the freshly-built binary is always used.
  • Drop exec before oscmix-gtk so the shell stays alive and the EXIT trap fires when the window is closed, killing the backend.

rhaist added 2 commits April 14, 2026 18:39
Kill any oscmix process using a deleted (rebuilt-since-start) binary or
a different path before starting the backend, so the freshly-built binary
is always used.

Drop `exec` before oscmix-gtk so the shell stays alive while GTK runs;
the EXIT trap can then kill the backend when the window is closed.
main.c:
- Lift SysEx reassembly buffer (midibuf/midibufend) to file scope with
  midireset() so disconnect transitions in the poll loop can clear any
  partial packet in flight, preventing stale bytes from being
  concatenated with fresh bytes on reconnect (corrupt SysEx).
- writeosc(): retry once on ECONNREFUSED — Linux stores the ICMP port-
  unreachable error from the startup /device/id send (before the GTK
  frontend has bound its socket) and reports it on the next write,
  dropping the frontend's /refresh response and leaving the UI stuck on
  the scanning page forever.
- openmidi(): change inner-loop error paths from return -1 to break so
  a failure on one card does not abort the whole scan; promote logged_race
  to file scope and reset it on each online transition so the driver-not-
  yet-bound warning fires again after a fast replug cycle.

coremidiio.c:
- Guard reconnect matching with g_src_name[0]/g_dst_name[0] checks so
  that endpoints are not silently matched when -p was not passed (virtual
  port mode), which previously caused spurious reconnects on unrelated
  device events.
- Zero-initialise ctx[2] before MIDIClientCreate so that an early hotplug
  notification cannot compare garbage endpoint values and trigger a
  spurious disconnect.

oscmix.c:
- Remove accidental debug fprintf in setoutputloopback().
- Remove two stray debug lines from oscsendenum().
- Reformat setreg() debug block to standard style.
@huddx01
Copy link
Copy Markdown
Owner

huddx01 commented Apr 14, 2026

Please test on Linux and macOS first.
Well, this PR seems not to handle/fix the issues from #18.

Keep the following im mind, please:

  • both systems (Linux/macOS) should be flawlessly compatible and tested
  • Multiple devices can be plugged in at the same time
  • for example an UCX II, UFX III and an 802
  • or even two UCX II at the same time (so it makes more sense to relate on the deviceuid (/device/uid path) instead on (/device/id)) as the latter one is not unique

Just a few examples:

However, thanks a lot again, but I need some time to verify/check/test all of this

rhaist added 2 commits April 16, 2026 16:13
…ffer

openmidi() (Linux self-scan):
- Previously pinned subdevice 1 via SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE
  and validated info.subdevice != 1, which only matched UCX II and broke every
  device whose host-control port is not index 1 (UFX III has 4 subdevices, so
  control is at subdev 3; 802 varies by mode; etc.).
- Now calls SNDRV_CTL_IOCTL_RAWMIDI_INFO on the ctlfd first to read
  subdevices_count for device 0, then targets subdevices_count - 1 — matching
  the "last port" convention documented in the README (alsarawio 0,0,3 for
  UFX III, etc.).

handleosc() (osc dispatch):
- Previously rejected any datagram whose address was "#bundle" as
  "invalid osc address '#bundle'", so every bundle from a frontend or from
  an external controller was silently dropped. This is what caused the
  maintainer's "larger osc #bundles are not recognized correctly in oscmix"
  observation on UFX III (94 I/O channels make bundle-sized refresh replies
  more common).
- Split into handleoscmsg (single message, original logic) and
  handleoscpacket (dispatches on "#bundle\0" header and recurses over the
  size-prefixed sub-packets per OSC 1.0 §3). Bundles may nest.

oscread() recv buffer:
- Raised from 8 KB to 64 KB. read() on a UDP socket returns one datagram and
  silently drops any bytes beyond the supplied buffer; 8 KB sat close to
  oscmix's own send-bundle size and would truncate larger inbound bundles.

Unrelated compile fix:
- main.c had two duplicate declarations of recvport/sendport + sockopen
  calls left over from an earlier edit; the second pair broke the build on
  macOS. Removed the stale second block.
On macOS oscmix cannot self-open MIDI — CoreMIDI is only reachable through
the coremidiio helper. Previously the script invoked ./oscmix directly on
both platforms, which on macOS produced:

    error: MIDI file descriptors 6 and 7 are not open.

Split start_backend into per-OS variants. On macOS:

1. Run coremidiio -l and pick the last source whose name starts with
   "Fireface" — same "last port" convention documented in the README.
2. Export MIDIPORT to the full endpoint display name (e.g. "Fireface UCX II
   (24217032) Port 2") so oscmix can match its -p / $MIDIPORT path. Do
   this in the shell rather than teaching coremidiio to setenv it itself:
   any CoreMIDI call in the parent process opens a midiserver Mach-port
   connection that does not survive the fork() inside coremidiio's
   spawn(), which in turn causes MIDIClientCreate in the post-fork branch
   to fail with OSStatus -304.
3. Launch coremidiio -f 6,7 -p <index> oscmix.

On Linux the previous behaviour is preserved (oscmix self-scans).

coremidiio.c: add an inline comment explaining the fork/CoreMIDI hazard
so the ordering above does not get "fixed" back into coremidiio and
re-broken.
@huddx01
Copy link
Copy Markdown
Owner

huddx01 commented Apr 16, 2026

Hello again,

This looks like you've committed the output of AI generated content again.

As an example: Just a first look into: e09cf18#diff-8086a26a9daa720011b33b93a1c2af675c747805b42544f6a63cf5b5561dc2b2R1774 (Lines 1774)
This is not necessary and just boils the code.

Another one: e09cf18#diff-a0cb465674c1b01a07d361f25a0ef2b0214b7dfe9412b7777f89add956da10ecR82 (Line 82)
Your AI agent did misunderstood how the 802 is set up...

So I need to ask clearly again:

  • Did you personally test this (at least with your device) on a gtk ui build on linux and macOS???

Please keep the code clean and review the AI code first.

PS: Basically a good idea/concept but it takes too much time to fix the AI generated content at all. Currently I am tending to revert the changes of #18, as the current state breaks the generic compatibility concept. Sorry.

@rhaist
Copy link
Copy Markdown
Author

rhaist commented Apr 17, 2026

Good morning - those are the first batch of changes I did with a testing and debug session with my UCX II on MacOS yesterday. I wanted to do another session today focused on Debian. Without haveing access to the other supported hardwares it's tough for me to verify all angles. We either have to work together on this or it's also fair if you don't want that in your code base.

Comment thread oscmix.c
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Why should we split the origin handleosc function into finally three functions here?
In fact, handleosc is a "dumb stub" here now, calls handleoscpacket, which calls handleoscmsg... Or did I misunderstood sth?
This makes code unnecessary complicated to read/maintain further at all.
According my experience - thats a typical indication of AI generated code. AI doesn't get the overall context of larger projects...
This is exactly the reason I meant, why AI code should be reviewed/understood first. Then optimized and adapted to fit the overall projects context.

@huddx01
Copy link
Copy Markdown
Owner

huddx01 commented Apr 18, 2026

Good morning - those are the first batch of changes I did with a testing and debug session with my UCX II on MacOS yesterday. I wanted to do another session today focused on Debian. Without haveing access to the other supported hardwares it's tough for me to verify all angles. We either have to work together on this or it's also fair if you don't want that in your code base.

Robert, I just own two different devices - its not the point of having access to all of the supported it.
But its a important to understand/review the code before merging or committing.
Thats why I came back to you, asked questions and mention all the details I dont understand, mention what does not work after tests, and share the things I noticed - transparently.

Of course - I know - its not easy to get behind all the details and the ideas of this project. Its a huge beast!
To be honest, I needed more than ~1 year to understand and get behind what @michaelforney had in mind by sharing his great idea. In fact, I just jumped in and try to keep his idea making accessible for others with the generic aspect in mind.

Of course I'd like to work together with you.
I am very happy about your contributions to this project!
Please, nothing personal. Correct me if I am wrong. But my impression is somehow like a copy/paste of my comments/results into an AI assistant and copy/paste the results back...

However, maybe it might make sense if I create sth like a "test" branch, and we'll use this as sth like a preliminary playground for this? Where I could merge the PRs without a very detailed look into the code... After a while of testing etc, and if we're both ok with this we could merge it into main afterwards.
What do you think?

@rhaist rhaist marked this pull request as draft April 18, 2026 10:07
@rhaist
Copy link
Copy Markdown
Author

rhaist commented Apr 18, 2026

No hard feelings - I get it's frustrating as a maintainer right now figuring out if it's bad human or bad AI code.

As C is not my main language I use AI to learn and improve my productivity for an unknown codebase like this but I agree this should not lead to overengineered refactors - or even worse - breaking funtionality.

I converted this to a Draft and will do so in the future so we can QA and test before it meets your standard.

@huddx01
Copy link
Copy Markdown
Owner

huddx01 commented Apr 19, 2026

Thank you for your understanding. I really appreciate that.

As C is not my main language I use AI to learn and improve my productivity for an unknown codebase like this but I agree this should not lead to overengineered refactors - or even worse - breaking funtionality.

I am far from being a "C professional" too (hm, just thinking: what's the definition of a professional?)…
Well, I also use AI for learning, getting new impressions, and exploring ideas. AI is also great for saving time, especially when it comes to typing recurring code routines. And admittedly, sometimes out of laziness too ;)

In the meantime, I set up the "playground" branch I mentioned:

Branch: hotplug see: https://github.com/huddx01/oscmix/tree/hotplug

This branch contains the full content of the original PR #18 rebased onto the current dev (so you get my latest changes as a base too). main and dev have been reverted back to pre-PR-18 state for now (again: nothing personal, Robert - its just meant to keep a working state, others may clone from, etc...).

For PR #20, could you:

  1. Rebase your hotplug-fixes branch onto hotplug instead of main
  2. Update the PR target to huddx01:hotplug

That way we can iterate freely on that branch. Squashing, rewriting, experimenting and without touching main or dev.

It was some fiddeling with git to achieve this, I hope, I didnt oversee sth and didnt mess sth up.

Maybe a note on how my branches are set up (always open for recommendations if sth could be optimized)

  • dev is where I push my own current work, after testing as thoroughly as I can on macOS and Linux, in combination with the Web UI, the GTK UI (and the upcoming Qt UI). The CI triggers for the nightly release builds and the Web UI deployment are tied to this branch. So, anything that lands on dev automatically becomes a nightly. The Qt UI is built on another (private)repo that also tracks dev, and gets bundled into the nightly releases from there.
  • main is the "stable-ish" branch. Once changes on dev have survived a few days of testing without complaints, I merge dev into main.

So the path for hotplug once we're both happy with it would be: hotplug -> dev (nightly exposure, real-world testing) -> main (once stable).

PS: I already got some insights why it didnt work and how i fixed it (will push it to hotplug soon).
Additionally, maybe a tip how I test "multiple devices of same type" although I just own one 802 for example: I use coremidiio on mac to start an virtual midi port (via -n arg). Sth like coremidiio -n "Fireface 802 (12345678) Port 2" and additionaly to "cross-pipe" the "real" 802s MIDI with it. This works even through a ssh session...
For this, I just expanded alsaseqio.c to add a similar functionaility. Needs to be tested more detailed, but basically works here on debian. (alsarawio doesn't make sense for this, as its intended to communicate with lower latency by directly hooking the raw midi ports on linux)

You can always ask if you stuck somewhere or I could help out with sth, Robert. Ill do my best to answer as quick as I can.

Looking forward to getting all of this merged properly - as its a really useful idea for all.

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.

2 participants