Initial release
This commit is contained in:
@@ -0,0 +1,467 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/dataexchange.cpp
|
||||
// Created by : Steinberg, 06/2023
|
||||
// Description : VST Data Exchange API 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 "dataexchange.h"
|
||||
#include "public.sdk/source/vst/utility/alignedalloc.h"
|
||||
#include "public.sdk/source/vst/utility/ringbuffer.h"
|
||||
#include "base/source/timer.h"
|
||||
#include "pluginterfaces/base/funknownimpl.h"
|
||||
#include "pluginterfaces/vst/ivsthostapplication.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
static constexpr DataExchangeBlock InvalidDataExchangeBlock = {nullptr, 0,
|
||||
InvalidDataExchangeBlockID};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool operator== (const DataExchangeHandler::Config& c1, const DataExchangeHandler::Config& c2)
|
||||
{
|
||||
return c1.userContextID == c2.userContextID && c1.alignment == c2.alignment &&
|
||||
c1.numBlocks == c2.numBlocks && c1.blockSize == c2.blockSize;
|
||||
}
|
||||
|
||||
static constexpr auto MessageIDDataExchange = "DataExchange";
|
||||
static constexpr auto MessageIDQueueOpened = "DataExchangeQueueOpened";
|
||||
static constexpr auto MessageIDQueueClosed = "DataExchangeQueueClosed";
|
||||
static constexpr auto MessageKeyData = "Data";
|
||||
static constexpr auto MessageKeyBlockSize = "BlockSize";
|
||||
static constexpr auto MessageKeyUserContextID = "UserContextID";
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct MessageHandler : ITimerCallback
|
||||
{
|
||||
using RingBuffer = OneReaderOneWriter::RingBuffer<void*>;
|
||||
|
||||
IPtr<Timer> timer;
|
||||
IPtr<IHostApplication> hostApp;
|
||||
IConnectionPoint* connection {nullptr};
|
||||
RingBuffer realtimeBuffer;
|
||||
RingBuffer messageBuffer;
|
||||
RingBuffer rtOnlyBuffer;
|
||||
void* lockedRealtimeBlock {nullptr};
|
||||
DataExchangeHandler::Config config {};
|
||||
|
||||
MessageHandler (FUnknown* hostContext, IConnectionPoint* connection) : connection (connection)
|
||||
{
|
||||
hostApp = U::cast<IHostApplication> (hostContext);
|
||||
}
|
||||
|
||||
~MessageHandler () noexcept override
|
||||
{
|
||||
if (timer)
|
||||
timer->stop ();
|
||||
timer = nullptr;
|
||||
}
|
||||
|
||||
bool openQueue (const DataExchangeHandler::Config& c)
|
||||
{
|
||||
if (!hostApp || !connection)
|
||||
return false;
|
||||
config = c;
|
||||
|
||||
timer = owned (Timer::create (this, 1));
|
||||
if (!timer)
|
||||
return false;
|
||||
|
||||
realtimeBuffer.resize (config.numBlocks);
|
||||
messageBuffer.resize (config.numBlocks);
|
||||
rtOnlyBuffer.resize (config.numBlocks);
|
||||
for (auto i = 0u; i < config.numBlocks; ++i)
|
||||
{
|
||||
auto data = aligned_alloc (config.blockSize, config.alignment);
|
||||
realtimeBuffer.push (data);
|
||||
}
|
||||
if (auto msg = owned (allocateMessage (hostApp)))
|
||||
{
|
||||
msg->setMessageID (MessageIDQueueOpened);
|
||||
if (auto attr = msg->getAttributes ())
|
||||
{
|
||||
attr->setInt (MessageKeyUserContextID, config.userContextID);
|
||||
attr->setInt (MessageKeyBlockSize, config.blockSize);
|
||||
}
|
||||
connection->notify (msg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool closeQueue ()
|
||||
{
|
||||
if (timer)
|
||||
timer->stop ();
|
||||
timer = nullptr;
|
||||
void* data;
|
||||
while (realtimeBuffer.pop (data))
|
||||
{
|
||||
aligned_free (data, config.alignment);
|
||||
}
|
||||
while (messageBuffer.pop (data))
|
||||
{
|
||||
aligned_free (data, config.alignment);
|
||||
}
|
||||
while (rtOnlyBuffer.pop (data))
|
||||
{
|
||||
aligned_free (data, config.alignment);
|
||||
}
|
||||
if (auto msg = owned (allocateMessage (hostApp)))
|
||||
{
|
||||
msg->setMessageID (MessageIDQueueClosed);
|
||||
if (auto attr = msg->getAttributes ())
|
||||
{
|
||||
attr->setInt (MessageKeyUserContextID, config.userContextID);
|
||||
}
|
||||
connection->notify (msg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void* lockBlock ()
|
||||
{
|
||||
if (lockedRealtimeBlock != nullptr)
|
||||
return nullptr;
|
||||
void* data;
|
||||
if (rtOnlyBuffer.pop (data))
|
||||
{
|
||||
lockedRealtimeBlock = data;
|
||||
return lockedRealtimeBlock;
|
||||
}
|
||||
if (realtimeBuffer.pop (data))
|
||||
{
|
||||
lockedRealtimeBlock = data;
|
||||
return lockedRealtimeBlock;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool freeBlock (bool send)
|
||||
{
|
||||
if (send)
|
||||
{
|
||||
if (messageBuffer.push (lockedRealtimeBlock))
|
||||
{
|
||||
lockedRealtimeBlock = nullptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (rtOnlyBuffer.push (lockedRealtimeBlock))
|
||||
{
|
||||
lockedRealtimeBlock = nullptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void onTimer (Timer* /*_timer*/) override
|
||||
{
|
||||
void* data;
|
||||
while (messageBuffer.pop (data))
|
||||
{
|
||||
if (auto msg = owned (allocateMessage (hostApp)))
|
||||
{
|
||||
msg->setMessageID (MessageIDDataExchange);
|
||||
if (auto attributes = msg->getAttributes ())
|
||||
{
|
||||
attributes->setInt (MessageKeyUserContextID, config.userContextID);
|
||||
attributes->setBinary (MessageKeyData, data, config.blockSize);
|
||||
connection->notify (msg);
|
||||
}
|
||||
}
|
||||
realtimeBuffer.push (data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct DataExchangeHandler::Impl
|
||||
{
|
||||
Config config {};
|
||||
ConfigCallback configCallback;
|
||||
IDataExchangeHandler* exchangeHandler {nullptr};
|
||||
IConnectionPoint* connectionPoint {nullptr};
|
||||
FUnknown* hostContext {nullptr};
|
||||
IAudioProcessor* processor {nullptr};
|
||||
std::unique_ptr<MessageHandler> fallbackMessageHandler;
|
||||
|
||||
DataExchangeQueueID queueID {InvalidDataExchangeQueueID};
|
||||
DataExchangeBlock currentBlock {InvalidDataExchangeBlock};
|
||||
bool enabled {true};
|
||||
bool internalUseExchangeManager {true};
|
||||
|
||||
bool isOpen () const { return queueID != InvalidDataExchangeQueueID; }
|
||||
|
||||
bool openQueue (bool forceUseMessageHandling)
|
||||
{
|
||||
if (exchangeHandler && !forceUseMessageHandling)
|
||||
{
|
||||
internalUseExchangeManager = true;
|
||||
return exchangeHandler->openQueue (processor, config.blockSize, config.numBlocks,
|
||||
config.alignment, config.userContextID,
|
||||
&queueID) == kResultTrue;
|
||||
}
|
||||
internalUseExchangeManager = false;
|
||||
fallbackMessageHandler = std::make_unique<MessageHandler> (hostContext, connectionPoint);
|
||||
if (fallbackMessageHandler->openQueue (config))
|
||||
{
|
||||
queueID = 0;
|
||||
return true;
|
||||
}
|
||||
fallbackMessageHandler.reset ();
|
||||
return false;
|
||||
}
|
||||
void closeQueue ()
|
||||
{
|
||||
if (queueID == InvalidDataExchangeQueueID)
|
||||
return;
|
||||
if (internalUseExchangeManager)
|
||||
exchangeHandler->closeQueue (queueID);
|
||||
else if (fallbackMessageHandler)
|
||||
{
|
||||
fallbackMessageHandler->closeQueue ();
|
||||
fallbackMessageHandler.reset ();
|
||||
}
|
||||
currentBlock = InvalidDataExchangeBlock;
|
||||
queueID = InvalidDataExchangeQueueID;
|
||||
}
|
||||
DataExchangeBlock lockBlock ()
|
||||
{
|
||||
if (!isOpen ())
|
||||
return InvalidDataExchangeBlock;
|
||||
else if (currentBlock.blockID != InvalidDataExchangeBlockID)
|
||||
return currentBlock;
|
||||
else if (internalUseExchangeManager)
|
||||
{
|
||||
auto res = exchangeHandler->lockBlock (queueID, ¤tBlock);
|
||||
if (res != kResultTrue)
|
||||
currentBlock = InvalidDataExchangeBlock;
|
||||
return currentBlock;
|
||||
}
|
||||
else if (fallbackMessageHandler)
|
||||
{
|
||||
if (auto data = fallbackMessageHandler->lockBlock ())
|
||||
{
|
||||
currentBlock.data = data;
|
||||
currentBlock.size = config.blockSize;
|
||||
currentBlock.blockID = 0;
|
||||
return currentBlock;
|
||||
}
|
||||
}
|
||||
return InvalidDataExchangeBlock;
|
||||
}
|
||||
bool freeBlock (bool send)
|
||||
{
|
||||
if (!isOpen () || currentBlock.blockID == InvalidDataExchangeBlockID)
|
||||
return true;
|
||||
if (internalUseExchangeManager)
|
||||
{
|
||||
auto res = exchangeHandler->freeBlock (queueID, currentBlock.blockID, send);
|
||||
currentBlock = InvalidDataExchangeBlock;
|
||||
return res == kResultTrue;
|
||||
}
|
||||
else if (fallbackMessageHandler)
|
||||
{
|
||||
if (fallbackMessageHandler->freeBlock (send))
|
||||
{
|
||||
currentBlock = InvalidDataExchangeBlock;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DataExchangeHandler::DataExchangeHandler (IAudioProcessor* processor)
|
||||
{
|
||||
impl = std::make_unique<Impl> ();
|
||||
impl->processor = processor;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DataExchangeHandler::DataExchangeHandler (IAudioProcessor* processor, ConfigCallback&& callback)
|
||||
: DataExchangeHandler (processor)
|
||||
{
|
||||
impl->configCallback = std::move (callback);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DataExchangeHandler::DataExchangeHandler (IAudioProcessor* processor,
|
||||
const ConfigCallback& callback)
|
||||
: DataExchangeHandler (processor)
|
||||
{
|
||||
impl->configCallback = callback;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DataExchangeHandler::~DataExchangeHandler () noexcept
|
||||
{
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DataExchangeHandler::onConnect (IConnectionPoint* other, FUnknown* hostContext)
|
||||
{
|
||||
impl->connectionPoint = other;
|
||||
impl->hostContext = hostContext;
|
||||
impl->exchangeHandler = U::cast<IDataExchangeHandler> (hostContext);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DataExchangeHandler::onDisconnect (IConnectionPoint* /*other*/)
|
||||
{
|
||||
impl->closeQueue ();
|
||||
impl->connectionPoint = nullptr;
|
||||
impl->hostContext = nullptr;
|
||||
impl->exchangeHandler = nullptr;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DataExchangeHandler::onActivate (const Vst::ProcessSetup& setup, bool forceUseMessageHandling)
|
||||
{
|
||||
Config conf {};
|
||||
if (impl->configCallback (conf, setup))
|
||||
{
|
||||
if (impl->isOpen ())
|
||||
{
|
||||
if (impl->config == conf)
|
||||
return;
|
||||
impl->closeQueue ();
|
||||
}
|
||||
impl->config = conf;
|
||||
impl->openQueue (forceUseMessageHandling);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DataExchangeHandler::onDeactivate ()
|
||||
{
|
||||
if (impl->isOpen ())
|
||||
impl->closeQueue ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DataExchangeHandler::enable (bool state)
|
||||
{
|
||||
impl->enabled = state;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool DataExchangeHandler::isEnabled () const
|
||||
{
|
||||
return impl->enabled;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DataExchangeBlock DataExchangeHandler::getCurrentOrNewBlock ()
|
||||
{
|
||||
if (!isEnabled ())
|
||||
return InvalidDataExchangeBlock;
|
||||
return impl->lockBlock ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool DataExchangeHandler::sendCurrentBlock ()
|
||||
{
|
||||
return impl->freeBlock (true);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool DataExchangeHandler::discardCurrentBlock ()
|
||||
{
|
||||
return impl->freeBlock (false);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------
|
||||
struct DataExchangeReceiverHandler::Impl
|
||||
{
|
||||
IDataExchangeReceiver* receiver {nullptr};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DataExchangeReceiverHandler::DataExchangeReceiverHandler (IDataExchangeReceiver* receiver)
|
||||
{
|
||||
impl = std::make_unique<Impl> ();
|
||||
impl->receiver = receiver;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DataExchangeReceiverHandler::~DataExchangeReceiverHandler () noexcept = default;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool DataExchangeReceiverHandler::onMessage (IMessage* msg)
|
||||
{
|
||||
std::string_view msgID = msg->getMessageID ();
|
||||
if (msgID == MessageIDDataExchange)
|
||||
{
|
||||
if (auto attributes = msg->getAttributes ())
|
||||
{
|
||||
const void* data;
|
||||
uint32 sizeInBytes;
|
||||
if (attributes->getBinary (MessageKeyData, data, sizeInBytes) != kResultTrue)
|
||||
return false;
|
||||
int64 userContext;
|
||||
if (attributes->getInt (MessageKeyUserContextID, userContext) != kResultTrue)
|
||||
return false;
|
||||
DataExchangeBlock block;
|
||||
block.size = sizeInBytes;
|
||||
block.data = const_cast<void*> (data);
|
||||
block.blockID = 0;
|
||||
impl->receiver->onDataExchangeBlocksReceived (static_cast<uint32> (userContext), 1,
|
||||
&block, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (msgID == MessageIDQueueOpened)
|
||||
{
|
||||
if (auto attributes = msg->getAttributes ())
|
||||
{
|
||||
int64 userContext;
|
||||
if (attributes->getInt (MessageKeyUserContextID, userContext) != kResultTrue)
|
||||
return false;
|
||||
int64 blockSize;
|
||||
if (attributes->getInt (MessageKeyBlockSize, blockSize) != kResultTrue)
|
||||
return false;
|
||||
TBool backgroundThread = false;
|
||||
impl->receiver->queueOpened (static_cast<uint32> (userContext),
|
||||
static_cast<uint32> (blockSize), backgroundThread);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (msgID == MessageIDQueueClosed)
|
||||
{
|
||||
if (auto attributes = msg->getAttributes ())
|
||||
{
|
||||
int64 userContext;
|
||||
if (attributes->getInt (MessageKeyUserContextID, userContext) != kResultTrue)
|
||||
return false;
|
||||
impl->receiver->queueClosed (static_cast<uint32> (userContext));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
Reference in New Issue
Block a user