I agree with Spogg, my first port of call would be to use Ruby's built in handling of MIDI events. As well as quite likely messing up the timing, splitting the Midi events externally is likely to lead to other problems. I haven't had time to completely analyse your code, but here are a few, I hope constructive, comments...
Firstly, despite what you might expect from the friendly "changed" trigger output, all of the integer outputs from the Midi splitters will be sending events to the Ruby primitive - each of the four in turn, followed by 'changed'. So, with both Midi inputs set to the same source (as saved), every incoming Midi event will be calling the Ruby 'event' method nine times! Because the inputs are updated one at a time, this also means that the Ruby 'event' method is being called when the integer data is 'unsafe' because it has only been partially updated (e.g. status of the current note changed, but channel, note, etc. are still from the previous one.)
The 'if i' branch of the event method (with no == value), will currently be called for all of these intermediate states (i.e. multiple times per Midi event), because only the discrete objects 'false' and 'nil' count as being a 'false' conditional in Ruby (the first argument to 'event' is a RubyEditConnector object, so is always counted as 'true', whatever its value.)
Handling each Midi event as a single Midi 'container' object will make this much easier to manage, as you'll always be presented with a 'whole' Midi event containing only valid data. There's no penalty for using Midi events as elements of a Ruby array, as all elements only store references to the object rather than copying it; so any buffering can be done directly with Midi objects.
After just a quick scan, I'm still a little uncertain how the buffers are operating. I can see why some buffering might be necessary in order to correctly handle changes to the transposition value while there are already notes playing. However, I get the impression that your buffers are partly a workaround for issues caused by the things I've noted above. I think this will probably become much clearer once the code is using Ruby Midi objects, as you shouldn't need to address the @ins array as much, and "@midi.channel" will be a lot easier to read than "@ins[1]" etc.! NB: You can assign new values to the Midi object properties as well as read them - though this isn't documented in the user guide (p.219 for the Ruby Midi stuff.)
One last thing. I'm not sure if having two separate Midi inputs is just to make testing easier, but as FlowStone currently only supports VST version 2.4, you can only have one Midi input to an exported VST plugin (one of the reasons I'd love to see an upgrade to VST 3+).
I hope that's helpful - feel free to fire back any questions if I've just made things more confusing!
