File: ReaperEmbeddedViewPluginDemo.h

package info (click to toggle)
juce 8.0.10%2Bds-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 78,768 kB
  • sloc: cpp: 526,464; ansic: 159,952; java: 3,038; javascript: 847; xml: 269; python: 224; sh: 167; makefile: 84
file content (451 lines) | stat: -rw-r--r-- 17,878 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
/*
  ==============================================================================

   This file is part of the JUCE framework examples.
   Copyright (c) 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" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
   REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
   AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
   INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
   LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
   OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
   PERFORMANCE OF THIS SOFTWARE.

  ==============================================================================
*/

/*******************************************************************************
 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 VST2ClientExtensions 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;

    virtual void setGlobalBypassFunction (void (*) (int)) = 0;
};

JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnon-virtual-dtor")

//==============================================================================
class EmbeddedUI final : 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 VST2Extensions final : public VST2ClientExtensions
{
public:
    explicit VST2Extensions (EmbeddedViewListener& l)
        : listener (l) {}

    pointer_sized_int handleVstPluginCanDo (int32, pointer_sized_int, void* ptr, float) override
    {
        if (auto* str = static_cast<const char*> (ptr))
            for (auto* key : { "hasCockosEmbeddedUI", "hasCockosExtensions" })
                if (strcmp (str, key) == 0)
                    return (pointer_sized_int) 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) listener.handledEmbeddedUIMessage ((int) opt,
                                                                      (Steinberg::TPtrInt) value,
                                                                      (Steinberg::TPtrInt) ptr);
    }

    void handleVstHostCallbackAvailable (std::function<VstHostCallbackType>&& hostcb) override
    {
        char functionName[] = "BypassFxAllTracks";
        listener.setGlobalBypassFunction (reinterpret_cast<void (*) (int)> (hostcb ((int32_t) 0xdeadbeef, (int32_t) 0xdeadf00d, 0, functionName, 0.0)));
    }

private:
    EmbeddedViewListener& listener;
};

class VST3Extensions final : public VST3ClientExtensions
{
public:
    explicit VST3Extensions (EmbeddedViewListener& l)
        : listener (l) {}

    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"))
                listener.setGlobalBypassFunction (reinterpret_cast<void (*) (int)> (fnPtr));
        }
    }

private:
    EmbeddedViewListener& listener;
    EmbeddedUI embeddedUi { listener };
};

//==============================================================================
class Editor final : 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] { NullCheckedInvocation::invoke (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 final : public AudioProcessor,
                                     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());
    }

    VST2ClientExtensions* getVST2ClientExtensions() override { return &vst2Extensions; }
    VST3ClientExtensions* getVST3ClientExtensions() override { return &vst3Extensions; }

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;
    }

    void setGlobalBypassFunction (void (*fn) (int)) override { globalBypassFn = fn; }

    AudioParameterFloat* gain = nullptr;
    void (*globalBypassFn) (int) = nullptr;

    std::atomic<float> storedLevel { 0.0f };
    float levelToDraw = 0.0f;

    VST2Extensions vst2Extensions { *this };
    VST3Extensions vst3Extensions { *this };
};