diff --git a/examples/GainPlugin/GainPlugin.cpp b/examples/GainPlugin/GainPlugin.cpp index d49bb87..fbdc0cf 100644 --- a/examples/GainPlugin/GainPlugin.cpp +++ b/examples/GainPlugin/GainPlugin.cpp @@ -85,5 +85,20 @@ void GainPlugin::setStateInformation(const void *data, int sizeInBytes) vts.replaceState(juce::ValueTree::fromXml(*xmlState)); } +void GainPlugin::paramIndicationSetMapping(const juce::RangedAudioParameter ¶m, + bool has_mapping, const juce::Colour *colour, + const juce::String &label, + const juce::String &description) noexcept +{ + paramIndicationHelper.paramIndicationSetMapping(param, has_mapping, colour, label, description); +} + +void GainPlugin::paramIndicationSetAutomation(const juce::RangedAudioParameter ¶m, + uint32_t automation_state, + const juce::Colour *colour) noexcept +{ + paramIndicationHelper.paramIndicationSetAutomation(param, automation_state, colour); +} + // This creates new instances of the plugin juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter() { return new GainPlugin(); } diff --git a/examples/GainPlugin/GainPlugin.h b/examples/GainPlugin/GainPlugin.h index 94f356d..8ddbc5d 100644 --- a/examples/GainPlugin/GainPlugin.h +++ b/examples/GainPlugin/GainPlugin.h @@ -2,6 +2,7 @@ #include #include "ModulatableFloatParameter.h" +#include "ParamIndicationHelper.h" class ModulatableFloatParameter; class GainPlugin : public juce::AudioProcessor, @@ -38,6 +39,15 @@ class GainPlugin : public juce::AudioProcessor, void getStateInformation(juce::MemoryBlock &data) override; void setStateInformation(const void *data, int sizeInBytes) override; + bool supportsParamIndication() const noexcept override { return true; } + void paramIndicationSetMapping(const juce::RangedAudioParameter ¶m, bool has_mapping, + const juce::Colour *colour, const juce::String &label, + const juce::String &description) noexcept override; + void paramIndicationSetAutomation(const juce::RangedAudioParameter ¶m, + uint32_t automation_state, + const juce::Colour *colour) noexcept override; + ParamIndicationHelper paramIndicationHelper; + juce::String getPluginTypeString() const; auto *getGainParameter() { return gainDBParameter; } auto &getValueTreeState() { return vts; } diff --git a/examples/GainPlugin/ParamIndicationHelper.h b/examples/GainPlugin/ParamIndicationHelper.h new file mode 100644 index 0000000..00b72c4 --- /dev/null +++ b/examples/GainPlugin/ParamIndicationHelper.h @@ -0,0 +1,94 @@ +#pragma once + +#include + +class ParamIndicationHelper +{ + public: + ParamIndicationHelper() = default; + + struct ParamIndicatorInfo + { + juce::Colour colour; + juce::String text; + }; + + const ParamIndicatorInfo *getParamIndicatorInfo(juce::RangedAudioParameter ¶m) const + { + const auto entry = indicatorInfoMap.find(¶m); + if (entry == indicatorInfoMap.end()) + return nullptr; + return &entry->second; + } + + void paramIndicationSetMapping(const juce::RangedAudioParameter ¶m, bool has_mapping, + const juce::Colour *colour, const juce::String &label, + const juce::String &description) noexcept + { + if (!has_mapping) + { + removeIndicatorInfo(param); + } + else + { + ParamIndicatorInfo info; + info.colour = colour == nullptr ? juce::Colours::transparentBlack : *colour; + info.text = label + ", " + description; + indicatorInfoMap[¶m] = std::move(info); + } + + listeners.call(&Listener::paramIndicatorInfoChanged, param); + } + + void paramIndicationSetAutomation(const juce::RangedAudioParameter ¶m, + uint32_t automation_state, + const juce::Colour *colour) noexcept + { + if (automation_state == CLAP_PARAM_INDICATION_AUTOMATION_NONE) + { + removeIndicatorInfo(param); + } + else + { + ParamIndicatorInfo info; + info.colour = colour == nullptr ? juce::Colours::transparentBlack : *colour; + info.text = "Automation: " + [automation_state]() -> juce::String { + if (automation_state == CLAP_PARAM_INDICATION_AUTOMATION_PRESENT) + return "PRESENT"; + else if (automation_state == CLAP_PARAM_INDICATION_AUTOMATION_RECORDING) + return "RECORDING"; + else if (automation_state == CLAP_PARAM_INDICATION_AUTOMATION_PLAYING) + return "PLAYING"; + else if (automation_state == CLAP_PARAM_INDICATION_AUTOMATION_OVERRIDING) + return "OVERRIDING"; + return ""; + }(); + indicatorInfoMap[¶m] = std::move(info); + } + + listeners.call(&Listener::paramIndicatorInfoChanged, param); + } + + struct Listener + { + virtual ~Listener() = default; + virtual void paramIndicatorInfoChanged(const juce::RangedAudioParameter &) {} + }; + + void addListener(Listener *listener) { listeners.add(listener); } + void removeListener(Listener *listener) { listeners.remove(listener); } + + private: + void removeIndicatorInfo(const juce::RangedAudioParameter ¶m) + { + const auto entry = indicatorInfoMap.find(¶m); + if (entry != indicatorInfoMap.end()) + indicatorInfoMap.erase(entry); + } + + std::unordered_map indicatorInfoMap; + + juce::ListenerList listeners; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ParamIndicationHelper) +}; diff --git a/examples/GainPlugin/PluginEditor.cpp b/examples/GainPlugin/PluginEditor.cpp index bc4c4f9..4ce183f 100644 --- a/examples/GainPlugin/PluginEditor.cpp +++ b/examples/GainPlugin/PluginEditor.cpp @@ -54,6 +54,7 @@ PluginEditor::PluginEditor(GainPlugin &plug) : juce::AudioProcessorEditor(plug), std::make_unique(*gainParameter, *gainSlider, nullptr); plugin.getValueTreeState().addParameterListener(gainParameter->paramID, this); + plugin.paramIndicationHelper.addListener(this); setSize(300, 300); setResizable (true, true); @@ -67,6 +68,7 @@ PluginEditor::~PluginEditor() { auto *gainParameter = plugin.getGainParameter(); plugin.getValueTreeState().removeParameterListener(gainParameter->paramID, this); + plugin.paramIndicationHelper.removeListener(this); } void PluginEditor::resized() @@ -83,6 +85,18 @@ void PluginEditor::paint(juce::Graphics &g) const auto titleBounds = getLocalBounds().removeFromTop(30); const auto titleText = "Gain Plugin " + plugin.getPluginTypeString(); g.drawFittedText(titleText, titleBounds, juce::Justification::centred, 1); + + if (auto *paramIndicatorInfo = + plugin.paramIndicationHelper.getParamIndicatorInfo(*plugin.getGainParameter())) + { + g.setColour(paramIndicatorInfo->colour); + g.fillRect(juce::Rectangle{0, 0, 15, 15}); + + g.setColour(juce::Colours::black); + const auto paramIndicationTextBounds = getLocalBounds().removeFromBottom(30); + g.drawFittedText(paramIndicatorInfo->text, paramIndicationTextBounds, + juce::Justification::centred, 1); + } } void PluginEditor::mouseDown(const juce::MouseEvent &e) @@ -124,3 +138,10 @@ void PluginEditor::parameterChanged(const juce::String &, float) auto &animator = juce::Desktop::getInstance().getAnimator(); animator.fadeOut(&flashComp, 100); } + +void PluginEditor::paramIndicatorInfoChanged(const juce::RangedAudioParameter ¶m) +{ + if (¶m != plugin.getGainParameter()) + return; + repaint(); +} diff --git a/examples/GainPlugin/PluginEditor.h b/examples/GainPlugin/PluginEditor.h index 2dc7c4d..6ee53a3 100644 --- a/examples/GainPlugin/PluginEditor.h +++ b/examples/GainPlugin/PluginEditor.h @@ -3,7 +3,8 @@ #include "GainPlugin.h" class PluginEditor : public juce::AudioProcessorEditor, - private juce::AudioProcessorValueTreeState::Listener + private juce::AudioProcessorValueTreeState::Listener, + private ParamIndicationHelper::Listener { public: explicit PluginEditor(GainPlugin &plugin); @@ -12,9 +13,11 @@ class PluginEditor : public juce::AudioProcessorEditor, void resized() override; void paint(juce::Graphics &g) override; + void paramIndicatorInfoChanged(const juce::RangedAudioParameter &) override; + private: void parameterChanged(const juce::String ¶meterID, float newValue) override; - void mouseDown (const juce::MouseEvent& e) override; + void mouseDown(const juce::MouseEvent &e) override; GainPlugin &plugin; diff --git a/include/clap-juce-extensions/clap-juce-extensions.h b/include/clap-juce-extensions/clap-juce-extensions.h index 7238bf5..5635a3f 100644 --- a/include/clap-juce-extensions/clap-juce-extensions.h +++ b/include/clap-juce-extensions/clap-juce-extensions.h @@ -31,6 +31,7 @@ class MidiBuffer; class AudioProcessorParameter; class RangedAudioParameter; class String; +class Colour; } // namespace juce namespace clap_juce_extensions @@ -233,6 +234,20 @@ struct clap_juce_audio_processor_capabilities noteNamesChangedSignal(); } + virtual bool supportsParamIndication() const noexcept { return false; } + virtual void paramIndicationSetMapping(const juce::RangedAudioParameter & /*param*/, + bool /*has_mapping*/, const juce::Colour * /*colour*/, + const juce::String & /*label*/, + const juce::String & /*description*/) noexcept + { + } + + virtual void paramIndicationSetAutomation(const juce::RangedAudioParameter & /*param*/, + uint32_t /*automation_state*/, + const juce::Colour * /*colour*/) noexcept + { + } + /** If your plugin supports remote controls, then override this method to return true. */ virtual bool supportsRemoteControls() const noexcept { return false; } diff --git a/src/wrapper/clap-juce-wrapper.cpp b/src/wrapper/clap-juce-wrapper.cpp index 2a73125..b9427c6 100644 --- a/src/wrapper/clap-juce-wrapper.cpp +++ b/src/wrapper/clap-juce-wrapper.cpp @@ -362,6 +362,12 @@ class EditorHostContext : public juce::AudioProcessorEditorHostContext }; #endif // JUCE_VERSION >= 0x060008 +/** Converts a clap_color to a juce::Colour */ +static juce::Colour clapColourToJUCEColour(const clap_color &clapColour) +{ + return {clapColour.red, clapColour.green, clapColour.blue, clapColour.alpha}; +} + /* * The ClapJuceWrapper is a class which immplements a collection * of CLAP and JUCE APIs @@ -1045,6 +1051,53 @@ class ClapJuceWrapper : public clap::helpers::Plugin< return false; } + bool implementsParamIndication() const noexcept override + { + if (processorAsClapExtensions) + return processorAsClapExtensions->supportsParamIndication(); + return false; + } + void paramIndicationSetMapping(clap_id param_id, bool has_mapping, const clap_color_t *color, + const char *label, const char *description) noexcept override + { + if (!processorAsClapExtensions) + return; + + const auto ¶m = paramPtrByClapID[param_id]; + + juce::Colour juceColour{}; + if (color != nullptr) + juceColour = clapColourToJUCEColour(*color); + const juce::Colour *juceColourPtr = color == nullptr ? nullptr : &juceColour; + + juce::String labelStr{}; + if (label != nullptr) + labelStr = (juce::CharPointer_UTF8)label; + + juce::String descStr{}; + if (label != nullptr) + descStr = (juce::CharPointer_UTF8)description; + + processorAsClapExtensions->paramIndicationSetMapping(*param.rangedParameter, has_mapping, + juceColourPtr, labelStr, descStr); + } + void paramIndicationSetAutomation(clap_id param_id, uint32_t automation_state, + const clap_color_t *color) noexcept override + { + if (!processorAsClapExtensions) + return; + + const auto ¶m = paramPtrByClapID[param_id]; + + juce::Colour juceColour{}; + if (color != nullptr) + juceColour = clapColourToJUCEColour(*color); + const juce::Colour *juceColourPtr = color == nullptr ? nullptr : &juceColour; + + processorAsClapExtensions->paramIndicationSetAutomation(*param.rangedParameter, + automation_state, juceColourPtr); + } + bool implementRemoteControls() const noexcept override { if (processorAsClapExtensions)