I am currently migrating a lot of my thinking from Pure Data / MAXMSP. In my SC GUI design I am thinking about what is the equivalent of abstractions and sub patches would be, where an edit to one can represent an edit to all, rather than monotonously making the same edit to multiple objects.
Eli begins to touch on an approach to this using arrays at right the very end of this tutorial.
I struggling to find good documentation/video/tutorial on best practice in this way of design.
Any tips or pointers in the right direction would be greatly appreciated. Thank you
“an edit to one can represent an edit to all” – I think there isn’t exactly such a thing in SC.
In Pd/Max (I’ll just say Pd from here on out b/c 1/ FLOSS and 2/ tbh I prefer it over Max), when you save changes to an abstraction that’s being used in an open patch, the existing instances get re-created while preserving all the inlet/outlet connections. The connections are independent of the specific instance.
In SC, “connections” are object references, and these are bound to specific instances. If you delete/re-create a GUI object, any other objects that hold references to the old GUI object will not automatically pick up the new object. Because there isn’t a concept of a patch cable representing the connection, there may be any number of objects, anywhere, holding an outdated reference! There’s no automatic way to find them.
So IMO the design strategy has to be different.
My opinion is that model-view-controller design gets you the closest to what you want to achieve. This separates the GUI away from program logic in a way that allows the GUI to be replaced, wholesale, without disrupting program logic.
“Model” maintains a value and broadcasts changes. We used to have a class called Model but now it’s either gone, or it changed to another name and I don’t remember the new name. It’s possible to hack a Model using event-style prototypes. If you find that you like this way of working, you could create your own class (which would allow a neater programming interface – prototypes have a restriction that they can’t use any method names that already exist for Event – hence getValue instead of value, and destroy instead of free – a real class wouldn’t be subject to that restriction).
(
~modelPrototype = (
value: 0,
// because '.value' gets eaten by the 'value' method
getValue: { |self| self[\value] },
value_: { |self, newValue|
self[\value] = newValue;
self.changed(\value, newValue);
},
destroy: { |self| self.changed(\didFree) }
);
)
// an array of 10 values
a = Array.fill(10, { ~modelPrototype.copy.put(\value, 1.0.rand) });
a[0].getValue
-> 0.92148387432098
The View is the GUI object. It’s OK for the view’s action function to talk directly to its specific model.
The Controller receives updates from the model and forwards them to the View.
You might think this is a long way to go around for a simple GUI. But the advantage is that you can close and re-create the GUI as many times as you need. onClose makes sure that the SimpleController connection goes away when the view disappears, so there’s no permanent linkage between the working data and the GUI.
// the "glue"
a[0].dependants // "a SimpleController"
// close the GUI
w.close;
a[0].dependants // empty, good!
// now you can rerun the block to create the window
So if you change the GUI-building code, then you’re very very close to “edit one, edit all.”
(For some extra fun, run the GUI block twice, and move one window so that you can see both of them, and play with the sliders.)
I can’t thank you enough for this detailed response and your code. It’s a very elegant solution, although I am still working to understand every dimension of it.
It’s funny, I assumed there would be a very straightforward way to do this, but I guess that is the ¨lock in¨ effect of thinking in terms of one (PD and Max being so similar) language.
Thanks again - I’m going to read and re-read this code many times, as it’s a great foundation for sophisticated GUI design