//----------------------------------------------------------------------------- // 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 #ifdef _MSC_VER #include #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; IPtr timer; IPtr 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 (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 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 (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->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 (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->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 (data); block.blockID = 0; impl->receiver->onDataExchangeBlocksReceived (static_cast (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 (userContext), static_cast (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 (userContext)); return true; } } return false; } //------------------------------------------------------------------------ } // Vst } // Steinberg