Re-engineering Cantabile's Bindings Framework

This is a companion discussion topic for the original entry at https://www.cantabilesoftware.com/blog/re-engineering-cantabiles-bindings-framework/
3 Likes

I thought I’d start this work (as described in the above linked blog post) by doing a rough mockup what I’m envisioning for the new bindings UI:

Notes:

  • The list of controls in each of the Source/Mapping/Target group boxes will change according to context and will show the settings for the currently selected binding in the main list.
  • The main table view list will probably be reduced to 4 columns - State (as it currently is), Source, Mapping, Target that will summarize each of the three group boxes.
  • There will be a way to collapse/hide the new form area to provide more room to view the full list of bindings.
  • The group boxes will be better sized - not sure why the Mapping box is so wide.
  • The rotary encoder options will move to the Source - Range dropdown which will include Clamp/Discard/Slide and the three rotary encoder modes.
  • Jump prevention will become a checkbox in the mapping group.
  • The options for conditional bindings will live in the mapping group and replace the curve editor.
  • The selected object indicies for “Object by Index” type bindings will become fields in the associated source/target group box.

What do you think?

8 Likes

Great plan and job @brad :v:
I like the new GUI for Bindings, clearer and better organized interface.
Thank you!

1 Like

I wanted to get an idea of the scope of this work so moved all the existing bindings code to a “old” namespace. 87 files affected. I’ve got my work cut out for me…

1 Like

My biggest gripe with the current bindings is that parameters such as curves or ranges don’t update in real time.
When you’re trying to tweak a setting having to repeated open and close the binding to test its effect is a bit 1990s :slight_smile:

The new UI is doing away with most modal dialogs and moving them into that lower half of the bindings panel. Adjustments will be immediate.

6 Likes

Hi Brad,

I like the proposed layout changes regarding simplifying the fields in the main table view. I had a few questions when you get a chance.

Will it still include a comments field?

Will you be able to call bindings fields up from other bindings that use rack custom button sources for editing bindings field values or use bindings to enable and disable other bindings in the bindings list?

I ask because the change to the use of the lower edit pane made me wonder how that would change things I do.

Thanks for the chance to pitch in!

Dave

2 Likes

Yes, in discussion with my in-house UX expert (Mitch) yesterday this came up and we thought as a full width text field below the three group boxes, but I need to mock it up - concerned it might take too much vertical screen space (that area’s already taller than I hoped).

There’s definitely going to be issues here and I did mention this and your fader rack in an early draft of the linked blog post but took it out because I thought it was too specific for most readers. I’m not exactly sure what I’ll do here, but it’ll probably involve a bunch of emails with you :).

Or, if you just want to put together a list of exactly what you need to be able to do I can start thinking about it now. I know you use bindings to trigger the binding delays settings what else is on that list?

What ever happens we’ll figure out something - but it will definitely require changes to your rack(s).

Over the next couple of days I’m going to be posting some tech/dev specific stuff here. If you’re not technically inclined this probably won’t be too interesting to you.

I’ve said this before and I’ll probably say it again - when coming up with solutions for technical problems I’m a big fan of pencil and paper. If I’m ever struggling to wrap my head around something, I step away from the computer and just write out all my thoughts. I rarely, if ever, go back and read any of this - it’s just a thought process.

This was me thinking about this work:

From this I’ve now put together a first pass of the core interfaces that will make up the new bindings framework:

Today’s job will be to start moving a couple of the existing bindable objects over to the new framework and see how it sits.

4 Likes

Made some good progress today:

  • continued refinement of the core binding interfaces
  • implemented some helper classes for implementing the source and target binding point interfaces
  • converted the “Engine” and “MasterLevels” bindable objects to the new framework

So far this seems to be working out nicely. The code seems simpler, more concise and more self contained. Previously each set of binding points on a binding object were kind of scattered through out the class, where as now everything to do with a particular binding point is completely self contained.

Tomorrow I think I’ll implement a couple more bindable objects (perhaps the Plugin object and one of the UI related objects) just to make sure the approach I’m taking works in a few other scenarios and isn’t to code verbose.

Once I’m happy with that I’ll start on reworking the actually bindings manager and binding objects.

5 Likes

Looks good!

One idea: since you’re separating bindings into source, curve, and target, this could be an opportunity to bring in not only jump prevention, but also “fading” at the curve level. So instead of waiting for the source to reach and catch the current level, there could be an option to fade to the target value at a defined speed (ideally with the option to sync to current tempo - e.g. quarter, half-note, bar, 2 bars…). So whenever a new target value is received, Cantabile starts a fade from the current to the target value.

BTW: This is also something I’d reeeeally like for levels in the routing page. Currently, making levels state-dependent can create nasty level jumps, so I use a “level automation” rack to smooth my levels when I need smooth transitions. Having the ability to right-click a level fader and set the fade time would make this far more comfortable and intuitive - I can see all level faders at a glance in routing table view and simply make the necessary adjustments.

Maybe something for the backlog after the bindings re-factoring is done :wink:

Cheers,

Torsten

6 Likes

Yes - this is what I was hinting at in the blog post where I mentioned animated bindings. It’s not something I’m actively designing for right now, but definitely bearing it in mind.

OK… this could probably be built on the same underlying engine pieces. Basically both of these depend on some improvements to the audio engine to support animated transitions, but that’s one step further down the road to what I’m working on right now.

Exactly.

1 Like

Not much progress yesterday because:

  1. I realized there was small problem with what I’d already done but it required reworking it with a new approach. Not the core interfaces but the way I was planning on implementing them. That’s sorted now.
  2. got a new laptop and needed to set it up.

Fun fact: you can’t clean install Microsoft Windows from USB on a Microsoft Surface Laptop - the keyboard and track pad don’t work and you need to use an external hub and devices. It was easier to install Windows on my Apple MacBook than on a Microsoft laptop.

4 Likes

OK, third time lucky.

Today I re-did implementation of binding points two more times. It’s important I get this right early because Cantabile has alot of binding points and I need to make sure it’s right before I convert everything over.

  • The first attempt made it too difficult/verbose/messy for the binding points to setup and remove event handlers (and what I realized yesterday was the event handlers weren’t getting removed - which would’ve leaked memory pretty badly)
  • The second attempt was much cleaner, but was creating tables of binding points for every instance of an object. eg: each plugin instance would have it’s own in-memory list of available binding points - which is silly when they’re the same on all instances.
  • Third attempt I think is pretty good.

The breakthrough was to use attributes and reflection. To explain a little more, there’s two parts to declaring a binding point:

  1. The meta data that’s required up front to build a list of binding points. This consists of three pieces of data:
    a. The id used for serialization (non-localized)
    b. The display name used for populating lists (localized)
    c. Flags indicating if the binding point is a source or target (or both)

  2. The other part is the actual binding point implementation - this is an object that gets created once the user selects to use a binding point.

A typical binding point now looks like this in code:

        [BindingPoint("gain", "Gain")]
        IBindingPoint Gain() => new MonitoredPluginBindingPoint(this)
        {
            Kind = BindingPointKind.GainLevel,
            SetValue = (value) => plugin.Gain = (float)value,
            GetValue = () => plugin.Gain,
        };

Notes:

  • The id and display name are declared using the [BindingPoint] attribute
  • Each binding point function is a factory for the binding point implementation.
  • The source/target flags are determined by whether the function returns an IBindingPoint, ITargetBindingPoint or ISourceBindingPoint

Also, since I didn’t want to have to write a separate class for every binding point, there’s a default implementation that implements the interface by forwarding to callback function properties. This lets me inline most of the code for most binding points like shown above. In other words, I don’t need to write an explicit PluginGainBindingPoint class - instead I can just use a generic binding point class and hook up things up via callbacks.

And I think that’s as concise as I can get it. The code above is everything required for the plugin gain binding point. This approach might need some more tweaking, but I’m pretty confident it’s correct enough to proceed
.
Time to move on to implementing the actual bindings manager and binding classes.

(I may not post anything here for the next week or so as I’m travelling interstate to visit family)

6 Likes

Well we planned to but our flight got cancelled and replacement flight is two days later, so…

I spent a good part of yesterday and today working on:

  • New bindings manager class for managing the set of bindings and binding groups on a rack.
  • Integrating the new bindings manager with the rack class.
  • Started implementing the Binding class that manages the source, mapping and target objects.
  • Parts of the serialization code.
  • More tweaking of the way binding points are implemented.

It’s coming along nicely, but there’s still a long way to go. I’m trying to do this without breaking the build. I’m effectively building a new binding system that for a short period during development will run in parallel to the old system. The hope is that I can then move things over piece by piece and check each part works as I go - as opposed to breaking everything and having to fix everything before I can test anything.

Pressing on regardless…

6 Likes

you love living on the ragged edge of disaster, don’t you? :joy:

2 Likes

OK, so the week away turned into two weeks due to some personal stuff, but I’m back at it this week. Yesterday was just catching up on support emails and getting build 4051 out. Today I got back into this bindings work.

Currently I’m working on the various mapping objects, and so far have implemented:

  • Switch to Command - including the auto-repeat and inverted modes
  • Switch to Switch - including the inverted mode
  • Switch to Value - mapping a switch to an on/off target value
  • Value to Value - including source/target ranges, out of range modes and curve.
  • Value to Gain - same as value to value but performs a linear to decibel mapping for the target values
  • Gain to Value - same as value to value but performs a decibel to linear mapping to the source values

Mostly this work involves extracting, cleaning up and refactoring code from the old binding implementation.

Tomorrow I’ll continue with the rest of the mappings.

7 Likes

Quick update… spent today finishing off the mapping implementations including porting jump prevention and relative encoder support (well part of it, since I’m splitting it between the source and the mapper now).

Also trying to figure out how this fits into things.

3 Likes

Didn’t know custom curves were available and doubt I’d have a need for them.