Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions distrho/DistrhoPlugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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().

Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions distrho/src/DistrhoPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions distrho/src/DistrhoPluginAU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <AudioToolbox/AudioUnitUtilities.h>

#include <map>
#include <memory>
#include <vector>

#ifndef DISTRHO_PLUGIN_BRAND_ID
Expand Down Expand Up @@ -2649,6 +2650,18 @@ class PluginAU
reinterpret_cast<const void**>(&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<bool> 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<bool[]> paramsSet(new bool[fParameterCount]());
#endif

#if DISTRHO_PLUGIN_WANT_PROGRAMS
CFNumberRef programRef = nullptr;
if (CFDictionaryGetValueIfPresent(data, CFSTR("program"), reinterpret_cast<const void**>(&programRef))
Expand All @@ -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);
}

Expand Down Expand Up @@ -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)
Expand All @@ -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<fParameterCount; ++j)
{
if (paramsSet[j] || fPlugin.isParameterOutputOrTrigger(j))
continue;

const float value = fPlugin.getParameterRanges(j).def;

fLastParameterValues[j] = value;
fPlugin.setParameterValue(j, value);
notifyPropertyListeners('DPFp', kAudioUnitScope_Global, j);

if (fBypassParameterIndex == j)
notifyPropertyListeners(kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0);
}
#endif
}

// ----------------------------------------------------------------------------------------------------------------
Expand Down
50 changes: 50 additions & 0 deletions distrho/src/DistrhoPluginCLAP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#endif

#include <map>
#include <memory>
#include <vector>

#include "clap/entry.h"
Expand Down Expand Up @@ -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<bool> 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<bool[]> paramsSet(new bool[fCachedParameters.numParams]());
#endif

char buffer[512], orig;
buffer[sizeof(buffer)-1] = '\xff';

Expand Down Expand Up @@ -1673,7 +1688,11 @@ class PluginCLAP : ClapEventQueue
DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0);

fCurrentProgram = static_cast<uint32_t>(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)
Expand Down Expand Up @@ -1730,6 +1749,9 @@ class PluginCLAP : ClapEventQueue
}
#endif
fPlugin.setParameterValue(j, fvalue);
#if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE
paramsSet[j] = true;
#endif
break;
}
}
Expand All @@ -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);

Expand Down
4 changes: 4 additions & 0 deletions distrho/src/DistrhoPluginChecks.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion distrho/src/DistrhoPluginInternal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
66 changes: 66 additions & 0 deletions distrho/src/DistrhoPluginVST3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#include "travesty/host.h"

#include <map>
#include <memory>
#include <string>
#include <vector>

Expand Down Expand Up @@ -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<bool> 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<bool[]> paramsSet(new bool[fParameterCount]());
#endif

char buffer[512], orig;
buffer[sizeof(buffer)-1] = '\xff';
v3_result res;
Expand Down Expand Up @@ -1063,7 +1078,11 @@ class PluginVst3
DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0);

fCurrentProgram = static_cast<uint32_t>(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)
Expand Down Expand Up @@ -1135,6 +1154,9 @@ class PluginVst3
}
#endif
fPlugin.setParameterValue(j, fvalue);
#if DISTRHO_PLUGIN_WANT_STRICT_PARAMETER_RESTORE
paramsSet[j] = true;
#endif
break;
}
}
Expand All @@ -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);

Expand Down