I've been trying to figure out a slick way for FluidPatcher to listen to incoming MIDI messages so I can map things like increment/decrement patch and select-patch controls. The obvious problem is that FluidSynth already has the MIDI input port, and I wasn't sure there was a good way for FluidPatcher to listen to messages going into a port that was already connected to FluidSynth. I tried Mido, but the latency was too high, and while python-rtmidi looked promising, it didn't seem to see the FluidSynth MIDI port on Windows, and I didn't want to break compatibility there.

While poking around in the FluidSynth code trying to figure out another approach, I stumbled across a much better solution: FluidSynth can call a custom Python function to handle incoming MIDI events. That means FluidPatcher can get a look at the MIDI message before passing it along to the synth, and can do whatever it needs with it in the meantime.

This turns out to be considerably more powerful than just solving the original problem. Router rules can now translate one kind of MIDI message into another, so if I want aftertouch to control filter cutoff, for example, that's straightforward. I can also send MIDI events to callbacks that handle things like changing patches or tweaking reverb and effect settings, without having to maintain a bunch of CCLinks and constantly poll CC values.

Best of all, I'm not seeing any noticeable increase in latency from the Python callback, so this is going into the next update.

It does mean a bit of a breaking change to the bank format. The cclinks keyword is going away, and router rules will get an optional target parameter. The target can be another FluidSynth rule type, fluidsetting, effect, or an arbitrary name handled by the particular FluidPatcher implementation, with additional parameters passed along as needed. I'll also try to make the transition painless by translating existing cclinks entries into the appropriate router rules automatically.

So what started as "I wish FluidPatcher could listen for MIDI messages" turns out to be a much more general and useful MIDI-routing system. That's usually a pretty good sign that I'm headed in the right direction.