diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index 955b6e14b8..871d364781 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -81,12 +81,6 @@ using namespace Steinberg; extern JUCE_API void setNativeHostWindowSizeVST (void* window, Component*, int newWidth, int newHeight, bool isNSView); #endif - enum - { - kJuceVST3BypassParamID = 0x62797073, // 'byps' - kMidiControllerMappingsStartID = 0x6d636d00 // 'mdm*' - }; - //============================================================================== class JuceAudioProcessor : public FUnknown { @@ -197,6 +191,13 @@ public: } //============================================================================== + enum InternalParameters + { + paramPreset = 0x70727374, // 'prst' + paramBypass = 0x62797073, // 'byps' + paramMidiControllerOffset = 0x6d636d00 // 'mdm*' + }; + struct Param : public Vst::Parameter { Param (AudioProcessor& p, int index, Vst::ParamID paramID) : owner (p), paramIndex (index) @@ -350,6 +351,79 @@ public: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BypassParam) }; + //============================================================================== + struct ProgramChangeParameter : public Vst::Parameter + { + ProgramChangeParameter (AudioProcessor& p) : owner (p) + { + jassert (owner.getNumPrograms() > 1); + + info.id = paramPreset; + toString128 (info.title, "Program"); + toString128 (info.shortTitle, "Program"); + toString128 (info.units, ""); + info.stepCount = owner.getNumPrograms() - 1; + info.defaultNormalizedValue = static_cast (owner.getCurrentProgram()) / static_cast (info.stepCount); + info.unitId = Vst::kRootUnitId; + info.flags = Vst::ParameterInfo::kIsProgramChange | Vst::ParameterInfo::kCanAutomate; + } + + virtual ~ProgramChangeParameter() {} + + bool setNormalized (Vst::ParamValue v) override + { + Vst::ParamValue program = v * info.stepCount; + + if (! isPositiveAndBelow ((int) program, owner.getNumPrograms())) + return false; + + if (valueNormalized != v) + { + valueNormalized = v; + changed(); + return true; + } + + return false; + } + + void toString (Vst::ParamValue value, Vst::String128 result) const override + { + Vst::ParamValue program = value * info.stepCount; + toString128 (result, owner.getProgramName ((int) program)); + } + + bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override + { + const String paramValueString (getStringFromVstTChars (text)); + + const int n = owner.getNumPrograms(); + for (int i = 0; i < n; ++i) + { + if (paramValueString == owner.getProgramName (i)) + { + outValueNormalized = static_cast (i) / info.stepCount; + return true; + } + } + + return false; + } + + static String getStringFromVstTChars (const Vst::TChar* text) + { + return juce::String (juce::CharPointer_UTF16 (reinterpret_cast (text))); + } + + Vst::ParamValue toPlain (Vst::ParamValue v) const override { return v * info.stepCount; } + Vst::ParamValue toNormalized (Vst::ParamValue v) const override { return v / info.stepCount; } + + private: + AudioProcessor& owner; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramChangeParameter) + }; + //============================================================================== tresult PLUGIN_API setComponentState (IBStream* stream) override { @@ -362,6 +436,10 @@ public: setParamNormalized (getVSTParamIDForIndex (i), (double) pluginInstance->getParameter (i)); setParamNormalized (bypassParamID, audioProcessor->isBypassed ? 1.0f : 0.0f); + + const int numPrograms = pluginInstance->getNumPrograms(); + if (numPrograms > 1) + setParamNormalized (paramPreset, static_cast (pluginInstance->getCurrentProgram()) / static_cast (numPrograms - 1)); } return Vst::EditController::setComponentState (stream); @@ -459,6 +537,13 @@ public: void audioProcessorChanged (AudioProcessor*) override { + if (AudioProcessor* pluginInstance = getPluginInstance()) + { + if (pluginInstance->getNumPrograms() > 1) + EditController::setParamNormalized (paramPreset, static_cast (pluginInstance->getCurrentProgram()) + / static_cast (pluginInstance->getNumPrograms() - 1)); + } + if (componentHandler != nullptr) componentHandler->restartComponent (Vst::kLatencyChanged | Vst::kParamValuesChanged); } @@ -531,12 +616,15 @@ private: parameters.addParameter (new Param (*pluginInstance, i, vstParamID)); } - bypassParamID = static_cast (usingManagedParameter ? kJuceVST3BypassParamID : numParameters); + bypassParamID = static_cast (usingManagedParameter ? paramBypass : numParameters); parameters.addParameter (new BypassParam (*pluginInstance, bypassParamID)); + + if (pluginInstance->getNumPrograms() > 1) + parameters.addParameter (new ProgramChangeParameter (*pluginInstance)); } #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS - parameterToMidiControllerOffset = static_cast (usingManagedParameter ? kMidiControllerMappingsStartID + parameterToMidiControllerOffset = static_cast (usingManagedParameter ? paramMidiControllerOffset : parameters.getParameterCount()); initialiseMidiControllerMappings (); @@ -1357,7 +1445,7 @@ public: { if (listIndex == 0) { - info.id = paramPreset; + info.id = JuceVST3EditController::paramPreset; info.programCount = (Steinberg::int32) getPluginInstance().getNumPrograms(); toString128 (info.name, TRANS("Factory Presets")); @@ -1372,7 +1460,7 @@ public: tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override { - if (listId == paramPreset + if (listId == JuceVST3EditController::paramPreset && isPositiveAndBelow ((int) programIndex, getPluginInstance().getNumPrograms())) { toString128 (name, getPluginInstance().getProgramName ((int) programIndex)); @@ -1722,6 +1810,15 @@ public: else if (juceVST3EditController->isMidiControllerParamID (vstParamID)) addParameterChangeToMidiBuffer (offsetSamples, vstParamID, value); #endif + else if (vstParamID == JuceVST3EditController::paramPreset) + { + const int numPrograms = pluginInstance->getNumPrograms(); + const int programValue = static_cast (std::round (value * static_cast (numPrograms))); + + if (numPrograms > 1 && isPositiveAndBelow (programValue, numPrograms) + && programValue != pluginInstance->getCurrentProgram()) + pluginInstance->setCurrentProgram (programValue); + } else { const int index = getJuceIndexForVSTParamID (vstParamID); @@ -1972,12 +2069,6 @@ private: return AudioBusPointerHelper::impl (data); } - //============================================================================== - enum InternalParameters - { - paramPreset = 'prst' - }; - void preparePlugin (double sampleRate, int bufferSize) { AudioProcessor& p = getPluginInstance(); @@ -1996,7 +2087,7 @@ private: const int numParameters = pluginInstance->getNumParameters(); usingManagedParameter = (pluginInstance->getParameters().size() == numParameters); - vstBypassParameterId = static_cast (usingManagedParameter ? kJuceVST3BypassParamID : numParameters); + vstBypassParameterId = static_cast (usingManagedParameter ? JuceVST3EditController::paramBypass : numParameters); for (int i = 0; i < numParameters; ++i) {