468 lines
13 KiB
C++
468 lines
13 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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
|