Browse Source

Add new ReaperEmbeddedViewPluginDemo example

v6.1.6
reuk 4 years ago
parent
commit
4b0b245b55
No known key found for this signature in database GPG Key ID: 9ADCD339CFC98A11
4 changed files with 597 additions and 0 deletions
  1. +407
    -0
      examples/Plugins/ReaperEmbeddedViewPluginDemo.h
  2. +19
    -0
      examples/Plugins/extern/LICENSE.md
  3. +140
    -0
      examples/Plugins/extern/reaper_plugin_fx_embed.h
  4. +31
    -0
      examples/Plugins/extern/reaper_vst3_interfaces.h

+ 407
- 0
examples/Plugins/ReaperEmbeddedViewPluginDemo.h View File

@@ -0,0 +1,407 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - 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, vs2019, 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
#include <pluginterfaces/base/ftypes.h>
#include <pluginterfaces/base/funknown.h>
#include <pluginterfaces/vst/ivsthostapplication.h>
namespace reaper
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant",
"-Wunused-parameter")
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_GCC_LIKE
}
//==============================================================================
struct EmbeddedViewListener
{
virtual ~EmbeddedViewListener() = default;
virtual Steinberg::TPtrInt handledEmbeddedUIMessage (int msg,
Steinberg::TPtrInt parm2,
Steinberg::TPtrInt parm3) = 0;
};
//==============================================================================
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 (Steinberg::uint32) ++refCount; }
Steinberg::uint32 PLUGIN_API release() override { return (Steinberg::uint32) --refCount; }
Steinberg::tresult PLUGIN_API queryInterface (const Steinberg::TUID tuid, void** obj) override
{
if (std::memcmp (tuid, iid, sizeof (Steinberg::TUID)) == 0)
{
*obj = this;
return Steinberg::kResultOk;
}
*obj = nullptr;
return Steinberg::kNoInterface;
}
private:
EmbeddedViewListener& listener;
std::atomic<int> refCount { 1 };
};
//==============================================================================
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", "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 {}; }
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 (std::memcmp (tuid, embeddedUi.iid, sizeof (Steinberg::TUID)) == 0)
{
*obj = &embeddedUi;
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,
pointer_sized_int value,
void* ptr,
float opt) override
{
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)
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;
};

+ 19
- 0
examples/Plugins/extern/LICENSE.md View File

@@ -0,0 +1,19 @@
The files in this folder were copied from the [reaper-sdk
repository](https://github.com/justinfrankel/reaper-sdk) on Github. At the time
of writing, these files were distributed under the following license:

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

+ 140
- 0
examples/Plugins/extern/reaper_plugin_fx_embed.h View File

@@ -0,0 +1,140 @@
#ifndef _REAPER_PLUGIN_FX_EMBED_H_
#define _REAPER_PLUGIN_FX_EMBED_H_
/*
* to support via VST2: canDo("hasCockosEmbeddedUI") should return 0xbeef0000
* dispatcher will be called with opcode=effVendorSpecific, index=effEditDraw, value=parm2, ptr=(void*)(INT_PTR)parm3, opt=message (REAPER_FXEMBED_WM_*)
*
* to support via VST3: IController should support IReaperUIEmbedInterface, see reaper_vst3_interfaces.h
*
* to support via LV2: todo
*/
// these alias to win32's WM_*
#define REAPER_FXEMBED_WM_IS_SUPPORTED 0x0000
/* return 1 if embedding is supported and available
* return -1 if embedding is supported and unavailable
* return 0 if embedding is not supported
*/
#define REAPER_FXEMBED_WM_CREATE 0x0001 // called when embedding begins (return value ignored)
#define REAPER_FXEMBED_WM_DESTROY 0x0002 // called when embedding ends (return value ignored)
typedef struct REAPER_FXEMBED_DrawInfo // alias of REAPER_inline_positioninfo
{
int context; // 0=unknown (v6.23 and earlier), 1=TCP, 2=MCP
int dpi; // 0=unknown (v6.23 and earlier), otherwise 24.8 fixed point (256=100%)
int mousewheel_amt; // for REAPER_FXEMBED_WM_MOUSEWHEEL, 120 = step, typically
double _res2;
int width, height;
int mouse_x, mouse_y;
int flags; // REAPER_FXEMBED_DRAWINFO_FLAG_PAINT_OPTIONAL etc
int _res3;
void *spare[6];
} REAPER_FXEMBED_DrawInfo;
#define REAPER_FXEMBED_DRAWINFO_FLAG_PAINT_OPTIONAL 1
#define REAPER_FXEMBED_DRAWINFO_FLAG_LBUTTON_CAPTURED 0x10000
#define REAPER_FXEMBED_DRAWINFO_FLAG_RBUTTON_CAPTURED 0x20000
#define REAPER_FXEMBED_WM_PAINT 0x000F
/*
* draw embedded UI.
* parm2: REAPER_FXEMBED_IBitmap * to draw into. note
* parm3: REAPER_FXEMBED_DrawInfo *
*
* if flags has REAPER_FXEMBED_DRAWINFO_FLAG_PAINT_OPTIONAL set, update is optional. if no change since last draw, return 0.
* if flags has REAPER_FXEMBED_DRAWINFO_FLAG_LBUTTON_CAPTURED set, left mouse button is down and captured
* if flags has REAPER_FXEMBED_DRAWINFO_FLAG_RBUTTON_CAPTURED set, right mouse button is down and captured
*
* HiDPI:
* if REAPER_FXEMBED_IBitmap::Extended(REAPER_FXEMBED_EXT_GET_ADVISORY_SCALING,NULL) returns nonzero, then it is a 24.8 scalefactor for UI drawing
*
* return 1 if drawing occurred, 0 otherwise.
*
*/
#define REAPER_FXEMBED_WM_SETCURSOR 0x0020 // parm3: REAPER_FXEMBED_DrawInfo*. set mouse cursor and return REAPER_FXEMBED_RETNOTIFY_HANDLED, or return 0.
#define REAPER_FXEMBED_WM_GETMINMAXINFO 0x0024
/*
* get size hints. parm3 = (REAPER_FXEMBED_SizeHints*). return 1 if supported
* note that these are just hints, the actual size may vary
*/
typedef struct REAPER_FXEMBED_SizeHints { // alias to MINMAXINFO
int preferred_aspect; // 16.16 fixed point (65536 = 1:1, 32768 = 1:2, etc)
int minimum_aspect; // 16.16 fixed point
int _res1, _res2, _res3, _res4;
int min_width, min_height;
int max_width, max_height;
} REAPER_FXEMBED_SizeHints;
/*
* mouse messages
* parm3 = (REAPER_FXEMBED_DrawInfo*)
* capture is automatically set on mouse down, released on mouse up
* when not captured, will always receive a mousemove when exiting the window
*/
#define REAPER_FXEMBED_WM_MOUSEMOVE 0x0200
#define REAPER_FXEMBED_WM_LBUTTONDOWN 0x0201
#define REAPER_FXEMBED_WM_LBUTTONUP 0x0202
#define REAPER_FXEMBED_WM_LBUTTONDBLCLK 0x0203
#define REAPER_FXEMBED_WM_RBUTTONDOWN 0x0204
#define REAPER_FXEMBED_WM_RBUTTONUP 0x0205
#define REAPER_FXEMBED_WM_RBUTTONDBLCLK 0x0206
#define REAPER_FXEMBED_WM_MOUSEWHEEL 0x020A
/* REAPER_FXEMBED_WM_SETCURSOR should return REAPER_FXEMBED_RETNOTIFY_HANDLED if a cursor was set
*/
#define REAPER_FXEMBED_RETNOTIFY_HANDLED 0x0000001
/* if the mouse messages return with REAPER_FXEMBED_RETNOTIFY_INVALIDATE set, a non-optional
* redraw is initiated (generally sooner than the next timer-based redraw)
*/
#define REAPER_FXEMBED_RETNOTIFY_INVALIDATE 0x1000000
/*
* bitmap interface
* this is an alias of LICE_IBitmap etc from WDL/lice/lice.h
*
*/
#define REAPER_FXEMBED_RGBA(r,g,b,a) (((b)&0xff)|(((g)&0xff)<<8)|(((r)&0xff)<<16)|(((a)&0xff)<<24))
#define REAPER_FXEMBED_GETB(v) ((v)&0xff)
#define REAPER_FXEMBED_GETG(v) (((v)>>8)&0xff)
#define REAPER_FXEMBED_GETR(v) (((v)>>16)&0xff)
#define REAPER_FXEMBED_GETA(v) (((v)>>24)&0xff)
#ifdef __cplusplus
class REAPER_FXEMBED_IBitmap // alias of LICE_IBitmap
{
public:
virtual ~REAPER_FXEMBED_IBitmap() { }
virtual unsigned int *getBits()=0;
virtual int getWidth()=0;
virtual int getHeight()=0;
virtual int getRowSpan()=0; // includes any off-bitmap data. this is in sizeof(unsigned int) units, not bytes.
virtual bool isFlipped() { return false; }
virtual bool resize(int w, int h)=0;
virtual void *getDC() { return 0; } // do not use
virtual INT_PTR Extended(int id, void* data) { return 0; }
};
#endif
#define REAPER_FXEMBED_EXT_GET_ADVISORY_SCALING 0x2003 // data ignored, returns .8 fixed point. returns 0 if unscaled
#endif

+ 31
- 0
examples/Plugins/extern/reaper_vst3_interfaces.h View File

@@ -0,0 +1,31 @@
#ifndef _REAPER_VST3_INTERFACES_H_
#define _REAPER_VST3_INTERFACES_H_
class IReaperHostApplication : public FUnknown // available from IHostApplication in REAPER v5.02+
{
public:
// Gets a REAPER Extension API function by name, returns NULL is failed
virtual void* PLUGIN_API getReaperApi(CStringA funcname) = 0;
virtual void* PLUGIN_API getReaperParent(uint32 w) = 0; // get parent track(=1), take(=2), project(=3), fxdsp(=4), trackchan(=5)
// Multi-purpose function, returns NULL if unsupported
virtual void* PLUGIN_API reaperExtended(uint32 call, void *parm1, void *parm2, void *parm3) = 0;
static const FUID iid;
};
DECLARE_CLASS_IID (IReaperHostApplication, 0x79655E36, 0x77EE4267, 0xA573FEF7, 0x4912C27C)
class IReaperUIEmbedInterface : public FUnknown // supported by REAPER v6.24+, queried from plug-in IController
{
public:
// note: VST2 uses CanDo "hasCockosEmbeddedUI"==0xbeef0000, then opcode=effVendorSpecific, index=effEditDraw, opt=(float)msg, value=parm2, ptr=parm3
// see reaper_plugin_fx_embed.h
virtual Steinberg::TPtrInt embed_message(int msg, Steinberg::TPtrInt parm2, Steinberg::TPtrInt parm3) = 0;
static const FUID iid;
};
DECLARE_CLASS_IID (IReaperUIEmbedInterface, 0x049bf9e7, 0xbc74ead0, 0xc4101e86, 0x7f725981)
#endif

Loading…
Cancel
Save