Hey all,
I’m a student still developing my mind in the wonderful world of OoP. I’m trying to figure out the best way to go about designing my subclasses for a project I’m working on at my internship.
I’m trying to wrap my head around the wisest way to go about making everything communicate properly. A subset of the project I’m working on is my ‘MultiOscSynth’- a subclass of Synthesiser. For simplicity’s sake, let’s say it has 3 user-controllable attributes- level, numberOfLayers, and coarseTuneInSemitones.
These user-controllable attributes should be changeable by the GUI or automation. Whenever the attribute is changed by anything, the following should happen:
• a parameter object representative of this user-controllable attribute should be updated
• the target object (MultiOscSynth) should be notified to handle the change with its local attributes
• the corresponding GUI element should reflect the new value (which does not happen by default with automation changes)
• The host (DAW) is notified of the change so that it may update automation lanes etc.
I created an AudioProcessorParameter ‘ISWParameter’ to address the first bit; an ISWParameter is any user controllable parameter. In addition to some meta data, an ISWParameter maintains pointer(s) to ISWObject(s)- a class made for the sake of polymorphism, whose subclasses must override/define the method “updateFromParameter(ISWParameter* paramToUpdateFrom)”. MultiOscSynth is one of these subclasses. Whenever setValue is called on an ISWParameter, it tells this ISWObject to updateFromParameter.
Additionally, I made an ISWParameterFollower class that maintains a pointer to an ISWParameter; this is useful for GUI elements to ‘know’ what parameter they control. My ISWSlider class, per example, is a Slider, ISWObject, and ISWParameterFollower- it’s an ISWObject so that an ISWParameter can maintain a pointer to it and tell it to update, and an ISWParameterFollower so that whenever my Slider::Listener detects a GUI change, it can generically tell the appropriate ISWParameter to update its value (rather than having a long list of conditionals per GUI element).
Question 1: Are these object relationships appropriate? Is the coupling between a GUI element and a parameter an issue? (ISWParameter knows of a corresponding GUI element ISWObject to update; a GUI element like ISWSlider knows of a corresponding ISWParameter to update).
The rest of my challenge is figuring out proper standardization/normalization between all objects responding to a parameter change.
‘level’ is the easy case. the host/DAW expects it to be 0->1, ISWParameter (an AudioProcessorParameter) expects it to be 0->1, the GUI Slider goes from 0->1, and the local attribute in MultiOscSynth goes from 0->1.
‘numberOfLayers’ is a bit more of a challenge. The associated GUI rotary Slider is to move in quantized integer steps from 1 to 7. multiOscSynth also wants to interpret values this way. However, the host/DAW and the ISWParameter still want this between 0->1.
The obvious solution that comes to mind is to store these values as reciprocals, and to perhaps have a boolean in ISWParameter denoting it as storing reciprocals.
‘coarseTuneInSemitones’ presents a second issue- negative values. The associated GUI element will go in quantized integer steps from -12 to 12, per example, which again multiOscSynth wants, but the host + ISWParameter do not. To address this, 0 could be mapped to 0.5 with negative values occupying 0->0.5 and positive values 0.5->1, with the reciprocal / 2.0 providing the displacement from 0.5. Still, this is a special case and I’m not sure where it should be addressed- if the ISWParameter should maintain a boolean denoting it as this case and handle values accordingly, or what.
There are other possible cases that could occur. For example, in designing a lowpass filter with a ‘cutoffFrequency’ the associated GUI element might go from 20->20000 (hz)… storing a scalar in ISWParameter (in this case, 1/20000) could address it.