Re-engineering Cantabile's Bindings Framework
In the video game "What Remains of Edith Finch", the main character Edith returns to her childhood home - a home which even from a distance you can tell has a history of modifications, add-ons and precarious extensions.
While this might be a great setting for a game, it's not a great way to engineer houses - nor computer software. Unfortunately, Cantabile's bindings framework is starting to look a little like Edith's home - it's simply outgrown its original design.
A Brief History of Bindings
So how did it get to this? Let's take a quick look a how bindings have evolved over the years...
The origin of bindings goes right back to Cantabile 2, starting with MIDI Controller Assignments for mapping incoming MIDI events:
and Triggers for sending MIDI in response to various Cantabile events:
In the early versions of Cantabile 3, "MIDI Assignments" were renamed to "Bindings", but triggers were still a separate concept:
Than in July 2016, build 3182 introduced a major revision of bindings that saw the unification of bindings and triggers, the concept of binding "anything to anything" and formed the basis of the current bindings implementation to this day.
What's changed over the last 6 years is more and more features have been piled on to the original design:
- Bi-directional bindings
- Changes to delayed bindings
- Explicit binding invocation order
- Value mapping curves
- Sending sys-ex bindings
- Indexed bindings (ie: plugin/rack/object by index/name etc...)
- New value types
- Rotary encoder support
- Transport position bindings
- Auto-repeat bindings
- And lots more binding points including PC keyboard, View controls, Navigation controls, UI Commands and more.
I've now built the software equivalent of Edith's home.
Side note: there's no regret or anything unusual about this kind of evolution in software design. In fact I'd prefer this than to have over-engineered it from the start. Over-engineered software tends to either never get released, released late or it gets finished with a bunch of stuff that's never used. Cantabile's current bindings implementation has worked for the last 6 years and has served its purpose well.
Performance Implications
One of the side effects of this evolution is that some bindings that should run on the audio thread actually run on the UI thread making them susceptible to stalling.
The current implementation isn't really amenable to fixing this - which has in-turn led to me holding back some other features for fear of overloading the binding system (in particular animated bindings).
The UI is a Little Shaky Too
Another side effect is that the UI is starting to become unwieldy.
Bindings are now defined across 12 columns of settings (8 primary columns, 4 sub-columns) with different binding types using different sets of columns and some using the same column for different purposes. This makes it difficult to fit the entire definition of a binding on-screen and leads to either constant horizontal scrolling and/or fiddling with the columns widths.
The Plan
There's no quick fix for this. Just like if you wanted to properly fix Edith's house it's going to require a certain amount of destruction, pulling things apart, salvaging what's usable and putting it all back together again.
For Cantabile, these are the main areas of work that need to be done:
- The biggest problem with the current implementation is that all the properties associated with a binding are stored on one binding object. So the first step will be to break this into three distinct objects, each with its own set of properties - a binding source, a value mapping and a binding target. Those objects can then be plugged together to make a working binding.
- In the current implementation there are multiple ways a binding can be invoked - triggers, MIDI events, value changes, PC key presses, string variable re-evaluation, transport triggers each invoke the binding in a slightly different way. This needs to be refactored and abstracted behind a consistent interface.
- Even if the UI was kept identical, it would need to be reworked to talk to the new binding objects so this is a good time to address that too. My current thinking here is to split the bindings panel horizontally and use the top half to list the bindings and the bottom half for a dynamic form to edit the currently selected binding (similar to the MIDI filters window).
- Once all the above is in place it should be much easier to move whole classes of bindings over to the audio thread.
The Downside
So what's the downside to all this? Well for me it's a fair bit of work. I'm terrible at estimating this kind of work, but it feels like about 3 or 4 weeks.
For you the user there's a couple of downsides:
- There will be a stabilization period after the work is done in which I'm sure there will be compatibility issues that will need to be ironed out.
- These changes won't be backwards compatible. Bindings saved by the new version won't be loadable by earlier versions of Cantabile so you'll need to keep a backup of your current songs and racks in case you need to go back to an earlier version.
- The bindings endpoint in the network API will need non-backwards compatible changes. I don't think its worth building a translation/compatibility layer for this - so all clients of the API will need to be updated and this includes the Web UI and the Stream Deck plugin.
I'd like to think these are all temporary/transitional problems that pave the way to something better.
I plan to start on this work today. I'm tempted to say it's "demo day" for Cantabile - but I hope to be a bit more surgical about it :)
Stay tuned.
btw: "What Remains of Edith Finch" is a great, if somewhat strange and morbid game. I do recommend though if you're into that kind of thing. Check it out here.