Broadcaster attachment design pattern
-
What I've got is working perfectly. I just think the code is a bit messy. It isn't just for envelopes, no. I'm doing the same pattern for a whole variety of areas in my synth.
But the one example is:
inline function addEngineAmpEnvListeners() { SharedData.broadcasters["Engine1AHDSRControl"].addListener("string", "metadata", function(param, value) { BroadcasterCallbacks.controlLinkedAmpEnvParameter("Engine1", param, value); }); SharedData.broadcasters["Engine2AHDSRControl"].addListener("string", "metadata", function(param, value) { BroadcasterCallbacks.controlLinkedAmpEnvParameter("Engine2", param, value); }); SharedData.broadcasters["Engine3AHDSRControl"].addListener("string", "metadata", function(param, value) { BroadcasterCallbacks.controlLinkedAmpEnvParameter("Engine3", param, value); }); }
And the callback I'm attaching to this is:
inline function controlLinkedAmpEnvParameter(engineName, param, value) { local paramMap = { "Monophonic": 0, "Retrigger": 1, "Attack": 2, "AttackLevel": 3, "Hold": 4, "Decay": 5, "Sustain": 6, "Release": 7, "AttackCurve": 8, "DecayCurve": 9, "EcoMode": 10, }; local engine = SharedData.engines[engineName]; for (mode in SharedData.modeMap) { local modeData = engine["modes"][mode]; local parts = param.getId().split("_"); local paramName = parts[parts.length - 1]; local target = modeData["ahdsrEnvObj"]; target.setAttribute(paramMap[paramName], value); } }
It works splendidly. I just don't like that initial bit of code, because I'm repeating myself and specifying a different engine name each time.
-
@Orvillain said in Broadcaster attachment design pattern:
What I've got is working perfectly.
Have you tested it in a compiled project?
The way I handle this kind of thing is to make a controller script. Then I just connect the knobs to the controller using processor/parameter ID.
Here's an example of an AHDSR controller that will work for any number of envelopes, you can add/remove envelopes and no change to the script is required, it's completely self contained.
It avoids that repetition you've ran into and can be reused in many projects.
-
@d-healey said in Broadcaster attachment design pattern:
Have you tested it in a compiled project?
Yes. Works fine!
-
@d-healey said in Broadcaster attachment design pattern:
The way I handle this kind of thing is to make a controller script. Then I just connect the knobs to the controller using processor/parameter ID.
Here's an example of an AHDSR controller that will work for any number of envelopes, you can add/remove envelopes and no change to the script is required, it's completely self contained.
It avoids that repetition you've ran into and can be reused in many projects.
Without bludgeoning you with loads of code, it is hard to explain. But I'm essentially dynamically creating parameter groups based on their names. Then I create a broadcaster for each group. Then I attach the broadcaster to each parameter based on the broadcaster type that is desired, then I am adding the listener callbacks to the broadcaster.
It is all very cool and modular. I just don't like this particular 3-engine manual duplicate approach, because if I want to move to 5 engines, then I need to do a bunch of manual work.
I was hoping for a design pattern where I could call the right number of addListener commands, against the right number of broadcaster or parameter groups. I can't just run a loop and use local variables, because local variables aren't allowed in inline functions in this manner.
-
@Orvillain
Engine1AHDSRControl
These are referring to AHDSR envelopes? -
@d-healey said in Broadcaster attachment design pattern:
@Orvillain
Engine1AHDSRControl
These are referring to AHDSR envelopes?No, it refers to a broadcaster ID created in a previous step.
I'm not going to post everything I'm doing, but imagine it more like this:
inline function addEngineLinkedFeatureListeners() { SharedData.broadcasters["ENGINE-NAME-HERE-FEATUREHERE"].addListener("string", "metadata", function(param, value) { BroadcasterCallbacks.controlLinkedFEATUREParameter(ENGINE-NAME-HERE, param, value); }); }
I want to do the above, where the engine name or identifier increments. But I can't do that with a for loop, because it complains about it not liking local variables.
Engine1AHDSRControl is a broadcaster id. and depending on the number of engines, I automatically create these. So for 3 engines I get:
Engine1AHDSRControl
Engine2AHDSRControl
Engine3AHDSRControlAnd if I change the number of engines, my code will dynamically create the right amount of parameters, the right amount of broadcasters, and the right amount of parameter/component groups, and then it will attach the correct broadcaster to the correct component group.
Next step is to add the listener to the broadcaster, in a dynamic way.
-
@Orvillain This error?
Interface:! Engine3AHDSRControl: BroadcastersBuilder.js (291): Illegal iterator target
The error is being triggered only for
Engine3
so that implies the for loop in general is fine, but there is a more specific issue. -
@d-healey Yeah, there's some underlying closure thing going on I think.
-
for (engine in engines) { local capturedEngine = engine; Globals.broadcasters[engine + "AHDSRControl"].addListener("string", "metadata", function(param, value) { BroadcasterCallbacks.controlLinkedAmpEnvParameter(capturedEngine, param, value); }); }
Oh I just realised the issue - the function within your loop doesn't have access to local variables outside of the function. So you'll need to find another structure.
One workaround is to use a lambda variable, I try to avoid this because I find their use usually indicates a structural issue. But it's a quick and dirty fix.
function[capturedEngine](param, value)
Another suggestion is instead of using an array of strings and a
for in
loop. Just use a for loop that counts from 0 to the number of engines you have. Then you can just declare a NUM_ENGINES constant at the top of your script and use it whenever you need to know the number of engine you have. -
@d-healey said in Broadcaster attachment design pattern:
Another suggestion is instead of using an array of strings and a for in loop. Just use a for loop that counts from 0 to the number of engines you have. Then you can just declare a NUM_ENGINES constant at the top of your script and use it whenever you need to know the number of engine you have.
If I try that, for example (different feature area this time):
for (i = 1; i < SharedData.engines.length; i++) { SharedData.broadcasters["Engine" + i + "ModeControl"].addListener("string", "metadata", function(index) { BroadcasterCallbacks.setActiveModeForSlot("Engine" + i, index); }); }
Then I get:
Interface:! BroadcastersBuilder.js (260): Can't reference local variables in nested function body {{SW50ZXJmYWNlfEJyb2FkY2FzdGVyc0J1aWxkZXIuanN8ODQ3MHwyNjB8NTc=}}
-
@Orvillain The error is basically saying what I said.
You can't access a local variable declared outside of the function.
-
@d-healey Indeed, indeed.
for (engine in SharedData.engines) { local capturedEngine = engine; SharedData.broadcasters[capturedEngine + "ModeControl"].addListener("string", "metadata", function[capturedEngine](index) { BroadcasterCallbacks.setActiveModeForSlot(capturedEngine, index); }); }
This did seem to work however.
-
@Orvillain Yep that will do it, it's not the most elegant solution but that's what it's designed for.
-
@d-healey More elegant than what I have right now I am thinking!
-
@Orvillain Yep it will certainly help reduce that repetition
-
@d-healey Yeah, I think I can essentially boil down 11 functions down to 3 functions with this. Pretty cool, cheers!