From 555408be400785adde97c17b59855ee8c90e49f3 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 27 Oct 2021 13:01:31 +0100 Subject: [PATCH] VST3 Host: Avoid resusing IMessage and IAttributeList instances This fixes an issue where the editor of VocAlign Project 5 would fail to open in the AudioPluginHost. --- .../format_types/juce_VST3PluginFormat.cpp | 433 +++++++++--------- 1 file changed, 211 insertions(+), 222 deletions(-) diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 630f7828cf..aef1dd4b8b 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -329,7 +329,6 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 VST3HostContext() { appName = File::getSpecialLocation (File::currentApplicationFile).getFileNameWithoutExtension(); - attributeList = new AttributeList (this); } ~VST3HostContext() override = default; @@ -513,18 +512,13 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 if (doUIDsMatch (cid, Vst::IMessage::iid) && doUIDsMatch (iid, Vst::IMessage::iid)) { - VSTComSmartPtr m (new Message (attributeList)); - messageMap.add (m); - m->addRef(); - *obj = m; + *obj = new Message; return kResultOk; } if (doUIDsMatch (cid, Vst::IAttributeList::iid) && doUIDsMatch (iid, Vst::IAttributeList::iid)) { - VSTComSmartPtr l (new AttributeList (this)); - l->addRef(); - *obj = l; + *obj = new AttributeList; return kResultOk; } @@ -544,12 +538,6 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 //============================================================================== tresult PLUGIN_API queryInterface (const TUID iid, void** obj) override { - if (doUIDsMatch (iid, Vst::IAttributeList::iid)) - { - *obj = attributeList.get(); - return kResultOk; - } - return testForMultiple (*this, iid, UniqueBase{}, @@ -572,23 +560,214 @@ private: void restartComponentOnMessageThread (int32 flags) override; //============================================================================== + class Attribute + { + public: + using Int = Steinberg::int64; + using Float = double; + using String = std::vector; + using Binary = std::vector; + + explicit Attribute (Int x) noexcept { constructFrom (std::move (x)); } + explicit Attribute (Float x) noexcept { constructFrom (std::move (x)); } + explicit Attribute (String x) noexcept { constructFrom (std::move (x)); } + explicit Attribute (Binary x) noexcept { constructFrom (std::move (x)); } + + Attribute (Attribute&& other) noexcept + { + moveFrom (std::move (other)); + } + + Attribute& operator= (Attribute&& other) noexcept + { + reset(); + moveFrom (std::move (other)); + return *this; + } + + ~Attribute() noexcept + { + reset(); + } + + tresult getInt (Steinberg::int64& result) const + { + if (kind != Kind::Int) + return kResultFalse; + + result = storage.storedInt; + return kResultTrue; + } + + tresult getFloat (double& result) const + { + if (kind != Kind::Float) + return kResultFalse; + + result = storage.storedFloat; + return kResultTrue; + } + + tresult getString (Vst::TChar* data, Steinberg::uint32 numBytes) const + { + if (kind != Kind::String) + return kResultFalse; + + std::memcpy (data, + storage.storedString.data(), + jmin (sizeof (Vst::TChar) * storage.storedString.size(), (size_t) numBytes)); + return kResultTrue; + } + + tresult getBinary (const void*& data, Steinberg::uint32& numBytes) const + { + if (kind != Kind::Binary) + return kResultFalse; + + data = storage.storedBinary.data(); + numBytes = (Steinberg::uint32) storage.storedBinary.size(); + return kResultTrue; + } + + private: + void constructFrom (Int x) noexcept { kind = Kind::Int; new (&storage.storedInt) Int (std::move (x)); } + void constructFrom (Float x) noexcept { kind = Kind::Float; new (&storage.storedFloat) Float (std::move (x)); } + void constructFrom (String x) noexcept { kind = Kind::String; new (&storage.storedString) String (std::move (x)); } + void constructFrom (Binary x) noexcept { kind = Kind::Binary; new (&storage.storedBinary) Binary (std::move (x)); } + + void reset() noexcept + { + switch (kind) + { + case Kind::Int: break; + case Kind::Float: break; + case Kind::String: storage.storedString.~vector(); break; + case Kind::Binary: storage.storedBinary.~vector(); break; + } + } + + void moveFrom (Attribute&& other) noexcept + { + switch (other.kind) + { + case Kind::Int: constructFrom (std::move (other.storage.storedInt)); break; + case Kind::Float: constructFrom (std::move (other.storage.storedFloat)); break; + case Kind::String: constructFrom (std::move (other.storage.storedString)); break; + case Kind::Binary: constructFrom (std::move (other.storage.storedBinary)); break; + } + } + + enum class Kind { Int, Float, String, Binary }; + + union Storage + { + Storage() {} + ~Storage() {} + + Steinberg::int64 storedInt; + double storedFloat; + std::vector storedString; + std::vector storedBinary; + }; + + Storage storage; + Kind kind; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Attribute) + }; + + //============================================================================== + class AttributeList : public Vst::IAttributeList + { + public: + AttributeList() = default; + virtual ~AttributeList() = default; + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + //============================================================================== + tresult PLUGIN_API setInt (AttrID attr, Steinberg::int64 value) override + { + return set (attr, value); + } + + tresult PLUGIN_API setFloat (AttrID attr, double value) override + { + return set (attr, value); + } + + tresult PLUGIN_API setString (AttrID attr, const Vst::TChar* string) override + { + return set (attr, std::vector (string, string + 1 + tstrlen (string))); + } + + tresult PLUGIN_API setBinary (AttrID attr, const void* data, Steinberg::uint32 size) override + { + const auto* ptr = static_cast (data); + return set (attr, std::vector (ptr, ptr + size)); + } + + tresult PLUGIN_API getInt (AttrID attr, Steinberg::int64& result) override + { + return get (attr, [&] (const auto& x) { return x.getInt (result); }); + } + + tresult PLUGIN_API getFloat (AttrID attr, double& result) override + { + return get (attr, [&] (const auto& x) { return x.getFloat (result); }); + } + + tresult PLUGIN_API getString (AttrID attr, Vst::TChar* result, Steinberg::uint32 length) override + { + return get (attr, [&] (const auto& x) { return x.getString (result, length); }); + } + + tresult PLUGIN_API getBinary (AttrID attr, const void*& data, Steinberg::uint32& size) override + { + return get (attr, [&] (const auto& x) { return x.getBinary (data, size); }); + } + + private: + template + tresult set (AttrID attr, Value&& value) + { + if (attr == nullptr) + return kInvalidArgument; + + const auto iter = attributes.find (attr); + + if (iter != attributes.end()) + iter->second = Attribute (std::move (value)); + else + attributes.emplace (attr, Attribute (std::move (value))); + + return kResultTrue; + } + + template + tresult get (AttrID attr, Visitor&& visitor) + { + if (attr == nullptr) + return kInvalidArgument; + + const auto iter = attributes.find (attr); + + if (iter == attributes.cend()) + return kResultFalse; + + return visitor (iter->second); + } + + std::map attributes; + Atomic refCount { 1 }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttributeList) + }; + struct Message : public Vst::IMessage { - explicit Message (Vst::IAttributeList* list) - : attributeList (list) - { - } - - Message (Vst::IAttributeList* list, FIDString id) - : attributeList (list), messageId (toString (id)) - { - } - - Message (Vst::IAttributeList* list, FIDString id, const var& v) - : value (v), attributeList (list), messageId (toString (id)) - { - } - + Message() = default; virtual ~Message() = default; JUCE_DECLARE_VST3_COM_REF_METHODS @@ -596,206 +775,16 @@ private: FIDString PLUGIN_API getMessageID() override { return messageId.toRawUTF8(); } void PLUGIN_API setMessageID (FIDString id) override { messageId = toString (id); } - Vst::IAttributeList* PLUGIN_API getAttributes() override { return attributeList; } - - var value; + Vst::IAttributeList* PLUGIN_API getAttributes() override { return &attributeList; } private: - VSTComSmartPtr attributeList; + AttributeList attributeList; String messageId; - Atomic refCount; + Atomic refCount { 1 }; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Message) }; - class MessageMap - { - public: - tresult getBinary (const char* id, const void*& data, Steinberg::uint32& size) - { - jassert (id != nullptr); - - const ScopedLock lock (mutex); - - const auto it = storage.find (id); - - if (it != storage.cend()) - { - if (auto* binaryData = it->second->value.getBinaryData()) - { - data = binaryData->getData(); - size = (Steinberg::uint32) binaryData->getSize(); - return kResultTrue; - } - } - - return kResultFalse; - } - - template - void addMessageToQueue (const char* id, Vst::IAttributeList* list, const Type& value) - { - jassert (id != nullptr); - - const ScopedLock lock (mutex); - - const auto it = storage.find (id); - - if (it != storage.cend()) - it->second->value = value; - else - storage.emplace (id, new Message (list, id, value)); - } - - template - bool findMessageOnQueueWithID (const char* id, Type& value) - { - jassert (id != nullptr); - - const ScopedLock lock (mutex); - - const auto it = storage.find (id); - - if (it == storage.cend()) - return false; - - value = it->second->value; - return true; - } - - void add (VSTComSmartPtr message) - { - const ScopedLock lock (mutex); - - const auto* id = message->getMessageID(); - storage.erase (id); - storage.emplace (id, std::move (message)); - } - - private: - struct Comparator - { - bool operator() (const char* a, const char* b) const noexcept - { - return std::strcmp (a, b) < 0; - } - }; - - // Steinberg's docs say: - // > Please note that messages from the processor to the controller must not be sent during - // the process call, as this would not be fast enough and would break the real time - // processing. Such tasks should be handled in a separate timer thread. - - // Using a lock here is fine (plugins should be aware that sending messages is not - // realtime-safe), and protects the data structure in case the processor sends messages from - // a background thread rather than from the message thread. - - std::map, Comparator> storage; - CriticalSection mutex; - }; - - MessageMap messageMap; - - //============================================================================== - struct AttributeList : public Vst::IAttributeList - { - AttributeList (VST3HostContext* o) : owner (o) {} - virtual ~AttributeList() = default; - - JUCE_DECLARE_VST3_COM_REF_METHODS - JUCE_DECLARE_VST3_COM_QUERY_METHODS - - //============================================================================== - tresult PLUGIN_API setInt (AttrID id, Steinberg::int64 value) override - { - addMessageToQueue (id, value); - return kResultTrue; - } - - tresult PLUGIN_API setFloat (AttrID id, double value) override - { - addMessageToQueue (id, value); - return kResultTrue; - } - - tresult PLUGIN_API setString (AttrID id, const Vst::TChar* string) override - { - addMessageToQueue (id, toString (string)); - return kResultTrue; - } - - tresult PLUGIN_API setBinary (AttrID id, const void* data, Steinberg::uint32 size) override - { - jassert (data != nullptr || size == 0); - addMessageToQueue (id, MemoryBlock (data, (size_t) size)); - return kResultTrue; - } - - //============================================================================== - tresult PLUGIN_API getInt (AttrID id, Steinberg::int64& result) override - { - jassert (id != nullptr); - - if (findMessageOnQueueWithID (id, result)) - return kResultTrue; - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API getFloat (AttrID id, double& result) override - { - jassert (id != nullptr); - - if (findMessageOnQueueWithID (id, result)) - return kResultTrue; - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API getString (AttrID id, Vst::TChar* result, Steinberg::uint32 length) override - { - jassert (id != nullptr); - - String stringToFetch; - if (findMessageOnQueueWithID (id, stringToFetch)) - { - Steinberg::String str (stringToFetch.toRawUTF8()); - str.copyTo (result, 0, (Steinberg::int32) jmin (length, (Steinberg::uint32) std::numeric_limits::max())); - - return kResultTrue; - } - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API getBinary (AttrID id, const void*& data, Steinberg::uint32& size) override - { - return owner->messageMap.getBinary (id, data, size); - } - - private: - VST3HostContext* owner; - Atomic refCount; - - //============================================================================== - template - void addMessageToQueue (AttrID id, const Type& value) - { - owner->messageMap.addMessageToQueue (id, this, value); - } - - template - bool findMessageOnQueueWithID (AttrID id, Type& value) - { - return owner->messageMap.findMessageOnQueueWithID (id, value); - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttributeList) - }; - VSTComSmartPtr attributeList; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3HostContext)