Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -0,0 +1,91 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/connectionproxy.cpp
// Created by : Steinberg, 04/2019
// Description : VST 3 Plug-in connection class
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "connectionproxy.h"
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
IMPLEMENT_FUNKNOWN_METHODS (ConnectionProxy, IConnectionPoint, IConnectionPoint::iid)
//------------------------------------------------------------------------
ConnectionProxy::ConnectionProxy (IConnectionPoint* srcConnection)
: srcConnection (srcConnection) // share it
{
FUNKNOWN_CTOR
}
//------------------------------------------------------------------------
ConnectionProxy::~ConnectionProxy ()
{
FUNKNOWN_DTOR
}
//------------------------------------------------------------------------
tresult PLUGIN_API ConnectionProxy::connect (IConnectionPoint* other)
{
if (other == nullptr)
return kInvalidArgument;
if (dstConnection)
return kResultFalse;
dstConnection = other; // share it
tresult res = srcConnection->connect (this);
if (res != kResultTrue)
dstConnection = nullptr;
return res;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ConnectionProxy::disconnect (IConnectionPoint* other)
{
if (!other)
return kInvalidArgument;
if (other == dstConnection)
{
if (srcConnection)
srcConnection->disconnect (this);
dstConnection = nullptr;
return kResultTrue;
}
return kInvalidArgument;
}
//------------------------------------------------------------------------
tresult PLUGIN_API ConnectionProxy::notify (IMessage* message)
{
if (dstConnection)
{
// We discard the message if we are not in the UI main thread
if (threadChecker && threadChecker->test ())
return dstConnection->notify (message);
}
return kResultFalse;
}
//------------------------------------------------------------------------
bool ConnectionProxy::disconnect ()
{
return disconnect (dstConnection) == kResultTrue;
}
//------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,51 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/connectionproxy.h
// Created by : Steinberg, 04/2020
// Description : VST 3 Plug-in connection class
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "pluginterfaces/vst/ivstmessage.h"
#include "public.sdk/source/common/threadchecker.h"
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
/** Helper for creating and initializing component.
\ingroup Helper */
//------------------------------------------------------------------------
class ConnectionProxy : public IConnectionPoint
{
public:
ConnectionProxy (IConnectionPoint* srcConnection);
virtual ~ConnectionProxy ();
//--- from IConnectionPoint
tresult PLUGIN_API connect (IConnectionPoint* other) override;
tresult PLUGIN_API disconnect (IConnectionPoint* other) override;
tresult PLUGIN_API notify (IMessage* message) override;
bool disconnect ();
//------------------------------------------------------------------------
DECLARE_FUNKNOWN_METHODS
protected:
std::unique_ptr<ThreadChecker> threadChecker {ThreadChecker::create ()};
IPtr<IConnectionPoint> srcConnection;
IPtr<IConnectionPoint> dstConnection;
};
}
} // namespaces
@@ -0,0 +1,86 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/eventlist.cpp
// Created by : Steinberg, 03/05/2008.
// Description : VST 3 event list implementation
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "eventlist.h"
namespace Steinberg {
namespace Vst {
//-----------------------------------------------------------------------------
IMPLEMENT_FUNKNOWN_METHODS (EventList, IEventList, IEventList::iid)
//-----------------------------------------------------------------------------
EventList::EventList (int32 inMaxSize)
{
FUNKNOWN_CTOR
setMaxSize (inMaxSize);
}
//-----------------------------------------------------------------------------
EventList::~EventList ()
{
setMaxSize (0);
FUNKNOWN_DTOR
}
//-----------------------------------------------------------------------------
void EventList::setMaxSize (int32 newMaxSize)
{
if (events)
{
delete[] events;
events = nullptr;
fillCount = 0;
}
if (newMaxSize > 0)
events = new Event[newMaxSize];
maxSize = newMaxSize;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API EventList::getEvent (int32 index, Event& e)
{
if (auto event = getEventByIndex (index))
{
memcpy (&e, event, sizeof (Event));
return kResultTrue;
}
return kResultFalse;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API EventList::addEvent (Event& e)
{
if (maxSize > fillCount)
{
memcpy (&events[fillCount], &e, sizeof (Event));
fillCount++;
return kResultTrue;
}
return kResultFalse;
}
//-----------------------------------------------------------------------------
Event* EventList::getEventByIndex (int32 index) const
{
if (index < fillCount)
return &events[index];
return nullptr;
}
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,53 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/eventlist.h
// Created by : Steinberg, 03/05/2008.
// Description : VST 3 event list implementation
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "pluginterfaces/vst/ivstevents.h"
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
/** Example implementation of IEventList.
\ingroup sdkBase
*/
class EventList : public IEventList
{
public:
EventList (int32 maxSize = 50);
virtual ~EventList ();
int32 PLUGIN_API getEventCount () SMTG_OVERRIDE { return fillCount; }
tresult PLUGIN_API getEvent (int32 index, Event& e) SMTG_OVERRIDE;
tresult PLUGIN_API addEvent (Event& e) SMTG_OVERRIDE;
void setMaxSize (int32 maxSize);
void clear () { fillCount = 0; }
Event* getEventByIndex (int32 index) const;
//------------------------------------------------------------------------
DECLARE_FUNKNOWN_METHODS
protected:
Event* events {nullptr};
int32 maxSize {0};
int32 fillCount {0};
};
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,319 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/hostclasses.cpp
// Created by : Steinberg, 03/05/2008.
// Description : VST 3 hostclasses, example impl. for IHostApplication, IAttributeList and IMessage
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "hostclasses.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include <algorithm>
namespace Steinberg {
namespace Vst {
//-----------------------------------------------------------------------------
HostApplication::HostApplication ()
{
FUNKNOWN_CTOR
mPlugInterfaceSupport = owned (new PlugInterfaceSupport);
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostApplication::getName (String128 name)
{
return StringConvert::convert ("My VST3 HostApplication", name) ? kResultTrue : kInternalError;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostApplication::createInstance (TUID cid, TUID _iid, void** obj)
{
if (FUnknownPrivate::iidEqual (cid, IMessage::iid) &&
FUnknownPrivate::iidEqual (_iid, IMessage::iid))
{
*obj = new HostMessage;
return kResultTrue;
}
if (FUnknownPrivate::iidEqual (cid, IAttributeList::iid) &&
FUnknownPrivate::iidEqual (_iid, IAttributeList::iid))
{
if (auto al = HostAttributeList::make ())
{
*obj = al.take ();
return kResultTrue;
}
return kOutOfMemory;
}
*obj = nullptr;
return kResultFalse;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostApplication::queryInterface (const char* _iid, void** obj)
{
QUERY_INTERFACE (_iid, obj, FUnknown::iid, IHostApplication)
QUERY_INTERFACE (_iid, obj, IHostApplication::iid, IHostApplication)
if (mPlugInterfaceSupport && mPlugInterfaceSupport->queryInterface (_iid, obj) == kResultTrue)
return kResultOk;
*obj = nullptr;
return kResultFalse;
}
//-----------------------------------------------------------------------------
uint32 PLUGIN_API HostApplication::addRef ()
{
return 1;
}
//-----------------------------------------------------------------------------
uint32 PLUGIN_API HostApplication::release ()
{
return 1;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
IMPLEMENT_FUNKNOWN_METHODS (HostMessage, IMessage, IMessage::iid)
//-----------------------------------------------------------------------------
HostMessage::HostMessage () {FUNKNOWN_CTOR}
//-----------------------------------------------------------------------------
HostMessage::~HostMessage () noexcept
{
setMessageID (nullptr);
FUNKNOWN_DTOR
}
//-----------------------------------------------------------------------------
const char* PLUGIN_API HostMessage::getMessageID ()
{
return messageId;
}
//-----------------------------------------------------------------------------
void PLUGIN_API HostMessage::setMessageID (const char* mid)
{
if (messageId)
delete[] messageId;
messageId = nullptr;
if (mid)
{
size_t len = strlen (mid) + 1;
messageId = new char[len];
strcpy (messageId, mid);
}
}
//-----------------------------------------------------------------------------
IAttributeList* PLUGIN_API HostMessage::getAttributes ()
{
if (!attributeList)
attributeList = HostAttributeList::make ();
return attributeList;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
struct HostAttributeList::Attribute
{
enum class Type
{
kUninitialized,
kInteger,
kFloat,
kString,
kBinary
};
Attribute () = default;
Attribute (int64 value) : type (Type::kInteger) { v.intValue = value; }
Attribute (double value) : type (Type::kFloat) { v.floatValue = value; }
/* size is in code unit (count of TChar) */
Attribute (const TChar* value, uint32 sizeInCodeUnit)
: size (sizeInCodeUnit), type (Type::kString)
{
v.stringValue = new TChar[sizeInCodeUnit];
memcpy (v.stringValue, value, sizeInCodeUnit * sizeof (TChar));
}
Attribute (const void* value, uint32 sizeInBytes) : size (sizeInBytes), type (Type::kBinary)
{
v.binaryValue = new char[sizeInBytes];
memcpy (v.binaryValue, value, sizeInBytes);
}
Attribute (Attribute&& o) SMTG_NOEXCEPT { *this = std::move (o); }
Attribute& operator= (Attribute&& o) SMTG_NOEXCEPT
{
v = o.v;
size = o.size;
type = o.type;
o.size = 0;
o.type = Type::kUninitialized;
o.v = {};
return *this;
}
~Attribute () noexcept
{
if (size)
delete[] v.binaryValue;
}
int64 intValue () const { return v.intValue; }
double floatValue () const { return v.floatValue; }
/* sizeInCodeUnit is in code unit (count of TChar) */
const TChar* stringValue (uint32& sizeInCodeUnit)
{
sizeInCodeUnit = size;
return v.stringValue;
}
const void* binaryValue (uint32& sizeInBytes)
{
sizeInBytes = size;
return v.binaryValue;
}
Type getType () const { return type; }
private:
union v
{
int64 intValue;
double floatValue;
TChar* stringValue;
char* binaryValue;
} v {};
uint32 size {0};
Type type {Type::kUninitialized};
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
IMPLEMENT_FUNKNOWN_METHODS (HostAttributeList, IAttributeList, IAttributeList::iid)
//-----------------------------------------------------------------------------
IPtr<IAttributeList> HostAttributeList::make ()
{
return owned (new HostAttributeList);
}
//-----------------------------------------------------------------------------
HostAttributeList::HostAttributeList () {FUNKNOWN_CTOR}
//-----------------------------------------------------------------------------
HostAttributeList::~HostAttributeList () noexcept {FUNKNOWN_DTOR}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostAttributeList::setInt (AttrID aid, int64 value)
{
if (!aid)
return kInvalidArgument;
list[aid] = Attribute (value);
return kResultTrue;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostAttributeList::getInt (AttrID aid, int64& value)
{
if (!aid)
return kInvalidArgument;
auto it = list.find (aid);
if (it != list.end () && it->second.getType () == Attribute::Type::kInteger)
{
value = it->second.intValue ();
return kResultTrue;
}
return kResultFalse;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostAttributeList::setFloat (AttrID aid, double value)
{
if (!aid)
return kInvalidArgument;
list[aid] = Attribute (value);
return kResultTrue;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostAttributeList::getFloat (AttrID aid, double& value)
{
if (!aid)
return kInvalidArgument;
auto it = list.find (aid);
if (it != list.end () && it->second.getType () == Attribute::Type::kFloat)
{
value = it->second.floatValue ();
return kResultTrue;
}
return kResultFalse;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostAttributeList::setString (AttrID aid, const TChar* string)
{
if (!aid)
return kInvalidArgument;
// + 1 for the null-terminate
auto length = tstrlen (string) + 1;
list[aid] = Attribute (string, length);
return kResultTrue;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostAttributeList::getString (AttrID aid, TChar* string, uint32 sizeInBytes)
{
if (!aid)
return kInvalidArgument;
auto it = list.find (aid);
if (it != list.end () && it->second.getType () == Attribute::Type::kString)
{
uint32 sizeInCodeUnit = 0;
const TChar* _string = it->second.stringValue (sizeInCodeUnit);
memcpy (string, _string, std::min<uint32> (sizeInCodeUnit * sizeof (TChar), sizeInBytes));
return kResultTrue;
}
return kResultFalse;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostAttributeList::setBinary (AttrID aid, const void* data, uint32 sizeInBytes)
{
if (!aid)
return kInvalidArgument;
list[aid] = Attribute (data, sizeInBytes);
return kResultTrue;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API HostAttributeList::getBinary (AttrID aid, const void*& data, uint32& sizeInBytes)
{
if (!aid)
return kInvalidArgument;
auto it = list.find (aid);
if (it != list.end () && it->second.getType () == Attribute::Type::kBinary)
{
data = it->second.binaryValue (sizeInBytes);
return kResultTrue;
}
sizeInBytes = 0;
return kResultFalse;
}
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,100 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/hostclasses.h
// Created by : Steinberg, 03/05/2008.
// Description : VST 3 hostclasses, example impl. for IHostApplication, IAttributeList and IMessage
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/source/vst/hosting/pluginterfacesupport.h"
#include "pluginterfaces/vst/ivsthostapplication.h"
#include <map>
#include <memory>
#include <string>
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
/** Implementation's example of IHostApplication.
\ingroup hostingBase
*/
class HostApplication : public IHostApplication
{
public:
HostApplication ();
virtual ~HostApplication () noexcept {FUNKNOWN_DTOR}
//--- IHostApplication ---------------
tresult PLUGIN_API getName (String128 name) override;
tresult PLUGIN_API createInstance (TUID cid, TUID _iid, void** obj) override;
DECLARE_FUNKNOWN_METHODS
PlugInterfaceSupport* getPlugInterfaceSupport () const { return mPlugInterfaceSupport; }
private:
IPtr<PlugInterfaceSupport> mPlugInterfaceSupport;
};
//------------------------------------------------------------------------
/** Example, ready to use implementation of IAttributeList.
\ingroup hostingBase
*/
class HostAttributeList final : public IAttributeList
{
public:
/** make a new attribute list instance */
static IPtr<IAttributeList> make ();
tresult PLUGIN_API setInt (AttrID aid, int64 value) override;
tresult PLUGIN_API getInt (AttrID aid, int64& value) override;
tresult PLUGIN_API setFloat (AttrID aid, double value) override;
tresult PLUGIN_API getFloat (AttrID aid, double& value) override;
tresult PLUGIN_API setString (AttrID aid, const TChar* string) override;
tresult PLUGIN_API getString (AttrID aid, TChar* string, uint32 sizeInBytes) override;
tresult PLUGIN_API setBinary (AttrID aid, const void* data, uint32 sizeInBytes) override;
tresult PLUGIN_API getBinary (AttrID aid, const void*& data, uint32& sizeInBytes) override;
virtual ~HostAttributeList () noexcept;
DECLARE_FUNKNOWN_METHODS
private:
HostAttributeList ();
struct Attribute;
std::map<std::string, Attribute> list;
};
//------------------------------------------------------------------------
/** Example implementation of IMessage.
\ingroup hostingBase
*/
class HostMessage final : public IMessage
{
public:
HostMessage ();
virtual ~HostMessage () noexcept;
const char* PLUGIN_API getMessageID () override;
void PLUGIN_API setMessageID (const char* messageID) override;
IAttributeList* PLUGIN_API getAttributes () override;
DECLARE_FUNKNOWN_METHODS
private:
char* messageId {nullptr};
IPtr<IAttributeList> attributeList;
};
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,390 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
// Flags : clang-format SMTGSequencer
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/hostdataexchangehandler.cpp
// Created by : Steinberg, 06/2023
// Description : VST Data Exchange API Host Helper
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "hostdataexchangehandler.h"
#include "../utility/alignedalloc.h"
#include "../utility/ringbuffer.h"
#include "pluginterfaces/base/funknownimpl.h"
#include <algorithm>
#include <cassert>
#include <mutex>
#include <vector>
#ifdef _MSC_VER
#include <malloc.h>
#endif
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
struct HostDataExchangeHandler::Impl
: U::ImplementsNonDestroyable<U::Directly<IDataExchangeHandler>>
{
struct Block
{
Block () = default;
Block (uint32 blockSize, uint32 alignment, DataExchangeBlockID id)
: blockID (id), alignment (alignment)
{
data = aligned_alloc (blockSize, alignment);
}
Block (Block&& other) { *this = std::move (other); }
~Block () noexcept
{
if (data)
aligned_free (data, alignment);
}
Block& operator= (Block&& other)
{
data = other.data;
other.data = nullptr;
blockID = other.blockID;
other.blockID = InvalidDataExchangeBlockID;
alignment = other.alignment;
return *this;
}
void* data {nullptr};
DataExchangeBlockID blockID {InvalidDataExchangeBlockID};
uint32 alignment {0};
};
struct Queue
{
using BlockRingBuffer = OneReaderOneWriter::RingBuffer<Block>;
// we do the assumption that the std::vector does not allocate memory when we don't push
// more items than we reserve before
using BlockVector = std::vector<Block>;
Queue (IAudioProcessor* owner, IDataExchangeReceiver* receiver,
DataExchangeUserContextID userContext, uint32 blockSize, uint32 numBlocks,
uint32 alignment)
: owner (owner)
, receiver (receiver)
, userContext (userContext)
, blockSize (blockSize)
, numBlocks (numBlocks)
{
receiver->queueOpened (userContext, blockSize, wantBlocksOnBackgroundThread);
freeList.resize (numBlocks);
sendList.resize (numBlocks);
lockList.reserve (numBlocks);
freeListOnRTThread.reserve (numBlocks);
for (auto idx = 0u; idx < numBlocks; ++idx)
freeList.push (Block (blockSize, alignment, idx));
}
~Queue () noexcept
{
if (receiver)
receiver->queueClosed (userContext);
}
bool lock (DataExchangeBlock& block)
{
if (freeListOnRTThread.empty () == false)
{
auto& back = freeListOnRTThread.back ();
block.data = back.data;
block.size = blockSize;
block.blockID = back.blockID;
lockList.emplace_back (std::move (back));
freeListOnRTThread.pop_back ();
return true;
}
Block b;
if (freeList.pop (b))
{
block.data = b.data;
block.size = blockSize;
block.blockID = b.blockID;
lockList.emplace_back (std::move (b));
return true;
}
return false;
}
bool free (DataExchangeBlockID blockID)
{
if (blockID >= numBlocks)
return false;
auto it = std::find_if (lockList.begin (), lockList.end (),
[&] (const auto& el) { return el.blockID == blockID; });
if (it == lockList.end ())
return false;
Block b = std::move (*it);
freeListOnRTThread.emplace_back (std::move (b));
lockList.erase (it);
return true;
}
bool readyToSend (DataExchangeBlockID blockID)
{
if (blockID >= numBlocks)
return false;
auto it = std::find_if (lockList.begin (), lockList.end (),
[&] (const auto& el) { return el.blockID == blockID; });
if (it == lockList.end ())
return false;
Block b = std::move (*it);
sendList.push (std::move (b));
lockList.erase (it);
return true;
}
uint32 sendBlocks (DataExchangeQueueID queueID)
{
BlockVector blocks;
Block b;
while (sendList.pop (b))
{
blocks.emplace_back (std::move (b));
}
if (blocks.empty ())
return 0;
std::vector<DataExchangeBlock> debs;
std::for_each (blocks.begin (), blocks.end (), [&] (const auto& el) {
DataExchangeBlock block;
block.data = el.data;
block.size = blockSize;
block.blockID = el.blockID;
debs.push_back (block);
});
receiver->onDataExchangeBlocksReceived (userContext, static_cast<uint32> (debs.size ()),
debs.data (), wantBlocksOnBackgroundThread);
std::for_each (blocks.begin (), blocks.end (),
[&] (auto&& el) { freeList.push (std::move (el)); });
return static_cast<uint32> (debs.size ());
}
IAudioProcessor* owner;
IPtr<IDataExchangeReceiver> receiver;
DataExchangeUserContextID userContext {};
TBool wantBlocksOnBackgroundThread {false};
BlockRingBuffer freeList;
BlockVector freeListOnRTThread;
BlockVector lockList;
BlockRingBuffer sendList;
uint32 blockSize {0};
uint32 numBlocks {0};
};
using QueuePtr = std::unique_ptr<Queue>;
using QueueList = std::vector<QueuePtr>;
Impl (IDataExchangeHandlerHost& host, uint32 maxQueues) : host (host)
{
queues.resize (maxQueues);
}
void setQueue (DataExchangeQueueID queueID, QueuePtr&& queue)
{
queuesLock.lock ();
queues[queueID] = std::move (queue);
if (queues[queueID]->wantBlocksOnBackgroundThread)
++numOpenBackgroundQueues;
else
++numOpenMainThreadQueues;
queuesLock.unlock ();
host.onQueueOpened (queues[queueID]->owner, queueID,
queues[queueID]->wantBlocksOnBackgroundThread);
host.numberOfQueuesChanged (numOpenMainThreadQueues, numOpenBackgroundQueues);
}
tresult PLUGIN_API openQueue (IAudioProcessor* owner, uint32 blockSize, uint32 numBlocks,
uint32 alignment, DataExchangeUserContextID userContext,
DataExchangeQueueID* outID) override
{
if (!host.isMainThread ())
return kResultFalse;
if (outID == nullptr)
return kInvalidArgument;
if (!host.isProcessorInactive (owner))
return kResultFalse;
auto receiver = host.findDataExchangeReceiver (owner);
if (!receiver)
return kInvalidArgument;
if (!host.allowAllocateSize (blockSize, numBlocks, alignment))
return kOutOfMemory;
for (auto queueID = 0; queueID < queues.size (); ++queueID)
{
if (queues[queueID] == nullptr)
{
auto newQueue = std::make_unique<Queue> (owner, receiver, userContext, blockSize,
numBlocks, alignment);
setQueue (queueID, std::move (newQueue));
*outID = queueID;
return kResultTrue;
}
}
auto queueSize = queues.size ();
if (host.allowQueueListResize (static_cast<uint32> (queueSize + 1)))
{
queues.resize (queueSize + 1);
assert (queues.size () == queueSize + 1);
DataExchangeQueueID queueID = static_cast<DataExchangeQueueID> (queueSize);
auto newQueue = std::make_unique<Queue> (owner, receiver, userContext, blockSize,
numBlocks, alignment);
setQueue (queueID, std::move (newQueue));
*outID = queueID;
return kResultTrue;
}
return kOutOfMemory;
}
tresult PLUGIN_API closeQueue (DataExchangeQueueID queueID) override
{
if (!host.isMainThread ())
return kResultFalse;
if (queues[queueID])
{
if (!host.isProcessorInactive (queues[queueID]->owner))
return kResultFalse;
QueuePtr q;
queuesLock.lock ();
std::swap (q, queues[queueID]);
if (q->wantBlocksOnBackgroundThread)
--numOpenBackgroundQueues;
else
--numOpenMainThreadQueues;
queuesLock.unlock ();
host.onQueueClosed (q->owner, queueID, q->wantBlocksOnBackgroundThread);
host.numberOfQueuesChanged (numOpenMainThreadQueues, numOpenBackgroundQueues);
q.reset ();
return kResultTrue;
}
return kResultFalse;
}
tresult PLUGIN_API lockBlock (DataExchangeQueueID queueId, DataExchangeBlock* block) override
{
if (!block || queueId >= queues.size () || queues[queueId] == nullptr)
return kInvalidArgument;
if (queues[queueId]->lock (*block))
return kResultTrue;
return kOutOfMemory;
}
tresult PLUGIN_API freeBlock (DataExchangeQueueID queueId, DataExchangeBlockID blockID,
TBool sendToController) override
{
if (queueId >= queues.size () || queues[queueId] == nullptr)
return kInvalidArgument;
if (sendToController)
{
if (queues[queueId]->readyToSend (blockID))
{
++numReadyToSendBlocks;
host.newBlockReadyToBeSend (queueId);
return kResultTrue;
}
return kResultFalse;
}
return queues[queueId]->free (blockID) ? kResultTrue : kResultFalse;
}
bool sendBlocks (bool isMainThread, size_t queueID, uint32& numSendBlocks)
{
LockGuard guard (queuesLock);
if (auto& queue = queues[queueID])
{
if (queue->wantBlocksOnBackgroundThread != static_cast<TBool> (isMainThread))
{
numSendBlocks = queue->sendBlocks (static_cast<DataExchangeQueueID> (queueID));
}
return true;
}
return false;
}
uint32 sendBlocks (bool isMainThread, DataExchangeQueueID queueFilter)
{
if (queueFilter != InvalidDataExchangeQueueID)
{
if (queueFilter < queues.size ())
{
uint32 numSendBlocks;
if (sendBlocks (isMainThread, queueFilter, numSendBlocks))
return numSendBlocks;
}
return 0;
}
uint32 totalSendBlocks = 0;
uint32 openQueues = numOpenBackgroundQueues + numOpenMainThreadQueues;
for (auto queueID = 0u; queueID < queues.size (); ++queueID)
{
uint32 numSendBlocks;
if (sendBlocks (isMainThread, queueID, numSendBlocks))
{
numReadyToSendBlocks -= numSendBlocks;
totalSendBlocks += numSendBlocks;
if ((--openQueues) == 0)
break;
}
}
return totalSendBlocks;
}
IDataExchangeHandlerHost& host;
QueueList queues;
std::atomic<uint32> numReadyToSendBlocks {0};
std::atomic<uint32> numOpenMainThreadQueues {0};
std::atomic<uint32> numOpenBackgroundQueues {0};
using Mutex = std::recursive_mutex;
using LockGuard = std::lock_guard<Mutex>;
Mutex queuesLock;
};
//------------------------------------------------------------------------
HostDataExchangeHandler::HostDataExchangeHandler (IDataExchangeHandlerHost& host, uint32 maxQueues)
{
impl = std::make_unique<Impl> (host, maxQueues);
}
//------------------------------------------------------------------------
HostDataExchangeHandler::~HostDataExchangeHandler () noexcept = default;
//------------------------------------------------------------------------
IDataExchangeHandler* HostDataExchangeHandler::getInterface () const
{
return impl.get ();
}
//------------------------------------------------------------------------
uint32 HostDataExchangeHandler::sendMainThreadBlocks ()
{
return impl->sendBlocks (true, InvalidDataExchangeQueueID);
}
//------------------------------------------------------------------------
uint32 HostDataExchangeHandler::sendBackgroundBlocks (DataExchangeQueueID queueId)
{
return impl->sendBlocks (false, queueId);
}
//------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,120 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
// Flags : clang-format SMTGSequencer
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/hostdataexchangehandler.h
// Created by : Steinberg, 06/2023
// Description : VST Data Exchange API Host Helper
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "pluginterfaces/vst/ivstdataexchange.h"
#include <memory>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
struct IDataExchangeHandlerHost
{
virtual ~IDataExchangeHandlerHost () noexcept = default;
/** return if the audioprocessor is in an inactive state
* [main thread]
*/
virtual bool isProcessorInactive (IAudioProcessor* processor) = 0;
/** return the data exchange receiver (most likely the edit controller) for the processor
* [main thread]
*/
virtual IPtr<IDataExchangeReceiver> findDataExchangeReceiver (IAudioProcessor* processor) = 0;
/** check if the requested queue size should be allowed
* [main thread]
*/
virtual bool allowAllocateSize (uint32 blockSize, uint32 numBlocks, uint32 alignment) = 0;
/** check if this call is made on the main thread
* [any thread]
*/
virtual bool isMainThread () = 0;
/** check if the number of queues can be changed in this moment.
*
* this is only allowed if no other thread can access the IDataExchangeManagerHost in this
* moment
* [main thread]
*/
virtual bool allowQueueListResize (uint32 newNumQueues) = 0;
/** notification that the number of open queues changed
* [main thread]
*/
virtual void numberOfQueuesChanged (uint32 openMainThreadQueues,
uint32 openBackgroundThreadQueues) = 0;
/** notification that a new queue was opened */
virtual void onQueueOpened (IAudioProcessor* processor, DataExchangeQueueID queueID,
bool dispatchOnMainThread) = 0;
/** notification that a queue was closed */
virtual void onQueueClosed (IAudioProcessor* processor, DataExchangeQueueID queueID,
bool dispatchOnMainThread) = 0;
/** notification that a new block is ready to be send
* [process thread]
*/
virtual void newBlockReadyToBeSend (DataExchangeQueueID queueID) = 0;
};
//------------------------------------------------------------------------
struct HostDataExchangeHandler
{
/** Constructor
*
* allocate and deallocate this object on the main thread
*
* the number of queues is constant
*
* @param host the managing host
* @param maxQueues number of maximal allowed open queues
*/
HostDataExchangeHandler (IDataExchangeHandlerHost& host, uint32 maxQueues = 64);
~HostDataExchangeHandler () noexcept;
/** get the IHostDataExchangeManager interface
*
* the interface you must provide to the IAudioProcessor
*/
IDataExchangeHandler* getInterface () const;
/** send blocks
*
* the host should periodically call this method on the main thread to send all queued blocks
* which should be send on the main thread
*/
uint32 sendMainThreadBlocks ();
/** send blocks
*
* the host should call this on a dedicated background thread
* inside a mutex is used, so don't delete this object while calling this
*
* @param queueId only send blocks from the specified queue. If queueId is equal to
* InvalidDataExchangeQueueID all blocks from all queues are send.
*/
uint32 sendBackgroundBlocks (DataExchangeQueueID queueId = InvalidDataExchangeQueueID);
private:
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,327 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/module.cpp
// Created by : Steinberg, 08/2016
// Description : hosting module classes
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "module.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "public.sdk/source/vst/utility/optional.h"
#include "pluginterfaces/base/funknownimpl.h"
#include <sstream>
#include <utility>
//------------------------------------------------------------------------
namespace VST3 {
namespace Hosting {
//------------------------------------------------------------------------
FactoryInfo::FactoryInfo (PFactoryInfo&& other) noexcept
{
*this = std::move (other);
}
//------------------------------------------------------------------------
FactoryInfo& FactoryInfo::operator= (FactoryInfo&& other) noexcept
{
info = std::move (other.info);
other.info = {};
return *this;
}
//------------------------------------------------------------------------
FactoryInfo& FactoryInfo::operator= (PFactoryInfo&& other) noexcept
{
info = std::move (other);
other = {};
return *this;
}
//------------------------------------------------------------------------
std::string FactoryInfo::vendor () const noexcept
{
namespace StringConvert = Steinberg::Vst::StringConvert;
return StringConvert::convert (info.vendor, PFactoryInfo::kNameSize);
}
//------------------------------------------------------------------------
std::string FactoryInfo::url () const noexcept
{
namespace StringConvert = Steinberg::Vst::StringConvert;
return StringConvert::convert (info.url, PFactoryInfo::kURLSize);
}
//------------------------------------------------------------------------
std::string FactoryInfo::email () const noexcept
{
namespace StringConvert = Steinberg::Vst::StringConvert;
return StringConvert::convert (info.email, PFactoryInfo::kEmailSize);
}
//------------------------------------------------------------------------
Steinberg::int32 FactoryInfo::flags () const noexcept
{
return info.flags;
}
//------------------------------------------------------------------------
bool FactoryInfo::classesDiscardable () const noexcept
{
return (info.flags & PFactoryInfo::kClassesDiscardable) != 0;
}
//------------------------------------------------------------------------
bool FactoryInfo::licenseCheck () const noexcept
{
return (info.flags & PFactoryInfo::kLicenseCheck) != 0;
}
//------------------------------------------------------------------------
bool FactoryInfo::componentNonDiscardable () const noexcept
{
return (info.flags & PFactoryInfo::kComponentNonDiscardable) != 0;
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
PluginFactory::PluginFactory (const PluginFactoryPtr& factory) noexcept : factory (factory)
{
}
//------------------------------------------------------------------------
void PluginFactory::setHostContext (Steinberg::FUnknown* context) const noexcept
{
if (auto f = Steinberg::FUnknownPtr<Steinberg::IPluginFactory3> (factory))
f->setHostContext (context);
}
//------------------------------------------------------------------------
FactoryInfo PluginFactory::info () const noexcept
{
Steinberg::PFactoryInfo i;
factory->getFactoryInfo (&i);
return FactoryInfo (std::move (i));
}
//------------------------------------------------------------------------
uint32_t PluginFactory::classCount () const noexcept
{
auto count = factory->countClasses ();
assert (count >= 0);
return static_cast<uint32_t> (count);
}
//------------------------------------------------------------------------
PluginFactory::ClassInfos PluginFactory::classInfos () const noexcept
{
auto count = classCount ();
Optional<FactoryInfo> factoryInfo;
ClassInfos classes;
classes.reserve (count);
auto f3 = Steinberg::U::cast<Steinberg::IPluginFactory3> (factory);
auto f2 = Steinberg::U::cast<Steinberg::IPluginFactory2> (factory);
Steinberg::PClassInfo ci;
Steinberg::PClassInfo2 ci2;
Steinberg::PClassInfoW ci3;
for (uint32_t i = 0; i < count; ++i)
{
if (f3 && f3->getClassInfoUnicode (i, &ci3) == Steinberg::kResultTrue)
classes.emplace_back (ci3);
else if (f2 && f2->getClassInfo2 (i, &ci2) == Steinberg::kResultTrue)
classes.emplace_back (ci2);
else if (factory->getClassInfo (i, &ci) == Steinberg::kResultTrue)
classes.emplace_back (ci);
auto& classInfo = classes.back ();
if (classInfo.vendor ().empty ())
{
if (!factoryInfo)
factoryInfo = Optional<FactoryInfo> (info ());
classInfo.get ().vendor = factoryInfo->vendor ();
}
}
return classes;
}
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
const UID& ClassInfo::ID () const noexcept
{
return data.classID;
}
//------------------------------------------------------------------------
int32_t ClassInfo::cardinality () const noexcept
{
return data.cardinality;
}
//------------------------------------------------------------------------
const std::string& ClassInfo::category () const noexcept
{
return data.category;
}
//------------------------------------------------------------------------
const std::string& ClassInfo::name () const noexcept
{
return data.name;
}
//------------------------------------------------------------------------
const std::string& ClassInfo::vendor () const noexcept
{
return data.vendor;
}
//------------------------------------------------------------------------
const std::string& ClassInfo::version () const noexcept
{
return data.version;
}
//------------------------------------------------------------------------
const std::string& ClassInfo::sdkVersion () const noexcept
{
return data.sdkVersion;
}
//------------------------------------------------------------------------
const ClassInfo::SubCategories& ClassInfo::subCategories () const noexcept
{
return data.subCategories;
}
//------------------------------------------------------------------------
Steinberg::uint32 ClassInfo::classFlags () const noexcept
{
return data.classFlags;
}
//------------------------------------------------------------------------
ClassInfo::ClassInfo (const PClassInfo& info) noexcept
{
namespace StringConvert = Steinberg::Vst::StringConvert;
data.classID = info.cid;
data.cardinality = info.cardinality;
data.category = StringConvert::convert (info.category, PClassInfo::kCategorySize);
data.name = StringConvert::convert (info.name, PClassInfo::kNameSize);
}
//------------------------------------------------------------------------
ClassInfo::ClassInfo (const PClassInfo2& info) noexcept
{
namespace StringConvert = Steinberg::Vst::StringConvert;
data.classID = info.cid;
data.cardinality = info.cardinality;
data.category = StringConvert::convert (info.category, PClassInfo::kCategorySize);
data.name = StringConvert::convert (info.name, PClassInfo::kNameSize);
data.vendor = StringConvert::convert (info.vendor, PClassInfo2::kVendorSize);
data.version = StringConvert::convert (info.version, PClassInfo2::kVersionSize);
data.sdkVersion = StringConvert::convert (info.sdkVersion, PClassInfo2::kVersionSize);
parseSubCategories (
StringConvert::convert (info.subCategories, PClassInfo2::kSubCategoriesSize));
data.classFlags = info.classFlags;
}
//------------------------------------------------------------------------
ClassInfo::ClassInfo (const PClassInfoW& info) noexcept
{
namespace StringConvert = Steinberg::Vst::StringConvert;
data.classID = info.cid;
data.cardinality = info.cardinality;
data.category = StringConvert::convert (info.category, PClassInfo::kCategorySize);
data.name = StringConvert::convert (info.name, PClassInfo::kNameSize);
data.vendor = StringConvert::convert (info.vendor, PClassInfo2::kVendorSize);
data.version = StringConvert::convert (info.version, PClassInfo2::kVersionSize);
data.sdkVersion = StringConvert::convert (info.sdkVersion, PClassInfo2::kVersionSize);
parseSubCategories (
StringConvert::convert (info.subCategories, PClassInfo2::kSubCategoriesSize));
data.classFlags = info.classFlags;
}
//------------------------------------------------------------------------
void ClassInfo::parseSubCategories (const std::string& str) noexcept
{
std::stringstream stream (str);
std::string item;
while (std::getline (stream, item, '|'))
data.subCategories.emplace_back (std::move (item));
}
//------------------------------------------------------------------------
std::string ClassInfo::subCategoriesString () const noexcept
{
std::string result;
if (data.subCategories.empty ())
return result;
result = data.subCategories[0];
for (auto index = 1u; index < data.subCategories.size (); ++index)
result += "|" + data.subCategories[index];
return result;
}
//------------------------------------------------------------------------
namespace {
//------------------------------------------------------------------------
std::pair<size_t, size_t> rangeOfScaleFactor (const std::string& name)
{
auto result = std::make_pair (std::string::npos, std::string::npos);
size_t xIndex = name.find_last_of ('x');
if (xIndex == std::string::npos)
return result;
size_t indicatorIndex = name.find_last_of ('_');
if (indicatorIndex == std::string::npos)
return result;
if (xIndex < indicatorIndex)
return result;
result.first = indicatorIndex + 1;
result.second = xIndex;
return result;
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
Optional<double> Module::Snapshot::decodeScaleFactor (const std::string& name)
{
auto range = rangeOfScaleFactor (name);
if (range.first == std::string::npos || range.second == std::string::npos)
return {};
std::string tmp (name.data () + range.first, range.second - range.first);
std::istringstream sstream (tmp);
sstream.imbue (std::locale::classic ());
sstream.precision (static_cast<std::streamsize> (3));
double result;
sstream >> result;
return Optional<double> (result);
}
//------------------------------------------------------------------------
Optional<UID> Module::Snapshot::decodeUID (const std::string& filename)
{
if (filename.size () < 45)
return {};
if (filename.find ("_snapshot") != 32)
return {};
auto uidStr = filename.substr (0, 32);
return UID::fromString (uidStr);
}
//------------------------------------------------------------------------
} // Hosting
} // VST3
@@ -0,0 +1,196 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/module.h
// Created by : Steinberg, 08/2016
// Description : hosting module classes
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "../utility/uid.h"
#include "pluginterfaces/base/ipluginbase.h"
#include <utility>
#include <vector>
//------------------------------------------------------------------------
namespace VST3 {
namespace Hosting {
//------------------------------------------------------------------------
class FactoryInfo
{
public:
//------------------------------------------------------------------------
using PFactoryInfo = Steinberg::PFactoryInfo;
FactoryInfo () noexcept {}
~FactoryInfo () noexcept {}
FactoryInfo (const FactoryInfo&) noexcept = default;
FactoryInfo (PFactoryInfo&&) noexcept;
FactoryInfo (FactoryInfo&&) noexcept = default;
FactoryInfo& operator= (const FactoryInfo&) noexcept = default;
FactoryInfo& operator= (FactoryInfo&&) noexcept;
FactoryInfo& operator= (PFactoryInfo&&) noexcept;
std::string vendor () const noexcept;
std::string url () const noexcept;
std::string email () const noexcept;
Steinberg::int32 flags () const noexcept;
bool classesDiscardable () const noexcept;
bool licenseCheck () const noexcept;
bool componentNonDiscardable () const noexcept;
PFactoryInfo& get () noexcept { return info; }
//------------------------------------------------------------------------
private:
PFactoryInfo info {};
};
//------------------------------------------------------------------------
class ClassInfo
{
public:
//------------------------------------------------------------------------
using SubCategories = std::vector<std::string>;
using PClassInfo = Steinberg::PClassInfo;
using PClassInfo2 = Steinberg::PClassInfo2;
using PClassInfoW = Steinberg::PClassInfoW;
//------------------------------------------------------------------------
ClassInfo () noexcept {}
explicit ClassInfo (const PClassInfo& info) noexcept;
explicit ClassInfo (const PClassInfo2& info) noexcept;
explicit ClassInfo (const PClassInfoW& info) noexcept;
ClassInfo (const ClassInfo&) = default;
ClassInfo& operator= (const ClassInfo&) = default;
ClassInfo (ClassInfo&&) = default;
ClassInfo& operator= (ClassInfo&&) = default;
const UID& ID () const noexcept;
int32_t cardinality () const noexcept;
const std::string& category () const noexcept;
const std::string& name () const noexcept;
const std::string& vendor () const noexcept;
const std::string& version () const noexcept;
const std::string& sdkVersion () const noexcept;
const SubCategories& subCategories () const noexcept;
std::string subCategoriesString () const noexcept;
Steinberg::uint32 classFlags () const noexcept;
struct Data
{
UID classID;
int32_t cardinality;
std::string category;
std::string name;
std::string vendor;
std::string version;
std::string sdkVersion;
SubCategories subCategories;
Steinberg::uint32 classFlags = 0;
};
Data& get () noexcept { return data; }
//------------------------------------------------------------------------
private:
void parseSubCategories (const std::string& str) noexcept;
Data data {};
};
//------------------------------------------------------------------------
class PluginFactory
{
public:
//------------------------------------------------------------------------
using ClassInfos = std::vector<ClassInfo>;
using PluginFactoryPtr = Steinberg::IPtr<Steinberg::IPluginFactory>;
//------------------------------------------------------------------------
explicit PluginFactory (const PluginFactoryPtr& factory) noexcept;
void setHostContext (Steinberg::FUnknown* context) const noexcept;
FactoryInfo info () const noexcept;
uint32_t classCount () const noexcept;
ClassInfos classInfos () const noexcept;
template <typename T>
Steinberg::IPtr<T> createInstance (const UID& classID) const noexcept;
const PluginFactoryPtr& get () const noexcept { return factory; }
//------------------------------------------------------------------------
private:
PluginFactoryPtr factory;
};
//------------------------------------------------------------------------
//------------------------------------------------------------------------
class Module
{
public:
//------------------------------------------------------------------------
struct Snapshot
{
struct ImageDesc
{
double scaleFactor {1.};
std::string path;
};
UID uid;
std::vector<ImageDesc> images;
static Optional<double> decodeScaleFactor (const std::string& path);
static Optional<UID> decodeUID (const std::string& filename);
};
using Ptr = std::shared_ptr<Module>;
using PathList = std::vector<std::string>;
using SnapshotList = std::vector<Snapshot>;
//------------------------------------------------------------------------
static Ptr create (const std::string& path, std::string& errorDescription);
static PathList getModulePaths ();
static SnapshotList getSnapshots (const std::string& modulePath);
/** get the path to the module info json file if it exists */
static Optional<std::string> getModuleInfoPath (const std::string& modulePath);
/** validate the bundle structure */
static bool validateBundleStructure (const std::string& path, std::string& errorDescription);
const std::string& getName () const noexcept { return name; }
const std::string& getPath () const noexcept { return path; }
const PluginFactory& getFactory () const noexcept { return factory; }
bool isBundle () const noexcept { return hasBundleStructure; }
//------------------------------------------------------------------------
protected:
virtual ~Module () noexcept = default;
virtual bool load (const std::string& path, std::string& errorDescription) = 0;
PluginFactory factory {nullptr};
std::string name;
std::string path;
bool hasBundleStructure {true};
};
//------------------------------------------------------------------------
template <typename T>
inline Steinberg::IPtr<T> PluginFactory::createInstance (const UID& classID) const noexcept
{
T* obj = nullptr;
if (factory->createInstance (classID.data (), T::iid, reinterpret_cast<void**> (&obj)) ==
Steinberg::kResultTrue)
return Steinberg::owned (obj);
return nullptr;
}
//------------------------------------------------------------------------
} // Hosting
} // VST3
@@ -0,0 +1,391 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/module_linux.cpp
// Created by : Steinberg, 08/2016
// Description : hosting module classes (linux implementation)
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "module.h"
#include "public.sdk/source/vst/utility/optional.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "pluginterfaces/base/funknownimpl.h"
#include <algorithm>
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#if SMTG_CPP17
#if __has_include(<filesystem>)
#define USE_EXPERIMENTAL_FS 0
#elif __has_include(<experimental/filesystem>)
#define USE_EXPERIMENTAL_FS 1
#endif
#else // !SMTG_CPP17
#define USE_EXPERIMENTAL_FS 1
#endif // SMTG_CPP17
#if USE_EXPERIMENTAL_FS == 1
#include <experimental/filesystem>
namespace filesystem = std::experimental::filesystem;
#else // USE_EXPERIMENTAL_FS == 0
#include <filesystem>
namespace filesystem = std::filesystem;
#endif // USE_EXPERIMENTAL_FS
//------------------------------------------------------------------------
extern "C" {
using ModuleEntryFunc = bool (PLUGIN_API*) (void*);
using ModuleExitFunc = bool (PLUGIN_API*) ();
}
//------------------------------------------------------------------------
namespace VST3 {
namespace Hosting {
using Path = filesystem::path;
//------------------------------------------------------------------------
namespace {
//------------------------------------------------------------------------
Optional<std::string> getCurrentMachineName ()
{
struct utsname unameData;
int res = uname (&unameData);
if (res != 0)
return {};
return {unameData.machine};
}
//------------------------------------------------------------------------
Optional<Path> getApplicationPath ()
{
std::string appPath = "";
pid_t pid = getpid ();
char buf[10];
sprintf (buf, "%d", pid);
std::string _link = "/proc/";
_link.append (buf);
_link.append ("/exe");
char proc[1024];
int ch = readlink (_link.c_str (), proc, 1024);
if (ch == -1)
return {};
proc[ch] = 0;
appPath = proc;
std::string::size_type t = appPath.find_last_of ("/");
appPath = appPath.substr (0, t);
return Path {appPath};
}
//------------------------------------------------------------------------
class LinuxModule : public Module
{
public:
template <typename T>
T getFunctionPointer (const char* name)
{
return reinterpret_cast<T> (dlsym (mModule, name));
}
~LinuxModule () override
{
factory = PluginFactory (nullptr);
if (mModule)
{
if (auto moduleExit = getFunctionPointer<ModuleExitFunc> ("ModuleExit"))
moduleExit ();
dlclose (mModule);
}
}
static Optional<Path> getSOPath (const std::string& inPath)
{
Path modulePath {inPath};
if (!filesystem::is_directory (modulePath))
return {};
auto stem = modulePath.stem ();
modulePath /= "Contents";
if (!filesystem::is_directory (modulePath))
return {};
// use the Machine Hardware Name (from uname cmd-line) as prefix for "-linux"
auto machine = getCurrentMachineName ();
if (!machine)
return {};
modulePath /= *machine + "-linux";
if (!filesystem::is_directory (modulePath))
return {};
modulePath /= stem;
modulePath += ".so";
return Optional<Path> (std::move (modulePath));
}
bool load (const std::string& inPath, std::string& errorDescription) override
{
auto modulePath = getSOPath (inPath);
if (!modulePath)
{
errorDescription = inPath + " is not a module directory.";
return false;
}
mModule = dlopen (reinterpret_cast<const char*> (modulePath->generic_string ().data ()),
RTLD_LAZY);
if (!mModule)
{
errorDescription = "dlopen failed.\n";
errorDescription += dlerror ();
return false;
}
// ModuleEntry is mandatory
auto moduleEntry = getFunctionPointer<ModuleEntryFunc> ("ModuleEntry");
if (!moduleEntry)
{
errorDescription =
"The shared library does not export the required 'ModuleEntry' function";
return false;
}
// ModuleExit is mandatory
auto moduleExit = getFunctionPointer<ModuleExitFunc> ("ModuleExit");
if (!moduleExit)
{
errorDescription =
"The shared library does not export the required 'ModuleExit' function";
return false;
}
auto factoryProc = getFunctionPointer<GetFactoryProc> ("GetPluginFactory");
if (!factoryProc)
{
errorDescription =
"The shared library does not export the required 'GetPluginFactory' function";
return false;
}
if (!moduleEntry (mModule))
{
errorDescription = "Calling 'ModuleEntry' failed";
return false;
}
auto f = Steinberg::U::cast<Steinberg::IPluginFactory> (owned (factoryProc ()));
if (!f)
{
errorDescription = "Calling 'GetPluginFactory' returned nullptr";
return false;
}
factory = PluginFactory (f);
return true;
}
void* mModule {nullptr};
};
//------------------------------------------------------------------------
void findFilesWithExt (const std::string& path, const std::string& ext, Module::PathList& pathList,
bool recursive = true)
{
try
{
for (auto& p : filesystem::directory_iterator (path))
{
if (p.path ().extension () == ext)
{
pathList.push_back (p.path ().generic_string ());
}
else if (recursive && p.status ().type () == filesystem::file_type::directory)
{
findFilesWithExt (p.path (), ext, pathList);
}
}
}
catch (...)
{
}
}
//------------------------------------------------------------------------
void findModules (const std::string& path, Module::PathList& pathList)
{
findFilesWithExt (path, ".vst3", pathList);
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
Module::Ptr Module::create (const std::string& path, std::string& errorDescription)
{
auto _module = std::make_shared<LinuxModule> ();
if (_module->load (path, errorDescription))
{
_module->path = path;
auto it = std::find_if (path.rbegin (), path.rend (),
[] (const std::string::value_type& c) { return c == '/'; });
if (it != path.rend ())
_module->name = {it.base (), path.end ()};
return _module;
}
return nullptr;
}
//------------------------------------------------------------------------
Module::PathList Module::getModulePaths ()
{
/* VST3 component locations on linux :
* User privately installed : $HOME/.vst3/
* Distribution installed : /usr/lib/vst3/
* Locally installed : /usr/local/lib/vst3/
* Application : /$APPFOLDER/vst3/
*/
const auto systemPaths = {"/usr/lib/vst3/", "/usr/local/lib/vst3/"};
PathList list;
if (auto homeDir = getenv ("HOME"))
{
filesystem::path homePath (homeDir);
homePath /= ".vst3";
findModules (homePath.generic_string (), list);
}
for (auto path : systemPaths)
findModules (path, list);
// application level
auto appPath = getApplicationPath ();
if (appPath)
{
*appPath /= "vst3";
findModules (appPath->generic_string (), list);
}
return list;
}
//------------------------------------------------------------------------
Module::SnapshotList Module::getSnapshots (const std::string& modulePath)
{
SnapshotList result;
filesystem::path path (modulePath);
path /= "Contents";
path /= "Resources";
path /= "Snapshots";
PathList pngList;
findFilesWithExt (path, ".png", pngList, false);
for (auto& png : pngList)
{
filesystem::path p (png);
auto filename = p.filename ().generic_string ();
auto uid = Snapshot::decodeUID (filename);
if (!uid)
continue;
auto scaleFactor = 1.;
if (auto decodedScaleFactor = Snapshot::decodeScaleFactor (filename))
scaleFactor = *decodedScaleFactor;
Module::Snapshot::ImageDesc desc;
desc.scaleFactor = scaleFactor;
desc.path = std::move (png);
bool found = false;
for (auto& entry : result)
{
if (entry.uid != *uid)
continue;
found = true;
entry.images.emplace_back (std::move (desc));
break;
}
if (found)
continue;
Module::Snapshot snapshot;
snapshot.uid = *uid;
snapshot.images.emplace_back (std::move (desc));
result.emplace_back (std::move (snapshot));
}
return result;
}
//------------------------------------------------------------------------
Optional<std::string> Module::getModuleInfoPath (const std::string& modulePath)
{
filesystem::path path (modulePath);
path /= "Contents";
path /= "Resources";
path /= "moduleinfo.json";
if (filesystem::exists (path))
return {path.generic_string ()};
return {};
}
//------------------------------------------------------------------------
bool Module::validateBundleStructure (const std::string& modulePath, std::string& errorDescription)
{
filesystem::path path (modulePath);
auto moduleName = path.filename ();
path /= "Contents";
if (filesystem::exists (path) == false)
{
errorDescription = "Expecting 'Contents' as first subfolder.";
return false;
}
auto machine = getCurrentMachineName ();
if (!machine)
{
errorDescription = "Could not get the current machine name.";
return false;
}
path /= *machine + "-linux";
if (filesystem::exists (path) == false)
{
errorDescription = "Expecting '" + *machine + "-linux' as architecture subfolder.";
return false;
}
moduleName.replace_extension (".so");
path /= moduleName;
if (filesystem::exists (path) == false)
{
errorDescription = "Shared library name is not equal to bundle folder name. Must be '" +
moduleName.string () + "'.";
return false;
}
return true;
}
//------------------------------------------------------------------------
} // Hosting
} // VST3
@@ -0,0 +1,383 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/module_mac.mm
// Created by : Steinberg, 08/2016
// Description : hosting module classes (macOS implementation)
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#import "module.h"
#import <Cocoa/Cocoa.h>
#import <CoreFoundation/CoreFoundation.h>
#if !__has_feature(objc_arc)
#error this file needs to be compiled with automatic reference counting enabled
#endif
//------------------------------------------------------------------------
extern "C" {
typedef bool (*BundleEntryFunc) (CFBundleRef);
typedef bool (*BundleExitFunc) ();
}
//------------------------------------------------------------------------
namespace VST3 {
namespace Hosting {
//------------------------------------------------------------------------
namespace {
//------------------------------------------------------------------------
template <typename T>
class CFPtr
{
public:
inline CFPtr (const T& obj = nullptr) : obj (obj) {}
inline CFPtr (CFPtr&& other) { *this = other; }
inline ~CFPtr ()
{
if (obj)
CFRelease (obj);
}
inline CFPtr& operator= (CFPtr&& other)
{
obj = other.obj;
other.obj = nullptr;
return *this;
}
inline CFPtr& operator= (const T& o)
{
if (obj)
CFRelease (obj);
obj = o;
return *this;
}
inline operator T () const { return obj; } // act as T
private:
CFPtr (const CFPtr& other) = delete;
CFPtr& operator= (const CFPtr& other) = delete;
T obj = nullptr;
};
//------------------------------------------------------------------------
class MacModule : public Module
{
public:
template <typename T>
T getFunctionPointer (const char* name)
{
assert (bundle);
CFPtr<CFStringRef> functionName (
CFStringCreateWithCString (kCFAllocatorDefault, name, kCFStringEncodingASCII));
return reinterpret_cast<T> (CFBundleGetFunctionPointerForName (bundle, functionName));
}
bool loadInternal (const std::string& path, std::string& errorDescription)
{
CFPtr<CFURLRef> url (CFURLCreateFromFileSystemRepresentation (
kCFAllocatorDefault, reinterpret_cast<const UInt8*> (path.data ()), path.length (),
true));
if (!url)
return false;
bundle = CFBundleCreate (kCFAllocatorDefault, url);
CFErrorRef error = nullptr;
if (!bundle || !CFBundleLoadExecutableAndReturnError (bundle, &error))
{
if (error)
{
CFPtr<CFStringRef> errorString (CFErrorCopyDescription (error));
if (errorString)
{
auto stringLength = CFStringGetLength (errorString);
auto maxSize =
CFStringGetMaximumSizeForEncoding (stringLength, kCFStringEncodingUTF8);
auto buffer = std::make_unique<char[]> (maxSize);
if (CFStringGetCString (errorString, buffer.get (), maxSize,
kCFStringEncodingUTF8))
errorDescription = buffer.get ();
CFRelease (error);
}
}
else
{
errorDescription = "Could not create Bundle for path: " + path;
}
return false;
}
// bundleEntry is mandatory
auto bundleEntry = getFunctionPointer<BundleEntryFunc> ("bundleEntry");
if (!bundleEntry)
{
errorDescription = "Bundle does not export the required 'bundleEntry' function";
return false;
}
// bundleExit is mandatory
auto bundleExit = getFunctionPointer<BundleExitFunc> ("bundleExit");
if (!bundleExit)
{
errorDescription = "Bundle does not export the required 'bundleExit' function";
return false;
}
auto factoryProc = getFunctionPointer<GetFactoryProc> ("GetPluginFactory");
if (!factoryProc)
{
errorDescription = "Bundle does not export the required 'GetPluginFactory' function";
return false;
}
if (!bundleEntry (bundle))
{
errorDescription = "Calling 'bundleEntry' failed";
return false;
}
auto f = owned (factoryProc ());
if (!f)
{
errorDescription = "Calling 'GetPluginFactory' returned nullptr";
return false;
}
factory = PluginFactory (f);
return true;
}
bool load (const std::string& path, std::string& errorDescription) override
{
if (!path.empty () && path[0] != '/')
{
auto buffer = std::make_unique<char[]> (PATH_MAX);
auto workDir = getcwd (buffer.get (), PATH_MAX);
if (workDir)
{
std::string wd (workDir);
wd += "/";
if (loadInternal (wd + path, errorDescription))
{
name = path;
return true;
}
return false;
}
}
return loadInternal (path, errorDescription);
}
~MacModule () override
{
factory = PluginFactory (nullptr);
if (bundle)
{
if (auto bundleExit = getFunctionPointer<BundleExitFunc> ("bundleExit"))
bundleExit ();
}
}
CFPtr<CFBundleRef> bundle;
};
//------------------------------------------------------------------------
void findModulesInDirectory (NSURL* dirUrl, Module::PathList& result)
{
dirUrl = [dirUrl URLByResolvingSymlinksInPath];
if (!dirUrl)
return;
NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager]
enumeratorAtURL: dirUrl
includingPropertiesForKeys:nil
options:NSDirectoryEnumerationSkipsPackageDescendants
errorHandler:nil];
for (NSURL* url in enumerator)
{
if ([[[url lastPathComponent] pathExtension] isEqualToString:@"vst3"])
{
CFPtr<CFArrayRef> archs (
CFBundleCopyExecutableArchitecturesForURL (static_cast<CFURLRef> (url)));
if (archs)
result.emplace_back ([url.path UTF8String]);
}
else
{
id resValue;
if (![url getResourceValue:&resValue forKey:NSURLIsSymbolicLinkKey error:nil])
continue;
if (!static_cast<NSNumber*> (resValue).boolValue)
continue;
auto resolvedUrl = [url URLByResolvingSymlinksInPath];
if (![resolvedUrl getResourceValue:&resValue forKey:NSURLIsDirectoryKey error:nil])
continue;
if (!static_cast<NSNumber*> (resValue).boolValue)
continue;
findModulesInDirectory (resolvedUrl, result);
}
}
}
//------------------------------------------------------------------------
void getModules (NSSearchPathDomainMask domain, Module::PathList& result)
{
NSURL* libraryUrl = [[NSFileManager defaultManager] URLForDirectory:NSLibraryDirectory
inDomain:domain
appropriateForURL:nil
create:NO
error:nil];
if (libraryUrl == nil)
return;
NSURL* audioUrl = [libraryUrl URLByAppendingPathComponent:@"Audio"];
if (audioUrl == nil)
return;
NSURL* plugInsUrl = [audioUrl URLByAppendingPathComponent:@"Plug-Ins"];
if (plugInsUrl == nil)
return;
NSURL* vst3Url =
[[plugInsUrl URLByAppendingPathComponent:@"VST3"] URLByResolvingSymlinksInPath];
if (vst3Url == nil)
return;
findModulesInDirectory (vst3Url, result);
}
//------------------------------------------------------------------------
void getApplicationModules (Module::PathList& result)
{
auto bundle = CFBundleGetMainBundle ();
if (!bundle)
return;
auto bundleUrl = static_cast<NSURL*> (CFBridgingRelease (CFBundleCopyBundleURL (bundle)));
if (!bundleUrl)
return;
auto resUrl = [bundleUrl URLByAppendingPathComponent:@"Contents"];
if (!resUrl)
return;
auto vst3Url = [resUrl URLByAppendingPathComponent:@"VST3"];
if (!vst3Url)
return;
findModulesInDirectory (vst3Url, result);
}
//------------------------------------------------------------------------
void getModuleSnapshots (const std::string& path, Module::SnapshotList& result)
{
auto* nsString = [NSString stringWithUTF8String:path.data ()];
if (!nsString)
return;
auto bundleUrl = [NSURL fileURLWithPath:nsString];
if (!bundleUrl)
return;
auto urls = [NSBundle URLsForResourcesWithExtension:@"png"
subdirectory:@"Snapshots"
inBundleWithURL:bundleUrl];
if (!urls || [urls count] == 0)
return;
for (NSURL* url in urls)
{
std::string fullpath ([[url path] UTF8String]);
std::string filename ([[[url path] lastPathComponent] UTF8String]);
auto uid = Module::Snapshot::decodeUID (filename);
if (!uid)
continue;
auto scaleFactor = 1.;
if (auto decodedScaleFactor = Module::Snapshot::decodeScaleFactor (filename))
scaleFactor = *decodedScaleFactor;
Module::Snapshot::ImageDesc desc;
desc.scaleFactor = scaleFactor;
desc.path = std::move (fullpath);
bool found = false;
for (auto& entry : result)
{
if (entry.uid != *uid)
continue;
found = true;
entry.images.emplace_back (std::move (desc));
break;
}
if (found)
continue;
Module::Snapshot snapshot;
snapshot.uid = *uid;
snapshot.images.emplace_back (std::move (desc));
result.emplace_back (std::move (snapshot));
}
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
Module::Ptr Module::create (const std::string& path, std::string& errorDescription)
{
auto module = std::make_shared<MacModule> ();
if (module->load (path, errorDescription))
{
module->path = path;
auto it = std::find_if (path.rbegin (), path.rend (),
[] (const std::string::value_type& c) { return c == '/'; });
if (it != path.rend ())
module->name = {it.base (), path.end ()};
return std::move (module);
}
return nullptr;
}
//------------------------------------------------------------------------
Module::PathList Module::getModulePaths ()
{
PathList list;
getModules (NSUserDomainMask, list);
getModules (NSLocalDomainMask, list);
// TODO getModules (NSNetworkDomainMask, list);
getApplicationModules (list);
return list;
}
//------------------------------------------------------------------------
Module::SnapshotList Module::getSnapshots (const std::string& modulePath)
{
SnapshotList list;
getModuleSnapshots (modulePath, list);
return list;
}
//------------------------------------------------------------------------
Optional<std::string> Module::getModuleInfoPath (const std::string& modulePath)
{
auto* nsString = [NSString stringWithUTF8String:modulePath.data ()];
if (!nsString)
return {};
auto bundleUrl = [NSURL fileURLWithPath:nsString];
if (!bundleUrl)
return {};
auto moduleInfoUrl = [NSBundle URLForResource:@"moduleinfo"
withExtension:@"json"
subdirectory:nullptr
inBundleWithURL:bundleUrl];
if (!moduleInfoUrl)
return {};
NSError* error = nil;
if ([moduleInfoUrl checkResourceIsReachableAndReturnError:&error])
return {std::string (moduleInfoUrl.fileSystemRepresentation)};
return {};
}
//------------------------------------------------------------------------
bool Module::validateBundleStructure (const std::string& path, std::string& errorDescription)
{
auto* nsString = [NSString stringWithUTF8String:path.data ()];
if (!nsString)
return false;
return [NSBundle bundleWithPath:nsString] != nil;
}
//------------------------------------------------------------------------
} // Hosting
} // VST3
@@ -0,0 +1,746 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/module_win32.cpp
// Created by : Steinberg, 08/2016
// Description : hosting module classes (win32 implementation)
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "module.h"
#include "public.sdk/source/vst/utility/optional.h"
#include "public.sdk/source/vst/utility/stringconvert.h"
#include "pluginterfaces/base/funknownimpl.h"
#include <shlobj.h>
#include <windows.h>
#include <algorithm>
#include <iostream>
#if SMTG_CPP17
#if __has_include(<filesystem>)
#define USE_FILESYSTEM 1
#elif __has_include(<experimental/filesystem>)
#define USE_FILESYSTEM 0
#endif
#else // !SMTG_CPP17
#define USE_FILESYSTEM 0
#endif // SMTG_CPP17
#if USE_FILESYSTEM == 1
#include <filesystem>
namespace filesystem = std::filesystem;
#else // USE_FILESYSTEM == 0
// The <experimental/filesystem> header is deprecated. It is superseded by the C++17 <filesystem>
// header. You can define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING to silence the
// warning, otherwise the build will fail in VS2019 16.3.0
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#include <experimental/filesystem>
namespace filesystem = std::experimental::filesystem;
#endif // USE_FILESYSTEM
#pragma comment(lib, "Shell32")
//------------------------------------------------------------------------
extern "C" {
using InitModuleFunc = bool (PLUGIN_API*) ();
using ExitModuleFunc = bool (PLUGIN_API*) ();
}
//------------------------------------------------------------------------
namespace VST3 {
namespace Hosting {
constexpr unsigned long kIPPathNameMax = 1024;
//------------------------------------------------------------------------
namespace {
#define USE_OLE !USE_FILESYSTEM
// for testing only
#if 0 // DEVELOPMENT
#define LOG_ENABLE 1
#else
#define LOG_ENABLE 0
#endif
#if SMTG_PLATFORM_64
#if SMTG_OS_WINDOWS_ARM
#if SMTG_CPU_ARM_64EC
constexpr auto architectureString = "arm64ec-win";
constexpr auto architectureX64String = "x86_64-win";
#else // !SMTG_CPU_ARM_64EC
constexpr auto architectureString = "arm64-win";
#endif // SMTG_CPU_ARM_64EC
constexpr auto architectureArm64XString = "arm64x-win";
#else // !SMTG_OS_WINDOWS_ARM
constexpr auto architectureString = "x86_64-win";
#endif // SMTG_OS_WINDOWS_ARM
#else // !SMTG_PLATFORM_64
#if SMTG_OS_WINDOWS_ARM
constexpr auto architectureString = "arm-win";
#else // !SMTG_OS_WINDOWS_ARM
constexpr auto architectureString = "x86-win";
#endif // SMTG_OS_WINDOWS_ARM
#endif // SMTG_PLATFORM_64
#if USE_OLE
//------------------------------------------------------------------------
struct Ole
{
static Ole& instance ()
{
static Ole gInstance;
return gInstance;
}
private:
Ole () { OleInitialize (nullptr); }
~Ole () { OleUninitialize (); }
};
#endif // USE_OLE
//------------------------------------------------------------------------
class Win32Module : public Module
{
public:
template <typename T>
T getFunctionPointer (const char* name)
{
return reinterpret_cast<T> (GetProcAddress (mModule, name));
}
~Win32Module () override
{
factory = PluginFactory (nullptr);
if (mModule)
{
// ExitDll is optional
if (auto dllExit = getFunctionPointer<ExitModuleFunc> ("ExitDll"))
dllExit ();
FreeLibrary ((HMODULE)mModule);
}
}
//--- -----------------------------------------------------------------------
HINSTANCE loadAsPackage (const std::string& inPath, std::string& errorDescription,
const char* archString = architectureString)
{
namespace StringConvert = Steinberg::Vst::StringConvert;
filesystem::path p (inPath);
auto filename = p.filename ();
p /= "Contents";
p /= archString;
p /= filename;
const std::wstring wString = p.generic_wstring ();
HINSTANCE instance = LoadLibraryW (reinterpret_cast<LPCWSTR> (wString.data ()));
#if SMTG_CPU_ARM_64EC
if (instance == nullptr)
instance = loadAsPackage (inPath, errorDescription, architectureArm64XString);
if (instance == nullptr)
instance = loadAsPackage (inPath, errorDescription, architectureX64String);
#endif // SMTG_CPU_ARM_64EC
if (instance == nullptr)
getLastError (p.string (), errorDescription);
return instance;
}
//--- -----------------------------------------------------------------------
HINSTANCE loadAsDll (const std::string& inPath, std::string& errorDescription)
{
namespace StringConvert = Steinberg::Vst::StringConvert;
auto wideStr = StringConvert::convert (inPath);
HINSTANCE instance = LoadLibraryW (reinterpret_cast<LPCWSTR> (wideStr.data ()));
if (instance == nullptr)
{
getLastError (inPath, errorDescription);
}
else
{
hasBundleStructure = false;
}
return instance;
}
//--- -----------------------------------------------------------------------
bool load (const std::string& inPath, std::string& errorDescription) override
{
// filesystem::u8path is deprecated in C++20
#if SMTG_CPP20
const filesystem::path tmp (inPath);
#else
const filesystem::path tmp = filesystem::u8path (inPath);
#endif // SMTG_CPP20
std::error_code ec;
if (filesystem::is_directory (tmp, ec))
{
// try as package (bundle)
mModule = loadAsPackage (inPath, errorDescription);
}
else
{
// try old definition without package
mModule = loadAsDll (inPath, errorDescription);
}
if (mModule == nullptr)
return false;
auto factoryProc = getFunctionPointer<GetFactoryProc> ("GetPluginFactory");
if (!factoryProc)
{
errorDescription = "The dll does not export the required 'GetPluginFactory' function";
return false;
}
// InitDll is optional
auto dllEntry = getFunctionPointer<InitModuleFunc> ("InitDll");
if (dllEntry && !dllEntry ())
{
errorDescription = "Calling 'InitDll' failed";
return false;
}
auto f = Steinberg::U::cast<Steinberg::IPluginFactory> (owned (factoryProc ()));
if (!f)
{
errorDescription = "Calling 'GetPluginFactory' returned nullptr";
return false;
}
factory = PluginFactory (f);
return true;
}
HINSTANCE mModule {nullptr};
private:
//--- -----------------------------------------------------------------------
void getLastError (const std::string& inPath, std::string& errorDescription)
{
auto lastError = GetLastError ();
LPVOID lpMessageBuffer {nullptr};
if (FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr,
lastError, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&lpMessageBuffer, 0, nullptr) > 0)
{
errorDescription = "LoadLibraryW failed for path " + inPath + ": " +
std::string ((char*)lpMessageBuffer);
LocalFree (lpMessageBuffer);
}
else
{
errorDescription = "LoadLibraryW failed with error number: " +
std::to_string (lastError) + " for path " + inPath;
}
}
};
//------------------------------------------------------------------------
bool openVST3Package (const filesystem::path& p, const char* archString,
filesystem::path* result = nullptr)
{
auto path = p;
path /= "Contents";
path /= archString;
path /= p.filename ();
const std::wstring wString = path.generic_wstring ();
auto hFile = CreateFileW (reinterpret_cast<LPCWSTR> (wString.data ()), GENERIC_READ,
FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle (hFile);
if (result)
*result = path;
return true;
}
return false;
}
//------------------------------------------------------------------------
bool checkVST3Package (const filesystem::path& p, filesystem::path* result = nullptr,
const char* archString = architectureString)
{
if (openVST3Package (p, archString, result))
return true;
#if SMTG_CPU_ARM_64EC
if (openVST3Package (p, architectureArm64XString, result))
return true;
if (openVST3Package (p, architectureX64String, result))
return true;
#endif // SMTG_CPU_ARM_64EC
return false;
}
//------------------------------------------------------------------------
bool isFolderSymbolicLink (const filesystem::path& p)
{
#if USE_FILESYSTEM
std::error_code ec;
if (filesystem::is_symlink (p, ec))
return true;
#else
const std::wstring wString = p.generic_wstring ();
auto attrib = GetFileAttributesW (reinterpret_cast<LPCWSTR> (wString.data ()));
if (attrib & FILE_ATTRIBUTE_REPARSE_POINT)
{
auto hFile = CreateFileW (reinterpret_cast<LPCWSTR> (wString.data ()), GENERIC_READ,
FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
if (hFile == INVALID_HANDLE_VALUE)
return true;
CloseHandle (hFile);
}
#endif // USE_FILESYSTEM
return false;
}
//------------------------------------------------------------------------
Optional<std::string> getKnownFolder (REFKNOWNFOLDERID folderID)
{
namespace StringConvert = Steinberg::Vst::StringConvert;
PWSTR wideStr {};
if (FAILED (SHGetKnownFolderPath (folderID, 0, nullptr, &wideStr)))
return {};
return StringConvert::convert (Steinberg::wscast (wideStr));
}
//------------------------------------------------------------------------
VST3::Optional<filesystem::path> resolveShellLink (const filesystem::path& p)
{
#if USE_FILESYSTEM
std::error_code ec;
auto target = filesystem::read_symlink (p, ec);
if (ec)
return {};
else
return { target.lexically_normal () };
#elif USE_OLE
Ole::instance ();
IShellLink* shellLink = nullptr;
if (!SUCCEEDED (CoCreateInstance (CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
IID_IShellLink, reinterpret_cast<LPVOID*> (&shellLink))))
return {};
IPersistFile* persistFile = nullptr;
if (!SUCCEEDED (
shellLink->QueryInterface (IID_IPersistFile, reinterpret_cast<void**> (&persistFile))))
return {};
if (!SUCCEEDED (persistFile->Load (p.wstring ().data (), STGM_READ)))
return {};
if (!SUCCEEDED (shellLink->Resolve (nullptr, MAKELONG (SLR_NO_UI, 500))))
return {};
WCHAR resolvedPath[kIPPathNameMax];
if (!SUCCEEDED (shellLink->GetPath (resolvedPath, kIPPathNameMax, nullptr, SLGP_SHORTPATH)))
return {};
std::wstring longPath;
longPath.resize (kIPPathNameMax);
auto numChars =
GetLongPathNameW (resolvedPath, const_cast<wchar_t*> (longPath.data ()), kIPPathNameMax);
if (!numChars)
return {};
longPath.resize (numChars);
persistFile->Release ();
shellLink->Release ();
return {filesystem::path (longPath)};
#else
return {};
#endif // USE_FILESYSTEM
}
//------------------------------------------------------------------------
void addToPathList (Module::PathList& pathList, const std::string& toAdd)
{
#if LOG_ENABLE
std::cout << "=> add: " << toAdd << "\n";
#endif
pathList.push_back (toAdd);
}
//------------------------------------------------------------------------
void findFilesWithExt (const filesystem::path& path, const std::string& ext,
Module::PathList& pathList, bool recursive = true)
{
for (auto& p : filesystem::directory_iterator (path))
{
#if USE_FILESYSTEM
filesystem::path finalPath (p);
if (isFolderSymbolicLink (p))
{
if (auto res = resolveShellLink (p))
{
finalPath = *res;
std::error_code ec;
if (!filesystem::exists (finalPath, ec))
continue;
}
else
continue;
}
const auto& cpExt = finalPath.extension ();
if (cpExt == ext)
{
filesystem::path result;
if (checkVST3Package (finalPath, &result))
{
#if SMTG_CPP20
std::u8string u8str = result.generic_u8string ();
std::string str;
str.assign (std::begin (u8str), std::end (u8str));
addToPathList (pathList, str);
#else
addToPathList (pathList, result.generic_u8string ());
#endif // SMTG_CPP20
continue;
}
}
std::error_code ec;
if (filesystem::is_directory (finalPath, ec))
{
if (recursive)
findFilesWithExt (finalPath, ext, pathList, recursive);
}
else if (cpExt == ext)
{
#if SMTG_CPP20
std::u8string u8str = finalPath.generic_u8string ();
std::string str;
str.assign (std::begin (u8str), std::end (u8str));
addToPathList (pathList, str);
#else
addToPathList (pathList, finalPath.generic_u8string ());
#endif // SMTG_CPP20
}
#else // !USE_FILESYSTEM
const auto& cp = p.path ();
const auto& cpExt = cp.extension ();
if (cpExt == ext)
{
if ((p.status ().type () == filesystem::file_type::directory) ||
isFolderSymbolicLink (p))
{
filesystem::path result;
if (checkVST3Package (p, &result))
{
addToPathList (pathList, result.generic_u8string ());
continue;
}
findFilesWithExt (cp, ext, pathList, recursive);
}
else
addToPathList (pathList, cp.generic_u8string ());
}
else if (recursive)
{
if (p.status ().type () == filesystem::file_type::directory)
{
findFilesWithExt (cp, ext, pathList, recursive);
}
else if (cpExt == ".lnk")
{
if (auto resolvedLink = resolveShellLink (cp))
{
if (resolvedLink->extension () == ext)
{
if (filesystem::is_directory (*resolvedLink) ||
isFolderSymbolicLink (*resolvedLink))
{
filesystem::path result;
if (checkVST3Package (*resolvedLink, &result))
{
addToPathList (pathList, result.generic_u8string ());
continue;
}
findFilesWithExt (*resolvedLink, ext, pathList, recursive);
}
else
addToPathList (pathList, resolvedLink->generic_u8string ());
}
else if (filesystem::is_directory (*resolvedLink))
{
const auto& str = resolvedLink->generic_u8string ();
if (cp.generic_u8string ().compare (0, str.size (), str.data (),
str.size ()) != 0)
findFilesWithExt (*resolvedLink, ext, pathList, recursive);
}
}
}
}
#endif // USE_FILESYSTEM
}
}
//------------------------------------------------------------------------
void findModules (const filesystem::path& path, Module::PathList& pathList)
{
std::error_code ec;
if (filesystem::exists (path, ec))
findFilesWithExt (path, ".vst3", pathList);
}
//------------------------------------------------------------------------
Optional<filesystem::path> getContentsDirectoryFromModuleExecutablePath (
const std::string& modulePath)
{
// filesystem::u8path is deprecated in C++20
#if SMTG_CPP20
filesystem::path path (modulePath);
#else
filesystem::path path = filesystem::u8path (modulePath);
#endif // SMTG_CPP20
path = path.parent_path ();
if (path.filename () != architectureString)
return {};
path = path.parent_path ();
if (path.filename () != "Contents")
return {};
return Optional<filesystem::path> {std::move (path)};
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
Module::Ptr Module::create (const std::string& path, std::string& errorDescription)
{
auto _module = std::make_shared<Win32Module> ();
if (_module->load (path, errorDescription))
{
_module->path = path;
auto it = std::find_if (path.rbegin (), path.rend (),
[] (const std::string::value_type& c) { return c == '/'; });
if (it != path.rend ())
_module->name = {it.base (), path.end ()};
return _module;
}
return nullptr;
}
//------------------------------------------------------------------------
Module::PathList Module::getModulePaths ()
{
namespace StringConvert = Steinberg::Vst::StringConvert;
// find plug-ins located in common/VST3
PathList list;
if (auto knownFolder = getKnownFolder (FOLDERID_UserProgramFilesCommon))
{
// filesystem::u8path is deprecated in C++20
#if SMTG_CPP20
filesystem::path path (*knownFolder);
#else
filesystem::path path = filesystem::u8path (*knownFolder);
#endif // SMTG_CPP20
path.append ("VST3");
#if LOG_ENABLE
std::cout << "Check folder: " << path << "\n";
#endif
findModules (path, list);
}
if (auto knownFolder = getKnownFolder (FOLDERID_ProgramFilesCommon))
{
// filesystem::u8path is deprecated in C++20
#if SMTG_CPP20
filesystem::path path (*knownFolder);
#else
filesystem::path path = filesystem::u8path (*knownFolder);
#endif // SMTG_CPP20
path.append ("VST3");
#if LOG_ENABLE
std::cout << "Check folder: " << path << "\n";
#endif
findModules (path, list);
}
// find plug-ins located in VST3 (application folder)
WCHAR modulePath[kIPPathNameMax];
GetModuleFileNameW (nullptr, modulePath, kIPPathNameMax);
auto appPath = StringConvert::convert (Steinberg::wscast (modulePath));
// filesystem::u8path is deprecated in C++20
#if SMTG_CPP20
filesystem::path path (appPath);
#else
filesystem::path path = filesystem::u8path (appPath);
#endif // SMTG_CPP20
path = path.parent_path ();
path = path.append ("VST3");
#if LOG_ENABLE
std::cout << "Check folder: " << path << "\n";
#endif
findModules (path, list);
return list;
}
//------------------------------------------------------------------------
Optional<std::string> Module::getModuleInfoPath (const std::string& modulePath)
{
auto path = getContentsDirectoryFromModuleExecutablePath (modulePath);
if (!path)
{
filesystem::path p;
if (!checkVST3Package ({modulePath}, &p))
return {};
p = p.parent_path ();
p = p.parent_path ();
path = Optional<filesystem::path> {p};
}
*path /= "Resources";
*path /= "moduleinfo.json";
std::error_code ec;
if (filesystem::exists (*path, ec))
{
return {path->generic_string ()};
}
return {};
}
//------------------------------------------------------------------------
bool Module::validateBundleStructure (const std::string& modulePath, std::string& errorDescription)
{
try
{
auto path = getContentsDirectoryFromModuleExecutablePath (modulePath);
if (!path)
{
filesystem::path p;
if (!checkVST3Package ({modulePath}, &p))
{
errorDescription = "Not a bundle: '" + modulePath + "'.";
return false;
}
p = p.parent_path ();
p = p.parent_path ();
path = Optional<filesystem::path> {p};
}
if (path->filename () != "Contents")
{
errorDescription = "Unexpected directory name, should be 'Contents' but is '" +
path->filename ().string () + "'.";
return false;
}
auto bundlePath = path->parent_path ();
*path /= architectureString;
*path /= bundlePath.filename ();
std::error_code ec;
if (filesystem::exists (*path, ec) == false)
{
errorDescription = "Shared library name is not equal to bundle folder name. Must be '" +
bundlePath.filename ().string () + "'.";
return false;
}
return true;
}
catch (const std::exception& exc)
{
errorDescription = exc.what ();
return false;
}
}
//------------------------------------------------------------------------
Module::SnapshotList Module::getSnapshots (const std::string& modulePath)
{
SnapshotList result;
auto path = getContentsDirectoryFromModuleExecutablePath (modulePath);
if (!path)
{
filesystem::path p;
if (!checkVST3Package ({modulePath}, &p))
return result;
p = p.parent_path ();
p = p.parent_path ();
path = Optional<filesystem::path> (p);
}
*path /= "Resources";
*path /= "Snapshots";
std::error_code ec;
if (filesystem::exists (*path, ec) == false)
return result;
PathList pngList;
findFilesWithExt (*path, ".png", pngList, false);
for (auto& png : pngList)
{
// filesystem::u8path is deprecated in C++20
#if SMTG_CPP20
const filesystem::path p (png);
#else
const filesystem::path p = filesystem::u8path (png);
#endif // SMTG_CPP20
auto filename = p.filename ().generic_string ();
auto uid = Snapshot::decodeUID (filename);
if (!uid)
continue;
auto scaleFactor = 1.;
if (auto decodedScaleFactor = Snapshot::decodeScaleFactor (filename))
scaleFactor = *decodedScaleFactor;
Module::Snapshot::ImageDesc desc;
desc.scaleFactor = scaleFactor;
desc.path = std::move (png);
bool found = false;
for (auto& entry : result)
{
if (entry.uid != *uid)
continue;
found = true;
entry.images.emplace_back (std::move (desc));
break;
}
if (found)
continue;
Module::Snapshot snapshot;
snapshot.uid = *uid;
snapshot.images.emplace_back (std::move (desc));
result.emplace_back (std::move (snapshot));
}
return result;
}
//------------------------------------------------------------------------
} // Hosting
} // VST3
@@ -0,0 +1,298 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/parameterchanges.cpp
// Created by : Steinberg, 03/05/2008.
// Description : VST 3 parameter changes implementation
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "parameterchanges.h"
namespace Steinberg {
namespace Vst {
//-----------------------------------------------------------------------------
IMPLEMENT_FUNKNOWN_METHODS (ParameterChanges, IParameterChanges, IParameterChanges::iid)
IMPLEMENT_FUNKNOWN_METHODS (ParameterValueQueue, IParamValueQueue, IParamValueQueue::iid)
constexpr int32 kQueueReservedPoints = 5;
//-----------------------------------------------------------------------------
ParameterValueQueue::ParameterValueQueue (ParamID paramID)
: paramID (paramID)
{
values.reserve (kQueueReservedPoints);
FUNKNOWN_CTOR
}
//-----------------------------------------------------------------------------
ParameterValueQueue::~ParameterValueQueue ()
{
FUNKNOWN_DTOR
}
//-----------------------------------------------------------------------------
void ParameterValueQueue::clear ()
{
values.clear ();
}
//-----------------------------------------------------------------------------
int32 PLUGIN_API ParameterValueQueue::getPointCount ()
{
return static_cast<int32> (values.size ());
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API ParameterValueQueue::getPoint (int32 index, int32& sampleOffset, ParamValue& value)
{
if (index >= 0 && index < static_cast<int32> (values.size ()))
{
const ParameterQueueValue& queueValue = values[index];
sampleOffset = queueValue.sampleOffset;
value = queueValue.value;
return kResultTrue;
}
return kResultFalse;
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API ParameterValueQueue::addPoint (int32 sampleOffset, ParamValue value, int32& index)
{
auto destIndex = static_cast<int32>(values.size ());
for (uint32 i = 0; i < values.size (); i++)
{
if (values[i].sampleOffset == sampleOffset)
{
values[i].value = value;
index = i;
return kResultTrue;
}
if (values[i].sampleOffset > sampleOffset)
{
destIndex = i;
break;
}
}
// need new point
ParameterQueueValue queueValue (value, sampleOffset);
if (destIndex == static_cast<int32> (values.size ()))
values.emplace_back (queueValue);
else
values.insert (values.begin () + destIndex, queueValue);
index = destIndex;
return kResultTrue;
}
//-----------------------------------------------------------------------------
// ParameterChanges
//-----------------------------------------------------------------------------
ParameterChanges::ParameterChanges (int32 maxParameters)
{
FUNKNOWN_CTOR
setMaxParameters (maxParameters);
}
//-----------------------------------------------------------------------------
ParameterChanges::~ParameterChanges ()
{
FUNKNOWN_DTOR
}
//-----------------------------------------------------------------------------
void ParameterChanges::setMaxParameters (int32 maxParameters)
{
if (maxParameters < 0)
return;
while (static_cast<int32> (queues.size ()) < maxParameters)
{
queues.emplace_back (owned (new ParameterValueQueue (kNoParamId)));
}
while (static_cast<int32> (queues.size ()) > maxParameters)
{
queues.pop_back ();
}
if (usedQueueCount > maxParameters)
usedQueueCount = maxParameters;
}
//-----------------------------------------------------------------------------
void ParameterChanges::clearQueue ()
{
usedQueueCount = 0;
}
//-----------------------------------------------------------------------------
int32 PLUGIN_API ParameterChanges::getParameterCount ()
{
return usedQueueCount;
}
//-----------------------------------------------------------------------------
IParamValueQueue* PLUGIN_API ParameterChanges::getParameterData (int32 index)
{
if (index >= 0 && index < usedQueueCount)
return queues[index];
return nullptr;
}
//-----------------------------------------------------------------------------
IParamValueQueue* PLUGIN_API ParameterChanges::addParameterData (const ParamID& pid, int32& index)
{
for (int32 i = 0; i < usedQueueCount; i++)
{
if (queues[i]->getParameterId () == pid)
{
index = i;
return queues[i];
}
}
ParameterValueQueue* valueQueue = nullptr;
if (usedQueueCount < static_cast<int32> (queues.size ()))
{
valueQueue = queues[usedQueueCount];
valueQueue->setParamID (pid);
valueQueue->clear ();
}
else
{
queues.emplace_back (owned (new ParameterValueQueue (pid)));
valueQueue = queues.back ();
}
index = usedQueueCount;
usedQueueCount++;
return valueQueue;
}
//-----------------------------------------------------------------------------
// ParameterChangeTransfer
//-----------------------------------------------------------------------------
ParameterChangeTransfer::ParameterChangeTransfer (int32 maxParameters)
: size (0)
, changes (nullptr)
, readIndex (0)
, writeIndex (0)
{
setMaxParameters (maxParameters);
}
//-----------------------------------------------------------------------------
ParameterChangeTransfer::~ParameterChangeTransfer ()
{
setMaxParameters (0);
}
//-----------------------------------------------------------------------------
void ParameterChangeTransfer::setMaxParameters (int32 maxParameters)
{
// reserve memory for twice the amount of all parameters
int32 newSize = maxParameters * 2;
if (size != newSize)
{
if (changes)
delete [] changes;
changes = nullptr;
size = newSize;
if (size > 0)
changes = new ParameterChange [size];
}
}
//-----------------------------------------------------------------------------
void ParameterChangeTransfer::addChange (ParamID pid, ParamValue value, int32 sampleOffset)
{
if (changes)
{
changes[writeIndex].id = pid;
changes[writeIndex].value = value;
changes[writeIndex].sampleOffset = sampleOffset;
int32 newWriteIndex = writeIndex + 1;
if (newWriteIndex >= size)
newWriteIndex = 0;
if (readIndex != newWriteIndex)
writeIndex = newWriteIndex;
}
}
//-----------------------------------------------------------------------------
bool ParameterChangeTransfer::getNextChange (ParamID& pid, ParamValue& value, int32& sampleOffset)
{
if (!changes)
return false;
int32 currentWriteIndex = writeIndex;
if (readIndex != currentWriteIndex)
{
pid = changes [readIndex].id;
value = changes [readIndex].value;
sampleOffset = changes [readIndex].sampleOffset;
int32 newReadIndex = readIndex + 1;
if (newReadIndex >= size)
newReadIndex = 0;
readIndex = newReadIndex;
return true;
}
return false;
}
//-----------------------------------------------------------------------------
void ParameterChangeTransfer::transferChangesTo (ParameterChanges& dest)
{
ParamID pid;
ParamValue value;
int32 sampleOffset;
int32 index;
while (getNextChange (pid, value, sampleOffset))
{
IParamValueQueue* queue = dest.addParameterData (pid, index);
if (queue)
{
queue->addPoint (sampleOffset, value, index);
}
}
}
//-----------------------------------------------------------------------------
void ParameterChangeTransfer::transferChangesFrom (ParameterChanges& source)
{
ParamValue value;
int32 sampleOffset;
for (int32 i = 0; i < source.getParameterCount (); i++)
{
IParamValueQueue* queue = source.getParameterData (i);
if (queue)
{
for (int32 j = 0; j < queue->getPointCount (); j++)
{
if (queue->getPoint (j, sampleOffset, value) == kResultTrue)
{
addChange (queue->getParameterId (), value, sampleOffset);
}
}
}
}
}
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,122 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/parameterchanges.h
// Created by : Steinberg, 03/05/2008.
// Description : VST 3 parameter changes implementation
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "pluginterfaces/vst/ivstparameterchanges.h"
#include <vector>
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
/** Implementation's example of IParamValueQueue - not threadsave!.
\ingroup hostingBase
*/
class ParameterValueQueue : public IParamValueQueue
{
public:
//------------------------------------------------------------------------
ParameterValueQueue (ParamID paramID);
virtual ~ParameterValueQueue ();
ParamID PLUGIN_API getParameterId () SMTG_OVERRIDE { return paramID; }
int32 PLUGIN_API getPointCount () SMTG_OVERRIDE;
tresult PLUGIN_API getPoint (int32 index, int32& sampleOffset, ParamValue& value) SMTG_OVERRIDE;
tresult PLUGIN_API addPoint (int32 sampleOffset, ParamValue value, int32& index) SMTG_OVERRIDE;
void setParamID (ParamID pID) {paramID = pID;}
void clear ();
//------------------------------------------------------------------------
DECLARE_FUNKNOWN_METHODS
protected:
ParamID paramID;
struct ParameterQueueValue
{
ParameterQueueValue (ParamValue value, int32 sampleOffset) : value (value), sampleOffset (sampleOffset) {}
ParamValue value;
int32 sampleOffset;
};
std::vector<ParameterQueueValue> values;
};
//------------------------------------------------------------------------
/** Implementation's example of IParameterChanges - not threadsave!.
\ingroup hostingBase
*/
class ParameterChanges : public IParameterChanges
{
public:
//------------------------------------------------------------------------
ParameterChanges (int32 maxParameters = 0);
virtual ~ParameterChanges ();
void clearQueue ();
void setMaxParameters (int32 maxParameters);
//---IParameterChanges-----------------------------
int32 PLUGIN_API getParameterCount () SMTG_OVERRIDE;
IParamValueQueue* PLUGIN_API getParameterData (int32 index) SMTG_OVERRIDE;
IParamValueQueue* PLUGIN_API addParameterData (const ParamID& pid, int32& index) SMTG_OVERRIDE;
//------------------------------------------------------------------------
DECLARE_FUNKNOWN_METHODS
protected:
std::vector<IPtr<ParameterValueQueue>> queues;
int32 usedQueueCount {0};
};
//------------------------------------------------------------------------
/** Ring buffer for transferring parameter changes from a writer to a read thread .
\ingroup hostingBase
*/
class ParameterChangeTransfer
{
public:
//------------------------------------------------------------------------
ParameterChangeTransfer (int32 maxParameters = 0);
virtual ~ParameterChangeTransfer ();
void setMaxParameters (int32 maxParameters);
void addChange (ParamID pid, ParamValue value, int32 sampleOffset);
bool getNextChange (ParamID& pid, ParamValue& value, int32& sampleOffset);
void transferChangesTo (ParameterChanges& dest);
void transferChangesFrom (ParameterChanges& source);
void removeChanges () { writeIndex = readIndex; }
//------------------------------------------------------------------------
protected:
struct ParameterChange
{
ParamID id;
ParamValue value;
int32 sampleOffset;
};
int32 size;
ParameterChange* changes;
volatile int32 readIndex;
volatile int32 writeIndex;
};
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,118 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/pluginterfacesupport.cpp
// Created by : Steinberg, 11/2018.
// Description : VST 3 hostclasses, example implementations for IPlugInterfaceSupport
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "pluginterfacesupport.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/ivstunits.h"
#include "pluginterfaces/vst/ivstmessage.h"
#include <algorithm>
//-----------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//-----------------------------------------------------------------------------
PlugInterfaceSupport::PlugInterfaceSupport ()
{
FUNKNOWN_CTOR
// add minimum set
//---VST 3.0.0--------------------------------
addPlugInterfaceSupported (IComponent::iid);
addPlugInterfaceSupported (IAudioProcessor::iid);
addPlugInterfaceSupported (IEditController::iid);
addPlugInterfaceSupported (IConnectionPoint::iid);
addPlugInterfaceSupported (IUnitInfo::iid);
addPlugInterfaceSupported (IUnitData::iid);
addPlugInterfaceSupported (IProgramListData::iid);
//---VST 3.0.1--------------------------------
addPlugInterfaceSupported (IMidiMapping::iid);
//---VST 3.1----------------------------------
addPlugInterfaceSupported (IEditController2::iid);
/*
//---VST 3.0.2--------------------------------
addPlugInterfaceSupported (IParameterFinder::iid);
//---VST 3.1----------------------------------
addPlugInterfaceSupported (IAudioPresentationLatency::iid);
//---VST 3.5----------------------------------
addPlugInterfaceSupported (IKeyswitchController::iid);
addPlugInterfaceSupported (IContextMenuTarget::iid);
addPlugInterfaceSupported (IEditControllerHostEditing::iid);
addPlugInterfaceSupported (IXmlRepresentationController::iid);
addPlugInterfaceSupported (INoteExpressionController::iid);
//---VST 3.6.5--------------------------------
addPlugInterfaceSupported (ChannelContext::IInfoListener::iid);
addPlugInterfaceSupported (IPrefetchableSupport::iid);
addPlugInterfaceSupported (IAutomationState::iid);
//---VST 3.6.11--------------------------------
addPlugInterfaceSupported (INoteExpressionPhysicalUIMapping::iid);
//---VST 3.6.12--------------------------------
addPlugInterfaceSupported (IMidiLearn::iid);
//---VST 3.7-----------------------------------
addPlugInterfaceSupported (IProcessContextRequirements::iid);
addPlugInterfaceSupported (IParameterFunctionName::iid);
addPlugInterfaceSupported (IProgress::iid);
//----VST 3.8------------------------------------
addPlugInterfaceSupported (IMidiMapping2::iid)
addPlugInterfaceSupported (IMidiLearn2::iid)
*/
}
//-----------------------------------------------------------------------------
tresult PLUGIN_API PlugInterfaceSupport::isPlugInterfaceSupported (const TUID _iid)
{
auto uid = FUID::fromTUID (_iid);
if (std::find (mFUIDArray.begin (), mFUIDArray.end (), uid) != mFUIDArray.end ())
return kResultTrue;
return kResultFalse;
}
//-----------------------------------------------------------------------------
void PlugInterfaceSupport::addPlugInterfaceSupported (const TUID _iid)
{
mFUIDArray.push_back (FUID::fromTUID (_iid));
}
//-----------------------------------------------------------------------------
bool PlugInterfaceSupport::removePlugInterfaceSupported (const TUID _iid)
{
auto uid = FUID::fromTUID (_iid);
auto it = std::find (mFUIDArray.begin (), mFUIDArray.end (), uid);
if (it == mFUIDArray.end ())
return false;
mFUIDArray.erase (it);
return true;
}
IMPLEMENT_FUNKNOWN_METHODS (PlugInterfaceSupport, IPlugInterfaceSupport, IPlugInterfaceSupport::iid)
//-----------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,50 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/pluginterfacesupport.h
// Created by : Steinberg, 11/20018.
// Description : VST 3 hostclasses, example implementations for IPlugInterfaceSupport
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "pluginterfaces/vst/ivstpluginterfacesupport.h"
#include <vector>
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
/** Example implementation of IPlugInterfaceSupport.
\ingroup hostingBase
*/
class PlugInterfaceSupport : public IPlugInterfaceSupport
{
public:
PlugInterfaceSupport ();
virtual ~PlugInterfaceSupport () = default;
//--- IPlugInterfaceSupport ---------
tresult PLUGIN_API isPlugInterfaceSupported (const TUID _iid) SMTG_OVERRIDE;
void addPlugInterfaceSupported (const TUID _iid);
bool removePlugInterfaceSupported (const TUID _iid);
DECLARE_FUNKNOWN_METHODS
private:
std::vector<FUID> mFUIDArray;
};
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,320 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/plugprovider.cpp
// Created by : Steinberg, 08/2016
// Description : VST 3 Plug-in Provider class
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "plugprovider.h"
#include "connectionproxy.h"
#include "pluginterfaces/vst/ivstcomponent.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/ivstmessage.h"
#include <cstdio>
#include <iostream>
static std::ostream* errorStream = &std::cout;
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
// PlugProvider
//------------------------------------------------------------------------
PlugProvider::PlugProvider (const PluginFactory& factory, ClassInfo classInfo, bool plugIsGlobal)
: factory (factory)
, component (nullptr)
, controller (nullptr)
, classInfo (classInfo)
, plugIsGlobal (plugIsGlobal)
{
}
//------------------------------------------------------------------------
PlugProvider::~PlugProvider ()
{
terminatePlugin ();
}
//------------------------------------------------------------------------
template <typename Proc>
void PlugProvider::printError (Proc p) const
{
if (errorStream)
{
p (*errorStream);
}
}
//------------------------------------------------------------------------
bool PlugProvider::initialize ()
{
if (plugIsGlobal)
{
return setupPlugin (PluginContextFactory::instance ().getPluginContext ());
}
return true;
}
//------------------------------------------------------------------------
IComponent* PLUGIN_API PlugProvider::getComponent ()
{
if (!component)
setupPlugin (PluginContextFactory::instance ().getPluginContext ());
if (component)
component->addRef ();
return component;
}
//------------------------------------------------------------------------
IEditController* PLUGIN_API PlugProvider::getController ()
{
if (controller)
controller->addRef ();
// 'iController == 0' is allowed! In this case the plug has no controller
return controller;
}
//------------------------------------------------------------------------
IPluginFactory* PLUGIN_API PlugProvider::getPluginFactory ()
{
if (auto f = factory.get ())
return f.get ();
return nullptr;
}
//------------------------------------------------------------------------
tresult PLUGIN_API PlugProvider::getComponentUID (FUID& uid) const
{
uid = FUID::fromTUID (classInfo.ID ().data ());
return kResultOk;
}
//------------------------------------------------------------------------
tresult PLUGIN_API PlugProvider::releasePlugIn (IComponent* iComponent,
IEditController* iController)
{
if (iComponent)
iComponent->release ();
if (iController)
iController->release ();
if (!plugIsGlobal)
{
terminatePlugin ();
}
return kResultOk;
}
//------------------------------------------------------------------------
bool PlugProvider::setupPlugin (FUnknown* hostContext)
{
bool res = false;
bool isSingleComponent = false;
//---create Plug-in here!--------------
// create its component part
component = factory.createInstance<IComponent> (classInfo.ID ());
if (component)
{
// initialize the component with our context
if (auto plugBase = U::cast<IPluginBase> (component))
{
res = (plugBase->initialize (hostContext) == kResultOk);
if (res == false)
{
printError ([&] (std::ostream& stream) {
stream << "Failed to initialize component of " << classInfo.name () << "!\n";
});
return false;
}
}
else
{
printError ([&] (std::ostream& stream) {
stream << "Failed to get IPluginBase from component of " << classInfo.name ()
<< "!\n";
});
return false;
}
// try to create the controller part from the component
// (for Plug-ins which did not succeed to separate component from controller)
if (component->queryInterface (IEditController::iid, (void**)&controller) == kResultTrue)
{
isSingleComponent = true;
}
else
{
TUID controllerCID;
// ask for the associated controller class ID
if (component->getControllerClassId (controllerCID) == kResultTrue)
{
// create its controller part created from the factory
controller = factory.createInstance<IEditController> (VST3::UID (controllerCID));
if (controller)
{
// initialize the component with our context
if (auto plugCtrlBase = U::cast<IPluginBase> (controller))
{
res = (plugCtrlBase->initialize (hostContext) == kResultOk);
if (res == false)
{
printError ([&] (std::ostream& stream) {
stream << "Failed to initialize controller of " << classInfo.name ()
<< "!\n";
});
}
}
else
{
printError ([&] (std::ostream& stream) {
stream << "Failed to get IPluginBase from controller of "
<< classInfo.name () << "!\n";
});
return false;
}
}
}
else
{
printError ([&] (std::ostream& stream) {
stream << "Component does not provide a required controller class ID ["
<< classInfo.name () << "]!\n";
});
}
}
if (!res)
{
component.reset ();
controller.reset ();
}
}
else if (errorStream)
{
printError ([&] (std::ostream& stream) {
stream << "Failed to create component instance of " << classInfo.name () << "!\n";
});
}
if (res && !isSingleComponent)
return connectComponents ();
return res;
}
//------------------------------------------------------------------------
bool PlugProvider::connectComponents ()
{
if (!component || !controller)
return false;
auto compICP = U::cast<IConnectionPoint> (component);
auto contrICP = U::cast<IConnectionPoint> (controller);
if (!compICP || !contrICP)
return false;
componentCP = owned (new ConnectionProxy (compICP));
controllerCP = owned (new ConnectionProxy (contrICP));
tresult tres = componentCP->connect (contrICP);
if (tres != kResultTrue)
{
printError ([&] (std::ostream& stream) {
stream << "Failed to connect the component with the controller with result code '"
<< tres << "'!\n";
});
return false;
}
tres = controllerCP->connect (compICP);
if (tres != kResultTrue)
{
printError ([&] (std::ostream& stream) {
stream << "Failed to connect the controller with the component with result code '"
<< tres << "'!\n";
});
return false;
}
return true;
}
//------------------------------------------------------------------------
bool PlugProvider::disconnectComponents ()
{
if (!componentCP || !controllerCP)
return false;
bool res = componentCP->disconnect ();
res &= controllerCP->disconnect ();
componentCP.reset ();
controllerCP.reset ();
return res;
}
//------------------------------------------------------------------------
void PlugProvider::terminatePlugin ()
{
disconnectComponents ();
bool controllerIsComponent = false;
if (component)
{
controllerIsComponent = FUnknownPtr<IEditController> (component).getInterface () != nullptr;
if (auto plugBase = U::cast<IPluginBase> (component))
plugBase->terminate ();
else
{
printError ([&](std::ostream& stream) {
stream << "Failed to get IPluginBase from component of " << classInfo.name ()
<< "!\n";
});
}
}
if (controller && controllerIsComponent == false)
{
if (auto plugCtrlBase = U::cast<IPluginBase> (controller))
plugCtrlBase->terminate ();
else
{
printError ([&](std::ostream& stream) {
stream << "Failed to get IPluginBase from controller of " << classInfo.name ()
<< "!\n";
});
}
}
component.reset ();
controller.reset ();
}
//------------------------------------------------------------------------
void PlugProvider::setErrorStream (std::ostream* stream)
{
errorStream = stream;
}
//------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,107 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/plugprovider.h
// Created by : Steinberg, 04/2005
// Description : VST 3 Plug-in Provider class
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "public.sdk/source/vst/hosting/module.h"
#include "pluginterfaces/vst/ivsttestplugprovider.h"
#include "pluginterfaces/base/funknownimpl.h"
#include <ostream>
namespace Steinberg {
namespace Vst {
class IComponent;
class IEditController;
class ConnectionProxy;
//------------------------------------------------------------------------
/** Helper for creating and initializing component.
\ingroup Validator */
//------------------------------------------------------------------------
class PlugProvider
: public U::Implements<U::Directly<ITestPlugProvider2>, U::Indirectly<ITestPlugProvider>>
{
public:
using ClassInfo = VST3::Hosting::ClassInfo;
using PluginFactory = VST3::Hosting::PluginFactory;
//--- ---------------------------------------------------------------------
PlugProvider (const PluginFactory& factory, ClassInfo info, bool plugIsGlobal = true);
~PlugProvider () override;
bool initialize ();
IPtr<IComponent> getComponentPtr () const { return component; }
IPtr<IEditController> getControllerPtr () const { return controller; }
const ClassInfo& getClassInfo () const { return classInfo; }
//--- from ITestPlugProvider ------------------
IComponent* PLUGIN_API getComponent () SMTG_OVERRIDE;
IEditController* PLUGIN_API getController () SMTG_OVERRIDE;
tresult PLUGIN_API releasePlugIn (IComponent* component, IEditController* controller) SMTG_OVERRIDE;
tresult PLUGIN_API getSubCategories (IStringResult& result) const SMTG_OVERRIDE
{
result.setText (classInfo.subCategoriesString ().data ());
return kResultTrue;
}
tresult PLUGIN_API getComponentUID (FUID& uid) const SMTG_OVERRIDE;
//--- from ITestPlugProvider2 ------------------
IPluginFactory* PLUGIN_API getPluginFactory () SMTG_OVERRIDE;
static void setErrorStream (std::ostream* stream);
//------------------------------------------------------------------------
protected:
bool setupPlugin (FUnknown* hostContext);
bool connectComponents ();
bool disconnectComponents ();
void terminatePlugin ();
template<typename Proc>
void printError (Proc p) const;
PluginFactory factory;
IPtr<IComponent> component;
IPtr<IEditController> controller;
ClassInfo classInfo;
IPtr<ConnectionProxy> componentCP;
IPtr<ConnectionProxy> controllerCP;
bool plugIsGlobal;
};
//------------------------------------------------------------------------
class PluginContextFactory
{
public:
static PluginContextFactory& instance ()
{
static PluginContextFactory factory;
return factory;
}
void setPluginContext (FUnknown* obj) { context = obj; }
FUnknown* getPluginContext () const { return context; }
private:
FUnknown* context;
};
//------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,204 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/processdata.cpp
// Created by : Steinberg, 10/2005
// Description : VST Hosting Utilities
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "processdata.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
// HostProcessData
//------------------------------------------------------------------------
HostProcessData::~HostProcessData () noexcept
{
unprepare ();
}
//------------------------------------------------------------------------
bool HostProcessData::prepare (IComponent& component, int32 bufferSamples,
int32 _symbolicSampleSize)
{
if (checkIfReallocationNeeded (component, bufferSamples, _symbolicSampleSize))
{
unprepare ();
symbolicSampleSize = _symbolicSampleSize;
channelBufferOwner = bufferSamples > 0;
numInputs = createBuffers (component, inputs, kInput, bufferSamples);
numOutputs = createBuffers (component, outputs, kOutput, bufferSamples);
}
else
{
// reset silence flags
for (int32 i = 0; i < numInputs; i++)
{
inputs[i].silenceFlags = 0;
}
for (int32 i = 0; i < numOutputs; i++)
{
outputs[i].silenceFlags = 0;
}
}
symbolicSampleSize = _symbolicSampleSize;
return true;
}
//------------------------------------------------------------------------
void HostProcessData::unprepare ()
{
destroyBuffers (inputs, numInputs);
destroyBuffers (outputs, numOutputs);
channelBufferOwner = false;
}
//------------------------------------------------------------------------
bool HostProcessData::checkIfReallocationNeeded (IComponent& component, int32 bufferSamples,
int32 _symbolicSampleSize) const
{
if (channelBufferOwner != (bufferSamples > 0))
return true;
if (symbolicSampleSize != _symbolicSampleSize)
return true;
int32 inBusCount = component.getBusCount (kAudio, kInput);
if (inBusCount != numInputs)
return true;
int32 outBusCount = component.getBusCount (kAudio, kOutput);
if (outBusCount != numOutputs)
return true;
for (int32 i = 0; i < inBusCount; i++)
{
BusInfo busInfo = {};
if (component.getBusInfo (kAudio, kInput, i, busInfo) == kResultTrue)
{
if (inputs[i].numChannels != busInfo.channelCount)
return true;
}
}
for (int32 i = 0; i < outBusCount; i++)
{
BusInfo busInfo = {};
if (component.getBusInfo (kAudio, kOutput, i, busInfo) == kResultTrue)
{
if (outputs[i].numChannels != busInfo.channelCount)
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
int32 HostProcessData::createBuffers (IComponent& component, AudioBusBuffers*& buffers,
BusDirection dir, int32 bufferSamples)
{
int32 busCount = component.getBusCount (kAudio, dir);
if (busCount > 0)
{
buffers = new AudioBusBuffers[busCount];
for (int32 i = 0; i < busCount; i++)
{
BusInfo busInfo = {};
if (component.getBusInfo (kAudio, dir, i, busInfo) == kResultTrue)
{
buffers[i].numChannels = busInfo.channelCount;
// allocate for each channel
if (busInfo.channelCount > 0)
{
if (symbolicSampleSize == kSample64)
buffers[i].channelBuffers64 = new Sample64*[busInfo.channelCount];
else
buffers[i].channelBuffers32 = new Sample32*[busInfo.channelCount];
for (int32 j = 0; j < busInfo.channelCount; j++)
{
if (symbolicSampleSize == kSample64)
{
if (bufferSamples > 0)
buffers[i].channelBuffers64[j] = new Sample64[bufferSamples];
else
buffers[i].channelBuffers64[j] = nullptr;
}
else
{
if (bufferSamples > 0)
buffers[i].channelBuffers32[j] = new Sample32[bufferSamples];
else
buffers[i].channelBuffers32[j] = nullptr;
}
}
}
}
}
}
return busCount;
}
//-----------------------------------------------------------------------------
void HostProcessData::destroyBuffers (AudioBusBuffers*& buffers, int32& busCount)
{
if (buffers)
{
for (int32 i = 0; i < busCount; i++)
{
if (channelBufferOwner)
{
for (int32 j = 0; j < buffers[i].numChannels; j++)
{
if (symbolicSampleSize == kSample64)
{
if (buffers[i].channelBuffers64 && buffers[i].channelBuffers64[j])
delete[] buffers[i].channelBuffers64[j];
}
else
{
if (buffers[i].channelBuffers32 && buffers[i].channelBuffers32[j])
delete[] buffers[i].channelBuffers32[j];
}
}
}
if (symbolicSampleSize == kSample64)
{
if (buffers[i].channelBuffers64)
delete[] buffers[i].channelBuffers64;
}
else
{
if (buffers[i].channelBuffers32)
delete[] buffers[i].channelBuffers32;
}
}
delete[] buffers;
buffers = nullptr;
}
busCount = 0;
}
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,192 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/processdata.h
// Created by : Steinberg, 10/2005
// Description : VST Hosting Utilities
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "pluginterfaces/vst/ivstcomponent.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
//------------------------------------------------------------------------
/** Extension of ProcessData.
Helps setting up the buffers for the process data structure for a component.
When the prepare method is called with bufferSamples != 0 the buffer management is handled by this class.
Otherwise the buffers need to be setup explicitly.
\ingroup hostingBase
*/
class HostProcessData : public ProcessData
{
public:
//------------------------------------------------------------------------
HostProcessData () = default;
virtual ~HostProcessData () noexcept;
/** Prepare buffer containers for all busses. If bufferSamples is not null buffers will be
* created. */
bool prepare (IComponent& component, int32 bufferSamples, int32 _symbolicSampleSize);
/** Remove bus buffers. */
void unprepare ();
/** Sets one sample buffer for all channels inside a bus. */
bool setChannelBuffers (BusDirection dir, int32 busIndex, Sample32* sampleBuffer);
bool setChannelBuffers64 (BusDirection dir, int32 busIndex, Sample64* sampleBuffer);
/** Sets individual sample buffers per channel inside a bus. */
bool setChannelBuffers (BusDirection dir, int32 busIndex, Sample32* sampleBuffers[],
int32 bufferCount);
bool setChannelBuffers64 (BusDirection dir, int32 busIndex, Sample64* sampleBuffers[],
int32 bufferCount);
/** Sets one sample buffer for a given channel inside a bus. */
bool setChannelBuffer (BusDirection dir, int32 busIndex, int32 channelIndex,
Sample32* sampleBuffer);
bool setChannelBuffer64 (BusDirection dir, int32 busIndex, int32 channelIndex,
Sample64* sampleBuffer);
static constexpr uint64 kAllChannelsSilent =
#if SMTG_OS_MACOS
0xffffffffffffffffULL;
#else
0xffffffffffffffffUL;
#endif
//------------------------------------------------------------------------
protected:
int32 createBuffers (IComponent& component, AudioBusBuffers*& buffers, BusDirection dir,
int32 bufferSamples);
void destroyBuffers (AudioBusBuffers*& buffers, int32& busCount);
bool checkIfReallocationNeeded (IComponent& component, int32 bufferSamples,
int32 _symbolicSampleSize) const;
bool isValidBus (BusDirection dir, int32 busIndex) const;
bool channelBufferOwner {false};
};
//------------------------------------------------------------------------
// inline
//------------------------------------------------------------------------
inline bool HostProcessData::isValidBus (BusDirection dir, int32 busIndex) const
{
if (dir == kInput && (!inputs || busIndex >= numInputs))
return false;
if (dir == kOutput && (!outputs || busIndex >= numOutputs))
return false;
return true;
}
//------------------------------------------------------------------------
inline bool HostProcessData::setChannelBuffers (BusDirection dir, int32 busIndex,
Sample32* sampleBuffer)
{
if (channelBufferOwner || symbolicSampleSize != SymbolicSampleSizes::kSample32)
return false;
if (!isValidBus (dir, busIndex))
return false;
AudioBusBuffers& busBuffers = dir == kInput ? inputs[busIndex] : outputs[busIndex];
for (int32 i = 0; i < busBuffers.numChannels; i++)
busBuffers.channelBuffers32[i] = sampleBuffer;
return true;
}
//------------------------------------------------------------------------
inline bool HostProcessData::setChannelBuffers64 (BusDirection dir, int32 busIndex,
Sample64* sampleBuffer)
{
if (channelBufferOwner || symbolicSampleSize != SymbolicSampleSizes::kSample64)
return false;
if (!isValidBus (dir, busIndex))
return false;
AudioBusBuffers& busBuffers = dir == kInput ? inputs[busIndex] : outputs[busIndex];
for (int32 i = 0; i < busBuffers.numChannels; i++)
busBuffers.channelBuffers64[i] = sampleBuffer;
return true;
}
//------------------------------------------------------------------------
inline bool HostProcessData::setChannelBuffers (BusDirection dir, int32 busIndex,
Sample32* sampleBuffers[], int32 bufferCount)
{
if (channelBufferOwner || symbolicSampleSize != SymbolicSampleSizes::kSample32)
return false;
if (!isValidBus (dir, busIndex))
return false;
AudioBusBuffers& busBuffers = dir == kInput ? inputs[busIndex] : outputs[busIndex];
int32 count = bufferCount < busBuffers.numChannels ? bufferCount : busBuffers.numChannels;
for (int32 i = 0; i < count; i++)
busBuffers.channelBuffers32[i] = sampleBuffers ? sampleBuffers[i] : nullptr;
return true;
}
//------------------------------------------------------------------------
inline bool HostProcessData::setChannelBuffers64 (BusDirection dir, int32 busIndex,
Sample64* sampleBuffers[], int32 bufferCount)
{
if (channelBufferOwner || symbolicSampleSize != SymbolicSampleSizes::kSample64)
return false;
if (!isValidBus (dir, busIndex))
return false;
AudioBusBuffers& busBuffers = dir == kInput ? inputs[busIndex] : outputs[busIndex];
int32 count = bufferCount < busBuffers.numChannels ? bufferCount : busBuffers.numChannels;
for (int32 i = 0; i < count; i++)
busBuffers.channelBuffers64[i] = sampleBuffers ? sampleBuffers[i] : nullptr;
return true;
}
//------------------------------------------------------------------------
inline bool HostProcessData::setChannelBuffer (BusDirection dir, int32 busIndex, int32 channelIndex,
Sample32* sampleBuffer)
{
if (symbolicSampleSize != SymbolicSampleSizes::kSample32)
return false;
if (!isValidBus (dir, busIndex))
return false;
AudioBusBuffers& busBuffers = dir == kInput ? inputs[busIndex] : outputs[busIndex];
if (channelIndex >= busBuffers.numChannels)
return false;
busBuffers.channelBuffers32[channelIndex] = sampleBuffer;
return true;
}
//------------------------------------------------------------------------
inline bool HostProcessData::setChannelBuffer64 (BusDirection dir, int32 busIndex,
int32 channelIndex, Sample64* sampleBuffer)
{
if (symbolicSampleSize != SymbolicSampleSizes::kSample64)
return false;
if (!isValidBus (dir, busIndex))
return false;
AudioBusBuffers& busBuffers = dir == kInput ? inputs[busIndex] : outputs[busIndex];
if (channelIndex >= busBuffers.numChannels)
return false;
busBuffers.channelBuffers64[channelIndex] = sampleBuffer;
return true;
}
//------------------------------------------------------------------------
} // namespace Vst
} // namespace Steinberg
@@ -0,0 +1,130 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/test/connectionproxytest.cpp
// Created by : Steinberg, 08/2021
// Description : Test connection proxy
// Flags : clang-format SMTGSequencer
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/source/main/moduleinit.h"
#include "public.sdk/source/vst/hosting/connectionproxy.h"
#include "public.sdk/source/vst/hosting/hostclasses.h"
#include "public.sdk/source/vst/utility/testing.h"
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <optional>
#include <thread>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace {
//------------------------------------------------------------------------
class ConnectionPoint : public IConnectionPoint
{
public:
tresult PLUGIN_API connect (IConnectionPoint* inOther) override
{
other = inOther;
return kResultTrue;
}
tresult PLUGIN_API disconnect (IConnectionPoint* inOther) override
{
if (inOther != other)
return kResultFalse;
return kResultTrue;
}
tresult PLUGIN_API notify (IMessage*) override
{
messageReceived = true;
return kResultTrue;
}
tresult PLUGIN_API queryInterface (const TUID, void**) override { return kNotImplemented; }
uint32 PLUGIN_API addRef () override { return 100; }
uint32 PLUGIN_API release () override { return 100; }
IConnectionPoint* other {nullptr};
bool messageReceived {false};
};
//------------------------------------------------------------------------
ModuleInitializer ConnectionProxyTests ([] () {
constexpr auto TestSuiteName = "ConnectionProxy";
registerTest (TestSuiteName, STR ("Connect and disconnect"), [] (ITestResult* testResult) {
ConnectionPoint cp1;
ConnectionPoint cp2;
ConnectionProxy proxy (&cp1);
EXPECT_EQ (proxy.connect (&cp2), kResultTrue);
EXPECT_EQ (proxy.disconnect (&cp2), kResultTrue);
return true;
});
registerTest (TestSuiteName, STR ("Disconnect wrong object"), [] (ITestResult* testResult) {
ConnectionPoint cp1;
ConnectionPoint cp2;
ConnectionPoint cp3;
ConnectionProxy proxy (&cp1);
EXPECT_EQ (proxy.connect (&cp2), kResultTrue);
EXPECT_NE (proxy.disconnect (&cp3), kResultTrue);
return true;
});
registerTest (TestSuiteName, STR ("Send message on UI thread"), [] (ITestResult* testResult) {
ConnectionPoint cp1;
ConnectionPoint cp2;
ConnectionProxy proxy (&cp1);
EXPECT_EQ (proxy.connect (&cp2), kResultTrue);
EXPECT_FALSE (cp2.messageReceived);
HostMessage msg;
EXPECT_EQ (proxy.notify (&msg), kResultTrue);
EXPECT_TRUE (cp2.messageReceived);
return true;
});
registerTest (TestSuiteName, STR ("Send message on 2nd thread"), [] (ITestResult* testResult) {
ConnectionPoint cp1;
ConnectionPoint cp2;
ConnectionProxy proxy (&cp1);
EXPECT_EQ (proxy.connect (&cp2), kResultTrue);
EXPECT_FALSE (cp2.messageReceived);
std::condition_variable cv;
std::mutex m;
std::optional<tresult> notifyResult;
std::thread thread ([&] () {
HostMessage msg;
{
const std::scoped_lock sl (m);
notifyResult = proxy.notify (&msg);
}
cv.notify_one ();
});
std::unique_lock ul (m);
cv.wait (ul, [&] { return notifyResult.has_value (); });
EXPECT_NE (*notifyResult, kResultTrue);
EXPECT_FALSE (cp2.messageReceived);
thread.join ();
return true;
});
});
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
} // Vst
} // Steinberg
@@ -0,0 +1,92 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/test/eventlisttest.cpp
// Created by : Steinberg, 08/2021
// Description : Test event list
// Flags : clang-format SMTGSequencer
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/source/main/moduleinit.h"
#include "public.sdk/source/vst/hosting/eventlist.h"
#include "public.sdk/source/vst/utility/testing.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace {
//------------------------------------------------------------------------
ModuleInitializer EventListTests ([] () {
constexpr auto TestSuiteName = "EventList";
registerTest (TestSuiteName, STR ("Set and get single event"), [] (ITestResult* testResult) {
EventList eventList;
Event event1 = {};
event1.type = Event::kNoteOnEvent;
event1.noteOn.noteId = 10;
EXPECT_EQ (eventList.addEvent (event1), kResultTrue);
Event event2;
EXPECT_EQ (eventList.getEvent (0, event2), kResultTrue);
EXPECT_EQ (memcmp (&event1, &event2, sizeof (Event)), 0);
return true;
});
registerTest (TestSuiteName, STR ("Count events"), [] (ITestResult* testResult) {
EventList eventList;
Event event = {};
for (auto i = 0; i < 20; ++i)
{
EXPECT_EQ (eventList.addEvent (event), kResultTrue);
}
EXPECT_EQ (eventList.getEventCount (), 20);
return true;
});
registerTest (TestSuiteName, STR ("Overflow"), [] (ITestResult* testResult) {
EventList eventList (20);
Event event = {};
for (auto i = 0; i < 20; ++i)
{
EXPECT_EQ (eventList.addEvent (event), kResultTrue);
}
EXPECT_EQ (eventList.getEventCount (), 20);
EXPECT_NE (eventList.addEvent (event), kResultTrue);
EXPECT_EQ (eventList.getEventCount (), 20);
return true;
});
registerTest (TestSuiteName, STR ("Get unknown event"), [] (ITestResult* testResult) {
EventList eventList;
Event event {};
EXPECT_NE (eventList.getEvent (0, event), kResultTrue);
return true;
});
registerTest (TestSuiteName, STR ("Resize"), [] (ITestResult* testResult) {
EventList eventList (1);
Event event {};
EXPECT_NE (eventList.getEvent (0, event), kResultTrue);
event = {};
EXPECT_EQ (eventList.addEvent (event), kResultTrue);
EXPECT_EQ (eventList.getEventCount (), 1);
EXPECT_NE (eventList.addEvent (event), kResultTrue);
EXPECT_EQ (eventList.getEventCount (), 1);
eventList.setMaxSize (2);
EXPECT_EQ (eventList.getEventCount (), 0);
EXPECT_EQ (eventList.addEvent (event), kResultTrue);
EXPECT_EQ (eventList.addEvent (event), kResultTrue);
EXPECT_EQ (eventList.getEventCount (), 2);
EXPECT_NE (eventList.addEvent (event), kResultTrue);
EXPECT_EQ (eventList.getEventCount (), 2);
return true;
});
});
//------------------------------------------------------------------------
} // anonymous
} // Vst
} // Steinberg
@@ -0,0 +1,129 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/test/hostclassestest.cpp
// Created by : Steinberg, 08/2021
// Description : Test host classes
// Flags : clang-format SMTGSequencer
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/source/main/moduleinit.h"
#include "public.sdk/source/vst/hosting/hostclasses.h"
#include "public.sdk/source/vst/utility/testing.h"
#include "pluginterfaces/base/fstrdefs.h"
#include <array>
#include <vector>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace {
//------------------------------------------------------------------------
ModuleInitializer HostApplicationTests ([] () {
constexpr auto TestSuiteName = "HostApplication";
registerTest (
TestSuiteName, STR ("Create instance of IAttributeList"), [] (ITestResult* testResult) {
HostApplication hostApp;
FUnknown* instance {nullptr};
TUID iid;
IAttributeList::iid.toTUID (iid);
EXPECT_EQ (hostApp.createInstance (iid, iid, reinterpret_cast<void**> (&instance)),
kResultTrue);
EXPECT_NE (instance, nullptr);
instance->release ();
return true;
});
registerTest (TestSuiteName, STR ("Create instance of IMessage"), [] (ITestResult* testResult) {
HostApplication hostApp;
FUnknown* instance {nullptr};
TUID iid;
IMessage::iid.toTUID (iid);
EXPECT_EQ (hostApp.createInstance (iid, iid, reinterpret_cast<void**> (&instance)),
kResultTrue);
EXPECT_NE (instance, nullptr);
instance->release ();
return true;
});
});
//------------------------------------------------------------------------
ModuleInitializer HostAttributeListTests ([] () {
constexpr auto TestSuiteName = "HostAttributeList";
registerTest (TestSuiteName, STR ("Int"), [] (ITestResult* testResult) {
auto attrList = HostAttributeList::make ();
constexpr int64 testValue = 5;
EXPECT_EQ (attrList->setInt ("Int", testValue), kResultTrue);
int64 value = 0;
EXPECT_EQ (attrList->getInt ("Int", value), kResultTrue);
EXPECT_EQ (value, testValue);
return true;
});
registerTest (TestSuiteName, STR ("Float"), [] (ITestResult* testResult) {
auto attrList = HostAttributeList::make ();
constexpr double testValue = 2.636;
EXPECT_EQ (attrList->setFloat ("Float", testValue), kResultTrue);
double value = 0;
EXPECT_EQ (attrList->getFloat ("Float", value), kResultTrue);
EXPECT_EQ (value, testValue);
return true;
});
registerTest (TestSuiteName, STR ("String"), [] (ITestResult* testResult) {
auto attrList = HostAttributeList::make ();
constexpr const TChar* testValue = STR ("TestValue");
EXPECT_EQ (attrList->setString ("Str", testValue), kResultTrue);
TChar value[10];
EXPECT_EQ (attrList->getString ("Str", value, 10 * sizeof (TChar)), kResultTrue);
EXPECT_EQ (tstrcmp (testValue, value), 0);
return true;
});
registerTest (TestSuiteName, STR ("Binary"), [] (ITestResult* testResult) {
auto attrList = HostAttributeList::make ();
std::array<int32, 20> testData {};
int32 val = 0;
for (auto item : testData)
{
item = val++;
}
uint32 testDataSize = static_cast<uint32>(testData.size ()) * sizeof (int32);
EXPECT_EQ (attrList->setBinary ("Binary", testData.data (), testDataSize), kResultTrue);
const void* data;
uint32 dataSize {0};
EXPECT_EQ (attrList->getBinary ("Binary", data, dataSize), kResultTrue);
EXPECT_EQ (dataSize, testDataSize);
auto s = reinterpret_cast<const int32*> (data);
for (auto i : testData)
{
EXPECT_EQ (i, *s);
s++;
}
return true;
});
registerTest (TestSuiteName, STR ("Multiple Set"), [] (ITestResult* testResult) {
auto attrList = HostAttributeList::make ();
constexpr int64 testValue1 = 5;
constexpr int64 testValue2 = 6;
constexpr int64 testValue3 = 7;
EXPECT_EQ (attrList->setInt ("Int", testValue1), kResultTrue);
EXPECT_EQ (attrList->setInt ("Int", testValue2), kResultTrue);
EXPECT_EQ (attrList->setInt ("Int", testValue3), kResultTrue);
int64 value = 0;
EXPECT_EQ (attrList->getInt ("Int", value), kResultTrue);
EXPECT_EQ (value, testValue3);
return true;
});
});
//------------------------------------------------------------------------
} // anonymous
} // Vst
} // Steinberg
@@ -0,0 +1,273 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/test/parameterchangestest.cpp
// Created by : Steinberg, 08/2021
// Description : Test parameter changes
// Flags : clang-format SMTGSequencer
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/source/main/moduleinit.h"
#include "public.sdk/source/vst/hosting/parameterchanges.h"
#include "public.sdk/source/vst/utility/testing.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace {
//------------------------------------------------------------------------
struct ValuePoint
{
int32 sampleOffset {};
ParamValue value {};
};
//------------------------------------------------------------------------
ModuleInitializer ParameterValueQueueTests ([] () {
constexpr auto TestSuiteName = "ParameterValueQueue";
registerTest (TestSuiteName, STR ("Set paramID"), [] (ITestResult* testResult) {
ParameterValueQueue queue (10);
EXPECT_EQ (queue.getParameterId (), 10);
queue.setParamID (5);
EXPECT_EQ (queue.getParameterId (), 5);
return true;
});
registerTest (TestSuiteName, STR ("Set/get point"), [] (ITestResult* testResult) {
ParameterValueQueue queue (0);
ValuePoint vp {100, 0.5};
int32 index {};
EXPECT_EQ (queue.addPoint (vp.sampleOffset, vp.value, index), kResultTrue);
EXPECT_EQ (queue.getPointCount (), 1);
EXPECT_EQ (index, 0);
ValuePoint test;
EXPECT_EQ (queue.getPoint (index, test.sampleOffset, test.value), kResultTrue);
EXPECT_EQ (vp.sampleOffset, test.sampleOffset);
EXPECT_EQ (vp.value, test.value);
return true;
});
registerTest (TestSuiteName, STR ("Set/get multiple points"), [] (ITestResult* testResult) {
ParameterValueQueue queue (0);
ValuePoint vp1 {10, 0.1};
ValuePoint vp2 {30, 0.3};
ValuePoint vp3 {50, 0.6};
ValuePoint vp4 {70, 0.8};
int32 index {};
EXPECT_EQ (queue.addPoint (vp1.sampleOffset, vp1.value, index), kResultTrue);
EXPECT_EQ (queue.addPoint (vp2.sampleOffset, vp2.value, index), kResultTrue);
EXPECT_EQ (queue.addPoint (vp3.sampleOffset, vp3.value, index), kResultTrue);
EXPECT_EQ (queue.addPoint (vp4.sampleOffset, vp4.value, index), kResultTrue);
EXPECT_EQ (queue.getPointCount (), 4);
EXPECT_EQ (index, 3);
ValuePoint test;
EXPECT_EQ (queue.getPoint (0, test.sampleOffset, test.value), kResultTrue);
EXPECT_EQ (vp1.sampleOffset, test.sampleOffset);
EXPECT_EQ (vp1.value, test.value);
EXPECT_EQ (queue.getPoint (1, test.sampleOffset, test.value), kResultTrue);
EXPECT_EQ (vp2.sampleOffset, test.sampleOffset);
EXPECT_EQ (vp2.value, test.value);
EXPECT_EQ (queue.getPoint (2, test.sampleOffset, test.value), kResultTrue);
EXPECT_EQ (vp3.sampleOffset, test.sampleOffset);
EXPECT_EQ (vp3.value, test.value);
EXPECT_EQ (queue.getPoint (3, test.sampleOffset, test.value), kResultTrue);
EXPECT_EQ (vp4.sampleOffset, test.sampleOffset);
EXPECT_EQ (vp4.value, test.value);
return true;
});
registerTest (TestSuiteName, STR ("Ordered points"), [] (ITestResult* testResult) {
ParameterValueQueue queue (0);
ValuePoint vp1 {70, 0.1};
ValuePoint vp2 {50, 0.3};
ValuePoint vp3 {30, 0.6};
ValuePoint vp4 {10, 0.8};
int32 index {};
EXPECT_EQ (queue.addPoint (vp1.sampleOffset, vp1.value, index), kResultTrue);
EXPECT_EQ (queue.addPoint (vp2.sampleOffset, vp2.value, index), kResultTrue);
EXPECT_EQ (queue.addPoint (vp3.sampleOffset, vp3.value, index), kResultTrue);
EXPECT_EQ (queue.addPoint (vp4.sampleOffset, vp4.value, index), kResultTrue);
EXPECT_EQ (queue.getPointCount (), 4);
ValuePoint test;
EXPECT_EQ (queue.getPoint (0, test.sampleOffset, test.value), kResultTrue);
EXPECT_EQ (vp4.sampleOffset, test.sampleOffset);
EXPECT_EQ (vp4.value, test.value);
EXPECT_EQ (queue.getPoint (1, test.sampleOffset, test.value), kResultTrue);
EXPECT_EQ (vp3.sampleOffset, test.sampleOffset);
EXPECT_EQ (vp3.value, test.value);
EXPECT_EQ (queue.getPoint (2, test.sampleOffset, test.value), kResultTrue);
EXPECT_EQ (vp2.sampleOffset, test.sampleOffset);
EXPECT_EQ (vp2.value, test.value);
EXPECT_EQ (queue.getPoint (3, test.sampleOffset, test.value), kResultTrue);
EXPECT_EQ (vp1.sampleOffset, test.sampleOffset);
EXPECT_EQ (vp1.value, test.value);
return true;
});
registerTest (TestSuiteName, STR ("Clear"), [] (ITestResult* testResult) {
ParameterValueQueue queue (0);
ValuePoint vp {100, 0.5};
int32 index {};
EXPECT_EQ (queue.addPoint (vp.sampleOffset, vp.value, index), kResultTrue);
EXPECT_EQ (queue.getPointCount (), 1);
EXPECT_EQ (index, 0);
queue.clear ();
EXPECT_EQ (queue.getPointCount (), 0);
ValuePoint test;
EXPECT_NE (queue.getPoint (index, test.sampleOffset, test.value), kResultTrue);
return true;
});
});
//------------------------------------------------------------------------
ModuleInitializer ParameterChangesTests ([] () {
constexpr auto TestSuiteName = "ParameterChanges";
registerTest (TestSuiteName, STR ("Parameter count"), [] (ITestResult* testResult) {
ParameterChanges changes (1);
EXPECT_EQ (changes.getParameterCount (), 0);
int32 index {};
auto queue = changes.addParameterData (0, index);
EXPECT_NE (queue, nullptr);
EXPECT_EQ (index, 0);
EXPECT_EQ (changes.getParameterCount (), 1);
return true;
});
registerTest (TestSuiteName, STR ("Clear queue"), [] (ITestResult* testResult) {
ParameterChanges changes (1);
int32 index {};
EXPECT_EQ (changes.getParameterCount (), 0);
changes.addParameterData (0, index);
EXPECT_EQ (changes.getParameterCount (), 1);
changes.clearQueue ();
EXPECT_EQ (changes.getParameterCount (), 0);
return true;
});
registerTest (TestSuiteName, STR ("Increase max parameters"), [] (ITestResult* testResult) {
ParameterChanges changes (1);
int32 index {};
EXPECT_EQ (changes.getParameterCount (), 0);
changes.addParameterData (0, index);
EXPECT_EQ (changes.getParameterCount (), 1);
EXPECT_NE (changes.addParameterData (1, index), nullptr);
EXPECT_EQ (changes.getParameterCount (), 2);
changes.setMaxParameters (4);
EXPECT_EQ (changes.getParameterCount (), 2);
return true;
});
registerTest (TestSuiteName, STR ("Get parameter data"), [] (ITestResult* testResult) {
ParameterChanges changes (1);
int32 index {};
auto queue1 = changes.addParameterData (0, index);
auto queue2 = changes.getParameterData (index);
EXPECT_EQ (queue1, queue2);
return true;
});
});
//------------------------------------------------------------------------
struct ParamChange
{
ParamID id {};
ParamValue value {};
int32 sampleOffset {};
bool operator== (const ParamChange& o) const
{
return id == o.id && value == o.value && sampleOffset == o.sampleOffset;
}
bool operator!= (const ParamChange& o) const
{
return id != o.id || value != o.value || sampleOffset != o.sampleOffset;
}
};
//------------------------------------------------------------------------
ModuleInitializer ParameterChangeTransferTests ([] () {
constexpr auto TestSuiteName = "ParameterChangeTransfer";
registerTest (TestSuiteName, STR ("Add/get change"), [] (ITestResult* testResult) {
ParameterChangeTransfer transfer (1);
ParamChange change {1, 0.8, 2};
transfer.addChange (change.id, change.value, change.sampleOffset);
ParamChange test {};
EXPECT_NE (change, test);
EXPECT_TRUE (transfer.getNextChange (test.id, test.value, test.sampleOffset));
EXPECT_EQ (change, test);
return true;
});
registerTest (TestSuiteName, STR ("Remove changes"), [] (ITestResult* testResult) {
ParameterChangeTransfer transfer (1);
ParamChange change {1, 0.8, 2};
transfer.addChange (change.id, change.value, change.sampleOffset);
transfer.removeChanges ();
ParamChange test {};
EXPECT_FALSE (transfer.getNextChange (test.id, test.value, test.sampleOffset));
return true;
});
registerTest (TestSuiteName, STR ("Transfer changes to"), [] (ITestResult* testResult) {
ParameterChangeTransfer transfer (10);
ParamChange ch1 {1, 0.8, 2};
ParamChange ch2 {2, 0.4, 8};
transfer.addChange (ch1.id, ch1.value, ch1.sampleOffset);
transfer.addChange (ch2.id, ch2.value, ch2.sampleOffset);
ParameterChanges changes (2);
transfer.transferChangesTo (changes);
EXPECT_EQ (changes.getParameterCount (), 2);
auto valueQueue1 = changes.getParameterData (0);
EXPECT_NE (valueQueue1, nullptr);
auto valueQueue2 = changes.getParameterData (1);
EXPECT_NE (valueQueue2, nullptr);
auto pid1 = valueQueue1->getParameterId ();
auto pid2 = valueQueue2->getParameterId ();
EXPECT (pid1 == ch1.id || pid1 == ch2.id);
EXPECT (pid2 == ch1.id || pid2 == ch2.id);
EXPECT_NE (pid1, pid2);
ValuePoint vp1;
ValuePoint vp2;
if (pid1 == ch1.id)
{
EXPECT_EQ (valueQueue1->getPoint (0, vp1.sampleOffset, vp1.value), kResultTrue);
EXPECT_EQ (valueQueue2->getPoint (0, vp2.sampleOffset, vp2.value), kResultTrue);
}
else
{
EXPECT_EQ (valueQueue2->getPoint (0, vp1.sampleOffset, vp1.value), kResultTrue);
EXPECT_EQ (valueQueue1->getPoint (0, vp2.sampleOffset, vp2.value), kResultTrue);
}
return true;
});
registerTest (TestSuiteName, STR ("Transfer changes from"), [] (ITestResult* testResult) {
ParamChange ch1 {1, 0.8, 2};
ParamChange ch2 {2, 0.4, 8};
ParameterChangeTransfer transfer (2);
ParameterChanges changes;
int32 index {};
auto valueQueue = changes.addParameterData (ch1.id, index);
EXPECT_NE (valueQueue, nullptr);
EXPECT_EQ (valueQueue->addPoint (ch1.sampleOffset, ch1.value, index), kResultTrue);
valueQueue = changes.addParameterData (ch2.id, index);
EXPECT_NE (valueQueue, nullptr);
EXPECT_EQ (valueQueue->addPoint (ch2.sampleOffset, ch2.value, index), kResultTrue);
transfer.transferChangesFrom (changes);
ParamChange test1 {};
ParamChange test2 {};
ParamChange test3 {};
EXPECT_TRUE (transfer.getNextChange (test1.id, test1.value, test1.sampleOffset));
EXPECT_TRUE (transfer.getNextChange (test2.id, test2.value, test2.sampleOffset));
EXPECT_FALSE (transfer.getNextChange (test3.id, test3.value, test3.sampleOffset));
EXPECT (test1 == ch1 || test1 == ch2);
EXPECT (test2 == ch1 || test2 == ch2);
EXPECT_NE (test1, test2);
return true;
});
});
//------------------------------------------------------------------------
} // anonymous
} // Vst
} // Steinberg
@@ -0,0 +1,72 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/test/pluginterfacesupporttest.cpp
// Created by : Steinberg, 08/2021
// Description : Test pluginterface support helper
// Flags : clang-format SMTGSequencer
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/source/main/moduleinit.h"
#include "public.sdk/source/vst/hosting/pluginterfacesupport.h"
#include "public.sdk/source/vst/utility/testing.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/ivstmessage.h"
#include "pluginterfaces/vst/ivstunits.h"
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace {
//------------------------------------------------------------------------
ModuleInitializer PlugInterfaceSupportTests ([] () {
constexpr auto TestSuiteName = "PlugInterfaceSupport";
registerTest (TestSuiteName, STR ("Initial interfaces"), [] (ITestResult* testResult) {
PlugInterfaceSupport pis;
//---VST 3.0.0--------------------------------
EXPECT_EQ (pis.isPlugInterfaceSupported (IComponent::iid), kResultTrue);
EXPECT_EQ (pis.isPlugInterfaceSupported (IAudioProcessor::iid), kResultTrue);
EXPECT_EQ (pis.isPlugInterfaceSupported (IEditController::iid), kResultTrue);
EXPECT_EQ (pis.isPlugInterfaceSupported (IConnectionPoint::iid), kResultTrue);
EXPECT_EQ (pis.isPlugInterfaceSupported (IUnitInfo::iid), kResultTrue);
EXPECT_EQ (pis.isPlugInterfaceSupported (IUnitData::iid), kResultTrue);
EXPECT_EQ (pis.isPlugInterfaceSupported (IProgramListData::iid), kResultTrue);
//---VST 3.0.1--------------------------------
EXPECT_EQ (pis.isPlugInterfaceSupported (IMidiMapping::iid), kResultTrue);
//---VST 3.1----------------------------------
EXPECT_EQ (pis.isPlugInterfaceSupported (IEditController2::iid), kResultTrue);
return true;
});
registerTest (TestSuiteName, STR ("Add interface"), [] (ITestResult* testResult) {
PlugInterfaceSupport pis;
EXPECT_NE (pis.isPlugInterfaceSupported (IEditControllerHostEditing::iid), kResultTrue);
pis.addPlugInterfaceSupported (IEditControllerHostEditing::iid);
EXPECT_EQ (pis.isPlugInterfaceSupported (IEditControllerHostEditing::iid), kResultTrue);
return true;
});
registerTest (TestSuiteName, STR ("Remove interface"), [] (ITestResult* testResult) {
PlugInterfaceSupport pis;
EXPECT_NE (pis.isPlugInterfaceSupported (IEditControllerHostEditing::iid), kResultTrue);
pis.addPlugInterfaceSupported (IEditControllerHostEditing::iid);
EXPECT_EQ (pis.isPlugInterfaceSupported (IEditControllerHostEditing::iid), kResultTrue);
EXPECT_TRUE (pis.removePlugInterfaceSupported (IEditControllerHostEditing::iid));
EXPECT_NE (pis.isPlugInterfaceSupported (IEditControllerHostEditing::iid), kResultTrue);
return true;
});
});
//------------------------------------------------------------------------
} // anonymous
} // Vst
} // Steinberg
@@ -0,0 +1,351 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
//
// Category : Helpers
// Filename : public.sdk/source/vst/hosting/test/processdatatest.cpp
// Created by : Steinberg, 08/2021
// Description : Test process data helper
// Flags : clang-format SMTGSequencer
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "public.sdk/source/main/moduleinit.h"
#include "public.sdk/source/vst/hosting/processdata.h"
#include "public.sdk/source/vst/utility/testing.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/ivstmessage.h"
#include "pluginterfaces/vst/ivstunits.h"
#include <functional>
#include <memory>
//------------------------------------------------------------------------
namespace Steinberg {
namespace Vst {
namespace {
//------------------------------------------------------------------------
struct TestComponent : public IComponent
{
using GetBusCountFunc = std::function<int32 (BusDirection dir)>;
using GetBusInfoFunc = std::function<tresult (BusDirection dir, int32 index, BusInfo& bus)>;
tresult PLUGIN_API queryInterface (const TUID /*_iid*/, void** /*obj*/) override
{
return kNoInterface;
}
uint32 PLUGIN_API addRef () override { return 100; }
uint32 PLUGIN_API release () override { return 100; }
tresult PLUGIN_API initialize (FUnknown* /*context*/) override { return kResultTrue; }
tresult PLUGIN_API terminate () override { return kResultTrue; }
tresult PLUGIN_API getControllerClassId (TUID /*classId*/) override { return kNotImplemented; }
tresult PLUGIN_API setIoMode (IoMode /*mode*/) override { return kNotImplemented; }
int32 PLUGIN_API getBusCount (MediaType type, BusDirection dir) override
{
if (type != MediaTypes::kAudio)
return 0;
return getBusCountFunc (dir);
}
tresult PLUGIN_API getBusInfo (MediaType type, BusDirection dir, int32 index,
BusInfo& bus) override
{
if (type != MediaTypes::kAudio)
return kResultFalse;
return getBusInfoFunc (dir, index, bus);
}
tresult PLUGIN_API getRoutingInfo (RoutingInfo& /*inInfo*/, RoutingInfo& /*outInfo*/) override
{
return kNotImplemented;
}
tresult PLUGIN_API activateBus (MediaType /*type*/, BusDirection /*dir*/, int32 /*index*/,
TBool /*state*/) override
{
return kNotImplemented;
}
tresult PLUGIN_API setActive (TBool /*state*/) override { return kNotImplemented; }
tresult PLUGIN_API setState (IBStream* /*state*/) override { return kNotImplemented; }
tresult PLUGIN_API getState (IBStream* /*state*/) override { return kNotImplemented; }
GetBusCountFunc getBusCountFunc = [] (BusDirection /*dir*/) { return 0; };
GetBusInfoFunc getBusInfoFunc = [] (BusDirection /*dir*/, int32 /*index*/, BusInfo& /*bus*/) {
return kNotImplemented;
};
};
//------------------------------------------------------------------------
ModuleInitializer HostProcessDataTests ([] () {
constexpr auto TestSuiteName = "HostProcessData";
registerTest (TestSuiteName, STR ("No bus"), [] (ITestResult* testResult) {
TestComponent tc;
HostProcessData processData;
EXPECT_TRUE (processData.prepare (tc, 1024, kSample32));
EXPECT_EQ (processData.numInputs, 0);
EXPECT_EQ (processData.numOutputs, 0);
return true;
});
registerTest (TestSuiteName, STR ("1 out bus no channels"), [] (ITestResult* testResult) {
TestComponent tc;
tc.getBusCountFunc = [] (BusDirection dir) {
return dir == BusDirections::kOutput ? 1 : 0;
};
HostProcessData processData;
EXPECT_TRUE (processData.prepare (tc, 1024, kSample32));
EXPECT_EQ (processData.numInputs, 0);
EXPECT_EQ (processData.numOutputs, 1);
EXPECT_EQ (processData.outputs[0].numChannels, 0);
return true;
});
registerTest (TestSuiteName, STR ("1 out bus 2 channels"), [] (ITestResult* testResult) {
TestComponent tc;
tc.getBusCountFunc = [] (BusDirection dir) {
return dir == BusDirections::kOutput ? 1 : 0;
};
tc.getBusInfoFunc = [] (BusDirection dir, int32 index, BusInfo& bus) {
if (dir == BusDirections::kInput || index != 0)
return kResultFalse;
bus.channelCount = 2;
return kResultTrue;
};
HostProcessData processData;
EXPECT_TRUE (processData.prepare (tc, 1024, kSample32));
EXPECT_EQ (processData.numInputs, 0);
EXPECT_EQ (processData.numOutputs, 1);
EXPECT_EQ (processData.outputs[0].numChannels, 2);
EXPECT_NE (processData.outputs[0].channelBuffers32[0], nullptr);
EXPECT_NE (processData.outputs[0].channelBuffers32[1], nullptr);
return true;
});
registerTest (TestSuiteName, STR ("1 in & out bus 2 channels"), [] (ITestResult* testResult) {
TestComponent tc;
tc.getBusCountFunc = [] (BusDirection /*dir*/) { return 1; };
tc.getBusInfoFunc = [] (BusDirection /*dir*/, int32 index, BusInfo& bus) {
if (index != 0)
return kResultFalse;
bus.channelCount = 2;
return kResultTrue;
};
HostProcessData processData;
EXPECT_TRUE (processData.prepare (tc, 1024, kSample32));
EXPECT_EQ (processData.numOutputs, 1);
EXPECT_EQ (processData.outputs[0].numChannels, 2);
EXPECT_NE (processData.outputs[0].channelBuffers32[0], nullptr);
EXPECT_NE (processData.outputs[0].channelBuffers32[1], nullptr);
EXPECT_EQ (processData.numInputs, 1);
EXPECT_EQ (processData.inputs[0].numChannels, 2);
EXPECT_NE (processData.inputs[0].channelBuffers32[0], nullptr);
EXPECT_NE (processData.inputs[0].channelBuffers32[1], nullptr);
return true;
});
registerTest (TestSuiteName, STR ("2 in & out bus dif channels"), [] (ITestResult* testResult) {
TestComponent tc;
tc.getBusCountFunc = [] (BusDirection /*dir*/) { return 2; };
tc.getBusInfoFunc = [] (BusDirection /*dir*/, int32 index, BusInfo& bus) {
if (index < 0 || index > 1)
return kResultFalse;
bus.channelCount = index == 0 ? 4 : 1;
return kResultTrue;
};
HostProcessData processData;
EXPECT_TRUE (processData.prepare (tc, 1024, kSample32));
EXPECT_EQ (processData.numOutputs, 2);
EXPECT_EQ (processData.outputs[0].numChannels, 4);
EXPECT_NE (processData.outputs[0].channelBuffers32[0], nullptr);
EXPECT_NE (processData.outputs[0].channelBuffers32[1], nullptr);
EXPECT_NE (processData.outputs[0].channelBuffers32[2], nullptr);
EXPECT_NE (processData.outputs[0].channelBuffers32[3], nullptr);
EXPECT_EQ (processData.outputs[1].numChannels, 1);
EXPECT_NE (processData.outputs[1].channelBuffers32[0], nullptr);
EXPECT_EQ (processData.numInputs, 2);
EXPECT_EQ (processData.inputs[0].numChannels, 4);
EXPECT_NE (processData.inputs[0].channelBuffers32[0], nullptr);
EXPECT_NE (processData.inputs[0].channelBuffers32[1], nullptr);
EXPECT_NE (processData.inputs[0].channelBuffers32[2], nullptr);
EXPECT_NE (processData.inputs[0].channelBuffers32[3], nullptr);
EXPECT_EQ (processData.inputs[1].numChannels, 1);
EXPECT_NE (processData.inputs[1].channelBuffers32[0], nullptr);
return true;
});
registerTest (TestSuiteName, STR ("Set all channel buffers 32"), [] (ITestResult* testResult) {
TestComponent tc;
tc.getBusCountFunc = [] (BusDirection /*dir*/) { return 1; };
tc.getBusInfoFunc = [] (BusDirection /*dir*/, int32 index, BusInfo& bus) {
if (index != 0)
return kResultFalse;
bus.channelCount = 2;
return kResultTrue;
};
auto buffer = std::unique_ptr<float[]> (new float[10]);
HostProcessData processData;
EXPECT_TRUE (processData.prepare (tc, 0, kSample32));
EXPECT_EQ (processData.numInputs, 1);
EXPECT_EQ (processData.numOutputs, 1);
EXPECT_FALSE (processData.setChannelBuffers (BusDirections::kInput, 1, nullptr));
EXPECT_FALSE (processData.setChannelBuffers64 (BusDirections::kInput, 1, nullptr));
EXPECT_TRUE (processData.setChannelBuffers (BusDirections::kInput, 0, buffer.get ()));
EXPECT_TRUE (processData.setChannelBuffers (BusDirections::kOutput, 0, buffer.get ()));
EXPECT_FALSE (processData.setChannelBuffers64 (BusDirections::kInput, 0, nullptr));
EXPECT_FALSE (processData.setChannelBuffers64 (BusDirections::kOutput, 0, nullptr));
EXPECT_EQ (processData.inputs[0].channelBuffers32[0], buffer.get ());
EXPECT_EQ (processData.inputs[0].channelBuffers32[1], buffer.get ());
EXPECT_EQ (processData.outputs[0].channelBuffers32[0], buffer.get ());
EXPECT_EQ (processData.outputs[0].channelBuffers32[1], buffer.get ());
return true;
});
registerTest (TestSuiteName, STR ("Set all channel buffers 64"), [] (ITestResult* testResult) {
TestComponent tc;
tc.getBusCountFunc = [] (BusDirection /*dir*/) { return 1; };
tc.getBusInfoFunc = [] (BusDirection /*dir*/, int32 index, BusInfo& bus) {
if (index != 0)
return kResultFalse;
bus.channelCount = 2;
return kResultTrue;
};
auto buffer = std::unique_ptr<double[]> (new double[10]);
HostProcessData processData;
EXPECT_TRUE (processData.prepare (tc, 0, kSample64));
EXPECT_EQ (processData.numInputs, 1);
EXPECT_EQ (processData.numOutputs, 1);
EXPECT_FALSE (processData.setChannelBuffers (BusDirections::kInput, 1, nullptr));
EXPECT_FALSE (processData.setChannelBuffers64 (BusDirections::kInput, 1, nullptr));
EXPECT_TRUE (processData.setChannelBuffers64 (BusDirections::kInput, 0, buffer.get ()));
EXPECT_TRUE (processData.setChannelBuffers64 (BusDirections::kOutput, 0, buffer.get ()));
EXPECT_FALSE (processData.setChannelBuffers (BusDirections::kInput, 0, nullptr));
EXPECT_FALSE (processData.setChannelBuffers (BusDirections::kOutput, 0, nullptr));
EXPECT_EQ (processData.inputs[0].channelBuffers64[0], buffer.get ());
EXPECT_EQ (processData.inputs[0].channelBuffers64[1], buffer.get ());
EXPECT_EQ (processData.outputs[0].channelBuffers64[0], buffer.get ());
EXPECT_EQ (processData.outputs[0].channelBuffers64[1], buffer.get ());
return true;
});
registerTest (
TestSuiteName, STR ("Set individual channel buffers 32"), [] (ITestResult* testResult) {
TestComponent tc;
tc.getBusCountFunc = [] (BusDirection /*dir*/) { return 1; };
tc.getBusInfoFunc = [] (BusDirection /*dir*/, int32 index, BusInfo& bus) {
if (index != 0)
return kResultFalse;
bus.channelCount = 2;
return kResultTrue;
};
auto bufferL = std::unique_ptr<float[]> (new float[10]);
auto bufferR = std::unique_ptr<float[]> (new float[10]);
HostProcessData pd;
EXPECT_TRUE (pd.prepare (tc, 0, kSample32));
EXPECT_EQ (pd.numInputs, 1);
EXPECT_EQ (pd.numOutputs, 1);
EXPECT_FALSE (pd.setChannelBuffer (BusDirections::kInput, 1, 0, nullptr));
EXPECT_FALSE (pd.setChannelBuffer64 (BusDirections::kInput, 1, 0, nullptr));
EXPECT_TRUE (pd.setChannelBuffer (BusDirections::kInput, 0, 0, bufferL.get ()));
EXPECT_TRUE (pd.setChannelBuffer (BusDirections::kInput, 0, 1, bufferR.get ()));
EXPECT_TRUE (pd.setChannelBuffer (BusDirections::kOutput, 0, 0, bufferL.get ()));
EXPECT_TRUE (pd.setChannelBuffer (BusDirections::kOutput, 0, 1, bufferR.get ()));
EXPECT_FALSE (pd.setChannelBuffer64 (BusDirections::kInput, 0, 0, nullptr));
EXPECT_FALSE (pd.setChannelBuffer64 (BusDirections::kOutput, 0, 1, nullptr));
EXPECT_EQ (pd.inputs[0].channelBuffers32[0], bufferL.get ());
EXPECT_EQ (pd.inputs[0].channelBuffers32[1], bufferR.get ());
EXPECT_EQ (pd.outputs[0].channelBuffers32[0], bufferL.get ());
EXPECT_EQ (pd.outputs[0].channelBuffers32[1], bufferR.get ());
return true;
});
registerTest (TestSuiteName, STR ("Set individual channel buffers 32 combined"),
[] (ITestResult* testResult) {
TestComponent tc;
tc.getBusCountFunc = [] (BusDirection /*dir*/) { return 1; };
tc.getBusInfoFunc = [] (BusDirection /*dir*/, int32 index, BusInfo& bus) {
if (index != 0)
return kResultFalse;
bus.channelCount = 2;
return kResultTrue;
};
auto bufferL = std::unique_ptr<float[]> (new float[10]);
auto bufferR = std::unique_ptr<float[]> (new float[10]);
float* buffers[2] = {bufferL.get (), bufferR.get ()};
HostProcessData pd;
EXPECT_TRUE (pd.prepare (tc, 0, kSample32));
EXPECT_EQ (pd.numInputs, 1);
EXPECT_EQ (pd.numOutputs, 1);
EXPECT_FALSE (pd.setChannelBuffers (BusDirections::kInput, 1, nullptr, 0));
EXPECT_FALSE (pd.setChannelBuffers64 (BusDirections::kInput, 1, nullptr, 0));
EXPECT_TRUE (pd.setChannelBuffers (BusDirections::kInput, 0, buffers, 2));
EXPECT_TRUE (pd.setChannelBuffers (BusDirections::kOutput, 0, buffers, 2));
EXPECT_FALSE (pd.setChannelBuffers64 (BusDirections::kInput, 0, nullptr, 0));
EXPECT_FALSE (pd.setChannelBuffers64 (BusDirections::kOutput, 0, nullptr, 0));
EXPECT_EQ (pd.inputs[0].channelBuffers32[0], bufferL.get ());
EXPECT_EQ (pd.inputs[0].channelBuffers32[1], bufferR.get ());
EXPECT_EQ (pd.outputs[0].channelBuffers32[0], bufferL.get ());
EXPECT_EQ (pd.outputs[0].channelBuffers32[1], bufferR.get ());
return true;
});
registerTest (
TestSuiteName, STR ("Set individual channel buffers 64"), [] (ITestResult* testResult) {
TestComponent tc;
tc.getBusCountFunc = [] (BusDirection /*dir*/) { return 1; };
tc.getBusInfoFunc = [] (BusDirection /*dir*/, int32 index, BusInfo& bus) {
if (index != 0)
return kResultFalse;
bus.channelCount = 2;
return kResultTrue;
};
auto bufferL = std::unique_ptr<double[]> (new double[10]);
auto bufferR = std::unique_ptr<double[]> (new double[10]);
HostProcessData pd;
EXPECT_TRUE (pd.prepare (tc, 0, kSample64));
EXPECT_EQ (pd.numInputs, 1);
EXPECT_EQ (pd.numOutputs, 1);
EXPECT_FALSE (pd.setChannelBuffer (BusDirections::kInput, 1, 0, nullptr));
EXPECT_FALSE (pd.setChannelBuffer64 (BusDirections::kInput, 1, 0, nullptr));
EXPECT_TRUE (pd.setChannelBuffer64 (BusDirections::kInput, 0, 0, bufferL.get ()));
EXPECT_TRUE (pd.setChannelBuffer64 (BusDirections::kInput, 0, 1, bufferR.get ()));
EXPECT_TRUE (pd.setChannelBuffer64 (BusDirections::kOutput, 0, 0, bufferL.get ()));
EXPECT_TRUE (pd.setChannelBuffer64 (BusDirections::kOutput, 0, 1, bufferR.get ()));
EXPECT_FALSE (pd.setChannelBuffer (BusDirections::kInput, 0, 0, nullptr));
EXPECT_FALSE (pd.setChannelBuffer (BusDirections::kOutput, 0, 1, nullptr));
EXPECT_EQ (pd.inputs[0].channelBuffers64[0], bufferL.get ());
EXPECT_EQ (pd.inputs[0].channelBuffers64[1], bufferR.get ());
EXPECT_EQ (pd.outputs[0].channelBuffers64[0], bufferL.get ());
EXPECT_EQ (pd.outputs[0].channelBuffers64[1], bufferR.get ());
return true;
});
registerTest (TestSuiteName, STR ("Set individual channel buffers 64 combined"),
[] (ITestResult* testResult) {
TestComponent tc;
tc.getBusCountFunc = [] (BusDirection /*dir*/) { return 1; };
tc.getBusInfoFunc = [] (BusDirection /*dir*/, int32 index, BusInfo& bus) {
if (index != 0)
return kResultFalse;
bus.channelCount = 2;
return kResultTrue;
};
auto bufferL = std::unique_ptr<double[]> (new double[10]);
auto bufferR = std::unique_ptr<double[]> (new double[10]);
double* buffers[2] = {bufferL.get (), bufferR.get ()};
HostProcessData pd;
EXPECT_TRUE (pd.prepare (tc, 0, kSample64));
EXPECT_EQ (pd.numInputs, 1);
EXPECT_EQ (pd.numOutputs, 1);
EXPECT_FALSE (pd.setChannelBuffers (BusDirections::kInput, 1, nullptr, 0));
EXPECT_FALSE (pd.setChannelBuffers64 (BusDirections::kInput, 1, nullptr, 0));
EXPECT_TRUE (pd.setChannelBuffers64 (BusDirections::kInput, 0, buffers, 2));
EXPECT_TRUE (pd.setChannelBuffers64 (BusDirections::kOutput, 0, buffers, 2));
EXPECT_FALSE (pd.setChannelBuffers (BusDirections::kInput, 0, nullptr, 0));
EXPECT_FALSE (pd.setChannelBuffers (BusDirections::kOutput, 0, nullptr, 0));
EXPECT_EQ (pd.inputs[0].channelBuffers64[0], bufferL.get ());
EXPECT_EQ (pd.inputs[0].channelBuffers64[1], bufferR.get ());
EXPECT_EQ (pd.outputs[0].channelBuffers64[0], bufferL.get ());
EXPECT_EQ (pd.outputs[0].channelBuffers64[1], bufferR.get ());
return true;
});
});
//------------------------------------------------------------------------
} // anonymous
} // Vst
} // Steinberg