SamplerPluginDemo and DataModel philosophy

I’m now refining the SamplerPluginDemo and adding ADSRs for amplitude and filters etc. The original project has an interesting “data model” pattern that I’d like to understand better.

Since I’m adding ADSRs, I extended the data model with a new class MPESamplerVoiceDataModel for the MPESamplerVoice. My UI is a mess, but the ADSR sliders work as desired with this new data model.

However, now I want to call getParameters() on the SamplerAudioProcessor class. I’m seeing that it has no parameters because the SamplerAudioProcessor’s constructor doesn’t create a layout and AudioProcessorValueTreeState like AudioProcessorValueTreeState and TheAudioProgrammer/helloSampler.

How can I reconcile the “data model” philosophy of the demo with what seems like the more common way of creating AudioParameterFloat? I think I would be violating the philosophy if my MPESamplerVoiceDataModel had an AudioProcessorValueTreeState, which would in turn be constructed with a reference to an audio processor. I’m pretty sure that a data model shouldn’t have a reference to a processor, which would violate model-view-controller.

Thank you!

I’m not familiar with that particular example you’re talking about, but this is the typical data model I use, and see recommended for most use cases:

The AudioProcessor class owns a bunch of parameter objects. These are exposed to the host, to make them automatable. Most modern Juce projects will use an AudioProcessorValueTreeState to store & manage their parameters.

Any other audio processing code that’s nested inside the AudioProcessor does not need knowledge of the actual parameter objects. For example, in my projects, I usually create a templated “engine” class that my AudioProcessor contains, and the “engine” class will have specific methods for responding to each parameter change – eg. engine.updateAdsrAttack, engine.updateAdsrDecay, etc. Then, inside the AudioProcessor class, you can simply poll for changes in your parameter values once per processBlock() and call the appropriate functions to route those new values to the actionable functions within your DSP code.

I generally think of the AudioProcessor itself as the “glue” code that connects everything specific to an actual plugin (parameters, state save/load, etc), to all the more generic, reusable DSP code that doesn’t know it’s inside a plugin.

As far as the GUI is concerned, I would create some kind of struct that contains your AudioProcessor’s entire state (if you’re using APVTS, this is done for you with its ValueTree member), then the GUI simply has to poll this state on a timer callback from the message thread to update its components appropriately.

Thanks. The points you made about automation and loading/saving state are what make me concerned about continuing with the data model way. The SamplerPluginDemo doesn’t seem to be setup for those features. I’d like to hear another argument in favor of the way this demo was designed. Maybe the intention all along was for the developer to create a second APVTS the traditional way, but the demo just didn’t do it. Then DataModel would be a data model for things not relevant to automation/loading/saving, but the familiar APVTS owned by the audioprocessor would still serve its purpose.

Earlier I should have just linked to this tutorial for the traditional method of the AudioProcessor owning the APVTS.