The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

424 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. The code included in this file is provided under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  7. To use, copy, modify, and/or distribute this software for any purpose with or
  8. without fee is hereby granted provided that the above copyright notice and
  9. this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
  11. WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
  12. PURPOSE, ARE DISCLAIMED.
  13. ==============================================================================
  14. */
  15. /*******************************************************************************
  16. The block below describes the properties of this PIP. A PIP is a short snippet
  17. of code that can be read by the Projucer and used to generate a JUCE project.
  18. BEGIN_JUCE_PIP_METADATA
  19. name: ReaperEmbeddedViewDemo
  20. version: 1.0.0
  21. vendor: JUCE
  22. website: http://juce.com
  23. description: An audio plugin which embeds a secondary view in VST2 and
  24. VST3 formats in REAPER
  25. dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
  26. juce_audio_plugin_client, juce_audio_processors,
  27. juce_audio_utils, juce_core, juce_data_structures,
  28. juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
  29. exporters: xcode_mac, vs2022, linux_make
  30. moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
  31. type: AudioProcessor
  32. mainClass: ReaperEmbeddedViewDemo
  33. useLocalCopy: 1
  34. END_JUCE_PIP_METADATA
  35. *******************************************************************************/
  36. /* This demo shows how to use the VSTCallbackHandler and VST3ClientExtensions
  37. classes to provide extended functionality in compatible VST/VST3 hosts.
  38. If this project is built as a VST or VST3 plugin and loaded in REAPER
  39. 6.29 or higher, it will provide an embedded level meter in the track
  40. control panel. To enable the embedded view, right-click on the plugin
  41. and select "Show embedded UI in TCP".
  42. The plugin's editor also include a button which can be used to toggle
  43. all inserts on and off.
  44. */
  45. #pragma once
  46. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wshadow-field-in-constructor",
  47. "-Wnon-virtual-dtor")
  48. #include <pluginterfaces/base/ftypes.h>
  49. #include <pluginterfaces/base/funknown.h>
  50. #include <pluginterfaces/vst/ivsthostapplication.h>
  51. #include <pluginterfaces/vst2.x/aeffect.h>
  52. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  53. namespace reaper
  54. {
  55. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant",
  56. "-Wunused-parameter",
  57. "-Wnon-virtual-dtor")
  58. JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100)
  59. using namespace Steinberg;
  60. using INT_PTR = pointer_sized_int;
  61. using uint32 = Steinberg::uint32;
  62. #include "extern/reaper_plugin_fx_embed.h"
  63. #include "extern/reaper_vst3_interfaces.h"
  64. //==============================================================================
  65. /* These should live in a file which is guaranteed to be compiled only once
  66. (i.e. a .cpp file, normally). This demo is a bit special, because we know
  67. that this header will only be included in a single translation unit.
  68. */
  69. DEF_CLASS_IID (IReaperHostApplication)
  70. DEF_CLASS_IID (IReaperUIEmbedInterface)
  71. JUCE_END_IGNORE_WARNINGS_MSVC
  72. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  73. }
  74. //==============================================================================
  75. struct EmbeddedViewListener
  76. {
  77. virtual ~EmbeddedViewListener() = default;
  78. virtual Steinberg::TPtrInt handledEmbeddedUIMessage (int msg,
  79. Steinberg::TPtrInt parm2,
  80. Steinberg::TPtrInt parm3) = 0;
  81. };
  82. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnon-virtual-dtor")
  83. //==============================================================================
  84. class EmbeddedUI : public reaper::IReaperUIEmbedInterface
  85. {
  86. public:
  87. explicit EmbeddedUI (EmbeddedViewListener& demo) : listener (demo) {}
  88. Steinberg::TPtrInt embed_message (int msg,
  89. Steinberg::TPtrInt parm2,
  90. Steinberg::TPtrInt parm3) override
  91. {
  92. return listener.handledEmbeddedUIMessage (msg, parm2, parm3);
  93. }
  94. Steinberg::uint32 PLUGIN_API addRef() override { return ++refCount; }
  95. Steinberg::uint32 PLUGIN_API release() override { return --refCount; }
  96. Steinberg::tresult PLUGIN_API queryInterface (const Steinberg::TUID tuid, void** obj) override
  97. {
  98. if (std::memcmp (tuid, iid, sizeof (Steinberg::TUID)) == 0)
  99. {
  100. ++refCount;
  101. *obj = this;
  102. return Steinberg::kResultOk;
  103. }
  104. *obj = nullptr;
  105. return Steinberg::kNoInterface;
  106. }
  107. private:
  108. EmbeddedViewListener& listener;
  109. std::atomic<Steinberg::uint32> refCount { 1 };
  110. };
  111. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  112. //==============================================================================
  113. class Editor : public AudioProcessorEditor
  114. {
  115. public:
  116. explicit Editor (AudioProcessor& proc,
  117. AudioParameterFloat& param,
  118. void (*globalBypass) (int))
  119. : AudioProcessorEditor (proc), attachment (param, slider)
  120. {
  121. addAndMakeVisible (slider);
  122. addAndMakeVisible (bypassButton);
  123. // Clicking will bypass *everything*
  124. bypassButton.onClick = [globalBypass] { if (globalBypass != nullptr) globalBypass (-1); };
  125. setSize (300, 80);
  126. }
  127. void resized() override
  128. {
  129. auto b = getLocalBounds();
  130. slider.setBounds (b.removeFromTop (40));
  131. bypassButton.setBounds (b);
  132. }
  133. void paint (Graphics& g) override
  134. {
  135. g.fillAll (Colours::darkgrey);
  136. }
  137. private:
  138. Slider slider;
  139. TextButton bypassButton { "global bypass" };
  140. SliderParameterAttachment attachment;
  141. };
  142. //==============================================================================
  143. class ReaperEmbeddedViewDemo : public AudioProcessor,
  144. public VSTCallbackHandler,
  145. public VST3ClientExtensions,
  146. private EmbeddedViewListener,
  147. private Timer
  148. {
  149. public:
  150. ReaperEmbeddedViewDemo()
  151. {
  152. addParameter (gain = new AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f));
  153. startTimerHz (60);
  154. }
  155. void prepareToPlay (double, int) override {}
  156. void reset() override {}
  157. void releaseResources() override {}
  158. void processBlock (AudioBuffer<float>& audio, MidiBuffer&) override { processBlockImpl (audio); }
  159. void processBlock (AudioBuffer<double>& audio, MidiBuffer&) override { processBlockImpl (audio); }
  160. //==============================================================================
  161. AudioProcessorEditor* createEditor() override { return new Editor (*this, *gain, globalBypassFn); }
  162. bool hasEditor() const override { return true; }
  163. //==============================================================================
  164. const String getName() const override { return "ReaperEmbeddedViewDemo"; }
  165. bool acceptsMidi() const override { return false; }
  166. bool producesMidi() const override { return false; }
  167. bool isMidiEffect() const override { return false; }
  168. double getTailLengthSeconds() const override { return 0.0; }
  169. //==============================================================================
  170. int getNumPrograms() override { return 1; }
  171. int getCurrentProgram() override { return 0; }
  172. void setCurrentProgram (int) override {}
  173. const String getProgramName (int) override { return "None"; }
  174. void changeProgramName (int, const String&) override {}
  175. //==============================================================================
  176. void getStateInformation (MemoryBlock& destData) override
  177. {
  178. MemoryOutputStream (destData, true).writeFloat (*gain);
  179. }
  180. void setStateInformation (const void* data, int sizeInBytes) override
  181. {
  182. gain->setValueNotifyingHost (MemoryInputStream (data,
  183. static_cast<size_t> (sizeInBytes),
  184. false).readFloat());
  185. }
  186. int32_t queryIEditController (const Steinberg::TUID tuid, void** obj) override
  187. {
  188. if (embeddedUi.queryInterface (tuid, obj) == Steinberg::kResultOk)
  189. return Steinberg::kResultOk;
  190. *obj = nullptr;
  191. return Steinberg::kNoInterface;
  192. }
  193. void setIHostApplication (Steinberg::FUnknown* ptr) override
  194. {
  195. if (ptr == nullptr)
  196. return;
  197. void* objPtr = nullptr;
  198. if (ptr->queryInterface (reaper::IReaperHostApplication::iid, &objPtr) == Steinberg::kResultOk)
  199. {
  200. if (void* fnPtr = static_cast<reaper::IReaperHostApplication*> (objPtr)->getReaperApi ("BypassFxAllTracks"))
  201. globalBypassFn = reinterpret_cast<void (*) (int)> (fnPtr);
  202. }
  203. }
  204. pointer_sized_int handleVstPluginCanDo (int32, pointer_sized_int, void* ptr, float) override
  205. {
  206. if (auto* str = static_cast<const char*> (ptr))
  207. {
  208. if (strcmp (str, "hasCockosEmbeddedUI") == 0)
  209. return 0xbeef0000;
  210. if (strcmp (str, "hasCockosExtensions") == 0)
  211. return 0xbeef0000;
  212. }
  213. return 0;
  214. }
  215. pointer_sized_int handleVstManufacturerSpecific (int32 index,
  216. pointer_sized_int value,
  217. void* ptr,
  218. float opt) override
  219. {
  220. // The docstring at the top of reaper_plugin_fx_embed.h specifies
  221. // that the index will always be effEditDraw, which is now deprecated.
  222. if (index != __effEditDrawDeprecated)
  223. return 0;
  224. return (pointer_sized_int) handledEmbeddedUIMessage ((int) opt,
  225. (Steinberg::TPtrInt) value,
  226. (Steinberg::TPtrInt) ptr);
  227. }
  228. void handleVstHostCallbackAvailable (std::function<VstHostCallbackType>&& hostcb) override
  229. {
  230. char functionName[] = "BypassFxAllTracks";
  231. globalBypassFn = reinterpret_cast<void (*) (int)> (hostcb ((int32_t) 0xdeadbeef, (int32_t) 0xdeadf00d, 0, functionName, 0.0));
  232. }
  233. private:
  234. template <typename Float>
  235. void processBlockImpl (AudioBuffer<Float>& audio)
  236. {
  237. audio.applyGain (*gain);
  238. const auto minMax = audio.findMinMax (0, 0, audio.getNumSamples());
  239. const auto newMax = (float) std::max (std::abs (minMax.getStart()), std::abs (minMax.getEnd()));
  240. auto loaded = storedLevel.load();
  241. while (loaded < newMax && ! storedLevel.compare_exchange_weak (loaded, newMax)) {}
  242. }
  243. void timerCallback() override
  244. {
  245. levelToDraw = std::max (levelToDraw * 0.95f, storedLevel.exchange (0.0f));
  246. }
  247. Steinberg::TPtrInt getSizeInfo (reaper::REAPER_FXEMBED_SizeHints* sizeHints)
  248. {
  249. if (sizeHints == nullptr)
  250. return 0;
  251. sizeHints->preferred_aspect = 1 << 16;
  252. sizeHints->minimum_aspect = 1 << 16;
  253. sizeHints->min_height = sizeHints->min_width = 50;
  254. sizeHints->max_height = sizeHints->max_width = 1000;
  255. return 1;
  256. }
  257. Steinberg::TPtrInt doPaint (reaper::REAPER_FXEMBED_IBitmap* bitmap,
  258. reaper::REAPER_FXEMBED_DrawInfo* drawInfo)
  259. {
  260. if (bitmap == nullptr || drawInfo == nullptr || bitmap->getWidth() <= 0 || bitmap->getHeight() <= 0)
  261. return 0;
  262. Image img (juce::Image::PixelFormat::ARGB, bitmap->getWidth(), bitmap->getHeight(), true);
  263. Graphics g (img);
  264. g.fillAll (Colours::black);
  265. const auto bounds = g.getClipBounds();
  266. const auto corner = 3.0f;
  267. g.setColour (Colours::darkgrey);
  268. g.fillRoundedRectangle (bounds.withSizeKeepingCentre (20, bounds.getHeight() - 6).toFloat(),
  269. corner);
  270. const auto minDb = -50.0f;
  271. const auto maxDb = 6.0f;
  272. const auto levelInDb = Decibels::gainToDecibels (levelToDraw, minDb);
  273. const auto fractionOfHeight = jmap (levelInDb, minDb, maxDb, 0.0f, 1.0f);
  274. const auto trackBounds = bounds.withSizeKeepingCentre (16, bounds.getHeight() - 10).toFloat();
  275. g.setColour (Colours::black);
  276. const auto zeroDbIndicatorY = trackBounds.proportionOfHeight (jmap (0.0f,
  277. minDb,
  278. maxDb,
  279. 0.0f,
  280. 1.0f));
  281. g.drawHorizontalLine ((int) (trackBounds.getBottom() - zeroDbIndicatorY),
  282. trackBounds.getX(),
  283. trackBounds.getRight());
  284. g.setGradientFill (ColourGradient (Colours::darkgreen,
  285. { 0.0f, (float) bounds.getHeight() },
  286. Colours::darkred,
  287. { 0.0f, 0.0f },
  288. false));
  289. g.fillRoundedRectangle (trackBounds.withHeight (trackBounds.proportionOfHeight (fractionOfHeight))
  290. .withBottomY (trackBounds.getBottom()),
  291. corner);
  292. Image::BitmapData imgData { img, Image::BitmapData::readOnly };
  293. const auto pixelsWidth = imgData.pixelStride * imgData.width;
  294. auto* px = bitmap->getBits();
  295. const auto rowSpan = bitmap->getRowSpan();
  296. const auto numRows = bitmap->getHeight();
  297. for (int y = 0; y < numRows; ++y)
  298. std::memcpy (px + (y * rowSpan), imgData.getLinePointer (y), (size_t) pixelsWidth);
  299. return 1;
  300. }
  301. Steinberg::TPtrInt handledEmbeddedUIMessage (int msg,
  302. Steinberg::TPtrInt parm2,
  303. Steinberg::TPtrInt parm3) override
  304. {
  305. switch (msg)
  306. {
  307. case REAPER_FXEMBED_WM_IS_SUPPORTED:
  308. return 1;
  309. case REAPER_FXEMBED_WM_PAINT:
  310. return doPaint (reinterpret_cast<reaper::REAPER_FXEMBED_IBitmap*> (parm2),
  311. reinterpret_cast<reaper::REAPER_FXEMBED_DrawInfo*> (parm3));
  312. case REAPER_FXEMBED_WM_GETMINMAXINFO:
  313. return getSizeInfo (reinterpret_cast<reaper::REAPER_FXEMBED_SizeHints*> (parm3));
  314. // Implementing mouse behaviour is left as an exercise for the reaper, I mean reader
  315. case REAPER_FXEMBED_WM_CREATE: break;
  316. case REAPER_FXEMBED_WM_DESTROY: break;
  317. case REAPER_FXEMBED_WM_SETCURSOR: break;
  318. case REAPER_FXEMBED_WM_MOUSEMOVE: break;
  319. case REAPER_FXEMBED_WM_LBUTTONDOWN: break;
  320. case REAPER_FXEMBED_WM_LBUTTONUP: break;
  321. case REAPER_FXEMBED_WM_LBUTTONDBLCLK: break;
  322. case REAPER_FXEMBED_WM_RBUTTONDOWN: break;
  323. case REAPER_FXEMBED_WM_RBUTTONUP: break;
  324. case REAPER_FXEMBED_WM_RBUTTONDBLCLK: break;
  325. case REAPER_FXEMBED_WM_MOUSEWHEEL: break;
  326. }
  327. return 0;
  328. }
  329. AudioParameterFloat* gain = nullptr;
  330. void (*globalBypassFn) (int) = nullptr;
  331. EmbeddedUI embeddedUi { *this };
  332. std::atomic<float> storedLevel { 0.0f };
  333. float levelToDraw = 0.0f;
  334. };