I'm running some experiments to see whether Bitwig is suited for some adaptive controller ideas I've been working with (more about these later, if the experiments come to fruition...)
Thanks to @moss 's REALLY excellent tutorial videos, and the general good design of the Bitwig API, I've been able to get up and running quite painlessly despite not programming in Java since university, so many thanks for all of that! But I've hit my first wall and before I start searching through tons of DrivenByMoss code
Experiment 0 for my use case is to (A) robustly identify when the selected device is changed by any means and (B) print out some information about the newly selected device. (B) is pretty simple; I define a .deviceChanged method that prints out deviceType().get(), name().get(), channel().name().get(), and a few other things I've marked with .markInterested() or am otherwise observing. But I'm having trouble getting (A) to handle some corner cases.
I'll start by defining what I mean by "robustly identify". Crucially, the .deviceChanged method has to fire whenever the device is changed by any means; it can never be missed. Preferably, it wouldn't be called more than once per selection change. Ideally, interface changes (the deletion of an earlier device without the selection of that device) wouldn't call it, but a few unnecessary calls aren't a dealbreaker; they just shouldn't happen on ordinary selection changes.
If each device has a a unique instance ID that could be observed, this would be a one-liner (so I hope I'm just missing something obvious!) If there were some non-observable way of deriving it from PinnableDeviceCursor, it would be more complex, but probably do-able (basically, identify all the ways in which selections can change, watch for them, and store a prevID which gets compared to the newID).
But this seems like it might actually be hard to do properly in an extension. Bitwig obviously has some internal unique value which identifies a given instance of a device, no matter where it is or what happens to it, but I'm having trouble getting the API to follow along. After some experimentation, and a few blind alleys (cursorInsertionPoints seem to compare as equal; createEqualsValue can't be called outside of init(); etc.) I realized that you can safely downcast a PinnableCursorDevice.channel() to (Track), letting you call .position() on it. (The docs say that .channel() can also return a CursorTrack, which may need some thought, but I'm not worrying about that now). Observing this, plus the more obvious PinnableCursorDevice.position(), tracks changes in the y and x coordinate pair that should uniquely identify a device. And I think this would work--if it weren't for editing!
The problem with the y/x method is that if we delete from anywhere except the end, the device that was to the right now occupies the same track (y) and device list position (x), so
Inserting to the left of the current device creates the same sort of problem--now it's the new device that is in the same position. So we miss device updates.
Firing .deviceChanged on, say, name changes would catch most of these, but deleting the first of two instances of the same device (or inserting a new instance of the same device next to an existing instance) would still get missed. Also, the observers fire separately (and I don't know enough about the event loop to effectively consolidate this), so linking to name change now creates double triggering every time the selection changes between two devices with different names.
Similarly, downcasting PinnableCursorDevice.channel() to (Track), creating a trackDeviceBank, and observing its .itemCount() seems to handle the insertion and deletion case. However, now there's double triggering every time we change between channels that have different numbers of devices in them. Also (and this is a problem for the other methods as well), replacing an existing device with a new device of the same type should constitute a device change. With this method, though, even replacing an existing device with a new device of a DIFFERENT type gets missed, and deviceChanged won't fire unless you move to a different device and back. That probably is a dealbreaker.
(EDIT: Also, deleting the currently selected channel breaks this as well; if the channel below has at least as many devices, deviceChanged won't fire, since the device below the current selection will simply slide up into the current position.)
So, if anyone has read this far (which, many thanks!!!) do any obvious solutions come to mind? Or do I need to make an API feature request and (for now) try to combine a bunch of different device-changed checks in a way that can handle a bunch of repeated calls (and which even then won't catch everything?)
