Old 04-29-2021, 07:14 PM   #1
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default IReaperUIEmbedInterface question

So I have spent the past week or so trying to minimally/generically patch JUCE so that an upstream patch can be landed with support for REAPER's VST3 interfaces -- particularly interested in TCP FX embedding.

(Have spoken briefly w/ JUCE devs about this, there is some interest/receptiveness on their side depending on implementation)

To get the base REAPER SDK working (IReaperApplication->getReaperApi()) with a "reaper.ShowConsoleMsg()" was very few changes.

---

However I wanted to ask two quick questions, as trying to get IReaperUIEmbedInterface functional did not work based on what I assumed.
  1. In "reaper_vst3_interfaces.h" and "reaper_plugin_fx_embed.h", the interface given for IReaperUIEmbedInterface is IController. But IController is a VSTGUI-specific interface, I assumed this was a typo and instead it was IEditController from VST3 SDK. Is this right or wrong?

    Ref: https://i.imgur.com/uEgPNVO.png
    -------
  2. If it's right, part of what I have done is modified JUCE's JuceVST3EditController (see below) to subclass/implement IReaperUIEmbedInterface, and I have overriden the ::embed_message() method, but nothing is ever called. Is there any way of debugging this from REAPER?

Here's the code for:
Would be grateful for any advise/feedback from folks who have a better clue about what they're doing than me. Getting this working and publishing it for others would be stellar!

JUCE patches made:


__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)

Last edited by gxray; 04-29-2021 at 07:20 PM.
gxray is offline   Reply With Quote
Old 04-30-2021, 07:42 AM   #2
robbert-vdh
Human being with feelings
 
Join Date: Nov 2020
Posts: 277
Default

I've never looked at these REAPER VST3 extensions, but did you add IReaperUIEmbedInterface to your edit controller's query interface?
robbert-vdh is offline   Reply With Quote
Old 04-30-2021, 07:48 AM   #3
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Quote:
Originally Posted by robbert-vdh View Post
I've never looked at these REAPER VST3 extensions, but did you add IReaperUIEmbedInterface to your edit controller's query interface?
Yeah -- the diff does not provide enough context I think, but the actual queryInterface changes are here:

https://github.com/GavinRay97/JUCE-r....cpp#L602-L667

PHP Code:
#ifdef JUCE_VST3_ENABLE_PASS_HOST_CONTEXT_TO_AUDIO_PROCESSOR_ON_INITIALIZE
#include "../../include/vendor/reaper-
DEF_CLASS_IID(IReaperUIEmbedInterface)
#endif
class JuceVST3EditController : public Vst::EditController,
                               public 
Vst::IMidiMapping,
                               public 
Vst::IUnitInfo,
                               public 
Vst::ChannelContext::IInfoListener,
                               public 
AudioProcessorListener
#ifdef JUCE_VST3_ENABLE_PASS_HOST_CONTEXT_TO_AUDIO_PROCESSOR_ON_INITIALIZE
                             
, public IReaperUIEmbedInterface
#endif
{
public:
    
JuceVST3EditController (Vst::IHostApplicationhost)
    {
        if (
host != nullptr)
            
host->queryInterface (FUnknown::iid, (void**) &hostContext);
    }

#ifdef JUCE_VST3_ENABLE_PASS_HOST_CONTEXT_TO_AUDIO_PROCESSOR_ON_INITIALIZE
    
Steinberg::TPtrInt embed_message(int msgSteinberg::TPtrInt parm2Steinberg::TPtrInt parm3override
    
{
        return 
this->audioProcessor.get()->get()->handleReaperEmbedMessage(msgparm2parm3);
    }
#endif
    //==============================================================================
    
static const FUID iid;

    
//==============================================================================
    
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Winconsistent-missing-override")

    
REFCOUNT_METHODS (ComponentBase)

    
JUCE_END_IGNORE_WARNINGS_GCC_LIKE

    tresult PLUGIN_API queryInterface 
(const TUID targetIIDvoid** objoverride
    
{
        
TEST_FOR_AND_RETURN_IF_VALID (targetIIDFObject)
        
TEST_FOR_AND_RETURN_IF_VALID (targetIIDJuceVST3EditController)
        
TEST_FOR_AND_RETURN_IF_VALID (targetIIDVst::IEditController)
        
TEST_FOR_AND_RETURN_IF_VALID (targetIIDVst::IEditController2)
        
TEST_FOR_AND_RETURN_IF_VALID (targetIIDVst::IConnectionPoint)
        
TEST_FOR_AND_RETURN_IF_VALID (targetIIDVst::IMidiMapping)
        
TEST_FOR_AND_RETURN_IF_VALID (targetIIDVst::IUnitInfo)
        
TEST_FOR_AND_RETURN_IF_VALID (targetIIDVst::ChannelContext::IInfoListener)
        
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIIDIPluginBaseVst::IEditController)
        
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIIDIDependentVst::IEditController)
        
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIIDFUnknownVst::IEditController)

#ifdef JUCE_VST3_ENABLE_PASS_HOST_CONTEXT_TO_AUDIO_PROCESSOR_ON_INITIALIZE
        
TEST_FOR_AND_RETURN_IF_VALID (targetIIDIReaperUIEmbedInterface)
#endif

        
if (doUIDsMatch (targetIIDJuceAudioProcessor::iid))
        {
            
audioProcessor->addRef();
            *
obj audioProcessor;
            return 
kResultOk;
        }

        *
obj nullptr;
        return 
kNoInterface;
    } 
Where the macro TEST_FOR_AND_RETURN_IF_VALID is:
https://github.com/juce-framework/JU...T3Common.h#L49

PHP Code:
#define TEST_FOR_AND_RETURN_IF_VALID(iidToTest, ClassType) \
    
if (doUIDsMatch (iidToTestClassType::iid)) \
    { \
        
addRef(); \
        *
obj dynamic_cast<ClassType*> (this); \
        return 
Steinberg::kResultOk; \
    } 
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)

Last edited by gxray; 04-30-2021 at 12:49 PM.
gxray is offline   Reply With Quote
Old 04-30-2021, 08:09 AM   #4
schwa
Administrator
 
schwa's Avatar
 
Join Date: Mar 2007
Location: NY
Posts: 16,954
Default

Quote:
Originally Posted by gxray View Post
In "reaper_vst3_interfaces.h" and "reaper_plugin_fx_embed.h", the interface given for IReaperUIEmbedInterface is IController. But IController is a VSTGUI-specific interface, I assumed this was a typo and instead it was IEditController from VST3 SDK. Is this right or wrong?
You're correct, this is a typo in the documentation. The plugin's IEditController should implement IReaperUIEmbedInterface.
schwa is offline   Reply With Quote
Old 04-30-2021, 08:20 AM   #5
schwa
Administrator
 
schwa's Avatar
 
Join Date: Mar 2007
Location: NY
Posts: 16,954
Default

Quote:
Originally Posted by gxray View Post
I have overriden the ::embed_message() method, but nothing is ever called
This is a bug on our side, sorry for the trouble! We'll fix this for the next +dev build.
schwa is offline   Reply With Quote
Old 04-30-2021, 08:40 AM   #6
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Quote:
Originally Posted by schwa View Post
This is a bug on our side, sorry for the trouble! We'll fix this for the next +dev build.
Thank you Schwa -- appreciate you guys a ton!!

Will keep doing my part to keep convo going with JUCE folks and see if we can get an acceptable generic implementation merged upstream, so that all JUCE projects have optional out-of-the-box functionality for REAPER UI embedding + SDK hosting in both VST2 + VST3

<3
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)
gxray is offline   Reply With Quote
Old 04-30-2021, 08:56 PM   #7
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Haven't had time to test thoroughly, but the greyed-out "Show/Embed FX in TCP" option is now clickable in my plugin FX slot in 6.30 dev, and after enabling it and trying to expand the track the plugin crashed.

Which is good! It means I fucked up, but that that REAPER let me fuck up.
So it sounds like this is going to be a fun and productive weekend =)

Will continue dev efforts after brief sleep.
Once again, really appreciate you folks
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)
gxray is offline   Reply With Quote
Old 05-03-2021, 07:34 AM   #8
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

I'm going to go off topic from original post here if that's alright

---

I'd like to ask for some basic C++ code-review/help from anyone here experienced and willing to spare it.

I have been working with JUCE devs on allowing users to implement REAPER's VST3 interfaces for TCP/MCP FX embedding and access to it's C++ API.

(So that we can get it merged upstream and all JUCE VST2/VST3 plugins can optionally choose to be REAPER-capable by default)

Got a rough version working but it was hacky (see below), then after talking to one of the JUCE dev's have been swapping it out for his implementation idea.

Problem is, I'm hitting the limits of my knowledge trying to debug why this isn't working.


  • So I have modified JUCE's IEditController implementation to allow runtime registration of VST3 interfaces and lookups during it's queryInterface() method.
  • The instance of the REAPER IReaperUIEmbedInterface class is getting put in here.

PHP Code:
std::string tuidToFUIDString(const TUID iid) {
    const 
auto targetFUID FUID::fromTUID(iid);
    
char tmpBuf[128];
    
targetFUID.toString(tmpBuf);
    return 
std::string(tmpBuf);
}

class 
VST3InterfaceContainer {
  
typedef std::string TUIDRegString;
public:
  
std::unordered_map<TUIDRegStringSteinberg::FUnknown*> vst3InterfaceMap;

  
template<class T>
  
bool registerInterface(const TUID iidT klazz) {
    const 
auto fuidStr tuidToFUIDString(iid);
    
//this->tuidToClassnameMap.insert_or_assign(fuidStr, std::string(typeid(T).name()));
    
const auto result this->vst3InterfaceMap.insert_or_assign(fuidStrdynamic_cast<FObject*>(klazz));
    return 
result.second;
  }

  
Steinberg::FUnknownfindInterface(const TUID iid) {
    const 
auto fuidStr tuidToFUIDString(iid);
    
auto klazz this->vst3InterfaceMap.find(fuidStr);
    if (
klazz != this->vst3InterfaceMap.end())
      return 
klazz->second;
    return 
nullptr;
  }
}; 
PHP Code:
class JuceVST3EditController // ... bunch of VST3 and JUCE classes
                               
public VST3InterfaceContainer
{
  
tresult PLUGIN_API queryInterface (const TUID targetIIDvoid** objoverride
  
{
      
TEST_FOR_AND_RETURN_IF_VALID (targetIIDFObject)
      
// etc..

      
auto klazz this->findInterface(targetIID);
      if (
klazz != nullptr) {
          
klazz->addRef();
          *
obj klazz;
          return 
kResultOk;
      }

      if (
doUIDsMatch (targetIIDJuceAudioProcessor::iid)) {
          
audioProcessor->addRef();
          *
obj audioProcessor;
          return 
kResultOk;
      }

      *
obj nullptr;
      return 
kNoInterface;
  }

  • In the factory instance for the plugin, I've just done this to test instantiating and inserting an instance of my class into this std::unordered_map

PHP Code:
static FUnknowncreateControllerInstance (Vst::IHostApplicationhost)
{
    
auto editController = new JuceVST3EditController (host);
    const 
auto reaperEditController = new ReaperVST3EditController();
    
editController->registerInterface(ReaperVST3EditController::iidreaperEditController);
    return 
static_cast<Vst::IEditController*>(editController);

  • And the actual implementation of the IReaperUIEmbedInterface class (ReaperVST3EditController) is:

PHP Code:
#pragma once
#include <juce_core/juce_core.h>
#include <juce_audio_processors/format_types/VST3_SDK/base/source/fobject.h>
#include <juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/ftypes.h>

#include "../ReaperVST3InterfaceWrapper.hpp"
#include "../include/vendor/reaper-sdk/sdk/reaper_plugin_fx_embed.h"

// I'm defining the same class IID as the IReaperUIEmbedInterface so that this implementation class
// gets resolved by this IID during lookups
DEF_CLASS_IID(IReaperUIEmbedInterface)
DECLARE_CLASS_IID(ReaperVST3EditController0x049bf9e70xbc74ead00xc4101e860x7f725981)
class 
ReaperVST3EditController : public Steinberg::FObject,
                                 public 
IReaperUIEmbedInterface {
private:
    
// PLUGIN_API = __stdcall
    
virtual Steinberg::uint32 PLUGIN_API addRef () override { return Steinberg::FObject::addRef (); }
    
virtual Steinberg::uint32 PLUGIN_API release () override { return Steinberg::FObject::release (); }
    
public:
    
Steinberg::TPtrInt embed_message(int msgSteinberg::TPtrInt parm2Steinberg::TPtrInt parm3override
    
{
        
DBG("[ReaperVST3EditController::embed_message] msg = " << msg << " parm2 = " << parm2 << " parm3 = " << parm3);
        switch (
msg) {
        case 
REAPER_FXEMBED_WM_IS_SUPPORTED:
            return 
0;
        }
        return 
0;
    };

    
tresult queryInterface(const TUID _iidvoid** objoverride
    
{
        return 
Steinberg::kResultOk;
    };

    static const 
FUID iid;
};

DEF_CLASS_IID(ReaperVST3EditController
================================================== ==========

Here's the problem:
  • when debugging this and setting breakpoints, it DOES call the constructor of ReaperVST3EditController
  • And the queryInterface() *obj gets set to the pointer to the instance of this ReaperVST3EditController
  • It enables the "Show UI in TCP" that's greyed out, and draws a black box
  • But the ReaperVST3EditController::embed_message() and every other function on the class is never called

Wtf is going on, how do I debug this? =/



__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)

Last edited by gxray; 05-03-2021 at 07:43 AM.
gxray is offline   Reply With Quote
Old 05-03-2021, 07:50 AM   #9
schwa
Administrator
 
schwa's Avatar
 
Join Date: Mar 2007
Location: NY
Posts: 16,954
Default

If you want to post a link to a test copy of your plugin, we can let you know what it looks like from REAPER's side.
schwa is offline   Reply With Quote
Old 05-03-2021, 07:58 AM   #10
schwa
Administrator
 
schwa's Avatar
 
Join Date: Mar 2007
Location: NY
Posts: 16,954
Default

Quote:
Originally Posted by gxray View Post
But the ReaperVST3EditController::embed_message() and every other function on the class is never called
Never called at all, or never called after the initial REAPER_FXEMBED_WM_IS_SUPPORTED query? REAPER won't call again if the initial query returns 0.

This is what the code looks like from REAPER's side. if queryInterface() is being called for IReaperUIEmbedInterface and returning properly, embed_message() should be called immediately.

Code:
          IReaperUIEmbedInterface *embed_ui=NULL;
          if (m_vst3->m_controller->queryInterface(IReaperUIEmbedInterface::iid,(void **)&embed_ui) == kResultTrue && embed_ui)
          {
            if (embed_ui->embed_message(0,0,0)) // 0=REAPER_FXEMBED_WM_IS_SUPPORTED
            {
              m_vst3->m_embed_ui = embed_ui;
            }
            else
            {
              embed_ui->release();
            }
          }

If your code is receiving the interface query from REAPER, as your screenshot/breakpoint suggests, my guess is that it's actually working properly, but you're not seeing the debug message in embed_message() for some reason.

I suppose another possibility is that your interface registration code is returning the IReaperUIEmbedInterface interface for the wrong query. You could dump the actual IID there to make sure.

Last edited by schwa; 05-03-2021 at 08:15 AM.
schwa is offline   Reply With Quote
Old 05-03-2021, 09:32 AM   #11
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Really appreciate you responding here and the offer to give a glimpse from REAPER's end.

Let me push current work up to github repo, then will post here =)

I touched C++ first time maybe 6 weeks ago (to add some popular single-header C++ imgui components to ReaImgui & learn REAPER's native API) and that was my "hello world" then it was onto this, this language is kicking my butt. Not an easy one to learn!

Quote:
Originally Posted by schwa View Post
Never called at all, or never called after the initial REAPER_FXEMBED_WM_IS_SUPPORTED query?
The actual invocation of ReaperVST3EditController::embed_message() is never called.

The code you see from the first post, where I had hackily #ifdef'ed the subclassing/extension of IReaperUIEmbedInterface onto JUCE's actual JuceVST3EditController did work!

Right after the v6.28+dev0430 patch (thank you again)

Quote:
Originally Posted by schwa View Post
If your code is receiving the interface query from REAPER, as your screenshot/breakpoint suggests, my guess is that it's actually working properly, but you're not seeing the debug message in embed_message() for some reason.
Hmm -- I think something is maybe wrong with my actual implementation of either the class, or maybe the VST3 COM IID registration stuff? ​(see below in the logs of targetIID)

Quote:
Originally Posted by schwa View Post
I suppose another possibility is that your interface registration code is returning the IReaperUIEmbedInterface interface for the wrong query. You could dump the actual IID there to make sure.

So I had added this statement inside of queryInterface():

PHP Code:
class JuceVST3EditController
{
tresult PLUGIN_API queryInterface(const TUID targetIIDvoid** objoverride
{
    
auto fuidStr tuidToFUIDString(targetIID);
    
DBG("[JuceVST3Component::queryInterface] targetIID=" << fuidStr);
    
// ​TEST_FOR_AND_RETURN_IF_VALID = if (doUIDsMatch (targetIID, ClassName::iid)) { } same impl code as below
    
TEST_FOR_AND_RETURN_IF_VALID(targetIIDFObject)
    
TEST_FOR_AND_RETURN_IF_VALID(targetIIDJuceVST3EditController)
    
TEST_FOR_AND_RETURN_IF_VALID(targetIIDVst::IEditController)
    
TEST_FOR_AND_RETURN_IF_VALID(targetIIDVst::IEditController2)
    
TEST_FOR_AND_RETURN_IF_VALID(targetIIDVst::IConnectionPoint)
    
TEST_FOR_AND_RETURN_IF_VALID(targetIIDVst::IMidiMapping)
    
TEST_FOR_AND_RETURN_IF_VALID(targetIIDVst::IUnitInfo)
    
TEST_FOR_AND_RETURN_IF_VALID(targetIIDVst::ChannelContext::IInfoListener)
    
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID(targetIIDIPluginBaseVst::IEditController)
    
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID(targetIIDIDependentVst::IEditController)
    
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID(targetIIDFUnknownVst::IEditController)

    
auto klazz this->findInterface(targetIID);
    if (
klazz != nullptr) {
        
DBG("[JuceVST3Component::queryInterface] FOUND MATCH \n targetIID=" << fuidStr << "\n klazzIID="
                                                                            
<< tuidToFUIDString(klazz->iid));
        
klazz->addRef();
        *
obj klazz;
        return 
Steinberg::kResultOk;
    }

    if (
doUIDsMatch(targetIIDJuceAudioProcessor::iid)) {
        
audioProcessor->addRef();
        *
obj audioProcessor;
        return 
kResultOk;
    }

    *
obj nullptr;
    return 
kNoInterface;
}

And this is what it logs:

PHP Code:
[JuceVST3Component::queryInterfacetargetIID=DCD7BBE37742448DA874AACC979C759E
[JuceVST3Component::queryInterfacetargetIID=70A4156F6E6E4026989148BFAA60D8D1
[JuceVST3Component::queryInterfacetargetIID=ABCDEF011234ABCD4A75636544656D30
[JuceVST3Component::queryInterfacetargetIID=B7F8F85941234872911695814F3721A3
[JuceVST3Component::queryInterfacetargetIID=1F2F76D3BFFB4B96B99527A55EBCCEF4
[JuceVST3Component::queryInterfacetargetIID=3D4BD6B5913A4FD2A886E768A5EB92C1
[JuceVST3Component::queryInterfacetargetIID=31E29A7AE55043AD8B95B9B8DA1FBE1E
[JuceVST3Component::queryInterfacetargetIID=C3B17BC02C17449480293402FBC4BBF8
[JuceVST3Component::queryInterfacetargetIID=0F1947818D984ADABBA0C1EFC011D8D0
[JuceVST3Component::queryInterfacetargetIID=049BF9E7BC74EAD0C4101E867F725981
[JuceVST3Component::queryInterfaceFOUND MATCH
targetIID
=049BF9E7BC74EAD0C4101E867F725981 
I'm not really sure how C++ works, I can see that IReaperUIEmbedInterface already has a class IID defined.

To provide an instance of it, I wasn't really sure what to do so I just did the below -- and it might be some weirdness with me using the same class IID for my implementation class as the IReaperUIEmbedInterface.

The reason I first noticed this was very broken was when I changed case REAPER_FXEMBED_WM_IS_SUPPORTED: to return 0 and it still gave me the option in context-menu to embed in TCP, then just drew a black square.

PHP Code:
class IReaperUIEmbedInterface : public FUnknown
{
  public:
  
virtual Steinberg::TPtrInt embed_message(int msgSteinberg::TPtrInt parm2Steinberg::TPtrInt parm3) = 0;
  static const 
FUID iid;
};

DECLARE_CLASS_IID (IReaperUIEmbedInterface0x049bf9e70xbc74ead00xc4101e860x7f725981)

// I'm defining the same class IID as the IReaperUIEmbedInterface so that this implementation class
// gets resolved by this IID during lookups
DEF_CLASS_IID(IReaperUIEmbedInterface)
DECLARE_CLASS_IID(ReaperVST3EditController0x049bf9e70xbc74ead00xc4101e860x7f725981)

// IReaperUIEmbedInterface has to be private (I think?), because it extends FUnknown and FObject extends FUnknown
// so an ambiguous base would occur when trying to cast ReaperVST3EditController -> FUnknown
// (The problem: Which FUnknown -- FObject's or IReaperUIEmbed's?)
class ReaperVST3EditController : public FObject,
                                 private 
IReaperUIEmbedInterface {
private:
    
// PLUGIN_API = __stdcall
    
virtual Steinberg::uint32 PLUGIN_API addRef () override { return Steinberg::FObject::addRef (); }
    
virtual Steinberg::uint32 PLUGIN_API release () override { return Steinberg::FObject::release (); }
    
public:
    
Steinberg::TPtrInt embed_message(int msgSteinberg::TPtrInt parm2Steinberg::TPtrInt parm3override
    
{
        
DBG("[ReaperVST3EditController::embed_message] msg = " << msg << " parm2 = " << parm2 << " parm3 = " << parm3);
        switch (
msg) {
        case 
REAPER_FXEMBED_WM_IS_SUPPORTED:
            return 
1;
        }
        return 
0;
    };

    
tresult queryInterface(const TUID _iidvoid** objoverride
    
{
        return 
Steinberg::kResultOk;
    };

    static const 
FUID iid;
};

DEF_CLASS_IID(ReaperVST3EditController
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)

Last edited by gxray; 05-03-2021 at 10:27 AM.
gxray is offline   Reply With Quote
Old 05-03-2021, 10:10 AM   #12
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Pushed the current implementation to branch "reuk-impl-idea":

https://github.com/GavinRay97/JUCE-r...reuk-impl-idea

There's a 7zip'ed copy of the debug VST along with the pdb and everything else in the root of the repo called "VST3-MSVC-x86-64.7z"

To compile this (if you want), you should just need to have cloned recursively, and the patch the "juce_VST3_Wrapper.cpp"
Code:
cp ./patches/JUCE/juce_VST3_Wrapper.cpp ./JUCE/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp
Then CMake build as usual =)

I uploaded a patch to see the slight alterations made to the JUCE VST3 wrapper here:
https://github.com/GavinRay97/JUCE-r...mpl_idea.patch

And finally, here's the file with the REAPER implementation:
https://github.com/GavinRay97/JUCE-r...Controller.hpp
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)
gxray is offline   Reply With Quote
Old 05-03-2021, 11:15 AM   #13
schwa
Administrator
 
schwa's Avatar
 
Join Date: Mar 2007
Location: NY
Posts: 16,954
Default

Do you mind posting a binary that doesn't depend on the debug runtime?
schwa is offline   Reply With Quote
Old 05-03-2021, 11:27 AM   #14
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Quote:
Originally Posted by schwa View Post
Do you mind posting a binary that doesn't depend on the debug runtime?
Sure no problem, give me a few moments =)

That would be the non-"D" versions here of VCRUNTIME140D and MSVCP140D right?
(Should just be a build in "RelWithDbgInfo" mode I think)



---

Edit:

Okay, built. New dependencies:
https://github.com/GavinRay97/JUCE-r...WithDebInfo.7z



Compressing and pushing now
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)

Last edited by gxray; 05-03-2021 at 11:41 AM.
gxray is offline   Reply With Quote
Old 05-03-2021, 12:22 PM   #15
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

So I annotated every single method (ctor, dtor, addRef, release, queryInterface, and embed_message) with logging.

And I faked a call to embed_message() right before I return pointer to the object and it works, the call gets logged.

PHP Code:
auto klazz this->findInterface(targetIID);
if (
klazz != nullptr) {
    
DBG("[JuceVST3Component::queryInterface] FOUND MATCH \n targetIID="
        
<< fuidStr << "\n klazzIID=" << tuidToFUIDString(klazz->iid));
    
klazz->addRef();
    
auto embedResult dynamic_cast<ReaperVST3EditController*>
        (
klazz)->embed_message(0100100);
    *
obj klazz;
    return 
Steinberg::kResultOk;

PHP Code:
[ReaperVST3EditController::Constructor()] ReaperVST3EditController created
[JuceVST3Component::queryInterfacetargetIID=DCD7BBE37742448DA874AACC979C759E
[JuceVST3Component::queryInterfacetargetIID=70A4156F6E6E4026989148BFAA60D8D1
[JuceVST3Component::queryInterfacetargetIID=ABCDEF011234ABCD4A75636544656D30
[JuceVST3Component::queryInterfacetargetIID=B7F8F85941234872911695814F3721A3
[JuceVST3Component::queryInterfacetargetIID=1F2F76D3BFFB4B96B99527A55EBCCEF4
[JuceVST3Component::queryInterfacetargetIID=3D4BD6B5913A4FD2A886E768A5EB92C1
[JuceVST3Component::queryInterfacetargetIID=31E29A7AE55043AD8B95B9B8DA1FBE1E
[JuceVST3Component::queryInterfacetargetIID=C3B17BC02C17449480293402FBC4BBF8
[JuceVST3Component::queryInterfacetargetIID=0F1947818D984ADABBA0C1EFC011D8D0
[JuceVST3Component::queryInterfacetargetIID=049BF9E7BC74EAD0C4101E867F725981
[JuceVST3Component::queryInterfaceFOUND MATCH 
 targetIID
=049BF9E7BC74EAD0C4101E867F725981
[ReaperVST3EditController::addRef()] CALLED
[ReaperVST3EditController::embed_messagemsg 0 parm2 100 parm3 100
// Removed the plugin
[ReaperVST3EditController::release()] CALLED
// Destructor is never called I guess? Was supposed to log it's call 
Below is a video of debugging it + behavior in REAPER and showing the log messages.

Does calling casts like dynamic_cast<T> and static_cast<T> on pointers that get sent to other functions change runtime behavior?

That's the only difference I can think of -- the pointer object is not casted before being returned whereas "TEST_FOR_AND_RETURN_IF_VALID" does a cast to the class type.



PHP Code:
// DEF_CLASS_IID(IReaperUIEmbedInterface) = (Expanded macro below)
const ::Steinberg::FUID IReaperUIEmbedInterface::iid(IReaperUIEmbedInterface_iid);

class 
ReaperVST3EditController : public FObject,
                                 private 
IReaperUIEmbedInterface {
private:
public:
    
ReaperVST3EditController()
    {
        
DBG("[ReaperVST3EditController::Constructor()] ReaperVST3EditController created");
    }

    ~
ReaperVST3EditController() override
    
{
        
DBG("[ReaperVST3EditController::Destructor()] ReaperVST3EditController destroyed");
    }

    
Steinberg::uint32 PLUGIN_API addRef() override
    
{
        
DBG("[ReaperVST3EditController::addRef()] CALLED");
        return (
Steinberg::uint32)++refCount;
    }

    
Steinberg::uint32 PLUGIN_API release() override
    
{
        
DBG("[ReaperVST3EditController::release()] CALLED");
        const 
int r = --refCount;
        if (
== 0)
            
delete this;
        return (
Steinberg::uint32)r;
    }

    
Steinberg::TPtrInt embed_message(int msgSteinberg::TPtrInt parm2Steinberg::TPtrInt parm3override
    
{
        
DBG("[ReaperVST3EditController::embed_message] msg = " << msg << " parm2 = " << parm2 << " parm3 = " << parm3);
        switch (
msg) {
        case 
REAPER_FXEMBED_WM_IS_SUPPORTED:
            return 
0;
        }
        return 
0;
    };

    
Steinberg::tresult PLUGIN_API queryInterface(const Steinberg::TUID iidvoid** objoverride
    
{
        
DBG("[ReaperVST3EditController::queryInterface()] iid=" << iid);

        
jassertfalse;
        *
obj nullptr;
        return 
Steinberg::kNotImplemented;
    }

    
// https://stackoverflow.com/a/61519399
    
inline static const FUID iid IReaperUIEmbedInterface::iid;
}; 
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)

Last edited by gxray; 05-03-2021 at 12:35 PM.
gxray is offline   Reply With Quote
Old 05-03-2021, 12:51 PM   #16
schwa
Administrator
 
schwa's Avatar
 
Join Date: Mar 2007
Location: NY
Posts: 16,954
Default

Thanks for posting the binary. REAPER is calling your embed_message(). If you're not receiving the call on your side, it may be because ReaperVST3EditController needs to implement the public interface of IReaperUIEmbedInterface, so the embed_message() REAPER thinks it's calling is the same one that IReaperUIEmbedInterface is implementing.

Also, since you're implementing add_ref and release yourself, you don't need to inherit from FObject. I think it will be helpful for you to make your class a standalone object that only inherits pure-virtual interface classes, with no state anywhere other than itself.
schwa is offline   Reply With Quote
Old 05-03-2021, 01:20 PM   #17
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Quote:
Originally Posted by schwa View Post
Thanks for posting the binary. REAPER is calling your embed_message(). If you're not receiving the call on your side, it may be because ReaperVST3EditController needs to implement the public interface of IReaperUIEmbedInterface, so the embed_message() REAPER thinks it's calling is the same one that IReaperUIEmbedInterface is implementing.

Also, since you're implementing add_ref and release yourself, you don't need to inherit from FObject. I think it will be helpful for you to make your class a standalone object that only inherits pure-virtual interface classes, with no state anywhere other than itself.
Ahh, thanks a ton Schwa, you're the best. Preserves my sanity a bit to know that it's mostly-working and it's something small in my implementation.

I'll strip the FObject out and try to rewrite the class again. I'm certain I can get this working in the next day or two now that I know it's on the right track!

Code:
═✿✿✿═════✿✿═════✿✿═════✿✿✿═
════════════ ('\../') ═════════════
════════════ (◕.◕) ═════════════
════════════ (,,)(,,) ═════════════
.▀█▀.█▄█.█▀█.█▄.█.█▄▀ █▄█.█▀█.█─█
─.█.─█▀█.█▀█.█.▀█.█▀▄ ─█.─█▄█.█▄█
Revisiting the code you posted, I had a thought:
PHP Code:
IReaperUIEmbedInterface *embed_ui=NULL;
if (
m_vst3->m_controller->queryInterface(IReaperUIEmbedInterface::iid,(void **)&embed_ui) == kResultTrue && embed_ui)
{
  if (
embed_ui->embed_message(0,0,0)) // 0=REAPER_FXEMBED_WM_IS_SUPPORTED
  
{
    
m_vst3->m_embed_ui embed_ui;
  }
  else
  {
    
embed_ui->release();
  }

It's just setting a pointer and then looking for + calling method, so what if?... What if I did something real dirty and just gave it the pointer to an inline struct that had those methods?...

LOL It crashes REAPER but the embed_message() gets logged (somehow AFTER the destructor is called, not sure how that's possible and is probably related to the crashing. But still!!)

PHP Code:
auto klazz this->findInterface(targetIID);
if (
klazz != nullptr) {
    
DBG("[JuceVST3Component::queryInterface] FOUND MATCH \n targetIID=" << fuidStr << "\n klazzIID=" << tuidToFUIDString(klazz->iid));
    
klazz->addRef();

    
auto embedResult dynamic_cast<ReaperVST3EditController*>(klazz)->embed_message(0100100);
    
DBG("[JuceVST3Component::queryInterface] embedResult=" << embedResult);

    
struct ReaperUIEmbedImpl IReaperUIEmbedInterface {
        
int32 refCount 0;

        
ReaperUIEmbedImpl() { DBG("[ReaperUIEmbedImpl::Constructor()] CALLED"); }
        
virtual ~ReaperUIEmbedImpl() { DBG("[ReaperUIEmbedImpl::Destructor()] CALLED"); }

        
Steinberg::uint32 PLUGIN_API addRef() override {
            
DBG("[ReaperUIEmbedImpl::addRef()] CALLED");
            return (
Steinberg::uint32)++this->refCount;
        }

        
Steinberg::uint32 PLUGIN_API release() override {
            
DBG("[ReaperUIEmbedImpl::release()] CALLED");
            const 
int r = --this->refCount;
            if (
== 0)
                
delete this;
            return (
Steinberg::uint32)r;
        }

        
Steinberg::tresult PLUGIN_API queryInterface(const Steinberg::TUID targetIIDvoid** objoverride {
            
DBG("[ReaperUIEmbedImpl::queryInterface()] iid=" << targetIID);
            
jassertfalse;
            *
obj nullptr;
            return 
Steinberg::kNotImplemented;
        }

        
Steinberg::TPtrInt embed_message(int msgSteinberg::TPtrInt parm2Steinberg::TPtrInt parm3override {
            
DBG("[ReaperUIEmbedImpl::embed_message] msg = " << msg << " parm2 = " << parm2 << " parm3 = " << parm3);
            switch (
msg) {
            case 
REAPER_FXEMBED_WM_IS_SUPPORTED:
                return 
0;
            }
            return 
0;
        };

    } 
ReaperUIEmbedImpl;

    *
obj = &ReaperUIEmbedImpl;
    return 
Steinberg::kResultOk;

__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)
gxray is offline   Reply With Quote
Old 05-03-2021, 01:27 PM   #18
schwa
Administrator
 
schwa's Avatar
 
Join Date: Mar 2007
Location: NY
Posts: 16,954
Default

It's crashing because you're returning an object on the stack. That would work fine (modulo other errors heh) if your ReaperUIEmbedImpl was on the heap. It's not "dirty" at all.

In fact the only reason you need any additional complication is that the ReaperUIEmbedImpl needs to have some connection to the actual plugin controller state. If you stored a pointer to the JUCE edit controller in ReaperUIEmbedImpl, you'd probably be most of the way to where you need to be.
schwa is offline   Reply With Quote
Old 05-03-2021, 02:17 PM   #19
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Quote:
Originally Posted by schwa View Post
If you stored a pointer to the JUCE edit controller in ReaperUIEmbedImpl, you'd probably be most of the way to where you need to be.
Absolutely, one of their devs (reuk) was the one who came up with this "installInterface" factory pattern kind of idea.

Then he said that the userland implementation of this function, on the JUCE AudioProcessor class, should take a "Location" variable -- which is the name/instance of the JUCE class holding the VST3 interface to install itself in.

PHP Code:
class VST3InterfaceContainer// Has registerInterface(), findInterface()
class JuceVST3EditController IEditControllerVST3InterfaceContainer // And other stuff
class JuceVST3Component IComponentVST3InterfaceContainer // And other stuff
class JuceAudioProcessor IUnitInfoVST3InterfaceContainer

// User-facing API function
template <class T, class JuceVST3Interface>
bool juce::AudioProcessor registerInterface(const TUID iidT klazzJuceVST3Interface location)
{
    
// Get a pointer to the JuceVST3EditController/JuceVST3Component/JuceAudioProcessor (IUnitInfo)
    
auto locationClassPtrToInstance JuceVST3Interface->getInstance();
    
// Instantiate the user class type with a handle to the pointer
    
auto instance = new klazz(locationClassPtrToInstance);
    
// I don't know if you can do this in C++ but you get the idea
    
return JuceVST3Interface->vst3InterfaceMap
        
.insert_or_assign(tuidToFUIDString(iid), dynamic_cast<FUnknown*>(klazz))
        .
second;
}

// Pseudocode example use
juce::AudioProcessor audioProcessor;
audioProcessor->registerInterface(IReaperUIEmbedInterface::iid,
    
ReaperVST3EditControllerJuceVST3EditController
So once I can sort out this implementation I think it should be ready for reuk to review and (cross our fingers) eventually get merged into JUCE =D
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)
gxray is offline   Reply With Quote
Old 05-03-2021, 05:26 PM   #20
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

WE'RE IN BUSINESS!

__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)
gxray is offline   Reply With Quote
Old 05-06-2021, 07:58 AM   #21
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

For anyone that might be following this, here's a status update if you're curious:
  • Daily dev work has still been going on, on my end
    -
  • It's been a lot of communication with one of the JUCE devs about architecture. For reasons that are difficult to explain unless you know the innards of JUCE, these are the relative difficulties of implementing both VST3 interfaces/features of REAPER:
    -
    • Access and use of the REAPER C++ SDK from the JUCE plugin userland code:
      • Trivial, a grand total of ~20-30 lines of code and non-invasive to the architecture.
      -
    • Ability to create and dynamically register classes that implement VST3 interfaces into the JUCE VST3 wrapper classes:
      • Incredibly thorny to do in a way that is unintrusive, generic, and keeps plugin-type-specific implementation details out of bits of the code
    -
  • The JUCE developer that has been helping me with this says he hopes to take a look at this during the coming workweek or so
    -
  • At minimum, a worst-case solution would be for them to add REAPER-specific code to support this, but obviously this would be a bit of a shame and would miss out on the opportunities/abilities that implementing a generic solution would open up for plugins

Also I'm figuring things out (making things up?) as I go along wrt. C++ so there's that too lol
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)
gxray is offline   Reply With Quote
Old 05-09-2021, 07:44 PM   #22
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Welp, it's Sunday night, and it took most of the weekend but I've got a generic solution (mostly) working.

There's a problem though: which is that I am using a scope-global variable in the JUCE VST3 wrapper file to share a reference between two classes.

(Would really appreciate any advice on a better way to do this, if the order of construction is definitive and one will always come first)

PHP Code:
// At file global scope:
AudioProcessor__SCOPE_PLUGIN_INSTANCE nullptr;

// This class is always constructed first, before JuceVST3EditController
// so this only works as a dirty hack, due to that
class JuceVST3Component {
public:
    
JuceVST3Component (Vst::IHostApplicationh)
      : 
pluginInstance (createPluginFilterOfType (AudioProcessor::wrapperType_VST3)),
        
host (h)
    {
        
// ...
        
__SCOPE_PLUGIN_INSTANCE pluginInstance;
    }
}

// I haven't seen a way to get "AudioProcessor" into constructor args
// here, so this is where we are a bit fucked as far as "clean code"
class JuceVST3EditController {
public:
    
JuceVST3EditController (Vst::IHostApplicationhost)
    {
        if (
__SCOPE_PLUGIN_INSTANCE == nullptr) return;

        const 
auto installLocation AudioProcessor::VST3InterfaceInstallLocation::EditController;
        for (
auto it __SCOPE_PLUGIN_INSTANCE->getVST3ExtensionsForInterface(installLocation)) {
          
auto [fuidVoidPtrvst3Interface] = it;
          const 
auto fuidPtr static_cast<FUID*>(fuidVoidPtr);
          
this->registerInterface(*fuidPtrvst3Interface);
        }
    }

PHP Code:
// Since base AudioProcessor is generic, these are opaque types
using FUID_ void;
using VST3Interface void;

// Userland implementation in MyPluginAudioProcessor
// getVST3ExtensionsForInterface() returns a:
//   std::vector<std::pair<FUID*, VST3Interface*>>
//
// The expected response is a list of tuples/pairs of TUID-to-Class-pointers
// for replying to queryInterface() calls with.
//
// This method is called by each of the VST3 interface classes
// in the wrapper.
//
// Each class gives a unique enum-value "location" you can use
// to dictate what to install where
std::vector<std::pair<FUID_*, VST3Interface*>>
MyPluginAudioProcessor::getVST3ExtensionsForInterface(VST3InterfaceInstallLocation location)
{
    switch (
location) {
    case 
EditController:
        return 
std::vector<std::pair<FUID_*, VST3Interface*>>({
            {
                
const_cast<FUID*>(&IReaperUIEmbedInterface::iid),
                
static_cast<VST3Interface*>(new ReaperVST3EditController(this))
            }
        });
    default:
        return 
std::vector<std::pair<FUID_*, VST3Interface*>>();
    }

With this, and the embed_message() paint code from earlier in the thread, what you end up with is:

__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)
gxray is offline   Reply With Quote
Old 05-10-2021, 05:14 PM   #23
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Okay, a fellow named Ben Vining on the JUCE Discord helped me out with making the file global var thing less hacky using "static" + thread-local variables.

I think it's pretty much ready to submit now =)

PHP Code:
struct AudioProcessorInitializerHelper
{  
    
inline static juce::ThreadLocalValue<juce::AudioProcessor*> lastProcessorCreated;

    static 
void JUCE_CALLTYPE setLastCreatedAudioProcessor(juce::AudioProcessorp) {
        
lastProcessorCreated =  p;
    }
    
    static 
juce::AudioProcessorJUCE_CALLTYPE getLastCreatedAudioProcessor() {
        return 
lastProcessorCreated.get();
    }
}; 
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)
gxray is offline   Reply With Quote
Old 05-10-2021, 07:01 PM   #24
gxray
Human being with feelings
 
Join Date: Dec 2020
Location: Miami, FL USA
Posts: 396
Default

Issue is posted here:

https://github.com/juce-framework/JUCE/issues/902
__________________
Seasoned codemonkey
Dunno a thing about making music (here to learn!)
gxray is offline   Reply With Quote
Old 05-20-2021, 06:13 AM   #25
Rodulf
Human being with feelings
 
Join Date: May 2019
Posts: 444
Default Thank you!

For your work on this. It would be great to embed all of my plugins into a super-channel strip. Looking forward to the fruits of your efforts!
Rodulf is offline   Reply With Quote
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 10:47 AM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2025, vBulletin Solutions Inc.