|
- /*
- ==============================================================================
-
- This file is part of the JUCE examples.
- Copyright (c) 2022 - Raw Material Software Limited
-
- The code included in this file is provided under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license. Permission
- To use, copy, modify, and/or distribute this software for any purpose with or
- without fee is hereby granted provided that the above copyright notice and
- this permission notice appear in all copies.
-
- THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
- WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
- PURPOSE, ARE DISCLAIMED.
-
- ==============================================================================
- */
-
- /*******************************************************************************
- The block below describes the properties of this PIP. A PIP is a short snippet
- of code that can be read by the Projucer and used to generate a JUCE project.
-
- BEGIN_JUCE_PIP_METADATA
-
- name: ReaperEmbeddedViewDemo
- version: 1.0.0
- vendor: JUCE
- website: http://juce.com
- description: An audio plugin which embeds a secondary view in VST2 and
- VST3 formats in REAPER
-
- dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
- juce_audio_plugin_client, juce_audio_processors,
- juce_audio_utils, juce_core, juce_data_structures,
- juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
- exporters: xcode_mac, vs2022, linux_make
-
- moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
-
- type: AudioProcessor
- mainClass: ReaperEmbeddedViewDemo
-
- useLocalCopy: 1
-
- END_JUCE_PIP_METADATA
-
- *******************************************************************************/
-
- /* This demo shows how to use the VSTCallbackHandler and VST3ClientExtensions
- classes to provide extended functionality in compatible VST/VST3 hosts.
-
- If this project is built as a VST or VST3 plugin and loaded in REAPER
- 6.29 or higher, it will provide an embedded level meter in the track
- control panel. To enable the embedded view, right-click on the plugin
- and select "Show embedded UI in TCP".
-
- The plugin's editor also include a button which can be used to toggle
- all inserts on and off.
- */
-
- #pragma once
-
- JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wshadow-field-in-constructor",
- "-Wnon-virtual-dtor")
-
- #include <pluginterfaces/base/ftypes.h>
- #include <pluginterfaces/base/funknown.h>
- #include <pluginterfaces/vst/ivsthostapplication.h>
- #include <pluginterfaces/vst2.x/aeffect.h>
-
- JUCE_END_IGNORE_WARNINGS_GCC_LIKE
-
- namespace reaper
- {
- JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant",
- "-Wunused-parameter",
- "-Wnon-virtual-dtor")
- JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100)
-
- using namespace Steinberg;
- using INT_PTR = pointer_sized_int;
- using uint32 = Steinberg::uint32;
-
- #include "extern/reaper_plugin_fx_embed.h"
- #include "extern/reaper_vst3_interfaces.h"
-
- //==============================================================================
- /* These should live in a file which is guaranteed to be compiled only once
- (i.e. a .cpp file, normally). This demo is a bit special, because we know
- that this header will only be included in a single translation unit.
- */
- DEF_CLASS_IID (IReaperHostApplication)
- DEF_CLASS_IID (IReaperUIEmbedInterface)
-
- JUCE_END_IGNORE_WARNINGS_MSVC
- JUCE_END_IGNORE_WARNINGS_GCC_LIKE
- }
-
- //==============================================================================
- struct EmbeddedViewListener
- {
- virtual ~EmbeddedViewListener() = default;
- virtual Steinberg::TPtrInt handledEmbeddedUIMessage (int msg,
- Steinberg::TPtrInt parm2,
- Steinberg::TPtrInt parm3) = 0;
- };
-
- JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnon-virtual-dtor")
-
- //==============================================================================
- class EmbeddedUI : public reaper::IReaperUIEmbedInterface
- {
- public:
- explicit EmbeddedUI (EmbeddedViewListener& demo) : listener (demo) {}
-
- Steinberg::TPtrInt embed_message (int msg,
- Steinberg::TPtrInt parm2,
- Steinberg::TPtrInt parm3) override
- {
- return listener.handledEmbeddedUIMessage (msg, parm2, parm3);
- }
-
- Steinberg::uint32 PLUGIN_API addRef() override { return ++refCount; }
- Steinberg::uint32 PLUGIN_API release() override { return --refCount; }
-
- Steinberg::tresult PLUGIN_API queryInterface (const Steinberg::TUID tuid, void** obj) override
- {
- if (std::memcmp (tuid, iid, sizeof (Steinberg::TUID)) == 0)
- {
- ++refCount;
- *obj = this;
- return Steinberg::kResultOk;
- }
-
- *obj = nullptr;
- return Steinberg::kNoInterface;
- }
-
- private:
- EmbeddedViewListener& listener;
- std::atomic<Steinberg::uint32> refCount { 1 };
- };
-
- JUCE_END_IGNORE_WARNINGS_GCC_LIKE
-
- //==============================================================================
- class Editor : public AudioProcessorEditor
- {
- public:
- explicit Editor (AudioProcessor& proc,
- AudioParameterFloat& param,
- void (*globalBypass) (int))
- : AudioProcessorEditor (proc), attachment (param, slider)
- {
- addAndMakeVisible (slider);
- addAndMakeVisible (bypassButton);
-
- // Clicking will bypass *everything*
- bypassButton.onClick = [globalBypass] { if (globalBypass != nullptr) globalBypass (-1); };
-
- setSize (300, 80);
- }
-
- void resized() override
- {
- auto b = getLocalBounds();
- slider.setBounds (b.removeFromTop (40));
- bypassButton.setBounds (b);
- }
-
- void paint (Graphics& g) override
- {
- g.fillAll (Colours::darkgrey);
- }
-
- private:
- Slider slider;
- TextButton bypassButton { "global bypass" };
- SliderParameterAttachment attachment;
- };
-
- //==============================================================================
- class ReaperEmbeddedViewDemo : public AudioProcessor,
- public VSTCallbackHandler,
- public VST3ClientExtensions,
- private EmbeddedViewListener,
- private Timer
- {
- public:
- ReaperEmbeddedViewDemo()
- {
- addParameter (gain = new AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f));
- startTimerHz (60);
- }
-
- void prepareToPlay (double, int) override {}
- void reset() override {}
-
- void releaseResources() override {}
-
- void processBlock (AudioBuffer<float>& audio, MidiBuffer&) override { processBlockImpl (audio); }
- void processBlock (AudioBuffer<double>& audio, MidiBuffer&) override { processBlockImpl (audio); }
-
- //==============================================================================
- AudioProcessorEditor* createEditor() override { return new Editor (*this, *gain, globalBypassFn); }
- bool hasEditor() const override { return true; }
-
- //==============================================================================
- const String getName() const override { return "ReaperEmbeddedViewDemo"; }
-
- bool acceptsMidi() const override { return false; }
- bool producesMidi() const override { return false; }
- bool isMidiEffect() const override { return false; }
-
- double getTailLengthSeconds() const override { return 0.0; }
-
- //==============================================================================
- int getNumPrograms() override { return 1; }
- int getCurrentProgram() override { return 0; }
- void setCurrentProgram (int) override {}
- const String getProgramName (int) override { return "None"; }
-
- void changeProgramName (int, const String&) override {}
-
- //==============================================================================
- void getStateInformation (MemoryBlock& destData) override
- {
- MemoryOutputStream (destData, true).writeFloat (*gain);
- }
-
- void setStateInformation (const void* data, int sizeInBytes) override
- {
- gain->setValueNotifyingHost (MemoryInputStream (data,
- static_cast<size_t> (sizeInBytes),
- false).readFloat());
- }
-
- int32_t queryIEditController (const Steinberg::TUID tuid, void** obj) override
- {
- if (embeddedUi.queryInterface (tuid, obj) == Steinberg::kResultOk)
- return Steinberg::kResultOk;
-
- *obj = nullptr;
- return Steinberg::kNoInterface;
- }
-
- void setIHostApplication (Steinberg::FUnknown* ptr) override
- {
- if (ptr == nullptr)
- return;
-
- void* objPtr = nullptr;
-
- if (ptr->queryInterface (reaper::IReaperHostApplication::iid, &objPtr) == Steinberg::kResultOk)
- {
- if (void* fnPtr = static_cast<reaper::IReaperHostApplication*> (objPtr)->getReaperApi ("BypassFxAllTracks"))
- globalBypassFn = reinterpret_cast<void (*) (int)> (fnPtr);
- }
- }
-
- pointer_sized_int handleVstPluginCanDo (int32, pointer_sized_int, void* ptr, float) override
- {
- if (auto* str = static_cast<const char*> (ptr))
- {
- if (strcmp (str, "hasCockosEmbeddedUI") == 0)
- return 0xbeef0000;
-
- if (strcmp (str, "hasCockosExtensions") == 0)
- return 0xbeef0000;
- }
-
- return 0;
- }
-
- pointer_sized_int handleVstManufacturerSpecific (int32 index,
- pointer_sized_int value,
- void* ptr,
- float opt) override
- {
- // The docstring at the top of reaper_plugin_fx_embed.h specifies
- // that the index will always be effEditDraw, which is now deprecated.
- if (index != __effEditDrawDeprecated)
- return 0;
-
- return (pointer_sized_int) handledEmbeddedUIMessage ((int) opt,
- (Steinberg::TPtrInt) value,
- (Steinberg::TPtrInt) ptr);
- }
-
- void handleVstHostCallbackAvailable (std::function<VstHostCallbackType>&& hostcb) override
- {
- char functionName[] = "BypassFxAllTracks";
- globalBypassFn = reinterpret_cast<void (*) (int)> (hostcb ((int32_t) 0xdeadbeef, (int32_t) 0xdeadf00d, 0, functionName, 0.0));
- }
-
- private:
- template <typename Float>
- void processBlockImpl (AudioBuffer<Float>& audio)
- {
- audio.applyGain (*gain);
-
- const auto minMax = audio.findMinMax (0, 0, audio.getNumSamples());
- const auto newMax = (float) std::max (std::abs (minMax.getStart()), std::abs (minMax.getEnd()));
-
- auto loaded = storedLevel.load();
- while (loaded < newMax && ! storedLevel.compare_exchange_weak (loaded, newMax)) {}
- }
-
- void timerCallback() override
- {
- levelToDraw = std::max (levelToDraw * 0.95f, storedLevel.exchange (0.0f));
- }
-
- Steinberg::TPtrInt getSizeInfo (reaper::REAPER_FXEMBED_SizeHints* sizeHints)
- {
- if (sizeHints == nullptr)
- return 0;
-
- sizeHints->preferred_aspect = 1 << 16;
- sizeHints->minimum_aspect = 1 << 16;
- sizeHints->min_height = sizeHints->min_width = 50;
- sizeHints->max_height = sizeHints->max_width = 1000;
- return 1;
- }
-
- Steinberg::TPtrInt doPaint (reaper::REAPER_FXEMBED_IBitmap* bitmap,
- reaper::REAPER_FXEMBED_DrawInfo* drawInfo)
- {
- if (bitmap == nullptr || drawInfo == nullptr || bitmap->getWidth() <= 0 || bitmap->getHeight() <= 0)
- return 0;
-
- Image img (juce::Image::PixelFormat::ARGB, bitmap->getWidth(), bitmap->getHeight(), true);
- Graphics g (img);
-
- g.fillAll (Colours::black);
-
- const auto bounds = g.getClipBounds();
- const auto corner = 3.0f;
-
- g.setColour (Colours::darkgrey);
- g.fillRoundedRectangle (bounds.withSizeKeepingCentre (20, bounds.getHeight() - 6).toFloat(),
- corner);
-
- const auto minDb = -50.0f;
- const auto maxDb = 6.0f;
- const auto levelInDb = Decibels::gainToDecibels (levelToDraw, minDb);
- const auto fractionOfHeight = jmap (levelInDb, minDb, maxDb, 0.0f, 1.0f);
- const auto trackBounds = bounds.withSizeKeepingCentre (16, bounds.getHeight() - 10).toFloat();
-
- g.setColour (Colours::black);
- const auto zeroDbIndicatorY = trackBounds.proportionOfHeight (jmap (0.0f,
- minDb,
- maxDb,
- 0.0f,
- 1.0f));
- g.drawHorizontalLine ((int) (trackBounds.getBottom() - zeroDbIndicatorY),
- trackBounds.getX(),
- trackBounds.getRight());
-
- g.setGradientFill (ColourGradient (Colours::darkgreen,
- { 0.0f, (float) bounds.getHeight() },
- Colours::darkred,
- { 0.0f, 0.0f },
- false));
-
- g.fillRoundedRectangle (trackBounds.withHeight (trackBounds.proportionOfHeight (fractionOfHeight))
- .withBottomY (trackBounds.getBottom()),
- corner);
-
- Image::BitmapData imgData { img, Image::BitmapData::readOnly };
- const auto pixelsWidth = imgData.pixelStride * imgData.width;
-
- auto* px = bitmap->getBits();
- const auto rowSpan = bitmap->getRowSpan();
- const auto numRows = bitmap->getHeight();
-
- for (int y = 0; y < numRows; ++y)
- std::memcpy (px + (y * rowSpan), imgData.getLinePointer (y), (size_t) pixelsWidth);
-
- return 1;
- }
-
- Steinberg::TPtrInt handledEmbeddedUIMessage (int msg,
- Steinberg::TPtrInt parm2,
- Steinberg::TPtrInt parm3) override
- {
- switch (msg)
- {
- case REAPER_FXEMBED_WM_IS_SUPPORTED:
- return 1;
-
- case REAPER_FXEMBED_WM_PAINT:
- return doPaint (reinterpret_cast<reaper::REAPER_FXEMBED_IBitmap*> (parm2),
- reinterpret_cast<reaper::REAPER_FXEMBED_DrawInfo*> (parm3));
-
- case REAPER_FXEMBED_WM_GETMINMAXINFO:
- return getSizeInfo (reinterpret_cast<reaper::REAPER_FXEMBED_SizeHints*> (parm3));
-
- // Implementing mouse behaviour is left as an exercise for the reaper, I mean reader
- case REAPER_FXEMBED_WM_CREATE: break;
- case REAPER_FXEMBED_WM_DESTROY: break;
- case REAPER_FXEMBED_WM_SETCURSOR: break;
- case REAPER_FXEMBED_WM_MOUSEMOVE: break;
- case REAPER_FXEMBED_WM_LBUTTONDOWN: break;
- case REAPER_FXEMBED_WM_LBUTTONUP: break;
- case REAPER_FXEMBED_WM_LBUTTONDBLCLK: break;
- case REAPER_FXEMBED_WM_RBUTTONDOWN: break;
- case REAPER_FXEMBED_WM_RBUTTONUP: break;
- case REAPER_FXEMBED_WM_RBUTTONDBLCLK: break;
- case REAPER_FXEMBED_WM_MOUSEWHEEL: break;
- }
-
- return 0;
- }
-
- AudioParameterFloat* gain = nullptr;
- void (*globalBypassFn) (int) = nullptr;
- EmbeddedUI embeddedUi { *this };
-
- std::atomic<float> storedLevel { 0.0f };
- float levelToDraw = 0.0f;
- };
|