diff --git a/distrho/DistrhoPlugin.hpp b/distrho/DistrhoPlugin.hpp index 8570f9e97..c42e0acab 100644 --- a/distrho/DistrhoPlugin.hpp +++ b/distrho/DistrhoPlugin.hpp @@ -51,6 +51,20 @@ START_NAMESPACE_DISTRHO DISTRHO_PLUGIN_WANT_PROGRAMS activates program related features.@n When enabled you need to implement initProgramName() and loadProgram(). + DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE makes full-state restore (as + used by e.g. a saved VST3/CLAP preset file) deterministic regardless of + the plugin's prior runtime state: any parameter not covered by the + incoming state is reset to its coded default, rather than being left at + whatever value the plugin instance happened to already have. This holds + whether or not DISTRHO_PLUGIN_WANT_PROGRAMS is also enabled. When it is, + this flag additionally changes loadProgram()'s signature to accept an + optional bool* parametersSet out-parameter (see loadProgram()'s own + documentation for its contract), so that a parameter already covered by a + restored program is not needlessly reset a second time, nor incorrectly + overwritten if the program left it at a value other than its coded + default. Existing loadProgram() overrides need updating to populate + parametersSet when this flag is turned on. + DISTRHO_PLUGIN_WANT_STATE activates internal state features.@n When enabled you need to implement initState() and setState(). @@ -335,12 +349,30 @@ class Plugin virtual void setParameterValue(uint32_t index, float value); #if DISTRHO_PLUGIN_WANT_PROGRAMS + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + /** + Load a program.@n + The host may call this function from any context, including realtime processing.@n + Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_PROGRAMS is enabled. + @param parametersSet Optional pointer to a caller-allocated, zero-initialized + bool array of size getParameterCount(). If non-null, implementations + must set parametersSet[i] = true for every parameter index i whose + value this call assigns. Callers that don't need this information + (e.g. a plain host-triggered program change) may omit it. + @note This extra parameter only exists because + DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE is enabled; see its + description above for why, and update any existing loadProgram() + override to match when turning this flag on. + */ + virtual void loadProgram(uint32_t index, bool* parametersSet = nullptr); + #else /** Load a program.@n The host may call this function from any context, including realtime processing.@n Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_PROGRAMS is enabled. */ virtual void loadProgram(uint32_t index); + #endif #endif #if DISTRHO_PLUGIN_WANT_FULL_STATE diff --git a/distrho/src/DistrhoPlugin.cpp b/distrho/src/DistrhoPlugin.cpp index e2ffadb28..e87c481f2 100644 --- a/distrho/src/DistrhoPlugin.cpp +++ b/distrho/src/DistrhoPlugin.cpp @@ -230,7 +230,11 @@ float Plugin::getParameterValue(uint32_t) const { return 0.0f; } void Plugin::setParameterValue(uint32_t, float) {} #if DISTRHO_PLUGIN_WANT_PROGRAMS + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE +void Plugin::loadProgram(uint32_t, bool*) {} + #else void Plugin::loadProgram(uint32_t) {} + #endif #endif #if DISTRHO_PLUGIN_WANT_FULL_STATE diff --git a/distrho/src/DistrhoPluginAU.cpp b/distrho/src/DistrhoPluginAU.cpp index 919faa719..ce78e0da3 100644 --- a/distrho/src/DistrhoPluginAU.cpp +++ b/distrho/src/DistrhoPluginAU.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #ifndef DISTRHO_PLUGIN_BRAND_ID @@ -2649,6 +2650,18 @@ class PluginAU reinterpret_cast(&data)),); DISTRHO_SAFE_ASSERT_RETURN(CFGetTypeID(data) == CFDictionaryGetTypeID(),); + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + // Tracks which parameters this call actually assigned a value to, + // whether via a loaded program or an explicit "params" entry, so that + // anything left untouched can be backfilled with its coded default + // below instead of silently keeping whatever value the plugin + // instance happened to have before this call. + // std::vector is NOT usable here since its bit-packed + // specialization can't hand back a real bool* for loadProgram()'s + // out-parameter. + const std::unique_ptr paramsSet(new bool[fParameterCount]()); + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS CFNumberRef programRef = nullptr; if (CFDictionaryGetValueIfPresent(data, CFSTR("program"), reinterpret_cast(&programRef)) @@ -2662,7 +2675,11 @@ class PluginAU if (program >= 0) { fLastFactoryProgram = program; + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + fPlugin.loadProgram(fLastFactoryProgram, paramsSet.get()); + #else fPlugin.loadProgram(fLastFactoryProgram); + #endif notifyPropertyListeners('DPFo', kAudioUnitScope_Global, 0); } @@ -2778,6 +2795,9 @@ class PluginAU fLastParameterValues[j] = value; fPlugin.setParameterValue(j, value); + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + paramsSet[j] = true; + #endif notifyPropertyListeners('DPFp', kAudioUnitScope_Global, j); if (fBypassParameterIndex == j) @@ -2789,6 +2809,32 @@ class PluginAU std::free(symbol); } + + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + // Anything not explicitly touched above -- whether because it was + // never mentioned by the incoming preset at all, because a "params" + // entry was malformed or named an unknown symbol, or (when + // DISTRHO_PLUGIN_WANT_PROGRAMS is also enabled) because the requested + // program did not assign it -- is reset to its coded default here, + // rather than being left at whatever value this plugin instance had + // before this call. Each parameter is still only ever assigned once + // per call: this loop is disjoint from both the program-load and the + // explicit "params" assignments above. + for (uint32_t j=0; j +#include #include #include "clap/entry.h" @@ -1567,6 +1568,20 @@ class PluginCLAP : ClapEventQueue bool fillingKey = true; // if filling key or value char queryingType = 'i'; // can be 'n', 's' or 'p' (none, states, parameters) + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + // Tracks which parameters this call actually assigned a value to, + // whether via a loaded program or an explicit "__dpf_parameters_begin__" + // entry, so that anything left untouched can be backfilled with its + // coded default below instead of silently keeping whatever value the + // plugin instance happened to have before this call. + // A plain new[]/delete[] pair would leak on the early-return paths + // below (several of which are inside the read loop), so this needs + // to be RAII; std::vector is NOT usable here since its + // bit-packed specialization can't hand back a real bool* for + // loadProgram()'s out-parameter. + const std::unique_ptr paramsSet(new bool[fCachedParameters.numParams]()); + #endif + char buffer[512], orig; buffer[sizeof(buffer)-1] = '\xff'; @@ -1673,7 +1688,11 @@ class PluginCLAP : ClapEventQueue DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0); fCurrentProgram = static_cast(program); + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + fPlugin.loadProgram(fCurrentProgram, paramsSet.get()); + #else fPlugin.loadProgram(fCurrentProgram); + #endif #if DISTRHO_PLUGIN_HAS_UI if (ui != nullptr) @@ -1730,6 +1749,9 @@ class PluginCLAP : ClapEventQueue } #endif fPlugin.setParameterValue(j, fvalue); + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + paramsSet[j] = true; + #endif break; } } @@ -1741,6 +1763,34 @@ class PluginCLAP : ClapEventQueue } } + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + // Anything not explicitly touched above -- whether because it was + // never mentioned by the incoming state at all, or (when + // DISTRHO_PLUGIN_WANT_PROGRAMS is also enabled) because a requested + // program index turned out to be invalid and fPlugin.loadProgram() + // silently no-op'd -- is reset to its coded default here, rather + // than being left at whatever value this plugin instance happened + // to have before this call. This is what makes a (possibly + // partial) state restore deterministic regardless of the plugin's + // prior runtime state. Each parameter is still only ever assigned + // once per call: this loop is disjoint from both the program-load + // and explicit-parameter-list assignments above. + for (uint32_t j = 0; j < fCachedParameters.numParams; ++j) + { + if (paramsSet[j] || fPlugin.isParameterOutputOrTrigger(j)) + continue; + + const float fvalue = fPlugin.getParameterRanges(j).def; + + fCachedParameters.values[j] = fvalue; + #if DISTRHO_PLUGIN_HAS_UI + if (ui != nullptr) + fCachedParameters.changed[j] = true; + #endif + fPlugin.setParameterValue(j, fvalue); + } + #endif // DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + if (fHostExtensions.params != nullptr) fHostExtensions.params->rescan(fHost, CLAP_PARAM_RESCAN_VALUES|CLAP_PARAM_RESCAN_TEXT); diff --git a/distrho/src/DistrhoPluginChecks.h b/distrho/src/DistrhoPluginChecks.h index 52dc9d765..70a250a66 100644 --- a/distrho/src/DistrhoPluginChecks.h +++ b/distrho/src/DistrhoPluginChecks.h @@ -81,6 +81,10 @@ # define DISTRHO_PLUGIN_WANT_PROGRAMS 0 #endif +#ifndef DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE +# define DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE 0 +#endif + #ifndef DISTRHO_PLUGIN_WANT_STATE # define DISTRHO_PLUGIN_WANT_STATE 0 #endif diff --git a/distrho/src/DistrhoPluginInternal.hpp b/distrho/src/DistrhoPluginInternal.hpp index d0bef80a4..212e5cb9a 100644 --- a/distrho/src/DistrhoPluginInternal.hpp +++ b/distrho/src/DistrhoPluginInternal.hpp @@ -841,12 +841,20 @@ class PluginExporter return fData->programNames[index]; } - void loadProgram(const uint32_t index) + void loadProgram(const uint32_t index + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + , bool* const parametersSet = nullptr + #endif + ) { DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,); DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->programCount,); + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + fPlugin->loadProgram(index, parametersSet); + #else fPlugin->loadProgram(index); + #endif } #endif diff --git a/distrho/src/DistrhoPluginVST3.cpp b/distrho/src/DistrhoPluginVST3.cpp index 7295c8923..c3b205943 100644 --- a/distrho/src/DistrhoPluginVST3.cpp +++ b/distrho/src/DistrhoPluginVST3.cpp @@ -55,6 +55,7 @@ #include "travesty/host.h" #include +#include #include #include @@ -954,6 +955,20 @@ class PluginVst3 bool fillingKey = true; // if filling key or value char queryingType = 'i'; // can be 'n', 's' or 'p' (none, states, parameters) + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + // Tracks which parameters this call actually assigned a value to, + // whether via a loaded program or an explicit "__dpf_parameters_begin__" + // entry, so that anything left untouched can be backfilled with its + // coded default below instead of silently keeping whatever value the + // plugin instance happened to have before this call. + // A plain new[]/delete[] pair would leak on the early-return paths + // below (several of which are inside the read loop), so this needs + // to be RAII; std::vector is NOT usable here since its + // bit-packed specialization can't hand back a real bool* for + // loadProgram()'s out-parameter. + const std::unique_ptr paramsSet(new bool[fParameterCount]()); + #endif + char buffer[512], orig; buffer[sizeof(buffer)-1] = '\xff'; v3_result res; @@ -1063,7 +1078,11 @@ class PluginVst3 DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0); fCurrentProgram = static_cast(program); + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + fPlugin.loadProgram(fCurrentProgram, paramsSet.get()); + #else fPlugin.loadProgram(fCurrentProgram); + #endif #if DISTRHO_PLUGIN_HAS_UI if (connectedToUI) @@ -1135,6 +1154,9 @@ class PluginVst3 } #endif fPlugin.setParameterValue(j, fvalue); + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + paramsSet[j] = true; + #endif break; } } @@ -1146,6 +1168,50 @@ class PluginVst3 } } + #if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + // Anything not explicitly touched above -- whether because it was + // never mentioned by the incoming state at all, or (when + // DISTRHO_PLUGIN_WANT_PROGRAMS is also enabled) because a requested + // program index turned out to be invalid and fPlugin.loadProgram() + // silently no-op'd -- is reset to its coded default here, rather + // than being left at whatever value this plugin instance happened + // to have before this call. This is what makes a (possibly + // partial) state restore deterministic regardless of the plugin's + // prior runtime state. Each parameter is still only ever assigned + // once per call: this loop is disjoint from both the program-load + // and explicit-parameter-list assignments above. + for (uint32_t j = 0; j < fParameterCount; ++j) + { + if (paramsSet[j] || fPlugin.isParameterOutputOrTrigger(j)) + continue; + + const float fvalue = fPlugin.getParameterRanges(j).def; + + fCachedParameterValues[kVst3InternalParameterBaseCount + j] = fvalue; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + // If this is the component make sure the controller also knows about the state change + if (fIsComponent) + { + componentValuesChanged = true; + fParameterValuesChangedDuringProcessing[kVst3InternalParameterBaseCount + j] = true; + } + #else + componentValuesChanged = true; + #endif + + #if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + { + // UI parameter updates are handled outside the read loop (after host param restart) + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + j] = true; + } + #endif + + fPlugin.setParameterValue(j, fvalue); + } + #endif // DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE + if (fComponentHandler != nullptr && componentValuesChanged) v3_cpp_obj(fComponentHandler)->restart_component(fComponentHandler, V3_RESTART_PARAM_VALUES_CHANGED);