1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

AudioProcessorEditor: Allow showing a host-provided parameter menu in VST3 plugins

This commit is contained in:
reuk 2021-05-10 21:26:31 +01:00
parent 4b0b245b55
commit bfb521b610
No known key found for this signature in database
GPG key ID: 9ADCD339CFC98A11
6 changed files with 323 additions and 95 deletions

View file

@ -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 (&param))
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)

View file

@ -1467,6 +1467,115 @@ private:
}
}
class EditorContextMenu : public HostProvidedContextMenu
{
public:
EditorContextMenu (VSTComSmartPtr<Steinberg::Vst::IContextMenu> 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<Submenu> 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<MenuTarget> 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<int> pos) const override
{
contextMenu->popup (pos.x, pos.y);
}
private:
VSTComSmartPtr<Steinberg::Vst::IContextMenu> 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<HostProvidedContextMenu> getContextMenuForParameterIndex (const AudioProcessorParameter* parameter) const override
{
if (componentHandler == nullptr || view == nullptr)
return {};
Steinberg::FUnknownPtr<Steinberg::Vst::IComponentHandler3> handler (componentHandler);
if (handler == nullptr)
return {};
const auto idToUse = parameter != nullptr ? processor.getVSTParamIDForIndex (parameter->getParameterIndex()) : 0;
const auto menu = VSTComSmartPtr<Steinberg::Vst::IContextMenu> (handler->createContextMenu (view, &idToUse));
return std::make_unique<EditorContextMenu> (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> messageThread;
SharedResourcePointer<EventHandler> eventHandler;

View file

@ -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"

View file

@ -26,7 +26,6 @@
namespace juce
{
class AudioProcessor;
class AudioProcessorEditorListener;
//==============================================================================
@ -186,6 +185,22 @@ public:
*/
void setBoundsConstrained (Rectangle<int> 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<Component> splashScreen;
AffineTransform hostScaleTransform;

View file

@ -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<int> 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<HostProvidedContextMenu> getContextMenuForParameterIndex (const AudioProcessorParameter *) const = 0;
};
} // namespace juce

View file

@ -26,6 +26,8 @@
namespace juce
{
class AudioProcessor;
//==============================================================================
/** An abstract base class for parameter objects that can be added to an
AudioProcessor.