diff --git a/examples/Plugins/DSPModulePluginDemo.h b/examples/Plugins/DSPModulePluginDemo.h index 4dd0f8d870..690b8ae78e 100644 --- a/examples/Plugins/DSPModulePluginDemo.h +++ b/examples/Plugins/DSPModulePluginDemo.h @@ -1596,13 +1596,35 @@ public: } private: - class AttachedSlider : public Component + class ComponentWithParamMenu : public Component { public: - explicit AttachedSlider (RangedAudioParameter& param) - : label ("", param.name), + ComponentWithParamMenu (AudioProcessorEditor& editorIn, RangedAudioParameter& paramIn) + : editor (editorIn), param (paramIn) {} + + void mouseUp (const MouseEvent& e) override + { + if (e.mods.isRightButtonDown()) + if (auto* c = editor.getHostContext()) + if (auto menuInfo = c->getContextMenuForParameterIndex (¶m)) + menuInfo->getEquivalentPopupMenu().showMenuAsync ({}); + } + + private: + AudioProcessorEditor& editor; + RangedAudioParameter& param; + }; + + class AttachedSlider : public ComponentWithParamMenu + { + public: + AttachedSlider (AudioProcessorEditor& editorIn, RangedAudioParameter& param) + : ComponentWithParamMenu (editorIn, param), + label ("", param.name), attachment (param, slider) { + slider.addMouseListener (this, true); + addAllAndMakeVisible (*this, slider, label); slider.setTextValueSuffix (" " + param.label); @@ -1619,13 +1641,15 @@ private: SliderParameterAttachment attachment; }; - class AttachedToggle : public Component + class AttachedToggle : public ComponentWithParamMenu { public: - explicit AttachedToggle (RangedAudioParameter& param) - : toggle (param.name), + AttachedToggle (AudioProcessorEditor& editorIn, RangedAudioParameter& param) + : ComponentWithParamMenu (editorIn, param), + toggle (param.name), attachment (param, toggle) { + toggle.addMouseListener (this, true); addAndMakeVisible (toggle); } @@ -1636,14 +1660,17 @@ private: ButtonParameterAttachment attachment; }; - class AttachedCombo : public Component + class AttachedCombo : public ComponentWithParamMenu { public: - explicit AttachedCombo (RangedAudioParameter& param) - : combo (param), + AttachedCombo (AudioProcessorEditor& editorIn, RangedAudioParameter& param) + : ComponentWithParamMenu (editorIn, param), + combo (param), label ("", param.name), attachment (param, combo) { + combo.addMouseListener (this, true); + addAllAndMakeVisible (*this, combo, label); label.attachToComponent (&combo, false); @@ -1748,10 +1775,10 @@ private: struct BasicControls : public Component { - explicit BasicControls (const DspModulePluginDemo::ParameterReferences& state) - : pan (state.pan), - input (state.inputGain), - output (state.outputGain) + explicit BasicControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : pan (editor, state.pan), + input (editor, state.inputGain), + output (editor, state.outputGain) { addAllAndMakeVisible (*this, pan, input, output); } @@ -1766,15 +1793,15 @@ private: struct DistortionControls : public Component { - explicit DistortionControls (const DspModulePluginDemo::ParameterReferences& state) - : toggle (state.distortionEnabled), - lowpass (state.distortionLowpass), - highpass (state.distortionHighpass), - mix (state.distortionMix), - gain (state.distortionInGain), - compv (state.distortionCompGain), - type (state.distortionType), - oversampling (state.distortionOversampler) + explicit DistortionControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : toggle (editor, state.distortionEnabled), + lowpass (editor, state.distortionLowpass), + highpass (editor, state.distortionHighpass), + mix (editor, state.distortionMix), + gain (editor, state.distortionInGain), + compv (editor, state.distortionCompGain), + type (editor, state.distortionType), + oversampling (editor, state.distortionOversampler) { addAllAndMakeVisible (*this, toggle, type, lowpass, highpass, mix, gain, compv, oversampling); } @@ -1791,10 +1818,10 @@ private: struct ConvolutionControls : public Component { - explicit ConvolutionControls (const DspModulePluginDemo::ParameterReferences& state) - : cab (state.convolutionCabEnabled), - reverb (state.convolutionReverbEnabled), - mix (state.convolutionReverbMix) + explicit ConvolutionControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : cab (editor, state.convolutionCabEnabled), + reverb (editor, state.convolutionReverbEnabled), + mix (editor, state.convolutionReverbMix) { addAllAndMakeVisible (*this, cab, reverb, mix); } @@ -1810,11 +1837,11 @@ private: struct MultiBandControls : public Component { - explicit MultiBandControls (const DspModulePluginDemo::ParameterReferences& state) - : toggle (state.multiBandEnabled), - low (state.multiBandLowVolume), - high (state.multiBandHighVolume), - lRFreq (state.multiBandFreq) + explicit MultiBandControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : toggle (editor, state.multiBandEnabled), + low (editor, state.multiBandLowVolume), + high (editor, state.multiBandHighVolume), + lRFreq (editor, state.multiBandFreq) { addAllAndMakeVisible (*this, toggle, low, high, lRFreq); } @@ -1830,12 +1857,12 @@ private: struct CompressorControls : public Component { - explicit CompressorControls (const DspModulePluginDemo::ParameterReferences& state) - : toggle (state.compressorEnabled), - threshold (state.compressorThreshold), - ratio (state.compressorRatio), - attack (state.compressorAttack), - release (state.compressorRelease) + explicit CompressorControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : toggle (editor, state.compressorEnabled), + threshold (editor, state.compressorThreshold), + ratio (editor, state.compressorRatio), + attack (editor, state.compressorAttack), + release (editor, state.compressorRelease) { addAllAndMakeVisible (*this, toggle, threshold, ratio, attack, release); } @@ -1851,12 +1878,12 @@ private: struct NoiseGateControls : public Component { - explicit NoiseGateControls (const DspModulePluginDemo::ParameterReferences& state) - : toggle (state.noiseGateEnabled), - threshold (state.noiseGateThreshold), - ratio (state.noiseGateRatio), - attack (state.noiseGateAttack), - release (state.noiseGateRelease) + explicit NoiseGateControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : toggle (editor, state.noiseGateEnabled), + threshold (editor, state.noiseGateThreshold), + ratio (editor, state.noiseGateRatio), + attack (editor, state.noiseGateAttack), + release (editor, state.noiseGateRelease) { addAllAndMakeVisible (*this, toggle, threshold, ratio, attack, release); } @@ -1872,10 +1899,10 @@ private: struct LimiterControls : public Component { - explicit LimiterControls (const DspModulePluginDemo::ParameterReferences& state) - : toggle (state.limiterEnabled), - threshold (state.limiterThreshold), - release (state.limiterRelease) + explicit LimiterControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : toggle (editor, state.limiterEnabled), + threshold (editor, state.limiterThreshold), + release (editor, state.limiterRelease) { addAllAndMakeVisible (*this, toggle, threshold, release); } @@ -1891,12 +1918,12 @@ private: struct DirectDelayControls : public Component { - explicit DirectDelayControls (const DspModulePluginDemo::ParameterReferences& state) - : toggle (state.directDelayEnabled), - type (state.directDelayType), - delay (state.directDelayValue), - smooth (state.directDelaySmoothing), - mix (state.directDelayMix) + explicit DirectDelayControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : toggle (editor, state.directDelayEnabled), + type (editor, state.directDelayType), + delay (editor, state.directDelayValue), + smooth (editor, state.directDelaySmoothing), + mix (editor, state.directDelayMix) { addAllAndMakeVisible (*this, toggle, type, delay, smooth, mix); } @@ -1913,14 +1940,14 @@ private: struct DelayEffectControls : public Component { - explicit DelayEffectControls (const DspModulePluginDemo::ParameterReferences& state) - : toggle (state.delayEffectEnabled), - type (state.delayEffectType), - value (state.delayEffectValue), - smooth (state.delayEffectSmoothing), - lowpass (state.delayEffectLowpass), - feedback (state.delayEffectFeedback), - mix (state.delayEffectMix) + explicit DelayEffectControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : toggle (editor, state.delayEffectEnabled), + type (editor, state.delayEffectType), + value (editor, state.delayEffectValue), + smooth (editor, state.delayEffectSmoothing), + lowpass (editor, state.delayEffectLowpass), + feedback (editor, state.delayEffectFeedback), + mix (editor, state.delayEffectMix) { addAllAndMakeVisible (*this, toggle, type, value, smooth, lowpass, feedback, mix); } @@ -1937,13 +1964,13 @@ private: struct PhaserControls : public Component { - explicit PhaserControls (const DspModulePluginDemo::ParameterReferences& state) - : toggle (state.phaserEnabled), - rate (state.phaserRate), - depth (state.phaserDepth), - centre (state.phaserCentreFrequency), - feedback (state.phaserFeedback), - mix (state.phaserMix) + explicit PhaserControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : toggle (editor, state.phaserEnabled), + rate (editor, state.phaserRate), + depth (editor, state.phaserDepth), + centre (editor, state.phaserCentreFrequency), + feedback (editor, state.phaserFeedback), + mix (editor, state.phaserMix) { addAllAndMakeVisible (*this, toggle, rate, depth, centre, feedback, mix); } @@ -1959,13 +1986,13 @@ private: struct ChorusControls : public Component { - explicit ChorusControls (const DspModulePluginDemo::ParameterReferences& state) - : toggle (state.chorusEnabled), - rate (state.chorusRate), - depth (state.chorusDepth), - centre (state.chorusCentreDelay), - feedback (state.chorusFeedback), - mix (state.chorusMix) + explicit ChorusControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : toggle (editor, state.chorusEnabled), + rate (editor, state.chorusRate), + depth (editor, state.chorusDepth), + centre (editor, state.chorusCentreDelay), + feedback (editor, state.chorusFeedback), + mix (editor, state.chorusMix) { addAllAndMakeVisible (*this, toggle, rate, depth, centre, feedback, mix); } @@ -1981,12 +2008,12 @@ private: struct LadderControls : public Component { - explicit LadderControls (const DspModulePluginDemo::ParameterReferences& state) - : toggle (state.ladderEnabled), - mode (state.ladderMode), - freq (state.ladderCutoff), - resonance (state.ladderResonance), - drive (state.ladderDrive) + explicit LadderControls (AudioProcessorEditor& editor, const DspModulePluginDemo::ParameterReferences& state) + : toggle (editor, state.ladderEnabled), + mode (editor, state.ladderMode), + freq (editor, state.ladderCutoff), + resonance (editor, state.ladderResonance), + drive (editor, state.ladderDrive) { addAllAndMakeVisible (*this, toggle, mode, freq, resonance, drive); } @@ -2010,18 +2037,18 @@ private: //============================================================================== DspModulePluginDemo& proc; - BasicControls basicControls { proc.getParameterValues() }; - DistortionControls distortionControls { proc.getParameterValues() }; - ConvolutionControls convolutionControls { proc.getParameterValues() }; - MultiBandControls multibandControls { proc.getParameterValues() }; - CompressorControls compressorControls { proc.getParameterValues() }; - NoiseGateControls noiseGateControls { proc.getParameterValues() }; - LimiterControls limiterControls { proc.getParameterValues() }; - DirectDelayControls directDelayControls { proc.getParameterValues() }; - DelayEffectControls delayEffectControls { proc.getParameterValues() }; - PhaserControls phaserControls { proc.getParameterValues() }; - ChorusControls chorusControls { proc.getParameterValues() }; - LadderControls ladderControls { proc.getParameterValues() }; + BasicControls basicControls { *this, proc.getParameterValues() }; + DistortionControls distortionControls { *this, proc.getParameterValues() }; + ConvolutionControls convolutionControls { *this, proc.getParameterValues() }; + MultiBandControls multibandControls { *this, proc.getParameterValues() }; + CompressorControls compressorControls { *this, proc.getParameterValues() }; + NoiseGateControls noiseGateControls { *this, proc.getParameterValues() }; + LimiterControls limiterControls { *this, proc.getParameterValues() }; + DirectDelayControls directDelayControls { *this, proc.getParameterValues() }; + DelayEffectControls delayEffectControls { *this, proc.getParameterValues() }; + PhaserControls phaserControls { *this, proc.getParameterValues() }; + ChorusControls chorusControls { *this, proc.getParameterValues() }; + LadderControls ladderControls { *this, proc.getParameterValues() }; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DspModulePluginDemoEditor) 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 7c573e8a2d..ab822a7079 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -1467,6 +1467,115 @@ private: } } + class EditorContextMenu : public HostProvidedContextMenu + { + public: + EditorContextMenu (VSTComSmartPtr contextMenuIn) + : contextMenu (contextMenuIn) {} + + PopupMenu getEquivalentPopupMenu() const override + { + using MenuItem = Steinberg::Vst::IContextMenuItem; + using MenuTarget = Steinberg::Vst::IContextMenuTarget; + + struct Submenu + { + PopupMenu menu; + String name; + bool enabled; + }; + + std::vector menuStack (1); + + for (int32_t i = 0, end = contextMenu->getItemCount(); i < end; ++i) + { + MenuItem item{}; + MenuTarget* target = nullptr; + contextMenu->getItem (i, item, &target); + + if ((item.flags & MenuItem::kIsGroupStart) == MenuItem::kIsGroupStart) + { + menuStack.push_back ({ PopupMenu{}, + toString (item.name), + (item.flags & MenuItem::kIsDisabled) == 0 }); + } + else if ((item.flags & MenuItem::kIsGroupEnd) == MenuItem::kIsGroupEnd) + { + const auto back = menuStack.back(); + menuStack.pop_back(); + + if (menuStack.empty()) + { + // malformed menu + jassertfalse; + return {}; + } + + menuStack.back().menu.addSubMenu (back.name, back.menu, back.enabled); + } + else if ((item.flags & MenuItem::kIsSeparator) == MenuItem::kIsSeparator) + { + menuStack.back().menu.addSeparator(); + } + else + { + VSTComSmartPtr ownedTarget (target); + const auto tag = item.tag; + menuStack.back().menu.addItem (toString (item.name), + (item.flags & MenuItem::kIsDisabled) == 0, + (item.flags & MenuItem::kIsChecked) != 0, + [ownedTarget, tag] { ownedTarget->executeMenuItem (tag); }); + } + } + + if (menuStack.size() != 1) + { + // malformed menu + jassertfalse; + return {}; + } + + return menuStack.back().menu; + } + + void showNativeMenu (Point pos) const override + { + contextMenu->popup (pos.x, pos.y); + } + + private: + VSTComSmartPtr contextMenu; + }; + + class EditorHostContext : public AudioProcessorEditorHostContext + { + public: + EditorHostContext (JuceAudioProcessor& processorIn, + Steinberg::Vst::IComponentHandler* handler, + Steinberg::IPlugView* viewIn) + : processor (processorIn), componentHandler (handler), view (viewIn) {} + + std::unique_ptr getContextMenuForParameterIndex (const AudioProcessorParameter* parameter) const override + { + if (componentHandler == nullptr || view == nullptr) + return {}; + + Steinberg::FUnknownPtr handler (componentHandler); + + if (handler == nullptr) + return {}; + + const auto idToUse = parameter != nullptr ? processor.getVSTParamIDForIndex (parameter->getParameterIndex()) : 0; + const auto menu = VSTComSmartPtr (handler->createContextMenu (view, &idToUse)); + return std::make_unique (menu); + } + + private: + JuceAudioProcessor& processor; + Steinberg::Vst::IComponentHandler* componentHandler = nullptr; + Steinberg::IPlugView* view = nullptr; + }; + //============================================================================== class JuceVST3Editor : public Vst::EditorView, public Steinberg::IPlugViewContentScaleSupport, @@ -1475,6 +1584,7 @@ private: public: JuceVST3Editor (JuceVST3EditController& ec, JuceAudioProcessor& p) : EditorView (&ec, nullptr), + editorHostContext (p, ec.getComponentHandler(), this), owner (&ec), pluginInstance (*p.get()) { @@ -1813,6 +1923,8 @@ private: if (pluginEditor != nullptr) { + pluginEditor->setHostContext (&owner.editorHostContext); + addAndMakeVisible (pluginEditor.get()); pluginEditor->setTopLeftPosition (0, 0); @@ -1980,6 +2092,8 @@ private: //============================================================================== ScopedJuceInitialiser_GUI libraryInitialiser; + EditorHostContext editorHostContext; + #if JUCE_LINUX || JUCE_BSD SharedResourcePointer messageThread; SharedResourcePointer eventHandler; diff --git a/modules/juce_audio_processors/juce_audio_processors.h b/modules/juce_audio_processors/juce_audio_processors.h index cbce4e90a6..3b3012b8a6 100644 --- a/modules/juce_audio_processors/juce_audio_processors.h +++ b/modules/juce_audio_processors/juce_audio_processors.h @@ -118,9 +118,10 @@ #include "utilities/juce_VSTCallbackHandler.h" #include "utilities/juce_VST3ClientExtensions.h" #include "utilities/juce_ExtensionsVisitor.h" +#include "processors/juce_AudioProcessorParameter.h" +#include "processors/juce_AudioProcessorEditorHostContext.h" #include "processors/juce_AudioProcessorEditor.h" #include "processors/juce_AudioProcessorListener.h" -#include "processors/juce_AudioProcessorParameter.h" #include "processors/juce_AudioProcessorParameterGroup.h" #include "processors/juce_AudioProcessor.h" #include "processors/juce_PluginDescription.h" diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h index af6a73eca3..3d0450d0f5 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h @@ -26,7 +26,6 @@ namespace juce { -class AudioProcessor; class AudioProcessorEditorListener; //============================================================================== @@ -186,6 +185,22 @@ public: */ void setBoundsConstrained (Rectangle newBounds); + /** Gets a context object, if one is available. + + Returns nullptr if the host does not provide any information that the editor + can query. + + The returned pointer is non-owning, so do not attempt to free it. + */ + AudioProcessorEditorHostContext* getHostContext() const noexcept { return hostContext; } + + /** Sets a context object that can be queried to find information that the host + makes available to the plugin. + + You will only need to call this function if you are implementing a plugin host. + */ + void setHostContext (AudioProcessorEditorHostContext* context) noexcept { hostContext = context; } + /** The ResizableCornerComponent which is currently being used by this editor, or nullptr if it does not have one. */ @@ -219,6 +234,7 @@ private: bool resizableByHost = false; ComponentBoundsConstrainer defaultConstrainer; ComponentBoundsConstrainer* constrainer = nullptr; + AudioProcessorEditorHostContext* hostContext = nullptr; Component::SafePointer splashScreen; AffineTransform hostScaleTransform; diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorEditorHostContext.h b/modules/juce_audio_processors/processors/juce_AudioProcessorEditorHostContext.h new file mode 100644 index 0000000000..66d597a3f0 --- /dev/null +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorEditorHostContext.h @@ -0,0 +1,68 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 6 End-User License + Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). + + End User License Agreement: www.juce.com/juce-6-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +/** This wraps a context menu for a specific parameter, as provided by the host. + + You can choose to create a standard PopupMenu to display the host-provided + options. Alternatively, you can ask the host to display a native menu at + a specific location. +*/ +struct HostProvidedContextMenu +{ + virtual ~HostProvidedContextMenu() = default; + + /** Get a PopupMenu holding entries specified by the host. + + Most hosts will populate this menu with options that relate to the + parameter, such as displaying its automation lane. You are free + to modify this menu before displaying it, if you wish to add additional + options. + */ + virtual PopupMenu getEquivalentPopupMenu() const = 0; + + /** Asks the host to display its native menu at a particular location. */ + virtual void showNativeMenu (Point pos) const = 0; +}; + +/** Calling AudioProcessorEditor::getHostContext() may return a pointer to an + instance of this class. + + At the moment, this can be used to retrieve context menus for parameters in + compatible VST3 hosts. Additional extensions may be added here in the future. +*/ +struct AudioProcessorEditorHostContext +{ + virtual ~AudioProcessorEditorHostContext() = default; + + /** Returns an object which can be used to display a context menu for the + parameter with the given index. + */ + virtual std::unique_ptr getContextMenuForParameterIndex (const AudioProcessorParameter *) const = 0; +}; + +} // namespace juce diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h b/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h index f661dcb192..4e39d4cc90 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorParameter.h @@ -26,6 +26,8 @@ namespace juce { +class AudioProcessor; + //============================================================================== /** An abstract base class for parameter objects that can be added to an AudioProcessor.