Initial release
This commit is contained in:
@@ -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
|
||||
+390
@@ -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
|
||||
+120
@@ -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
|
||||
+118
@@ -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
|
||||
+130
@@ -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
|
||||
+129
@@ -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
|
||||
+273
@@ -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
|
||||
+72
@@ -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
|
||||
+351
@@ -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
|
||||
Reference in New Issue
Block a user