Initial release
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/alignedalloc.h
|
||||
// Created by : Steinberg, 05/2023
|
||||
// Description : aligned memory allocations
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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/base/ftypes.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#if __APPLE__
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** aligned allocation
|
||||
*
|
||||
* note that you need to use aligned_free to free the block of memory
|
||||
*
|
||||
* @param numBytes number of bytes to allocate
|
||||
* @param alignment alignment of memory base address.
|
||||
* must be a power of 2 and at least as large as sizeof (void*) or zero in which it uses malloc
|
||||
* for allocation
|
||||
*
|
||||
* @return allocated memory
|
||||
*/
|
||||
inline void* aligned_alloc (size_t numBytes, uint32_t alignment)
|
||||
{
|
||||
if (alignment == 0)
|
||||
return malloc (numBytes);
|
||||
void* data {nullptr};
|
||||
#if SMTG_OS_MACOS && defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \
|
||||
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_15
|
||||
posix_memalign (&data, alignment, numBytes);
|
||||
#elif defined(_MSC_VER)
|
||||
data = _aligned_malloc (numBytes, alignment);
|
||||
#else
|
||||
data = std::aligned_alloc (alignment, numBytes);
|
||||
#endif
|
||||
return data;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline void aligned_free (void* addr, uint32_t alignment)
|
||||
{
|
||||
if (alignment == 0)
|
||||
std::free (addr);
|
||||
else
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
_aligned_free (addr);
|
||||
#else
|
||||
std::free (addr);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,47 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/audiobuffers.h
|
||||
// Created by : Steinberg, 04/2021
|
||||
// Description : Audio Buffer 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/ivstaudioprocessor.h"
|
||||
#include <type_traits>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** get channel buffers from audio bus buffers 32 bit variant */
|
||||
template <SymbolicSampleSizes SampleSize,
|
||||
typename std::enable_if<SampleSize == SymbolicSampleSizes::kSample32>::type* = nullptr>
|
||||
inline Sample32** getChannelBuffers (AudioBusBuffers& buffer)
|
||||
{
|
||||
return buffer.channelBuffers32;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** get channel buffers from audio bus buffers 64 bit variant */
|
||||
template <SymbolicSampleSizes SampleSize,
|
||||
typename std::enable_if<SampleSize == SymbolicSampleSizes::kSample64>::type* = nullptr>
|
||||
inline Sample64** getChannelBuffers (AudioBusBuffers& buffer)
|
||||
{
|
||||
return buffer.channelBuffers64;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -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
|
||||
@@ -0,0 +1,166 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/dataexchange.h
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "public.sdk/source/vst/vstaudioeffect.h"
|
||||
#include "pluginterfaces/vst/ivstdataexchange.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Helper class to provide a single API for plug-ins to transfer data from the realtime audio
|
||||
* process to the edit controller either via the backwards compatible message handling protocol
|
||||
* (see IMessage) or the new IDataExchangeHandler/IDataExchangeReceiver API.
|
||||
*
|
||||
* To use this, make an instance of DataExchangeHandler a member of your IAudioProcessor class and
|
||||
* call onConnect, onDisconnect, onActivate and onDeactivate when the processor is (dis-)connected
|
||||
* and (de)activated. In your IAudioProcessor::process method you call getCurrentOrNewBlock () to
|
||||
* get a block fill it with the data you want to send and then call sendCurrentBlock.
|
||||
* See DataExchangeReceiverHandler on how to receive that data.
|
||||
*/
|
||||
class DataExchangeHandler
|
||||
{
|
||||
public:
|
||||
struct Config
|
||||
{
|
||||
/** the size of one block in bytes */
|
||||
uint32 blockSize;
|
||||
/** the number of blocks to request */
|
||||
uint32 numBlocks;
|
||||
/** the alignment of the buffer */
|
||||
uint32 alignment {32};
|
||||
/** a user defined context ID */
|
||||
DataExchangeUserContextID userContextID {0};
|
||||
};
|
||||
/** the callback will be called on setup processing to get the required configuration for the
|
||||
* data exchange */
|
||||
using ConfigCallback = std::function<bool (Config& config, const ProcessSetup& setup)>;
|
||||
|
||||
DataExchangeHandler (IAudioProcessor* processor, ConfigCallback&& callback);
|
||||
DataExchangeHandler (IAudioProcessor* processor, const ConfigCallback& callback);
|
||||
~DataExchangeHandler () noexcept;
|
||||
|
||||
/** call this in AudioEffect::connect
|
||||
*
|
||||
* provide the hostContext you get via AudioEffect::initiailze to this method
|
||||
*/
|
||||
void onConnect (IConnectionPoint* other, FUnknown* hostContext);
|
||||
|
||||
/** call this in AudioEffect::disconnect
|
||||
*/
|
||||
void onDisconnect (IConnectionPoint* other);
|
||||
|
||||
/** call this in AudioEffect::setActive(true)
|
||||
*/
|
||||
void onActivate (const Vst::ProcessSetup& setup, bool forceUseMessageHandling = false);
|
||||
|
||||
/** call this in AudioEffect::setActive(false)
|
||||
*/
|
||||
void onDeactivate ();
|
||||
|
||||
//--- ---------------------------------------------------------------------
|
||||
/** Get the current or a new block
|
||||
*
|
||||
* On the first call this will always return a new block, only after sendCurrentBlock or
|
||||
* discardCurrentBlock is called a new block will be acquired.
|
||||
* This may return an invalid DataExchangeBlock (check the blockID for
|
||||
* InvalidDataExchangeBlockID) when the queue is full.
|
||||
*
|
||||
* [call only in process call]
|
||||
*/
|
||||
DataExchangeBlock getCurrentOrNewBlock ();
|
||||
|
||||
/** Send the current block to the receiver
|
||||
*
|
||||
* [call only in process call]
|
||||
*/
|
||||
bool sendCurrentBlock ();
|
||||
|
||||
/** Discard the current block
|
||||
*
|
||||
* [call only in process call]
|
||||
*/
|
||||
bool discardCurrentBlock ();
|
||||
|
||||
/** Enable or disable the acquiring of new blocks (per default it is enabled)
|
||||
*
|
||||
* If you disable this then the getCurrentOrNewBlock will always return an invalid block.
|
||||
*
|
||||
* [call only in process call]
|
||||
*/
|
||||
void enable (bool state);
|
||||
|
||||
/** Ask if enabled
|
||||
*
|
||||
* [call only in process call]
|
||||
*/
|
||||
bool isEnabled () const;
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
private:
|
||||
DataExchangeHandler (IAudioProcessor* processor);
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Helper class to provide a single API for plug-ins to transfer data from the realtime audio
|
||||
* process to the edit controller either via the message handling protocol (see IMessage) or the
|
||||
* new IDataExchangeHandler/IDataExchangeReceiver API.
|
||||
*
|
||||
* This is the other side of the DataExchangeHandler on the edit controller side. Make this a
|
||||
* member of your edit controller and call onMessage for every IMessage you get via
|
||||
* IConnectionPoint::notify. Your edit controller must implement the IDataExchangeReceiver
|
||||
* interface.
|
||||
*/
|
||||
class DataExchangeReceiverHandler
|
||||
{
|
||||
public:
|
||||
DataExchangeReceiverHandler (IDataExchangeReceiver* receiver);
|
||||
~DataExchangeReceiverHandler () noexcept;
|
||||
|
||||
/** call this for every message you receive via IConnectionPoint::notify
|
||||
*
|
||||
* @return true if the message was handled
|
||||
*/
|
||||
bool onMessage (IMessage* msg);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline bool operator!= (const DataExchangeBlock& lhs, const DataExchangeBlock& rhs)
|
||||
{
|
||||
return lhs.data != rhs.data || lhs.size != rhs.size || lhs.blockID != rhs.blockID;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline bool operator== (const DataExchangeBlock& lhs, const DataExchangeBlock& rhs)
|
||||
{
|
||||
return lhs.data == rhs.data && lhs.size == rhs.size && lhs.blockID == rhs.blockID;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,153 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/memoryibstream.h
|
||||
// Created by : Steinberg, 12/2023
|
||||
// Description :
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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/base/funknownimpl.h"
|
||||
#include "pluginterfaces/base/ibstream.h"
|
||||
#include <vector>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class ResizableMemoryIBStream : public U::Implements<U::Directly<IBStream>>
|
||||
{
|
||||
public:
|
||||
inline ResizableMemoryIBStream (size_t reserve = 0);
|
||||
|
||||
inline tresult PLUGIN_API read (void* buffer, int32 numBytes, int32* numBytesRead) override;
|
||||
inline tresult PLUGIN_API write (void* buffer, int32 numBytes, int32* numBytesWritten) override;
|
||||
inline tresult PLUGIN_API seek (int64 pos, int32 mode, int64* result) override;
|
||||
inline tresult PLUGIN_API tell (int64* pos) override;
|
||||
|
||||
inline size_t getCursor () const;
|
||||
inline const void* getData () const;
|
||||
inline void rewind ();
|
||||
inline std::vector<uint8>&& take ();
|
||||
|
||||
private:
|
||||
std::vector<uint8> data;
|
||||
size_t cursor {0};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline ResizableMemoryIBStream::ResizableMemoryIBStream (size_t reserve)
|
||||
{
|
||||
if (reserve)
|
||||
data.reserve (reserve);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline tresult PLUGIN_API ResizableMemoryIBStream::read (void* buffer, int32 numBytes,
|
||||
int32* numBytesRead)
|
||||
{
|
||||
if (numBytes < 0 || buffer == nullptr)
|
||||
return kInvalidArgument;
|
||||
auto byteCount = std::min<int64> (numBytes, data.size () - cursor);
|
||||
if (byteCount > 0)
|
||||
{
|
||||
memcpy (buffer, data.data () + cursor, byteCount);
|
||||
cursor += byteCount;
|
||||
}
|
||||
if (numBytesRead)
|
||||
*numBytesRead = static_cast<int32> (byteCount);
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline tresult PLUGIN_API ResizableMemoryIBStream::write (void* buffer, int32 numBytes,
|
||||
int32* numBytesWritten)
|
||||
{
|
||||
if (numBytes < 0 || buffer == nullptr)
|
||||
return kInvalidArgument;
|
||||
auto requiredSize = cursor + numBytes;
|
||||
if (requiredSize >= data.capacity ())
|
||||
{
|
||||
auto mod = (requiredSize % 1024);
|
||||
if (mod)
|
||||
{
|
||||
auto reserve = requiredSize + (1024 - mod);
|
||||
data.reserve (reserve);
|
||||
}
|
||||
}
|
||||
if (data.size () < requiredSize)
|
||||
data.resize (requiredSize);
|
||||
memcpy (data.data () + cursor, buffer, numBytes);
|
||||
cursor += numBytes;
|
||||
if (numBytesWritten)
|
||||
*numBytesWritten = numBytes;
|
||||
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline tresult PLUGIN_API ResizableMemoryIBStream::seek (int64 pos, int32 mode, int64* result)
|
||||
{
|
||||
int64 newCursor = static_cast<int64> (cursor);
|
||||
switch (mode)
|
||||
{
|
||||
case kIBSeekSet: newCursor = pos; break;
|
||||
case kIBSeekCur: newCursor += pos; break;
|
||||
case kIBSeekEnd: newCursor = data.size () + pos; break;
|
||||
default: return kInvalidArgument;
|
||||
}
|
||||
if (newCursor < 0)
|
||||
return kInvalidArgument;
|
||||
if (newCursor > static_cast<int64> (data.size ()))
|
||||
return kInvalidArgument;
|
||||
if (result)
|
||||
*result = newCursor;
|
||||
cursor = static_cast<size_t> (newCursor);
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline tresult PLUGIN_API ResizableMemoryIBStream::tell (int64* pos)
|
||||
{
|
||||
if (pos == nullptr)
|
||||
return kInvalidArgument;
|
||||
*pos = static_cast<int64> (cursor);
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline size_t ResizableMemoryIBStream::getCursor () const
|
||||
{
|
||||
return cursor;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline const void* ResizableMemoryIBStream::getData () const
|
||||
{
|
||||
return data.data ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline std::vector<uint8>&& ResizableMemoryIBStream::take ()
|
||||
{
|
||||
cursor = 0;
|
||||
return std::move (data);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline void ResizableMemoryIBStream::rewind ()
|
||||
{
|
||||
cursor = 0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,418 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/mpeprocessor.cpp
|
||||
// Created by : Steinberg, 07/2017
|
||||
// Description : VST 3 MIDI-MPE decomposer
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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 "mpeprocessor.h"
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace MPE {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct Note
|
||||
{
|
||||
NoteID noteID;
|
||||
Pitch pitch;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct ChannelData
|
||||
{
|
||||
using NoteList = std::vector<Note>;
|
||||
|
||||
NoteList notes;
|
||||
NormalizedValue pressure {0.};
|
||||
NormalizedValue x {0.5};
|
||||
NormalizedValue y {0.};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct Processor::Impl
|
||||
{
|
||||
static constexpr auto NumMIDIChannels = 16u;
|
||||
using ChannelDataList = std::array<ChannelData, NumMIDIChannels>;
|
||||
|
||||
Handler* delegate {nullptr};
|
||||
Setup setup;
|
||||
ChannelDataList channelData {};
|
||||
size_t dataBufferUsed {0};
|
||||
size_t maxNotesPerChannel {16};
|
||||
bool inSysex {false};
|
||||
|
||||
Impl (Handler* delegate, size_t maxNotesPerChannel)
|
||||
: delegate (delegate), maxNotesPerChannel (maxNotesPerChannel)
|
||||
{
|
||||
for (auto& cd : channelData)
|
||||
cd.notes.reserve (maxNotesPerChannel);
|
||||
}
|
||||
|
||||
bool inMPEZone (uint8_t channel) const
|
||||
{
|
||||
return channel >= setup.memberChannelBegin && channel <= setup.memberChannelEnd;
|
||||
}
|
||||
|
||||
Controller getController (InputMIDIMessage input) const
|
||||
{
|
||||
if (setup.pressure == input)
|
||||
return Controller::Pressure;
|
||||
if (setup.x == input)
|
||||
return Controller::X;
|
||||
if (setup.y == input)
|
||||
return Controller::Y;
|
||||
return Controller::None;
|
||||
}
|
||||
|
||||
NormalizedValue getControllerValue (Controller controller, const ChannelData& data) const
|
||||
{
|
||||
switch (controller)
|
||||
{
|
||||
case Controller::Pressure: return data.pressure;
|
||||
case Controller::X: return data.x;
|
||||
case Controller::Y: return data.y;
|
||||
case Controller::None: assert (false); break;
|
||||
}
|
||||
return 0.;
|
||||
}
|
||||
|
||||
void setControllerValue (Controller controller, ChannelData& data, NormalizedValue value)
|
||||
{
|
||||
switch (controller)
|
||||
{
|
||||
case Controller::Pressure: data.pressure = value; break;
|
||||
case Controller::X: data.x = value; break;
|
||||
case Controller::Y: data.y = value; break;
|
||||
case Controller::None: assert (false); break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
Processor::Processor (Handler* delegate, size_t maxNotesPerChannel)
|
||||
{
|
||||
impl = std::make_unique<Impl> (delegate, maxNotesPerChannel);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
Processor::~Processor () noexcept = default;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
const Setup& Processor::getSetup () const
|
||||
{
|
||||
return impl->setup;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void Processor::changeSetup (const Setup& setup)
|
||||
{
|
||||
impl->setup = setup;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void Processor::reset ()
|
||||
{
|
||||
for (auto& cd : impl->channelData)
|
||||
{
|
||||
for (auto& note : cd.notes)
|
||||
{
|
||||
impl->delegate->onMPENoteOff (note.noteID, note.pitch, 0.f);
|
||||
impl->delegate->releaseNoteID (note.noteID);
|
||||
}
|
||||
cd.notes.clear ();
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
int32_t Processor::onNoteOn (const uint8_t* data, size_t dataSize)
|
||||
{
|
||||
assert (dataSize >= 2);
|
||||
if (data[2] == 0)
|
||||
return onNoteOff (data, dataSize);
|
||||
auto channel = data[0] & 0x0F;
|
||||
if (impl->inMPEZone (channel))
|
||||
{
|
||||
auto& channelData = impl->channelData[channel];
|
||||
auto pitch = data[1];
|
||||
if (channelData.notes.size () >= impl->maxNotesPerChannel)
|
||||
{
|
||||
// error note stack full
|
||||
impl->delegate->errorNoteDroppedBecauseNoteStackFull (channel, pitch);
|
||||
}
|
||||
else
|
||||
{
|
||||
Note note;
|
||||
if (impl->delegate->generateNewNoteID (note.noteID))
|
||||
{
|
||||
note.pitch = pitch;
|
||||
channelData.notes.push_back (note);
|
||||
auto velocity = static_cast<Velocity> (data[2]) / 127.f;
|
||||
impl->delegate->onMPENoteOn (note.noteID, note.pitch, velocity);
|
||||
impl->delegate->onMPEControllerChange (note.noteID, Controller::Pressure,
|
||||
channelData.pressure);
|
||||
impl->delegate->onMPEControllerChange (note.noteID, Controller::X, channelData.x);
|
||||
impl->delegate->onMPEControllerChange (note.noteID, Controller::Y, channelData.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
// error: note will be dropped
|
||||
impl->delegate->errorNoteDroppedBecauseNoNoteID (pitch);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
impl->delegate->onOtherInput (data, 3);
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
int32_t Processor::onNoteOff (const uint8_t* data, size_t dataSize)
|
||||
{
|
||||
assert (dataSize >= 2);
|
||||
auto channel = data[0] & 0x0F;
|
||||
if (impl->inMPEZone (channel))
|
||||
{
|
||||
bool noteFound = false;
|
||||
auto& notes = impl->channelData[channel].notes;
|
||||
for (auto it = notes.begin (); it != notes.end (); ++it)
|
||||
{
|
||||
if (it->pitch == data[1])
|
||||
{
|
||||
auto velocity = static_cast<Velocity> (data[2]) / 127.f;
|
||||
impl->delegate->onMPENoteOff (it->noteID, it->pitch, velocity);
|
||||
impl->delegate->releaseNoteID (it->noteID);
|
||||
notes.erase (it);
|
||||
noteFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!noteFound)
|
||||
{
|
||||
// error: no note for note off found
|
||||
impl->delegate->errorNoteForNoteOffNotFound (channel, data[1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
impl->delegate->onOtherInput (data, 3);
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
int32_t Processor::onAftertouch (const uint8_t* data, size_t dataSize)
|
||||
{
|
||||
assert (dataSize >= 2);
|
||||
auto controller = impl->getController (ChannelPressure);
|
||||
if (controller != Controller::None)
|
||||
{
|
||||
auto channel = data[0] & 0x0F;
|
||||
if (impl->inMPEZone (channel))
|
||||
{
|
||||
auto& channelData = impl->channelData[channel];
|
||||
auto value = static_cast<NormalizedValue> (data[2]) / 127.;
|
||||
impl->setControllerValue (controller, channelData, value);
|
||||
for (auto& note : channelData.notes)
|
||||
{
|
||||
impl->delegate->onMPEControllerChange (note.noteID, controller, value);
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
impl->delegate->onOtherInput (data, 3);
|
||||
return 3;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
int32_t Processor::onController (const uint8_t* data, size_t dataSize)
|
||||
{
|
||||
assert (dataSize >= 2);
|
||||
auto cc = data[1];
|
||||
auto controller = impl->getController (static_cast<InputMIDIMessage> (cc));
|
||||
if (controller != Controller::None)
|
||||
{
|
||||
auto channel = data[0] & 0x0F;
|
||||
if (impl->inMPEZone (channel))
|
||||
{
|
||||
auto& channelData = impl->channelData[channel];
|
||||
auto value = static_cast<NormalizedValue> (data[2]) / 127.;
|
||||
impl->setControllerValue (controller, channelData, value);
|
||||
for (auto& note : channelData.notes)
|
||||
{
|
||||
impl->delegate->onMPEControllerChange (note.noteID, controller, value);
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
impl->delegate->onOtherInput (data, 3);
|
||||
return 3;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
int32_t Processor::onProgramChange (const uint8_t* data, size_t dataSize)
|
||||
{
|
||||
assert (dataSize >= 1);
|
||||
auto channel = data[0] & 0x0F;
|
||||
if (impl->inMPEZone (channel))
|
||||
{
|
||||
// error: program change send in MPE zone
|
||||
impl->delegate->errorProgramChangeReceivedInMPEZone ();
|
||||
}
|
||||
else
|
||||
{
|
||||
impl->delegate->onOtherInput (data, 2);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
int32_t Processor::onChannelPressure (const uint8_t* data, size_t dataSize)
|
||||
{
|
||||
assert (dataSize >= 1);
|
||||
auto controller = impl->getController (ChannelPressure);
|
||||
if (controller != Controller::None)
|
||||
{
|
||||
auto channel = data[0] & 0x0F;
|
||||
if (impl->inMPEZone (channel))
|
||||
{
|
||||
auto& channelData = impl->channelData[channel];
|
||||
auto value = static_cast<NormalizedValue> (data[1]) / 127.;
|
||||
impl->setControllerValue (controller, channelData, value);
|
||||
for (auto& note : channelData.notes)
|
||||
{
|
||||
impl->delegate->onMPEControllerChange (note.noteID, controller, value);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
impl->delegate->onOtherInput (data, 2);
|
||||
return 2;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
int32_t Processor::onPitchWheel (const uint8_t* data, size_t dataSize)
|
||||
{
|
||||
assert (dataSize >= 2);
|
||||
auto controller = impl->getController (PitchBend);
|
||||
if (controller != Controller::None)
|
||||
{
|
||||
auto channel = data[0] & 0x0F;
|
||||
if (impl->inMPEZone (channel))
|
||||
{
|
||||
auto& channelData = impl->channelData[channel];
|
||||
auto value =
|
||||
static_cast<NormalizedValue> ((data[1] & 0x7F) + ((data[2] & 0x7F) << 7)) / 16383.;
|
||||
impl->setControllerValue (controller, channelData, value);
|
||||
for (auto& note : channelData.notes)
|
||||
{
|
||||
impl->delegate->onMPEControllerChange (note.noteID, controller, value);
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
impl->delegate->onOtherInput (data, 3);
|
||||
return 3;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void Processor::processMIDIInput (const uint8_t* data, size_t dataSize)
|
||||
{
|
||||
assert (dataSize > 0);
|
||||
if (impl->inSysex || data[0] == 0xf0)
|
||||
{
|
||||
for (size_t index = 0; index < dataSize; ++index)
|
||||
{
|
||||
if (data[index] == 0xf7)
|
||||
{
|
||||
++index;
|
||||
impl->delegate->onSysexInput (data, index);
|
||||
impl->inSysex = false;
|
||||
auto dataLeft = dataSize - index;
|
||||
if (dataLeft != 0)
|
||||
{
|
||||
processMIDIInput (data + index, dataSize - index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
impl->inSysex = true;
|
||||
impl->delegate->onSysexInput (data, dataSize);
|
||||
return;
|
||||
}
|
||||
|
||||
auto status = static_cast<uint8_t> (data[0] & 0xF0);
|
||||
int32_t packetSize = 0;
|
||||
switch (status)
|
||||
{
|
||||
case 0x90: // Note On
|
||||
{
|
||||
packetSize = onNoteOn (data, dataSize);
|
||||
break;
|
||||
}
|
||||
case 0x80: // Note Off
|
||||
{
|
||||
packetSize = onNoteOff (data, dataSize);
|
||||
break;
|
||||
}
|
||||
case 0xa0: // Aftertouch
|
||||
{
|
||||
packetSize = onAftertouch (data, dataSize);
|
||||
break;
|
||||
}
|
||||
case 0xb0: // Controller
|
||||
{
|
||||
packetSize = onController (data, dataSize);
|
||||
break;
|
||||
}
|
||||
case 0xc0: // Program Change
|
||||
{
|
||||
packetSize = onProgramChange (data, dataSize);
|
||||
break;
|
||||
}
|
||||
case 0xd0: // Channel Pressure
|
||||
{
|
||||
packetSize = onChannelPressure (data, dataSize);
|
||||
break;
|
||||
}
|
||||
case 0xe0: // Pitch Wheel
|
||||
{
|
||||
packetSize = onPitchWheel (data, dataSize);
|
||||
break;
|
||||
}
|
||||
case 0xf0: // System Realtime Messages
|
||||
{
|
||||
impl->delegate->onOtherInput (data, 1);
|
||||
packetSize = 1;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// Ehm...
|
||||
assert (false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (dataSize > static_cast<size_t> (packetSize))
|
||||
processMIDIInput (data + packetSize, dataSize - packetSize);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // MPE
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,191 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/mpeprocessor.h
|
||||
// Created by : Steinberg, 07/2017
|
||||
// Description : VST 3 MIDI-MPE decomposer
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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 <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace MPE {
|
||||
|
||||
using NoteID = int32_t;
|
||||
using Pitch = uint32_t;
|
||||
using Channel = uint32_t;
|
||||
using Velocity = float;
|
||||
using NormalizedValue = double;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** MPE per note controller enumeration */
|
||||
enum class Controller : uint32_t
|
||||
{
|
||||
/** Pressure MPE controller */
|
||||
Pressure,
|
||||
/** X / horizontal MPE controller */
|
||||
X,
|
||||
/** Y / vertical MPE controller */
|
||||
Y,
|
||||
/** no MPE controller */
|
||||
None
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct Handler
|
||||
{
|
||||
/** Generate a new noteID
|
||||
*
|
||||
* called by the processor for a new NoteID. The handler has to make sure that the noteID is
|
||||
* not used again until the releaseNoteID method is called.
|
||||
*
|
||||
* @param outNoteID on return contains the new noteID if this call succeed
|
||||
* @return true if outNoteID was filled with a new noteID
|
||||
*/
|
||||
virtual bool generateNewNoteID (NoteID& outNoteID) = 0;
|
||||
|
||||
/** Release a noteID
|
||||
*
|
||||
* called by the processor when the NoteID is no longer used.
|
||||
*
|
||||
* @param noteID the noteID not longer in use
|
||||
*/
|
||||
virtual void releaseNoteID (NoteID noteID) = 0;
|
||||
|
||||
/** A note on was transmitted
|
||||
*
|
||||
* @param noteID unique note identifier
|
||||
* @param pitch note pitch
|
||||
* @param velocity note on velocity
|
||||
*/
|
||||
virtual void onMPENoteOn (NoteID noteID, Pitch pitch, Velocity velocity) = 0;
|
||||
|
||||
/** A note off was transmitted
|
||||
*
|
||||
* @param noteID unique note identifier
|
||||
* @param pitch note pitch
|
||||
* @param velocity note off velocity
|
||||
*/
|
||||
virtual void onMPENoteOff (NoteID noteID, Pitch pitch, Velocity velocity) = 0;
|
||||
|
||||
/** A new per note controller change was transmitted
|
||||
*
|
||||
* @param noteID unique note identifier
|
||||
* @param cc the MIDI controller which changed
|
||||
* @param value the value of the change in the range [0..1]
|
||||
*/
|
||||
virtual void onMPEControllerChange (NoteID noteID, Controller cc, NormalizedValue value) = 0;
|
||||
|
||||
/** Non MPE MIDI input data was transmitted
|
||||
*
|
||||
* @param data MIDI data buffer
|
||||
* @param dataSize size of the MIDI data buffer in bytes
|
||||
*/
|
||||
virtual void onOtherInput (const uint8_t* data, size_t dataSize) = 0;
|
||||
|
||||
/** Sysex MIDI data was transmitted
|
||||
*
|
||||
* @param data Sysex data buffer
|
||||
* @param dataSize size of sysex data buffer in bytes
|
||||
*/
|
||||
virtual void onSysexInput (const uint8_t* data, size_t dataSize) = 0;
|
||||
|
||||
// error handling
|
||||
/** called when the handler did not return a new note ID */
|
||||
virtual void errorNoteDroppedBecauseNoNoteID (Pitch pitch) = 0;
|
||||
/** the internal note stack for this channel is full, happens on too many note ons per channel */
|
||||
virtual void errorNoteDroppedBecauseNoteStackFull (Channel channel, Pitch pitch) = 0;
|
||||
/** called when the internal data has no reference to this note off */
|
||||
virtual void errorNoteForNoteOffNotFound (Channel channel, Pitch pitch) = 0;
|
||||
/** called when a program change was received inside the MPE zone which is a protocol violation */
|
||||
virtual void errorProgramChangeReceivedInMPEZone () = 0;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Input MIDI Message enumeration */
|
||||
enum InputMIDIMessage : uint32_t
|
||||
{
|
||||
MIDICC_0 = 0,
|
||||
MIDICC_127 = 127,
|
||||
ChannelPressure = 128,
|
||||
PitchBend = 129,
|
||||
Aftertouch = 130,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** MPE setup structure */
|
||||
struct Setup
|
||||
{
|
||||
Channel masterChannel {0};
|
||||
Channel memberChannelBegin {1};
|
||||
Channel memberChannelEnd {14};
|
||||
InputMIDIMessage pressure {ChannelPressure};
|
||||
InputMIDIMessage x {PitchBend};
|
||||
InputMIDIMessage y {static_cast<InputMIDIMessage> (74)};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** MPE Decompose Processor
|
||||
*
|
||||
* decomposes MPE MIDI messages
|
||||
*
|
||||
*/
|
||||
class Processor
|
||||
{
|
||||
public:
|
||||
Processor (Handler* delegate, size_t maxNotesPerChannel = 16);
|
||||
~Processor () noexcept;
|
||||
|
||||
const Setup& getSetup () const;
|
||||
/** change the MPE setup
|
||||
*
|
||||
* make sure that MIDI processing is stopped while this is called.
|
||||
*
|
||||
* @param setup new setup
|
||||
*/
|
||||
void changeSetup (const Setup& setup);
|
||||
/** reset all notes
|
||||
*
|
||||
* All playing notes will be stopped and note identifiers are released.
|
||||
*
|
||||
*/
|
||||
void reset ();
|
||||
|
||||
/** feed new native MIDI data
|
||||
*
|
||||
* @param data MIDI data buffer
|
||||
* @param dataSize data buffer size in bytes
|
||||
*/
|
||||
void processMIDIInput (const uint8_t* data, size_t dataSize);
|
||||
|
||||
private:
|
||||
int32_t onNoteOn (const uint8_t* data, size_t dataSize);
|
||||
int32_t onNoteOff (const uint8_t* data, size_t dataSize);
|
||||
int32_t onAftertouch (const uint8_t* data, size_t dataSize);
|
||||
int32_t onController (const uint8_t* data, size_t dataSize);
|
||||
int32_t onProgramChange (const uint8_t* data, size_t dataSize);
|
||||
int32_t onChannelPressure (const uint8_t* data, size_t dataSize);
|
||||
int32_t onPitchWheel (const uint8_t* data, size_t dataSize);
|
||||
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // MPE
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,263 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/objcclassbuilder.h
|
||||
// Created by : Steinberg, 06/2022
|
||||
// Description : Objective-C class builder 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 <objc/runtime.h>
|
||||
#include <objc/message.h>
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
template<typename T>
|
||||
struct ObjCVariable
|
||||
{
|
||||
ObjCVariable (__unsafe_unretained id obj, Ivar ivar) : obj (obj), ivar (ivar) {}
|
||||
ObjCVariable (ObjCVariable&& o) { *this = std::move (o); }
|
||||
|
||||
ObjCVariable& operator= (ObjCVariable&& o)
|
||||
{
|
||||
obj = o.obj;
|
||||
ivar = o.ivar;
|
||||
o.obj = nullptr;
|
||||
o.ivar = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
T get () const
|
||||
{
|
||||
auto offset = ivar_getOffset (ivar);
|
||||
return *reinterpret_cast<T*> (((__bridge uintptr_t)obj) + offset);
|
||||
}
|
||||
|
||||
void set (const T& value)
|
||||
{
|
||||
auto offset = ivar_getOffset (ivar);
|
||||
auto storage = reinterpret_cast<T*> (((__bridge uintptr_t)obj) + offset);
|
||||
*storage = value;
|
||||
}
|
||||
|
||||
private:
|
||||
__unsafe_unretained id obj;
|
||||
Ivar ivar {nullptr};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
struct ObjCInstance
|
||||
{
|
||||
ObjCInstance (__unsafe_unretained id obj, Class superClass = nullptr) : obj (obj)
|
||||
{
|
||||
os.super_class = superClass;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::optional<ObjCVariable<T>> getVariable (const char* name) const
|
||||
{
|
||||
if (__strong auto ivar = class_getInstanceVariable (object_getClass (obj), name))
|
||||
{
|
||||
return {ObjCVariable<T> (obj, ivar)};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename Func, typename... T>
|
||||
void callSuper (SEL selector, T... args) const
|
||||
{
|
||||
void (*f) (__unsafe_unretained id, SEL, T...) =
|
||||
(void (*) (__unsafe_unretained id, SEL, T...))objc_msgSendSuper;
|
||||
f (getSuper (), selector, args...);
|
||||
}
|
||||
|
||||
template<typename Func, typename R, typename... T>
|
||||
R callSuper (SEL selector, T... args) const
|
||||
{
|
||||
R (*f)
|
||||
(__unsafe_unretained id, SEL, T...) =
|
||||
(R (*) (__unsafe_unretained id, SEL, T...))objc_msgSendSuper;
|
||||
return f (getSuper (), selector, args...);
|
||||
}
|
||||
|
||||
private:
|
||||
id getSuper () const
|
||||
{
|
||||
if (os.receiver == nullptr)
|
||||
{
|
||||
os.receiver = obj;
|
||||
}
|
||||
if (os.super_class == nullptr)
|
||||
{
|
||||
os.super_class = class_getSuperclass (object_getClass (obj));
|
||||
}
|
||||
return (__bridge id) (&os);
|
||||
}
|
||||
|
||||
__unsafe_unretained id obj;
|
||||
mutable objc_super os {};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
struct ObjCClassBuilder
|
||||
{
|
||||
ObjCClassBuilder& init (const char* name, Class baseClass);
|
||||
|
||||
template<typename Func>
|
||||
ObjCClassBuilder& addMethod (SEL selector, Func imp);
|
||||
template<typename T>
|
||||
ObjCClassBuilder& addIvar (const char* name);
|
||||
|
||||
ObjCClassBuilder& addProtocol (const char* name);
|
||||
ObjCClassBuilder& addProtocol (Protocol* proto);
|
||||
|
||||
Class finalize ();
|
||||
|
||||
private:
|
||||
static Class generateUniqueClass (const std::string& inClassName, Class baseClass);
|
||||
|
||||
template<typename Func>
|
||||
ObjCClassBuilder& addMethod (SEL selector, Func imp, const char* types);
|
||||
|
||||
ObjCClassBuilder& addIvar (const char* name, size_t size, uint8_t alignment, const char* types);
|
||||
|
||||
template<typename R, typename... T>
|
||||
static constexpr std::tuple<R, T...> functionArgs (R (*) (T...))
|
||||
{
|
||||
return std::tuple<R, T...> ();
|
||||
}
|
||||
|
||||
template<typename... T>
|
||||
static constexpr std::tuple<T...> functionArgs (void (*) (T...))
|
||||
{
|
||||
return std::tuple<T...> ();
|
||||
}
|
||||
|
||||
template<typename R, typename... T>
|
||||
static constexpr bool isVoidReturnType (R (*) (T...))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename... T>
|
||||
static constexpr bool isVoidReturnType (void (*) (T...))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Proc>
|
||||
static std::string encodeFunction (Proc proc)
|
||||
{
|
||||
std::string result;
|
||||
if (isVoidReturnType (proc))
|
||||
result = "v";
|
||||
std::apply ([&] (auto&&... args) { ((result += @encode (decltype (args))), ...); },
|
||||
functionArgs (proc));
|
||||
return result;
|
||||
}
|
||||
|
||||
Class cl {nullptr};
|
||||
Class baseClass {nullptr};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline ObjCClassBuilder& ObjCClassBuilder::init (const char* name, Class bc)
|
||||
{
|
||||
baseClass = bc;
|
||||
cl = generateUniqueClass (name, baseClass);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
inline Class ObjCClassBuilder::generateUniqueClass (const std::string& inClassName, Class baseClass)
|
||||
{
|
||||
std::string className (inClassName);
|
||||
int32_t iteration = 0;
|
||||
while (objc_lookUpClass (className.data ()) != nil)
|
||||
{
|
||||
iteration++;
|
||||
className = inClassName + "_" + std::to_string (iteration);
|
||||
}
|
||||
Class resClass = objc_allocateClassPair (baseClass, className.data (), 0);
|
||||
return resClass;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
inline Class ObjCClassBuilder::finalize ()
|
||||
{
|
||||
objc_registerClassPair (cl);
|
||||
|
||||
auto res = cl;
|
||||
baseClass = cl = nullptr;
|
||||
return res;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
template<typename Func>
|
||||
inline ObjCClassBuilder& ObjCClassBuilder::addMethod (SEL selector, Func imp, const char* types)
|
||||
{
|
||||
auto res = class_addMethod (cl, selector, IMP (imp), types);
|
||||
assert (res == true);
|
||||
(void)res;
|
||||
return *this;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
template<typename Func>
|
||||
ObjCClassBuilder& ObjCClassBuilder::addMethod (SEL selector, Func imp)
|
||||
{
|
||||
return addMethod (selector, imp, encodeFunction (imp).data ());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template<typename T>
|
||||
inline ObjCClassBuilder& ObjCClassBuilder::addIvar (const char* name)
|
||||
{
|
||||
return addIvar (name, sizeof (T), static_cast<uint8_t> (std::log2 (sizeof (T))), @encode (T));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
inline ObjCClassBuilder& ObjCClassBuilder::addIvar (const char* name, size_t size,
|
||||
uint8_t alignment, const char* types)
|
||||
{
|
||||
auto res = class_addIvar (cl, name, size, alignment, types);
|
||||
assert (res == true);
|
||||
(void)res;
|
||||
return *this;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
inline ObjCClassBuilder& ObjCClassBuilder::addProtocol (const char* name)
|
||||
{
|
||||
if (auto protocol = objc_getProtocol (name))
|
||||
return addProtocol (protocol);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
inline ObjCClassBuilder& ObjCClassBuilder::addProtocol (Protocol* proto)
|
||||
{
|
||||
auto res = class_addProtocol (cl, proto);
|
||||
assert (res == true);
|
||||
(void)res;
|
||||
return *this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,115 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/optional.h
|
||||
// Created by : Steinberg, 08/2016
|
||||
// Description : optional 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 <cassert>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VST3 {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <typename T>
|
||||
struct Optional
|
||||
{
|
||||
Optional () noexcept : valid (false) {}
|
||||
explicit Optional (const T& v) noexcept : _value (v), valid (true) {}
|
||||
Optional (T&& v) noexcept : _value (std::move (v)), valid (true) {}
|
||||
|
||||
Optional (Optional&& other) noexcept { *this = std::move (other); }
|
||||
Optional& operator= (Optional&& other) noexcept
|
||||
{
|
||||
valid = other.valid;
|
||||
_value = std::move (other._value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool () const noexcept
|
||||
{
|
||||
setValidationChecked ();
|
||||
return valid;
|
||||
}
|
||||
|
||||
const T& operator* () const noexcept
|
||||
{
|
||||
checkValid ();
|
||||
return _value;
|
||||
}
|
||||
|
||||
const T* operator-> () const noexcept
|
||||
{
|
||||
checkValid ();
|
||||
return &_value;
|
||||
}
|
||||
|
||||
T& operator* () noexcept
|
||||
{
|
||||
checkValid ();
|
||||
return _value;
|
||||
}
|
||||
|
||||
T* operator-> () noexcept
|
||||
{
|
||||
checkValid ();
|
||||
return &_value;
|
||||
}
|
||||
|
||||
T&& value () noexcept
|
||||
{
|
||||
checkValid ();
|
||||
return std::move (_value);
|
||||
}
|
||||
|
||||
const T& value () const noexcept
|
||||
{
|
||||
checkValid ();
|
||||
return _value;
|
||||
}
|
||||
|
||||
void swap (T& other) noexcept
|
||||
{
|
||||
checkValid ();
|
||||
auto tmp = std::move (other);
|
||||
other = std::move (_value);
|
||||
_value = std::move (tmp);
|
||||
}
|
||||
|
||||
private:
|
||||
T _value {};
|
||||
bool valid;
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
mutable bool validationChecked {false};
|
||||
#endif
|
||||
|
||||
void setValidationChecked () const
|
||||
{
|
||||
#if !defined(NDEBUG)
|
||||
validationChecked = true;
|
||||
#endif
|
||||
}
|
||||
void checkValid () const
|
||||
{
|
||||
#if !defined(NDEBUG)
|
||||
assert (validationChecked);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/processcontextrequirements.h
|
||||
// Created by : Steinberg, 12/2019
|
||||
// Description : Helper class to work with IProcessContextRequirements
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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/ivstaudioprocessor.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class ProcessContextRequirements
|
||||
{
|
||||
private:
|
||||
using Self = ProcessContextRequirements;
|
||||
|
||||
public:
|
||||
ProcessContextRequirements (uint32 inFlags = 0) : flags (inFlags) {}
|
||||
|
||||
bool wantsNone () const { return flags == 0; }
|
||||
bool wantsSystemTime () const
|
||||
{
|
||||
return (flags & IProcessContextRequirements::kNeedSystemTime) != 0;
|
||||
}
|
||||
bool wantsContinousTimeSamples () const
|
||||
{
|
||||
return (flags & IProcessContextRequirements::kNeedContinousTimeSamples) != 0;
|
||||
}
|
||||
bool wantsProjectTimeMusic () const
|
||||
{
|
||||
return (flags & IProcessContextRequirements::kNeedProjectTimeMusic) != 0;
|
||||
}
|
||||
bool wantsBarPositionMusic () const
|
||||
{
|
||||
return (flags & IProcessContextRequirements::kNeedBarPositionMusic) != 0;
|
||||
}
|
||||
bool wantsCycleMusic () const
|
||||
{
|
||||
return (flags & IProcessContextRequirements::kNeedCycleMusic) != 0;
|
||||
}
|
||||
bool wantsSamplesToNextClock () const
|
||||
{
|
||||
return (flags & IProcessContextRequirements::kNeedSamplesToNextClock) != 0;
|
||||
}
|
||||
bool wantsTempo () const { return (flags & IProcessContextRequirements::kNeedTempo) != 0; }
|
||||
bool wantsTimeSignature () const
|
||||
{
|
||||
return (flags & IProcessContextRequirements::kNeedTimeSignature) != 0;
|
||||
}
|
||||
bool wantsChord () const { return (flags & IProcessContextRequirements::kNeedChord) != 0; }
|
||||
bool wantsFrameRate () const
|
||||
{
|
||||
return (flags & IProcessContextRequirements::kNeedFrameRate) != 0;
|
||||
}
|
||||
bool wantsTransportState () const
|
||||
{
|
||||
return (flags & IProcessContextRequirements::kNeedTransportState) != 0;
|
||||
}
|
||||
|
||||
/** set SystemTime as requested */
|
||||
Self& needSystemTime ()
|
||||
{
|
||||
flags |= IProcessContextRequirements::kNeedSystemTime;
|
||||
return *this;
|
||||
}
|
||||
/** set ContinousTimeSamples as requested */
|
||||
Self& needContinousTimeSamples ()
|
||||
{
|
||||
flags |= IProcessContextRequirements::kNeedContinousTimeSamples;
|
||||
return *this;
|
||||
}
|
||||
/** set ProjectTimeMusic as requested */
|
||||
Self& needProjectTimeMusic ()
|
||||
{
|
||||
flags |= IProcessContextRequirements::kNeedProjectTimeMusic;
|
||||
return *this;
|
||||
}
|
||||
/** set BarPositionMusic as needed */
|
||||
Self& needBarPositionMusic ()
|
||||
{
|
||||
flags |= IProcessContextRequirements::kNeedBarPositionMusic;
|
||||
return *this;
|
||||
}
|
||||
/** set CycleMusic as needed */
|
||||
Self& needCycleMusic ()
|
||||
{
|
||||
flags |= IProcessContextRequirements::kNeedCycleMusic;
|
||||
return *this;
|
||||
}
|
||||
/** set SamplesToNextClock as needed */
|
||||
Self& needSamplesToNextClock ()
|
||||
{
|
||||
flags |= IProcessContextRequirements::kNeedSamplesToNextClock;
|
||||
return *this;
|
||||
}
|
||||
/** set Tempo as needed */
|
||||
Self& needTempo ()
|
||||
{
|
||||
flags |= IProcessContextRequirements::kNeedTempo;
|
||||
return *this;
|
||||
}
|
||||
/** set TimeSignature as needed */
|
||||
Self& needTimeSignature ()
|
||||
{
|
||||
flags |= IProcessContextRequirements::kNeedTimeSignature;
|
||||
return *this;
|
||||
}
|
||||
/** set Chord as needed */
|
||||
Self& needChord ()
|
||||
{
|
||||
flags |= IProcessContextRequirements::kNeedChord;
|
||||
return *this;
|
||||
}
|
||||
/** set FrameRate as needed */
|
||||
Self& needFrameRate ()
|
||||
{
|
||||
flags |= IProcessContextRequirements::kNeedFrameRate;
|
||||
return *this;
|
||||
}
|
||||
/** set TransportState as needed */
|
||||
Self& needTransportState ()
|
||||
{
|
||||
flags |= IProcessContextRequirements::kNeedTransportState;
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint32 flags {0};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,111 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/processdataslicer.h
|
||||
// Created by : Steinberg, 04/2021
|
||||
// Description : Process the process data in slices
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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/ivstaudioprocessor.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Process Data Slicer
|
||||
*
|
||||
* Cuts the VST process data into slices to process
|
||||
*
|
||||
* Example:
|
||||
* \code{.cpp}
|
||||
* tresult PLUGIN_API Processor::process (ProcessData& data)
|
||||
* {
|
||||
* ProcessDataSlicer slicer (32);
|
||||
* slicer.process<SymbolicSampleSizes::kSample32> (data, [&] (ProcessData& data) {
|
||||
* doSlicedProcessing (data); // data.numSamples <= 32
|
||||
* });
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
class ProcessDataSlicer
|
||||
{
|
||||
public:
|
||||
/** Constructor
|
||||
*
|
||||
* @param inSliceSice slice size in samples
|
||||
*/
|
||||
ProcessDataSlicer (int32 inSliceSize = 8) : sliceSize (inSliceSize) {}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Process the data
|
||||
*
|
||||
* @tparam SampleSize sample size 32 or 64 bit processing
|
||||
* @tparam DoProcessCallback the callback proc
|
||||
* @param data Process data
|
||||
* @param doProcessing process callback
|
||||
*/
|
||||
template <SymbolicSampleSizes SampleSize, typename DoProcessCallback>
|
||||
void process (ProcessData& data, DoProcessCallback doProcessing) noexcept
|
||||
{
|
||||
stopIt = false;
|
||||
auto numSamples = data.numSamples;
|
||||
auto samplesLeft = data.numSamples;
|
||||
while (samplesLeft > 0 && !stopIt)
|
||||
{
|
||||
auto currentSliceSize = samplesLeft > sliceSize ? sliceSize : samplesLeft;
|
||||
|
||||
data.numSamples = currentSliceSize;
|
||||
doProcessing (data);
|
||||
|
||||
advanceBuffers<SampleSize> (data.inputs, data.numInputs, currentSliceSize);
|
||||
advanceBuffers<SampleSize> (data.outputs, data.numOutputs, currentSliceSize);
|
||||
samplesLeft -= currentSliceSize;
|
||||
}
|
||||
// revert buffer pointers (otherwise some hosts may use these wrong pointers)
|
||||
advanceBuffers<SampleSize> (data.inputs, data.numInputs, -(numSamples - samplesLeft));
|
||||
advanceBuffers<SampleSize> (data.outputs, data.numOutputs, -(numSamples - samplesLeft));
|
||||
data.numSamples = numSamples;
|
||||
}
|
||||
|
||||
/** Stop the slice process
|
||||
*
|
||||
* If you want to break the slice processing early, you have to capture the slicer in the
|
||||
* DoProcessCallback and call the stop method.
|
||||
*/
|
||||
void stop () noexcept { stopIt = true; }
|
||||
|
||||
private:
|
||||
template <SymbolicSampleSizes SampleSize>
|
||||
void advanceBuffers (AudioBusBuffers* buffers, int32 numBuffers, int32 numSamples) const
|
||||
noexcept
|
||||
{
|
||||
for (auto index = 0; index < numBuffers; ++index)
|
||||
{
|
||||
for (auto channelIndex = 0; channelIndex < buffers[index].numChannels; ++channelIndex)
|
||||
{
|
||||
if (SampleSize == SymbolicSampleSizes::kSample32)
|
||||
buffers[index].channelBuffers32[channelIndex] += numSamples;
|
||||
else
|
||||
buffers[index].channelBuffers64[channelIndex] += numSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
int32 sliceSize;
|
||||
bool stopIt {false};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,175 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/ringbuffer.h
|
||||
// Created by : Steinberg, 01/2018
|
||||
// Description : Simple RingBuffer with one reader and one writer thread
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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/base/ftypes.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace OneReaderOneWriter {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Ringbuffer
|
||||
*
|
||||
* A ringbuffer supporting one reader and one writer thread
|
||||
*/
|
||||
template <typename ItemT>
|
||||
class RingBuffer
|
||||
{
|
||||
private:
|
||||
using AtomicUInt32 = std::atomic<uint32>;
|
||||
using Index = uint32;
|
||||
using StorageT = std::vector<ItemT>;
|
||||
|
||||
StorageT buffer;
|
||||
Index readPosition {0u};
|
||||
Index writePosition {0u};
|
||||
AtomicUInt32 elementCount {0u};
|
||||
|
||||
public:
|
||||
/** Default constructor
|
||||
*
|
||||
* @param initialNumberOfItems initial ring buffer size
|
||||
*/
|
||||
RingBuffer (size_t initialNumberOfItems = 0) noexcept
|
||||
{
|
||||
if (initialNumberOfItems)
|
||||
resize (initialNumberOfItems);
|
||||
}
|
||||
|
||||
/** size
|
||||
*
|
||||
* @return number of elements the buffer can hold
|
||||
*/
|
||||
size_t size () const noexcept { return buffer.size (); }
|
||||
|
||||
/** resize
|
||||
*
|
||||
* note that you have to make sure that no other thread is reading or writing while calling
|
||||
* this method
|
||||
* @param newNumberOfItems resize buffer
|
||||
*/
|
||||
void resize (size_t newNumberOfItems) noexcept { buffer.resize (newNumberOfItems); }
|
||||
|
||||
/** push a new item into the ringbuffer
|
||||
*
|
||||
* @param item to push
|
||||
* @return true on success or false if buffer is full
|
||||
*/
|
||||
bool push (ItemT&& item) noexcept
|
||||
{
|
||||
if (elementCount.load () == buffer.size ())
|
||||
return false; // full
|
||||
|
||||
auto pos = writePosition;
|
||||
|
||||
buffer[pos] = std::move (item);
|
||||
elementCount++;
|
||||
++pos;
|
||||
if (pos >= buffer.size ())
|
||||
pos = 0u;
|
||||
|
||||
writePosition = pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** push a new item into the ringbuffer
|
||||
*
|
||||
* @param item to push
|
||||
* @return true on success or false if buffer is full
|
||||
*/
|
||||
bool push (const ItemT& item) noexcept
|
||||
{
|
||||
if (elementCount.load () == buffer.size ())
|
||||
return false; // full
|
||||
|
||||
auto pos = writePosition;
|
||||
|
||||
buffer[pos] = item;
|
||||
elementCount++;
|
||||
++pos;
|
||||
if (pos >= buffer.size ())
|
||||
pos = 0u;
|
||||
|
||||
writePosition = pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** push multiple items at once into the ringbuffer
|
||||
*
|
||||
* if there are insufficient free slots in the ring buffer, no item will be pushed.
|
||||
* furthermore, it is guaranteed that the newly added items can only be popped from the buffer
|
||||
* after all items have been added.
|
||||
*
|
||||
* @param items list of items to push
|
||||
* @return true on success or false if there's not enough free space in the buffer
|
||||
*/
|
||||
bool push (const std::initializer_list<ItemT>& items) noexcept
|
||||
{
|
||||
if (items.size () == 0)
|
||||
return true;
|
||||
uint32 elementsPushed = 0u;
|
||||
auto freeElementCount = buffer.size () - elementCount.load ();
|
||||
if (freeElementCount < items.size ())
|
||||
return false;
|
||||
auto pos = writePosition;
|
||||
for (const auto& el : items)
|
||||
{
|
||||
buffer[pos] = el;
|
||||
++pos;
|
||||
if (pos >= buffer.size ())
|
||||
pos = 0u;
|
||||
++elementsPushed;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
uint32 expected = elementCount.load ();
|
||||
uint32 desired = expected + elementsPushed;
|
||||
if (elementCount.compare_exchange_strong (expected, desired))
|
||||
break;
|
||||
}
|
||||
writePosition = pos;
|
||||
return elementsPushed;
|
||||
}
|
||||
|
||||
/** pop an item out of the ringbuffer
|
||||
*
|
||||
* @param item
|
||||
* @return true on success or false if buffer is empty
|
||||
*/
|
||||
bool pop (ItemT& item) noexcept
|
||||
{
|
||||
if (elementCount.load () == 0)
|
||||
return false; // empty
|
||||
|
||||
auto pos = readPosition;
|
||||
item = std::move (buffer[pos]);
|
||||
elementCount--;
|
||||
++pos;
|
||||
if (pos >= buffer.size ())
|
||||
pos = 0;
|
||||
readPosition = pos;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // OneReaderOneWriter
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,142 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/rttransfer.h
|
||||
// Created by : Steinberg, 04/2021
|
||||
// Description : Realtime Object Transfer
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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 <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Transfer objects from a non realtime thread to a realtime one
|
||||
*
|
||||
* You have to use it from two threads, the realtime context thread where you are not allowed to
|
||||
* block and a non realtime thread from where the object is coming.
|
||||
*
|
||||
* It's guaranteed that the function you should only call in the realtime context is wait free and
|
||||
* does not do any allocations or deallocations
|
||||
*
|
||||
*/
|
||||
template <typename ObjectT, typename Deleter = std::default_delete<ObjectT>>
|
||||
struct RTTransferT
|
||||
{
|
||||
using ObjectType = ObjectT;
|
||||
using ObjectTypePtr = std::unique_ptr<ObjectType, Deleter>;
|
||||
|
||||
RTTransferT () { assert (storage[0].is_lock_free ()); }
|
||||
~RTTransferT () noexcept { clear_ui (); }
|
||||
|
||||
/** Access the transfer object.
|
||||
*
|
||||
* If there's a new object, the proc is called with the new object. The object is only valid
|
||||
* inside the proc.
|
||||
*
|
||||
* To be called from the realtime context.
|
||||
*/
|
||||
template <typename Proc>
|
||||
void accessTransferObject_rt (Proc proc) noexcept
|
||||
{
|
||||
ObjectType* newObject {nullptr};
|
||||
ObjectType* currentObject = storage[0].load ();
|
||||
if (currentObject && storage[0].compare_exchange_strong (currentObject, newObject))
|
||||
{
|
||||
proc (*currentObject);
|
||||
ObjectType* transitObj = storage[1].load ();
|
||||
if (storage[1].compare_exchange_strong (transitObj, currentObject) == false)
|
||||
{
|
||||
assert (false);
|
||||
}
|
||||
ObjectType* oldObject = storage[2].load ();
|
||||
if (storage[2].compare_exchange_strong (oldObject, transitObj) == false)
|
||||
{
|
||||
assert (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Transfer an object to the realtime context.
|
||||
*
|
||||
* The ownership of newObject is transfered to this object and the Deleter is used to free
|
||||
* the memory of it afterwards.
|
||||
*
|
||||
* If there's already an object in transfer the previous object will be deallocated and
|
||||
* replaced with the new one without passing to the realtime context.
|
||||
*
|
||||
* To be called from the non realtime context.
|
||||
*/
|
||||
void transferObject_ui (ObjectTypePtr&& newObjectPtr)
|
||||
{
|
||||
ObjectType* newObject = newObjectPtr.release ();
|
||||
clear_ui ();
|
||||
while (true)
|
||||
{
|
||||
ObjectType* currentObject = storage[0].load ();
|
||||
if (storage[0].compare_exchange_strong (currentObject, newObject))
|
||||
{
|
||||
deallocate (currentObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Clear the transfer.
|
||||
*
|
||||
* To be called from the non realtime context.
|
||||
*/
|
||||
void clear_ui ()
|
||||
{
|
||||
clearStorage (storage[0]);
|
||||
clearStorage (storage[1]);
|
||||
clearStorage (storage[2]);
|
||||
}
|
||||
|
||||
private:
|
||||
using AtomicObjectPtr = std::atomic<ObjectType*>;
|
||||
|
||||
void clearStorage (AtomicObjectPtr& atomObj)
|
||||
{
|
||||
ObjectType* newObject = nullptr;
|
||||
while (ObjectType* current = atomObj.load ())
|
||||
{
|
||||
if (atomObj.compare_exchange_strong (current, newObject))
|
||||
{
|
||||
deallocate (current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void deallocate (ObjectType* object)
|
||||
{
|
||||
if (object)
|
||||
{
|
||||
Deleter d;
|
||||
d (object);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<AtomicObjectPtr, 3> storage {};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,285 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/sampleaccurate.h
|
||||
// Created by : Steinberg, 04/2021
|
||||
// Description :
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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 <cassert>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace SampleAccurate {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Utility class to handle sample accurate parameter changes coming from IParamValueQueue
|
||||
*
|
||||
* The normal use case is to setup the Parameter class once for a specific ParamID and then only
|
||||
* use it in the realtime process method.
|
||||
*
|
||||
* If there's a change for the parameter in the inputParameterChanges of the ProcessData structure
|
||||
* the Parameters beginChange method should be called with the valueQueue of the parmID and then
|
||||
* while processing the current audio block the parameter should be advanced as many samples as you
|
||||
* would like to handle parameter changes. In the end the endChanges method must be called to
|
||||
* cleanup internal data structures.
|
||||
* For convenience the endChanges method can be called without a previous beginChanges call.
|
||||
*/
|
||||
struct Parameter
|
||||
{
|
||||
Parameter (ParamID pid = 0, ParamValue initValue = 0.) noexcept;
|
||||
|
||||
/** Set the value of the parameter
|
||||
*
|
||||
* When this is called during the beginChanges() and endChanges() sequence, the changes in the
|
||||
* value queue are ignored
|
||||
*
|
||||
* @param v the new value of the parameter
|
||||
*/
|
||||
void setValue (ParamValue v) noexcept;
|
||||
|
||||
/** Set the ID of the parameter
|
||||
*
|
||||
* @param pid the new ID of the parameter
|
||||
*/
|
||||
void setParamID (ParamID pid) noexcept;
|
||||
|
||||
/** Get the ID of the parameter
|
||||
*
|
||||
* @return ID of the parameter
|
||||
*/
|
||||
ParamID getParamID () const noexcept;
|
||||
|
||||
/** Get the current value of the parameter
|
||||
*
|
||||
* @return current value
|
||||
*/
|
||||
ParamValue getValue () const noexcept;
|
||||
/** Are there any pending changes
|
||||
*
|
||||
* @return true when there are changes
|
||||
*/
|
||||
bool hasChanges () const noexcept;
|
||||
|
||||
/** Begin change sequence
|
||||
*
|
||||
* @param valueQueue the queue with the changes
|
||||
*/
|
||||
void beginChanges (IParamValueQueue* valueQueue) noexcept;
|
||||
|
||||
/** Advance the changes in queue
|
||||
*
|
||||
* @param numSamples how many samples to advance in the queue
|
||||
* @return current value
|
||||
*/
|
||||
ParamValue advance (int32 numSamples) noexcept;
|
||||
|
||||
/** Flush all changes in the queue
|
||||
*
|
||||
* @return value after flushing
|
||||
*/
|
||||
ParamValue flushChanges () noexcept;
|
||||
|
||||
/** End change sequence
|
||||
*
|
||||
* @return value after flushing all possible pending changes
|
||||
*/
|
||||
ParamValue endChanges () noexcept;
|
||||
|
||||
/** Templated variant of advance
|
||||
*
|
||||
* calls Proc p with the new value if the value changes
|
||||
*/
|
||||
template <typename Proc>
|
||||
void advance (int32 numSamples, Proc p);
|
||||
|
||||
/** Templated variant of flushChanges
|
||||
*
|
||||
* calls Proc p with the new value if the value changes
|
||||
*/
|
||||
template <typename Proc>
|
||||
void flushChanges (Proc p);
|
||||
|
||||
/** Templated variant of endChanges
|
||||
*
|
||||
* calls Proc p with the new value if the value changes
|
||||
*/
|
||||
template <typename Proc>
|
||||
void endChanges (Proc p);
|
||||
|
||||
private:
|
||||
struct ValuePoint
|
||||
{
|
||||
ParamValue value {0.};
|
||||
double rampPerSample {0.};
|
||||
int32 sampleOffset {-1};
|
||||
};
|
||||
|
||||
ValuePoint processNextValuePoint () noexcept;
|
||||
|
||||
ParamID paramID;
|
||||
int32 pointCount {-1};
|
||||
int32 pointIndex {0};
|
||||
int32 sampleCounter {0};
|
||||
ParamValue currentValue {0.};
|
||||
ValuePoint valuePoint;
|
||||
|
||||
IParamValueQueue* queue {nullptr};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_ALWAYS_INLINE Parameter::Parameter (ParamID pid, ParamValue initValue) noexcept
|
||||
{
|
||||
setParamID (pid);
|
||||
setValue (initValue);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_ALWAYS_INLINE void Parameter::setValue (ParamValue v) noexcept
|
||||
{
|
||||
currentValue = v;
|
||||
pointCount = 0;
|
||||
valuePoint = {currentValue, 0., -1};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_ALWAYS_INLINE void Parameter::setParamID (ParamID pid) noexcept
|
||||
{
|
||||
paramID = pid;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_ALWAYS_INLINE ParamID Parameter::getParamID () const noexcept
|
||||
{
|
||||
return paramID;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_ALWAYS_INLINE ParamValue Parameter::getValue () const noexcept
|
||||
{
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_ALWAYS_INLINE bool Parameter::hasChanges () const noexcept
|
||||
{
|
||||
return pointCount >= 0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_ALWAYS_INLINE void Parameter::beginChanges (IParamValueQueue* valueQueue) noexcept
|
||||
{
|
||||
assert (queue == nullptr);
|
||||
assert (valueQueue->getParameterId () == getParamID ());
|
||||
queue = valueQueue;
|
||||
pointCount = queue->getPointCount ();
|
||||
pointIndex = 0;
|
||||
sampleCounter = 0;
|
||||
if (pointCount)
|
||||
valuePoint = processNextValuePoint ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_ALWAYS_INLINE ParamValue Parameter::advance (int32 numSamples) noexcept
|
||||
{
|
||||
if (pointCount < 0)
|
||||
return currentValue;
|
||||
while (valuePoint.sampleOffset >= 0 && valuePoint.sampleOffset < numSamples)
|
||||
{
|
||||
sampleCounter += valuePoint.sampleOffset;
|
||||
numSamples -= valuePoint.sampleOffset;
|
||||
currentValue = valuePoint.value;
|
||||
valuePoint = processNextValuePoint ();
|
||||
}
|
||||
currentValue += (valuePoint.rampPerSample * numSamples);
|
||||
valuePoint.sampleOffset -= numSamples;
|
||||
sampleCounter += numSamples;
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_ALWAYS_INLINE ParamValue Parameter::flushChanges () noexcept
|
||||
{
|
||||
while (pointCount >= 0)
|
||||
{
|
||||
currentValue = valuePoint.value;
|
||||
valuePoint = processNextValuePoint ();
|
||||
}
|
||||
currentValue = valuePoint.value;
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_ALWAYS_INLINE ParamValue Parameter::endChanges () noexcept
|
||||
{
|
||||
flushChanges ();
|
||||
pointCount = -1;
|
||||
queue = nullptr;
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <typename Proc>
|
||||
SMTG_ALWAYS_INLINE void Parameter::advance (int32 numSamples, Proc p)
|
||||
{
|
||||
auto originalValue = currentValue;
|
||||
if (advance (numSamples) != originalValue)
|
||||
{
|
||||
p (currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <typename Proc>
|
||||
SMTG_ALWAYS_INLINE void Parameter::flushChanges (Proc p)
|
||||
{
|
||||
auto originalValue = currentValue;
|
||||
if (flushChanges () != originalValue)
|
||||
p (currentValue);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <typename Proc>
|
||||
SMTG_ALWAYS_INLINE void Parameter::endChanges (Proc p)
|
||||
{
|
||||
auto originalValue = currentValue;
|
||||
if (endChanges () != originalValue)
|
||||
p (currentValue);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_ALWAYS_INLINE auto Parameter::processNextValuePoint () noexcept -> ValuePoint
|
||||
{
|
||||
ValuePoint nv;
|
||||
if (pointCount == 0 || queue->getPoint (pointIndex, nv.sampleOffset, nv.value) != kResultTrue)
|
||||
{
|
||||
pointCount = -1;
|
||||
return {currentValue, 0., -1};
|
||||
}
|
||||
nv.sampleOffset -= sampleCounter;
|
||||
++pointIndex;
|
||||
--pointCount;
|
||||
if (nv.sampleOffset == 0)
|
||||
nv.rampPerSample = (nv.value - currentValue);
|
||||
else
|
||||
nv.rampPerSample = (nv.value - currentValue) / static_cast<double> (nv.sampleOffset);
|
||||
return nv;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // SampleAccurate
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,136 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/stringconvert.cpp
|
||||
// Created by : Steinberg, 11/2014
|
||||
// Description : c++11 unicode string convert functions
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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 "pluginterfaces/base/fplatform.h"
|
||||
|
||||
#if SMTG_OS_WINDOWS
|
||||
#ifndef _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||
#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||
#endif
|
||||
#endif // SMTG_OS_WINDOWS
|
||||
|
||||
#include "public.sdk/source/vst/utility/stringconvert.h"
|
||||
#include "public.sdk/source/common/commonstringconvert.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <istream>
|
||||
#include <locale>
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace StringConvert {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace {
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1900
|
||||
#define USE_WCHAR_AS_UTF16TYPE
|
||||
using UTF16Type = wchar_t;
|
||||
#else
|
||||
using UTF16Type = char16_t;
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
using Converter = std::wstring_convert<std::codecvt_utf8_utf16<UTF16Type>, UTF16Type>;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
Converter& converter ()
|
||||
{
|
||||
static Converter conv;
|
||||
return conv;
|
||||
}
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // anonymous
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
std::u16string convert (const std::string& utf8Str)
|
||||
{
|
||||
return Steinberg::StringConvert::convert (utf8Str);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool convert (const std::string& utf8Str, Steinberg::Vst::String128 str)
|
||||
{
|
||||
return convert (utf8Str, str, 128);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool convert (const std::string& utf8Str, Steinberg::Vst::TChar* str, uint32_t maxCharacters)
|
||||
{
|
||||
auto ucs2 = convert (utf8Str);
|
||||
if (ucs2.length () < maxCharacters)
|
||||
{
|
||||
ucs2.copy (reinterpret_cast<char16_t*> (str), ucs2.length ());
|
||||
str[ucs2.length ()] = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
std::string convert (const Steinberg::Vst::TChar* str)
|
||||
{
|
||||
return converter ().to_bytes (reinterpret_cast<const UTF16Type*> (str));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
std::string convert (const Steinberg::Vst::TChar* str, uint32_t max)
|
||||
{
|
||||
std::string result;
|
||||
if (str)
|
||||
{
|
||||
Steinberg::Vst::TChar tmp[2] {};
|
||||
for (uint32_t i = 0; i < max; ++i, ++str)
|
||||
{
|
||||
tmp[0] = *str;
|
||||
if (tmp[0] == 0)
|
||||
break;
|
||||
result += convert (tmp);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
std::string convert (const std::u16string& str)
|
||||
{
|
||||
return Steinberg::StringConvert::convert (str);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
std::string convert (const char* str, uint32_t max)
|
||||
{
|
||||
return Steinberg::StringConvert::convert (str, max);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // StringConvert
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,191 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/stringconvert.h
|
||||
// Created by : Steinberg, 11/2014
|
||||
// Description : c++11 unicode string convert functions
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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/vsttypes.h"
|
||||
#include <string>
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace StringConvert {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/**
|
||||
* Forward to Steinberg::StringConvert::convert (...)
|
||||
*/
|
||||
std::u16string convert (const std::string& utf8Str);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/**
|
||||
* Forward to Steinberg::StringConvert::convert (...)
|
||||
*/
|
||||
std::string convert (const std::u16string& str);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/**
|
||||
* Forward to Steinberg::StringConvert::convert (...)
|
||||
*/
|
||||
std::string convert (const char* str, uint32_t max);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/**
|
||||
* convert an UTF-8 string to an UTF-16 string buffer with max 127 characters
|
||||
*
|
||||
* @param utf8Str UTF-8 string
|
||||
* @param str UTF-16 string buffer
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool convert (const std::string& utf8Str, Steinberg::Vst::String128 str);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/**
|
||||
* convert an UTF-8 string to an UTF-16 string buffer
|
||||
*
|
||||
* @param utf8Str UTF-8 string
|
||||
* @param str UTF-16 string buffer
|
||||
* @param maxCharacters max characters that fit into str
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool convert (const std::string& utf8Str, Steinberg::Vst::TChar* str,
|
||||
uint32_t maxCharacters);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/**
|
||||
* convert an UTF-16 string buffer to an UTF-8 string
|
||||
*
|
||||
* @param str UTF-16 string buffer
|
||||
*
|
||||
* @return UTF-8 string
|
||||
*/
|
||||
std::string convert (const Steinberg::Vst::TChar* str);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/**
|
||||
* convert an UTF-16 string buffer to an UTF-8 string
|
||||
*
|
||||
* @param str UTF-16 string buffer
|
||||
* @param max maximum characters in str
|
||||
*
|
||||
* @return UTF-8 string
|
||||
*/
|
||||
std::string convert (const Steinberg::Vst::TChar* str, uint32_t max);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // StringConvert
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline const Steinberg::Vst::TChar* toTChar (const std::u16string& str)
|
||||
{
|
||||
return reinterpret_cast<const Steinberg::Vst::TChar*> (str.data ());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/**
|
||||
* convert a number to an UTF-16 string
|
||||
*
|
||||
* @param value number
|
||||
*
|
||||
* @return UTF-16 string
|
||||
*/
|
||||
template <typename NumberT>
|
||||
std::u16string toString (NumberT value)
|
||||
{
|
||||
auto u8str = std::to_string (value);
|
||||
return StringConvert::convert (u8str);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Deprecated VST3 namespace
|
||||
//------------------------------------------------------------------------
|
||||
namespace VST3 {
|
||||
namespace StringConvert {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_DEPRECATED_MSG ("Use Steinberg::Vst::StringConvert::convert (...)")
|
||||
inline std::u16string convert (const std::string& utf8Str)
|
||||
{
|
||||
return Steinberg::Vst::StringConvert::convert (utf8Str);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_DEPRECATED_MSG ("Use Steinberg::Vst::StringConvert::convert (...)")
|
||||
inline std::string convert (const std::u16string& str)
|
||||
{
|
||||
return Steinberg::Vst::StringConvert::convert (str);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_DEPRECATED_MSG ("Use Steinberg::Vst::StringConvert::convert (...)")
|
||||
inline std::string convert (const char* str, uint32_t max)
|
||||
{
|
||||
return Steinberg::Vst::StringConvert::convert (str, max);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_DEPRECATED_MSG ("Use Steinberg::Vst::StringConvert::convert (...)")
|
||||
inline bool convert (const std::string& utf8Str, Steinberg::Vst::String128 str)
|
||||
{
|
||||
return Steinberg::Vst::StringConvert::convert (utf8Str, str);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_DEPRECATED_MSG ("Use Steinberg::Vst::StringConvert::convert (...)")
|
||||
inline bool convert (const std::string& utf8Str, Steinberg::Vst::TChar* str, uint32_t maxCharacters)
|
||||
{
|
||||
return Steinberg::Vst::StringConvert::convert (utf8Str, str, maxCharacters);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_DEPRECATED_MSG ("Use Steinberg::Vst::StringConvert::convert (...)")
|
||||
inline std::string convert (const Steinberg::Vst::TChar* str)
|
||||
{
|
||||
return Steinberg::Vst::StringConvert::convert (str);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_DEPRECATED_MSG ("Use Steinberg::Vst::StringConvert::convert (...)")
|
||||
inline std::string convert (const Steinberg::Vst::TChar* str, uint32_t max)
|
||||
{
|
||||
return Steinberg::Vst::StringConvert::convert (str, max);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // StringConvert
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SMTG_DEPRECATED_MSG ("Use Steinberg::Vst::toTChar (...)")
|
||||
inline const Steinberg::Vst::TChar* toTChar (const std::u16string& str)
|
||||
{
|
||||
return Steinberg::Vst::toTChar (str);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <typename NumberT>
|
||||
SMTG_DEPRECATED_MSG ("Use Steinberg::Vst::toString (...)")
|
||||
std::u16string toString (NumberT value)
|
||||
{
|
||||
return Steinberg::Vst::toString (value);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // VST3
|
||||
@@ -0,0 +1,169 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/systemtime.cpp
|
||||
// Created by : Steinberg, 06/2023
|
||||
// Description : VST Component System Time 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 "systemtime.h"
|
||||
#include "pluginterfaces/base/funknownimpl.h"
|
||||
#include <limits>
|
||||
|
||||
#if SMTG_OS_OSX
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static Steinberg::Vst::SystemTime::GetImplFunc makeNativeGetSystemTimeFunc ()
|
||||
{
|
||||
return [] () {
|
||||
return static_cast<Steinberg::int64> (
|
||||
AudioConvertHostTimeToNanos (AudioGetCurrentHostTime ()));
|
||||
};
|
||||
}
|
||||
#elif SMTG_OS_IOS
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static Steinberg::Vst::SystemTime::GetImplFunc makeNativeGetSystemTimeFunc ()
|
||||
{
|
||||
static struct mach_timebase_info timebaseInfo;
|
||||
mach_timebase_info (&timebaseInfo);
|
||||
|
||||
return [&] () {
|
||||
double absTime = static_cast<double> (mach_absolute_time ());
|
||||
// nano seconds
|
||||
double d = (absTime / timebaseInfo.denom) * timebaseInfo.numer;
|
||||
return static_cast<Steinberg::int64> (d);
|
||||
};
|
||||
}
|
||||
|
||||
#elif SMTG_OS_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct WinmmDll
|
||||
{
|
||||
static WinmmDll& instance ()
|
||||
{
|
||||
static WinmmDll gInstance;
|
||||
return gInstance;
|
||||
}
|
||||
bool valid () const { return func != nullptr; }
|
||||
DWORD timeGetTime () const { return func (); }
|
||||
|
||||
private:
|
||||
using TimeGetTimeFunc = DWORD (WINAPI*) ();
|
||||
WinmmDll ()
|
||||
{
|
||||
dll = LoadLibraryA ("winmm.dll");
|
||||
if (dll)
|
||||
{
|
||||
func = reinterpret_cast<TimeGetTimeFunc> (GetProcAddress (dll, "timeGetTime"));
|
||||
}
|
||||
}
|
||||
TimeGetTimeFunc func {nullptr};
|
||||
HMODULE dll;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static Steinberg::Vst::SystemTime::GetImplFunc makeNativeGetSystemTimeFunc ()
|
||||
{
|
||||
if (WinmmDll::instance ().valid ())
|
||||
{
|
||||
return [dll = WinmmDll::instance ()] ()
|
||||
{
|
||||
return static_cast<Steinberg::int64> (dll.timeGetTime ()) * 1000000;
|
||||
};
|
||||
}
|
||||
return [] () { return std::numeric_limits<Steinberg::int64>::max (); };
|
||||
}
|
||||
#elif SMTG_OS_LINUX
|
||||
//------------------------------------------------------------------------
|
||||
#include <time.h>
|
||||
static uint64_t getUptimeByClockGettime ()
|
||||
{
|
||||
struct timespec time_spec;
|
||||
|
||||
if (clock_gettime (CLOCK_BOOTTIME, &time_spec) != 0)
|
||||
return 0;
|
||||
|
||||
const uint64_t uptime = time_spec.tv_sec * 1000 + time_spec.tv_nsec / 1000000;
|
||||
return uptime;
|
||||
}
|
||||
|
||||
static Steinberg::Vst::SystemTime::GetImplFunc makeNativeGetSystemTimeFunc ()
|
||||
{
|
||||
return [] () { return static_cast<Steinberg::int64> (getUptimeByClockGettime ()); };
|
||||
}
|
||||
#else
|
||||
//------------------------------------------------------------------------
|
||||
static Steinberg::Vst::SystemTime::GetImplFunc makeNativeGetSystemTimeFunc ()
|
||||
{
|
||||
return [] () { return std::numeric_limits<Steinberg::int64>::max (); };
|
||||
}
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SystemTime::SystemTime (IComponentHandler* componentHandler)
|
||||
{
|
||||
if (auto chst = U::cast<IComponentHandlerSystemTime> (componentHandler))
|
||||
{
|
||||
getImpl = [host = std::move (chst)] ()->int64
|
||||
{
|
||||
int64 value = 0;
|
||||
if (host->getSystemTime (value) == kResultTrue)
|
||||
return value;
|
||||
return std::numeric_limits<int64>::max ();
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
getImpl = makeNativeGetSystemTimeFunc ();
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SystemTime::SystemTime (const SystemTime& st)
|
||||
{
|
||||
*this = st;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SystemTime::SystemTime (SystemTime&& st) noexcept
|
||||
{
|
||||
*this = std::move (st);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SystemTime& SystemTime::operator= (const SystemTime& st)
|
||||
{
|
||||
getImpl = st.getImpl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
SystemTime& SystemTime::operator= (SystemTime&& st) noexcept
|
||||
{
|
||||
std::swap (getImpl, st.getImpl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,64 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/systemtime.h
|
||||
// Created by : Steinberg, 06/2023
|
||||
// Description : VST Component System Time 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginterfaces/vst/ivsteditcontroller.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** SystemTime Helper class
|
||||
*
|
||||
* Get the system time on the vst controller side.
|
||||
*
|
||||
* If supported by the host this uses the same clock as used in the
|
||||
* realtime audio process block. Otherwise an approximation via platform APIs is used.
|
||||
*
|
||||
* This can be used to synchronize audio and visuals. As known, the audio process block is always
|
||||
* called ealier as the audio which was generated passes the audio monitors or headphones.
|
||||
* Depending on the audio graph this can be so long that your eyes will see the visualization (if
|
||||
* not synchronized) earlier then your ears will hear the sound.
|
||||
* To synchronize you need to queue your visualization data on the controller side timestamped with
|
||||
* the time from the process block and dequed when it's time for the data to be visualized.
|
||||
*/
|
||||
class SystemTime
|
||||
{
|
||||
public:
|
||||
SystemTime (IComponentHandler* componentHandler);
|
||||
SystemTime (const SystemTime& st);
|
||||
SystemTime (SystemTime&& st) noexcept;
|
||||
SystemTime& operator= (const SystemTime& st);
|
||||
SystemTime& operator= (SystemTime&& st) noexcept;
|
||||
|
||||
/** get the current system time
|
||||
*/
|
||||
int64 get () const { return getImpl (); }
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
using GetImplFunc = std::function<int64 ()>;
|
||||
|
||||
private:
|
||||
GetImplFunc getImpl;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,104 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/test/ringbuffertest.cpp
|
||||
// Created by : Steinberg, 03/2018
|
||||
// Description : Test ringbuffer
|
||||
// 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/utility/ringbuffer.h"
|
||||
#include "public.sdk/source/vst/utility/testing.h"
|
||||
#include "pluginterfaces/base/fstrdefs.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static ModuleInitializer InitRingbufferTests ([] () {
|
||||
registerTest ("RingBuffer", STR ("push until full"), [] (ITestResult*) {
|
||||
OneReaderOneWriter::RingBuffer<uint32> rb (4);
|
||||
if (!rb.push (0))
|
||||
return false;
|
||||
if (!rb.push (1))
|
||||
return false;
|
||||
if (!rb.push (2))
|
||||
return false;
|
||||
if (!rb.push (3))
|
||||
return false;
|
||||
if (!rb.push (4))
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
registerTest ("RingBuffer", STR ("pop until empty"), [] (ITestResult*) {
|
||||
OneReaderOneWriter::RingBuffer<uint32> rb (4);
|
||||
if (!rb.push (0))
|
||||
return false;
|
||||
if (!rb.push (1))
|
||||
return false;
|
||||
if (!rb.push (2))
|
||||
return false;
|
||||
if (!rb.push (3))
|
||||
return false;
|
||||
|
||||
uint32 value;
|
||||
if (!rb.pop (value) || value != 0)
|
||||
return false;
|
||||
if (!rb.pop (value) || value != 1)
|
||||
return false;
|
||||
if (!rb.pop (value) || value != 2)
|
||||
return false;
|
||||
if (!rb.pop (value) || value != 3)
|
||||
return false;
|
||||
if (!rb.pop (value))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
});
|
||||
registerTest ("RingBuffer", STR ("roundtrip"), [] (ITestResult*) {
|
||||
OneReaderOneWriter::RingBuffer<uint32> rb (2);
|
||||
uint32 value;
|
||||
|
||||
for (auto i = 0u; i < rb.size () * 2; ++i)
|
||||
{
|
||||
if (!rb.push (i))
|
||||
return false;
|
||||
if (!rb.pop (value) || value != i)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
registerTest ("RingBuffer", STR ("push multiple"), [] (ITestResult*) {
|
||||
OneReaderOneWriter::RingBuffer<uint32> rb (3);
|
||||
|
||||
if (!rb.push ({32u, 64u}))
|
||||
return false;
|
||||
if (rb.push ({32u, 64u}))
|
||||
return false;
|
||||
uint32 value;
|
||||
if (!rb.pop (value))
|
||||
return false;
|
||||
if (!rb.pop (value))
|
||||
return false;
|
||||
if (rb.pop (value))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,150 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/test/rtstatetransfertest.cpp
|
||||
// Created by : Steinberg, 04/2021
|
||||
// Description : Realtime State Transfer
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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/utility/rttransfer.h"
|
||||
#include "public.sdk/source/vst/utility/testing.h"
|
||||
#include "pluginterfaces/vst/vsttypes.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
namespace {
|
||||
//------------------------------------------------------------------------
|
||||
using ParameterVector = std::vector<std::pair<ParamID, ParamValue>>;
|
||||
using RTTransfer = RTTransferT<ParameterVector>;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct RaceConditionTestObject
|
||||
{
|
||||
static std::atomic<uint32> numDeletes;
|
||||
//------------------------------------------------------------------------
|
||||
struct MyDeleter
|
||||
{
|
||||
void operator () (double* v) const noexcept
|
||||
{
|
||||
delete v;
|
||||
++numDeletes;
|
||||
}
|
||||
};
|
||||
|
||||
RTTransferT<double, MyDeleter> transfer;
|
||||
std::thread thread;
|
||||
std::mutex m1;
|
||||
std::mutex m2;
|
||||
std::condition_variable c1;
|
||||
|
||||
bool test (ITestResult* result)
|
||||
{
|
||||
numDeletes = 0;
|
||||
{
|
||||
auto obj1 = std::unique_ptr<double, MyDeleter> (new double (0.5));
|
||||
auto obj2 = std::unique_ptr<double, MyDeleter> (new double (1.));
|
||||
transfer.transferObject_ui (std::move (obj1));
|
||||
m2.lock ();
|
||||
thread = std::thread ([&] () {
|
||||
transfer.accessTransferObject_rt ([&] (const double&) {
|
||||
c1.notify_all ();
|
||||
m2.lock ();
|
||||
m2.unlock ();
|
||||
});
|
||||
transfer.accessTransferObject_rt ([&] (const double&) {});
|
||||
});
|
||||
std::unique_lock<std::mutex> lm1 (m1);
|
||||
c1.wait (lm1);
|
||||
transfer.transferObject_ui (std::move (obj2));
|
||||
m2.unlock ();
|
||||
|
||||
thread.join ();
|
||||
transfer.clear_ui ();
|
||||
}
|
||||
return numDeletes == 2;
|
||||
}
|
||||
};
|
||||
std::atomic<uint32> RaceConditionTestObject::numDeletes {0};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static std::atomic<uint32> CustomDeleterCallCount;
|
||||
struct CustomDeleter
|
||||
{
|
||||
template <typename T>
|
||||
void operator () (T* v) const noexcept
|
||||
{
|
||||
delete v;
|
||||
++CustomDeleterCallCount;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
ModuleInitializer InitStateTransferTests ([] () {
|
||||
registerTest ("RTTransfer", STR ("Simple Transfer"), [] (ITestResult*) {
|
||||
RTTransfer helper;
|
||||
auto list = std::make_unique<ParameterVector> ();
|
||||
list->emplace_back (std::make_pair (0, 1.));
|
||||
helper.transferObject_ui (std::move (list));
|
||||
bool success = false;
|
||||
constexpr double one = 1.;
|
||||
helper.accessTransferObject_rt ([&success, one = one] (const auto& list) {
|
||||
if (list.size () == 1)
|
||||
{
|
||||
if (list[0].first == 0)
|
||||
{
|
||||
if (Test::equal (one, list[0].second))
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
list = std::make_unique<ParameterVector> ();
|
||||
list->emplace_back (std::make_pair (0, 1.));
|
||||
helper.transferObject_ui (std::move (list));
|
||||
helper.accessTransferObject_rt ([] (auto&) {});
|
||||
list = std::make_unique<ParameterVector> ();
|
||||
list->emplace_back (std::make_pair (0, 1.));
|
||||
helper.transferObject_ui (std::move (list));
|
||||
helper.accessTransferObject_rt ([] (auto&) {});
|
||||
return success;
|
||||
});
|
||||
registerTest ("RTTransfer", STR ("CheckRaceCondition"), [] (ITestResult* r) {
|
||||
RaceConditionTestObject obj;
|
||||
return obj.test (r);
|
||||
});
|
||||
registerTest ("RTTransfer", STR ("Custom Deleter"), [] (ITestResult* result) {
|
||||
CustomDeleterCallCount = 0;
|
||||
RTTransferT<double, CustomDeleter> transfer;
|
||||
auto obj1 = std::unique_ptr<double, CustomDeleter> (new double (1.));
|
||||
transfer.transferObject_ui (std::move (obj1));
|
||||
if (CustomDeleterCallCount != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
transfer.clear_ui ();
|
||||
return CustomDeleterCallCount == 1;
|
||||
});
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Anonymous
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Examples
|
||||
// Filename : public.sdk/source/vst/utility/test/sampleaccuratetest.cpp
|
||||
// Created by : Steinberg, 04/2021
|
||||
// Description : Tests for Sample Accurate Parameter Changes
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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/sampleaccurate.h"
|
||||
#include "public.sdk/source/vst/utility/testing.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static ModuleInitializer InitTests ([] () {
|
||||
registerTest ("SampleAccurate::Parameter", STR ("Single Change"), [] (ITestResult* result) {
|
||||
ParamID pid = 1;
|
||||
SampleAccurate::Parameter param (pid, 0.);
|
||||
ParameterValueQueue queue (pid);
|
||||
int32 index = 0;
|
||||
queue.addPoint (0, 0., index);
|
||||
queue.addPoint (100, 1., index);
|
||||
|
||||
param.beginChanges (&queue);
|
||||
param.advance (50);
|
||||
if (Test::notEqual (param.getValue (), 0.5))
|
||||
{
|
||||
result->addErrorMessage (STR ("Unexpected Value"));
|
||||
return false;
|
||||
}
|
||||
param.advance (50);
|
||||
if (Test::notEqual (param.getValue (), 1.))
|
||||
{
|
||||
result->addErrorMessage (STR ("Unexpected Value"));
|
||||
return false;
|
||||
}
|
||||
param.endChanges ();
|
||||
|
||||
return true;
|
||||
});
|
||||
registerTest ("SampleAccurate::Parameter", STR ("Multi Change"), [] (ITestResult* result) {
|
||||
ParamID pid = 1;
|
||||
SampleAccurate::Parameter param (pid, 0.);
|
||||
ParameterValueQueue queue (pid);
|
||||
int32 index = 0;
|
||||
queue.addPoint (0, 0., index);
|
||||
queue.addPoint (100, 1., index);
|
||||
queue.addPoint (120, 0., index);
|
||||
|
||||
param.beginChanges (&queue);
|
||||
param.advance (50);
|
||||
if (Test::notEqual (param.getValue (), 0.5))
|
||||
{
|
||||
result->addErrorMessage (STR ("Unexpected Value"));
|
||||
return false;
|
||||
}
|
||||
param.advance (50);
|
||||
if (Test::notEqual (param.getValue (), 1.))
|
||||
{
|
||||
result->addErrorMessage (STR ("Unexpected Value"));
|
||||
return false;
|
||||
}
|
||||
param.advance (20);
|
||||
if (Test::notEqual (param.getValue (), 0.))
|
||||
{
|
||||
result->addErrorMessage (STR ("Unexpected Value"));
|
||||
return false;
|
||||
}
|
||||
param.endChanges ();
|
||||
|
||||
return true;
|
||||
});
|
||||
registerTest ("SampleAccurate::Parameter", STR ("Edge"), [] (ITestResult* result) {
|
||||
ParamID pid = 1;
|
||||
SampleAccurate::Parameter param (pid, 0.);
|
||||
ParameterValueQueue queue (pid);
|
||||
int32 index = 0;
|
||||
queue.addPoint (0, 0., index);
|
||||
queue.addPoint (1, 1., index);
|
||||
queue.addPoint (2, 0., index);
|
||||
|
||||
param.beginChanges (&queue);
|
||||
param.advance (2);
|
||||
if (Test::notEqual (param.getValue (), 0.))
|
||||
{
|
||||
result->addErrorMessage (STR ("Unexpected Value"));
|
||||
return false;
|
||||
}
|
||||
param.endChanges ();
|
||||
|
||||
return true;
|
||||
});
|
||||
registerTest ("SampleAccurate::Parameter", STR ("Flush"), [] (ITestResult* result) {
|
||||
ParamID pid = 1;
|
||||
SampleAccurate::Parameter param (pid, 0.);
|
||||
ParameterValueQueue queue (pid);
|
||||
int32 index = 0;
|
||||
queue.addPoint (0, 0., index);
|
||||
queue.addPoint (256, 1., index);
|
||||
queue.addPoint (258, 0.5, index);
|
||||
|
||||
param.beginChanges (&queue);
|
||||
param.flushChanges ();
|
||||
if (Test::notEqual (param.getValue (), 0.5))
|
||||
{
|
||||
result->addErrorMessage (STR ("Unexpected Value"));
|
||||
return false;
|
||||
}
|
||||
param.endChanges ();
|
||||
|
||||
return true;
|
||||
});
|
||||
registerTest ("SampleAccurate::Parameter", STR ("Callback"), [] (ITestResult* result) {
|
||||
ParamID pid = 1;
|
||||
SampleAccurate::Parameter param (pid, 0.);
|
||||
ParameterValueQueue queue (pid);
|
||||
int32 index = 0;
|
||||
queue.addPoint (0, 0., index);
|
||||
queue.addPoint (128, 0., index);
|
||||
queue.addPoint (256, 1., index);
|
||||
queue.addPoint (258, 0.5, index);
|
||||
|
||||
param.beginChanges (&queue);
|
||||
bool failure = false;
|
||||
param.advance (128, [&result, &failure] (auto) {
|
||||
result->addErrorMessage (STR ("Unexpected Value"));
|
||||
failure = true;
|
||||
});
|
||||
if (failure)
|
||||
return false;
|
||||
constexpr auto half = 0.5;
|
||||
param.advance (514, [&result, &failure, half = half] (auto value) {
|
||||
if (Test::notEqual (value, half))
|
||||
{
|
||||
result->addErrorMessage (STR ("Unexpected Value"));
|
||||
failure = true;
|
||||
}
|
||||
else
|
||||
failure = false;
|
||||
});
|
||||
if (failure)
|
||||
return false;
|
||||
|
||||
param.endChanges ();
|
||||
|
||||
return true;
|
||||
});
|
||||
registerTest ("SampleAccurate::Parameter", STR ("NoChanges"), [] (ITestResult* result) {
|
||||
ParamID pid = 1;
|
||||
SampleAccurate::Parameter param (pid, 1.);
|
||||
param.endChanges ();
|
||||
|
||||
if (Test::notEqual (param.getValue (), 1.))
|
||||
{
|
||||
result->addErrorMessage (STR ("Unexpected Value"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/test/versionparsertest.cpp
|
||||
// Created by : Steinberg, 12/2019
|
||||
// Description : Test version parser
|
||||
// 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/utility/testing.h"
|
||||
#include "public.sdk/source/vst/utility/versionparser.h"
|
||||
#include "pluginterfaces/base/fstrdefs.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static ModuleInitializer InitVersionParserTests ([] () {
|
||||
registerTest ("VersionParser", STR ("Parsing 'SDK 3.7'"), [] (ITestResult* testResult) {
|
||||
auto version = VST3::Version::parse ("SDK 3.7");
|
||||
if (version.getMajor () != 3 || version.getMinor () != 7 || version.getSub () != 0 ||
|
||||
version.getBuildnumber () != 0)
|
||||
{
|
||||
testResult->addErrorMessage (STR ("Parsing 'SDK 3.7' failed"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
registerTest ("VersionParser", STR ("Parsing 'SDK 3.7.1.38'"), [] (ITestResult* testResult) {
|
||||
auto version = VST3::Version::parse ("3.7.1.38");
|
||||
if (version.getMajor () != 3 || version.getMinor () != 7 || version.getSub () != 1 ||
|
||||
version.getBuildnumber () != 38)
|
||||
{
|
||||
testResult->addErrorMessage (STR ("Parsing '3.7.1.38' failed"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
registerTest ("VersionParser", STR ("Parsing 'SDK 3.7 Prerelease'"),
|
||||
[] (ITestResult* testResult) {
|
||||
auto version = VST3::Version::parse ("SDK 3.7 Prerelease");
|
||||
if (version.getMajor () != 3 || version.getMinor () != 7 ||
|
||||
version.getSub () != 0 || version.getBuildnumber () != 0)
|
||||
{
|
||||
testResult->addErrorMessage (STR ("Parsing 'SDK 3.7 Prerelease' failed"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
registerTest ("VersionParser", STR ("Parsing 'SDK 3.7-99'"), [] (ITestResult* testResult) {
|
||||
auto version = VST3::Version::parse ("SDK 3.7-99");
|
||||
if (version.getMajor () != 3 || version.getMinor () != 7 || version.getSub () != 0 ||
|
||||
version.getBuildnumber () != 0)
|
||||
{
|
||||
testResult->addErrorMessage (STR ("Parsing 'SDK 3.7-99' failed"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
registerTest ("VersionParser", STR ("Parsing 'No version at all'"),
|
||||
[] (ITestResult* testResult) {
|
||||
auto version = VST3::Version::parse ("No version at all");
|
||||
if (version.getMajor () != 0 || version.getMinor () != 0 ||
|
||||
version.getSub () != 0 || version.getBuildnumber () != 0)
|
||||
{
|
||||
testResult->addErrorMessage (STR ("Parsing 'No version at all' failed"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,222 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Examples
|
||||
// Filename : public.sdk/source/vst/utility/testing.cpp
|
||||
// Created by : Steinberg, 04/2021
|
||||
// Description : Utility classes for custom testing in the vst validator
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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/vst/utility/testing.h"
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
|
||||
DEF_CLASS_IID (ITest)
|
||||
DEF_CLASS_IID (ITestSuite)
|
||||
DEF_CLASS_IID (ITestFactory)
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Vst {
|
||||
namespace {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct TestRegistry
|
||||
{
|
||||
struct TestWithContext
|
||||
{
|
||||
std::u16string desc;
|
||||
TestFuncWithContext func;
|
||||
};
|
||||
using Tests = std::vector<std::pair<std::string, IPtr<ITest>>>;
|
||||
using TestsWithContext = std::vector<std::pair<std::string, TestWithContext>>;
|
||||
|
||||
static TestRegistry& instance ()
|
||||
{
|
||||
static TestRegistry gInstance;
|
||||
return gInstance;
|
||||
}
|
||||
|
||||
Tests tests;
|
||||
TestsWithContext testsWithContext;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct TestBase : ITest
|
||||
{
|
||||
TestBase (const tchar* inDesc)
|
||||
{
|
||||
if (inDesc)
|
||||
desc = reinterpret_cast<const std::u16string::value_type*> (inDesc);
|
||||
}
|
||||
TestBase (const std::u16string& inDesc) : desc (inDesc) {}
|
||||
|
||||
virtual ~TestBase () = default;
|
||||
|
||||
bool PLUGIN_API setup () override { return true; }
|
||||
bool PLUGIN_API teardown () override { return true; }
|
||||
const tchar* PLUGIN_API getDescription () override
|
||||
{
|
||||
return reinterpret_cast<const tchar*> (desc.data ());
|
||||
}
|
||||
|
||||
tresult PLUGIN_API queryInterface (const TUID _iid, void** obj) override
|
||||
{
|
||||
QUERY_INTERFACE (_iid, obj, FUnknown::iid, FUnknown)
|
||||
QUERY_INTERFACE (_iid, obj, ITest::iid, ITest)
|
||||
*obj = nullptr;
|
||||
return kNoInterface;
|
||||
}
|
||||
uint32 PLUGIN_API addRef () override { return ++refCount; }
|
||||
uint32 PLUGIN_API release () override
|
||||
{
|
||||
if (--refCount == 0)
|
||||
{
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
return refCount;
|
||||
}
|
||||
|
||||
std::atomic<uint32> refCount {1};
|
||||
std::u16string desc;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct FuncTest : TestBase
|
||||
{
|
||||
FuncTest (const tchar* desc, const TestFunc& func) : TestBase (desc), func (func) {}
|
||||
FuncTest (const tchar* desc, TestFunc&& func) : TestBase (desc), func (std::move (func)) {}
|
||||
|
||||
bool PLUGIN_API run (ITestResult* testResult) override { return func (testResult); }
|
||||
|
||||
TestFunc func;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct FuncWithContextTest : TestBase
|
||||
{
|
||||
FuncWithContextTest (FUnknown* context, const std::u16string& desc,
|
||||
const TestFuncWithContext& func)
|
||||
: TestBase (desc), func (func), context (context)
|
||||
{
|
||||
}
|
||||
|
||||
bool PLUGIN_API run (ITestResult* testResult) override { return func (context, testResult); }
|
||||
|
||||
TestFuncWithContext func;
|
||||
FUnknown* context;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct TestFactoryImpl : ITestFactory
|
||||
{
|
||||
TestFactoryImpl () = default;
|
||||
virtual ~TestFactoryImpl () = default;
|
||||
|
||||
tresult PLUGIN_API createTests (FUnknown* context, ITestSuite* parentSuite) override
|
||||
{
|
||||
for (auto& t : TestRegistry::instance ().tests)
|
||||
{
|
||||
t.second->addRef ();
|
||||
parentSuite->addTest (t.first.data (), t.second);
|
||||
}
|
||||
for (auto& t : TestRegistry::instance ().testsWithContext)
|
||||
parentSuite->addTest (t.first.data (),
|
||||
new FuncWithContextTest (context, t.second.desc, t.second.func));
|
||||
return kResultTrue;
|
||||
}
|
||||
tresult PLUGIN_API queryInterface (const TUID _iid, void** obj) override
|
||||
{
|
||||
QUERY_INTERFACE (_iid, obj, FUnknown::iid, FUnknown)
|
||||
QUERY_INTERFACE (_iid, obj, ITestFactory::iid, ITestFactory)
|
||||
*obj = nullptr;
|
||||
return kNoInterface;
|
||||
}
|
||||
|
||||
uint32 PLUGIN_API addRef () override { return ++refCount; }
|
||||
|
||||
uint32 PLUGIN_API release () override
|
||||
{
|
||||
if (--refCount == 0)
|
||||
{
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
return refCount;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<uint32> refCount {1};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // anonymous
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void registerTest (FIDString name, const tchar* desc, const TestFunc& func)
|
||||
{
|
||||
registerTest (name, new FuncTest (desc, func));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void registerTest (FIDString name, const tchar* desc, TestFunc&& func)
|
||||
{
|
||||
registerTest (name, new FuncTest (desc, std::move (func)));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void registerTest (FIDString name, ITest* test)
|
||||
{
|
||||
assert (name != nullptr);
|
||||
TestRegistry::instance ().tests.push_back (std::make_pair (name, owned (test)));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void registerTest (FIDString name, const tchar* desc, const TestFuncWithContext& func)
|
||||
{
|
||||
std::u16string descStr;
|
||||
if (desc)
|
||||
descStr = reinterpret_cast<const std::u16string::value_type*> (desc);
|
||||
TestRegistry::instance ().testsWithContext.push_back (
|
||||
std::make_pair (name, TestRegistry::TestWithContext {descStr, func}));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void registerTest (FIDString name, const tchar* desc, TestFuncWithContext&& func)
|
||||
{
|
||||
std::u16string descStr;
|
||||
if (desc)
|
||||
descStr = reinterpret_cast<const std::u16string::value_type*> (desc);
|
||||
TestRegistry::instance ().testsWithContext.push_back (
|
||||
std::make_pair (name, TestRegistry::TestWithContext {descStr, std::move (func)}));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
FUnknown* createTestFactoryInstance (void*)
|
||||
{
|
||||
return new TestFactoryImpl;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
const FUID& getTestFactoryUID ()
|
||||
{
|
||||
static FUID uid = FUID::fromTUID (TestFactoryUID);
|
||||
return uid;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,195 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Examples
|
||||
// Filename : public.sdk/source/vst/utility/testing.h
|
||||
// Created by : Steinberg, 04/2021
|
||||
// Description : Utility classes for custom testing in the vst validator
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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/test/itest.h"
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** How to use the validator to run your own tests?
|
||||
\ingroup TestClass
|
||||
|
||||
It is possible to run your own tests when the validator checks your plug-in.
|
||||
|
||||
First you have to register a test factory in your plugin factory:
|
||||
|
||||
\code{.cpp}
|
||||
|
||||
#include "public.sdk/source/vst/utility/testing.h"
|
||||
|
||||
BEGIN_FACTORY_DEF(...
|
||||
|
||||
DEF_CLASS2 (Your Plugin Processor)
|
||||
DEF_CLASS2 (Your Plugin Controller)
|
||||
|
||||
DEF_CLASS2 (INLINE_UID_FROM_FUID (getTestFactoryUID ()), PClassInfo::kManyInstances, kTestClass,
|
||||
"Test Factory", 0, "", "", "", createTestFactoryInstance)
|
||||
|
||||
END_FACTORY
|
||||
|
||||
\endcode
|
||||
|
||||
Second: write your tests:
|
||||
|
||||
\code{.cpp}
|
||||
|
||||
#include "public.sdk/source/main/moduleinit.h"
|
||||
#include "public.sdk/source/vst/utility/testing.h"
|
||||
|
||||
static ModuleInitializer InitMyTests ([] () {
|
||||
registerTest ("MyTests", STR ("two plus two is four"), [] (ITestResult* testResult) {
|
||||
auto result = 2 + 2;
|
||||
if (result == 4)
|
||||
return true;
|
||||
testResult->addErrorMessage (STR ("Unexpected universe change where 2+2 != 4."));
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
\endcode
|
||||
|
||||
If you need access to your audio effect or edit controller you can write your tests as done in the
|
||||
adelay example:
|
||||
|
||||
\code{.cpp}
|
||||
|
||||
#include "public.sdk/source/main/moduleinit.h"
|
||||
#include "public.sdk/source/vst/testsuite/vsttestsuite.h"
|
||||
#include "public.sdk/source/vst/utility/testing.h"
|
||||
|
||||
static ModuleInitializer InitMyTests ([] () {
|
||||
registerTest ("MyTests", STR ("check one two three"), [] (FUnknown* context, ITestResult*
|
||||
testResult)
|
||||
{
|
||||
if (auto plugProvider = U::cast<ITestPlugProvider> (context))
|
||||
{
|
||||
auto controller = plugProvider->getController ();
|
||||
auto testController = U::cast<IDelayTestController> (controller);
|
||||
if (!controller)
|
||||
{
|
||||
testResult->addErrorMessage (String ("Unknown IEditController"));
|
||||
return false;
|
||||
}
|
||||
bool result = testController->doTest ();
|
||||
plugProvider->releasePlugIn (nullptr, controller);
|
||||
|
||||
return (result);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
\endcode
|
||||
|
||||
After that recompile and if the validator does not run automatically after every build, start the
|
||||
validator manually and let it check your plug-in.
|
||||
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** create a Test Factory instance */
|
||||
FUnknown* createTestFactoryInstance (void*);
|
||||
|
||||
/** the test factory class ID */
|
||||
static const DECLARE_UID (TestFactoryUID, 0x70AA33A3, 0x1AE74B24, 0xB726F784, 0xB706C080);
|
||||
|
||||
/** get the test factory class ID */
|
||||
const FUID& getTestFactoryUID ();
|
||||
|
||||
/** simple test function */
|
||||
using TestFunc = std::function<bool (ITestResult*)>;
|
||||
/** register a simple test function */
|
||||
void registerTest (FIDString name, const tchar* desc, const TestFunc& func);
|
||||
/** register a simple test function */
|
||||
void registerTest (FIDString name, const tchar* desc, TestFunc&& func);
|
||||
|
||||
/** test function with context pointer */
|
||||
using TestFuncWithContext = std::function<bool (FUnknown*, ITestResult*)>;
|
||||
/** register a test function with context pointer */
|
||||
void registerTest (FIDString name, const tchar* desc, const TestFuncWithContext& func);
|
||||
/** register a test function with context pointer */
|
||||
void registerTest (FIDString name, const tchar* desc, TestFuncWithContext&& func);
|
||||
|
||||
/** register a custom test, the test object will be owned by the implementation */
|
||||
void registerTest (FIDString name, ITest* test);
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Test {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
|
||||
inline constexpr bool equal (const T& lhs, const T& rhs) noexcept
|
||||
{
|
||||
return std::abs (lhs - rhs) <= std::numeric_limits<T>::epsilon ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
|
||||
inline constexpr bool equal (const T& lhs, const T& rhs) noexcept
|
||||
{
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <typename T>
|
||||
inline constexpr bool notEqual (const T& lhs, const T& rhs) noexcept
|
||||
{
|
||||
return equal (lhs, rhs) == false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <typename T>
|
||||
inline constexpr bool maxDiff (const T& lhs, const T& rhs, const T& maxDiff) noexcept
|
||||
{
|
||||
return std::abs (lhs - rhs) <= maxDiff;
|
||||
}
|
||||
|
||||
#ifndef SMTG_DISABLE_VST_TEST_MACROS
|
||||
|
||||
#ifndef SMTG_MAKE_STRING_PRIVATE_DONT_USE
|
||||
#define SMTG_MAKE_STRING_PRIVATE_DONT_USE(x) #x
|
||||
#define SMTG_MAKE_STRING(x) SMTG_MAKE_STRING_PRIVATE_DONT_USE (x)
|
||||
#endif // SMTG_MAKE_STRING_PRIVATE_DONT_USE
|
||||
|
||||
#define EXPECT(condition) \
|
||||
{ \
|
||||
if (!(condition)) \
|
||||
{ \
|
||||
testResult->addErrorMessage (STR (__FILE__ ":" SMTG_MAKE_STRING ( \
|
||||
__LINE__) ": error: " SMTG_MAKE_STRING (condition))); \
|
||||
return false; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define EXPECT_TRUE(condition) EXPECT (condition)
|
||||
#define EXPECT_FALSE(condition) EXPECT (!condition)
|
||||
#define EXPECT_EQ(var1, var2) EXPECT ((var1 == var2))
|
||||
#define EXPECT_NE(var1, var2) EXPECT ((var1 != var2))
|
||||
|
||||
#endif // SMTG_DISABLE_VST_TEST_MACROS
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Test
|
||||
} // Vst
|
||||
} // Steinberg
|
||||
@@ -0,0 +1,266 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
//
|
||||
// Category : Helpers
|
||||
// Filename : public.sdk/source/vst/utility/uid.h
|
||||
// Created by : Steinberg, 08/2016
|
||||
// Description : UID
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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 "optional.h"
|
||||
#include "pluginterfaces/base/funknown.h"
|
||||
#include <string>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VST3 {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct UID
|
||||
{
|
||||
#if defined(SMTG_OS_WINDOWS) && SMTG_OS_WINDOWS == 1
|
||||
static constexpr bool defaultComFormat = true;
|
||||
#else
|
||||
static constexpr bool defaultComFormat = false;
|
||||
#endif
|
||||
|
||||
using TUID = Steinberg::TUID;
|
||||
|
||||
constexpr UID () noexcept = default;
|
||||
UID (uint32_t l1, uint32_t l2, uint32_t l3, uint32_t l4, bool comFormat = defaultComFormat)
|
||||
noexcept;
|
||||
UID (const TUID& uid) noexcept;
|
||||
UID (const UID& uid) noexcept;
|
||||
UID& operator= (const UID& uid) noexcept;
|
||||
UID& operator= (const TUID& uid) noexcept;
|
||||
|
||||
constexpr const TUID& data () const noexcept;
|
||||
constexpr size_t size () const noexcept;
|
||||
|
||||
std::string toString (bool comFormat = defaultComFormat) const noexcept;
|
||||
|
||||
template<typename StringT>
|
||||
static Optional<UID> fromString (const StringT& str,
|
||||
bool comFormat = defaultComFormat) noexcept;
|
||||
|
||||
static UID fromTUID (const TUID _uid) noexcept;
|
||||
//------------------------------------------------------------------------
|
||||
private:
|
||||
Steinberg::TUID _data {};
|
||||
|
||||
struct GUID
|
||||
{
|
||||
uint32_t Data1;
|
||||
uint16_t Data2;
|
||||
uint16_t Data3;
|
||||
uint8_t Data4[8];
|
||||
};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline bool operator== (const UID& uid1, const UID& uid2)
|
||||
{
|
||||
const uint64_t* p1 = reinterpret_cast<const uint64_t*> (uid1.data ());
|
||||
const uint64_t* p2 = reinterpret_cast<const uint64_t*> (uid2.data ());
|
||||
return p1[0] == p2[0] && p1[1] == p2[1];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline bool operator!= (const UID& uid1, const UID& uid2)
|
||||
{
|
||||
return !(uid1 == uid2);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline bool operator< (const UID& uid1, const UID& uid2)
|
||||
{
|
||||
const uint64_t* p1 = reinterpret_cast<const uint64_t*> (uid1.data ());
|
||||
const uint64_t* p2 = reinterpret_cast<const uint64_t*> (uid2.data ());
|
||||
return (p1[0] < p2[0]) && (p1[1] < p2[1]);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline UID::UID (uint32_t l1, uint32_t l2, uint32_t l3, uint32_t l4, bool comFormat) noexcept
|
||||
{
|
||||
if (comFormat)
|
||||
{
|
||||
_data[0] = static_cast<int8_t> ((l1 & 0x000000FF));
|
||||
_data[1] = static_cast<int8_t> ((l1 & 0x0000FF00) >> 8);
|
||||
_data[2] = static_cast<int8_t> ((l1 & 0x00FF0000) >> 16);
|
||||
_data[3] = static_cast<int8_t> ((l1 & 0xFF000000) >> 24);
|
||||
_data[4] = static_cast<int8_t> ((l2 & 0x00FF0000) >> 16);
|
||||
_data[5] = static_cast<int8_t> ((l2 & 0xFF000000) >> 24);
|
||||
_data[6] = static_cast<int8_t> ((l2 & 0x000000FF));
|
||||
_data[7] = static_cast<int8_t> ((l2 & 0x0000FF00) >> 8);
|
||||
_data[8] = static_cast<int8_t> ((l3 & 0xFF000000) >> 24);
|
||||
_data[9] = static_cast<int8_t> ((l3 & 0x00FF0000) >> 16);
|
||||
_data[10] = static_cast<int8_t> ((l3 & 0x0000FF00) >> 8);
|
||||
_data[11] = static_cast<int8_t> ((l3 & 0x000000FF));
|
||||
_data[12] = static_cast<int8_t> ((l4 & 0xFF000000) >> 24);
|
||||
_data[13] = static_cast<int8_t> ((l4 & 0x00FF0000) >> 16);
|
||||
_data[14] = static_cast<int8_t> ((l4 & 0x0000FF00) >> 8);
|
||||
_data[15] = static_cast<int8_t> ((l4 & 0x000000FF));
|
||||
}
|
||||
else
|
||||
{
|
||||
_data[0] = static_cast<int8_t> ((l1 & 0xFF000000) >> 24);
|
||||
_data[1] = static_cast<int8_t> ((l1 & 0x00FF0000) >> 16);
|
||||
_data[2] = static_cast<int8_t> ((l1 & 0x0000FF00) >> 8);
|
||||
_data[3] = static_cast<int8_t> ((l1 & 0x000000FF));
|
||||
_data[4] = static_cast<int8_t> ((l2 & 0xFF000000) >> 24);
|
||||
_data[5] = static_cast<int8_t> ((l2 & 0x00FF0000) >> 16);
|
||||
_data[6] = static_cast<int8_t> ((l2 & 0x0000FF00) >> 8);
|
||||
_data[7] = static_cast<int8_t> ((l2 & 0x000000FF));
|
||||
_data[8] = static_cast<int8_t> ((l3 & 0xFF000000) >> 24);
|
||||
_data[9] = static_cast<int8_t> ((l3 & 0x00FF0000) >> 16);
|
||||
_data[10] = static_cast<int8_t> ((l3 & 0x0000FF00) >> 8);
|
||||
_data[11] = static_cast<int8_t> ((l3 & 0x000000FF));
|
||||
_data[12] = static_cast<int8_t> ((l4 & 0xFF000000) >> 24);
|
||||
_data[13] = static_cast<int8_t> ((l4 & 0x00FF0000) >> 16);
|
||||
_data[14] = static_cast<int8_t> ((l4 & 0x0000FF00) >> 8);
|
||||
_data[15] = static_cast<int8_t> ((l4 & 0x000000FF));
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline UID::UID (const TUID& uid) noexcept
|
||||
{
|
||||
*this = uid;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline UID::UID (const UID& uid) noexcept
|
||||
{
|
||||
*this = uid;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline UID& UID::operator= (const UID& uid) noexcept
|
||||
{
|
||||
*this = uid.data ();
|
||||
return *this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline UID& UID::operator= (const TUID& uid) noexcept
|
||||
{
|
||||
memcpy (_data, reinterpret_cast<const void*>(uid), 16);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline constexpr auto UID::data () const noexcept -> const TUID&
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline constexpr size_t UID::size () const noexcept
|
||||
{
|
||||
return sizeof (TUID);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline std::string UID::toString (bool comFormat) const noexcept
|
||||
{
|
||||
std::string result;
|
||||
result.reserve (32);
|
||||
if (comFormat)
|
||||
{
|
||||
const auto& g = reinterpret_cast<const GUID*> (_data);
|
||||
|
||||
char tmp[21] {};
|
||||
snprintf (tmp, 21, "%08X%04X%04X", g->Data1, g->Data2, g->Data3);
|
||||
result = tmp;
|
||||
|
||||
for (uint32_t i = 0; i < 8; ++i)
|
||||
{
|
||||
char s[3] {};
|
||||
snprintf (s, 3, "%02X", g->Data4[i]);
|
||||
result += s;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32_t i = 0; i < 16; ++i)
|
||||
{
|
||||
char s[3] {};
|
||||
snprintf (s, 3, "%02X", static_cast<uint8_t> (_data[i]));
|
||||
result += s;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template<typename StringT>
|
||||
inline Optional<UID> UID::fromString (const StringT& str, bool comFormat) noexcept
|
||||
{
|
||||
if (str.length () != 32)
|
||||
return {};
|
||||
// TODO: this is a copy from FUID. there are no input validation checks !!!
|
||||
if (comFormat)
|
||||
{
|
||||
TUID uid {};
|
||||
GUID g;
|
||||
char s[33];
|
||||
|
||||
strcpy (s, str.data ());
|
||||
s[8] = 0;
|
||||
sscanf (s, "%x", &g.Data1);
|
||||
strcpy (s, str.data () + 8);
|
||||
s[4] = 0;
|
||||
sscanf (s, "%hx", &g.Data2);
|
||||
strcpy (s, str.data () + 12);
|
||||
s[4] = 0;
|
||||
sscanf (s, "%hx", &g.Data3);
|
||||
|
||||
memcpy (uid, &g, 8);
|
||||
|
||||
for (uint32_t i = 8; i < 16; ++i)
|
||||
{
|
||||
char s2[3] {};
|
||||
s2[0] = str[i * 2];
|
||||
s2[1] = str[i * 2 + 1];
|
||||
|
||||
int32_t d = 0;
|
||||
sscanf (s2, "%2x", &d);
|
||||
uid[i] = static_cast<char> (d);
|
||||
}
|
||||
return {uid};
|
||||
}
|
||||
else
|
||||
{
|
||||
TUID uid {};
|
||||
for (uint32_t i = 0; i < 16; ++i)
|
||||
{
|
||||
char s[3] {};
|
||||
s[0] = str[i * 2];
|
||||
s[1] = str[i * 2 + 1];
|
||||
|
||||
int32_t d = 0;
|
||||
sscanf (s, "%2x", &d);
|
||||
uid[i] = static_cast<char> (d);
|
||||
}
|
||||
return {uid};
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline UID UID::fromTUID (const TUID _uid) noexcept
|
||||
{
|
||||
UID result;
|
||||
memcpy (result._data, reinterpret_cast<const void*>(_uid), 16);
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // VST3
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,152 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST3 SDK
|
||||
// Filename : public.sdk/source/vst/utility/versionparser.h
|
||||
// Created by : Steinberg, 04/2018
|
||||
// Description : version parser 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 "public.sdk/source/vst/utility/optional.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#include <string_view>
|
||||
#define SMTG_VERSIONPARSER_USE_STRINGVIEW
|
||||
#else
|
||||
#include <string>
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VST3 {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct Version
|
||||
{
|
||||
private:
|
||||
enum
|
||||
{
|
||||
Major,
|
||||
Minor,
|
||||
Sub,
|
||||
BuildNumber
|
||||
};
|
||||
|
||||
public:
|
||||
#ifdef SMTG_VERSIONPARSER_USE_STRINGVIEW
|
||||
using StringType = std::string_view;
|
||||
#else
|
||||
using StringType = std::string;
|
||||
#endif
|
||||
Version (uint32_t inMajor = 0, uint32_t inMinor = 0, uint32_t inSub = 0,
|
||||
uint32_t inBuildnumber = 0)
|
||||
{
|
||||
setMajor (inMajor);
|
||||
setMinor (inMinor);
|
||||
setSub (inSub);
|
||||
setBuildnumber (inBuildnumber);
|
||||
}
|
||||
|
||||
void setMajor (uint32_t v) { storage[Major] = v; }
|
||||
void setMinor (uint32_t v) { storage[Minor] = v; }
|
||||
void setSub (uint32_t v) { storage[Sub] = v; }
|
||||
void setBuildnumber (uint32_t v) { storage[BuildNumber] = v; }
|
||||
|
||||
uint32_t getMajor () const { return storage[Major]; }
|
||||
uint32_t getMinor () const { return storage[Minor]; }
|
||||
uint32_t getSub () const { return storage[Sub]; }
|
||||
uint32_t getBuildnumber () const { return storage[BuildNumber]; }
|
||||
|
||||
bool operator> (const Version& v) const
|
||||
{
|
||||
if (getMajor () < v.getMajor ())
|
||||
return false;
|
||||
if (getMajor () > v.getMajor ())
|
||||
return true;
|
||||
if (getMinor () < v.getMinor ())
|
||||
return false;
|
||||
if (getMinor () > v.getMinor ())
|
||||
return true;
|
||||
if (getSub () < v.getSub ())
|
||||
return false;
|
||||
if (getSub () > v.getSub ())
|
||||
return true;
|
||||
if (getBuildnumber () < v.getBuildnumber ())
|
||||
return false;
|
||||
return getBuildnumber () > v.getBuildnumber ();
|
||||
}
|
||||
|
||||
static Version parse (StringType str)
|
||||
{
|
||||
// skip non digits in the front
|
||||
auto it = std::find_if (str.begin (), str.end (),
|
||||
[] (const auto& c) { return std::isdigit (c); });
|
||||
if (it == str.end ())
|
||||
return {};
|
||||
#ifdef SMTG_VERSIONPARSER_USE_STRINGVIEW
|
||||
str = StringType (&(*it), std::distance (it, str.end ()));
|
||||
#else
|
||||
str = StringType (it, str.end ());
|
||||
#endif
|
||||
Version version {};
|
||||
auto part = static_cast<size_t> (Major);
|
||||
StringType::size_type index;
|
||||
while (!str.empty ())
|
||||
{
|
||||
index = str.find_first_of ('.');
|
||||
if (index == StringType::npos)
|
||||
{
|
||||
// skip non digits in the back
|
||||
auto itBack = std::find_if (str.begin (), str.end (),
|
||||
[] (const auto& c) { return !std::isdigit (c); });
|
||||
index = std::distance (str.begin (), itBack);
|
||||
if (index == 0)
|
||||
break;
|
||||
str = {str.data (), index};
|
||||
index = str.size ();
|
||||
}
|
||||
StringType numberStr (str.data (), index);
|
||||
if (auto n = toNumber (numberStr))
|
||||
version.storage[part] = *n;
|
||||
if (++part > BuildNumber)
|
||||
break;
|
||||
if (str.size () - index == 0)
|
||||
break;
|
||||
++index;
|
||||
str = {str.data () + index, str.size () - index};
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint32_t, 4> storage {};
|
||||
|
||||
static Optional<int32_t> toNumber (StringType str)
|
||||
{
|
||||
if (str.size () > 9)
|
||||
str = {str.data (), 9};
|
||||
int32_t result = 0;
|
||||
for (const auto& c : str)
|
||||
{
|
||||
if (c < 48 || c > 57)
|
||||
return {};
|
||||
result *= 10;
|
||||
result += c - 48;
|
||||
}
|
||||
return Optional<int32_t> {result};
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // VST3
|
||||
@@ -0,0 +1,613 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST3 SDK
|
||||
// Filename : public.sdk/source/vst/utility/vst2persistence.cpp
|
||||
// Created by : Steinberg, 12/2019
|
||||
// Description : vst2 persistence 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 "public.sdk/source/vst/utility/vst2persistence.h"
|
||||
#include "pluginterfaces/base/fplatform.h"
|
||||
#include <limits>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VST3 {
|
||||
namespace {
|
||||
namespace IO {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
enum class Error
|
||||
{
|
||||
NoError,
|
||||
Unknown,
|
||||
EndOfFile,
|
||||
BufferToBig,
|
||||
NotAllowed,
|
||||
InvalidArgument,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
enum class SeekMode
|
||||
{
|
||||
Set,
|
||||
End,
|
||||
Current
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct Result
|
||||
{
|
||||
Error error {Error::Unknown};
|
||||
uint64_t bytes {0u};
|
||||
|
||||
Result () noexcept = default;
|
||||
Result (Error error, uint64_t bytes = 0) noexcept : error (error), bytes (bytes) {}
|
||||
|
||||
operator bool () const noexcept { return error == Error::NoError; }
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct ReadBufferDesc
|
||||
{
|
||||
const uint64_t bytes;
|
||||
void* ptr;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct WriteBufferDesc
|
||||
{
|
||||
const uint64_t bytes;
|
||||
const void* ptr;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <uint32_t StreamByteOrder>
|
||||
class ByteOrderStream
|
||||
{
|
||||
public:
|
||||
//------------------------------------------------------------------------
|
||||
ByteOrderStream (Steinberg::IBStream& stream) noexcept : stream (stream) {}
|
||||
ByteOrderStream (ByteOrderStream&&) noexcept = delete;
|
||||
ByteOrderStream& operator= (ByteOrderStream&&) noexcept = delete;
|
||||
ByteOrderStream (const ByteOrderStream&) noexcept = delete;
|
||||
ByteOrderStream& operator= (const ByteOrderStream&) noexcept = delete;
|
||||
|
||||
inline Result operator<< (const std::string& input) noexcept;
|
||||
inline Result operator>> (std::string& output) noexcept;
|
||||
|
||||
template <typename T>
|
||||
inline Result operator<< (const T& input) noexcept;
|
||||
template <typename T>
|
||||
inline Result operator>> (T& output) const noexcept;
|
||||
|
||||
inline Result read (const ReadBufferDesc& buffer) const noexcept;
|
||||
inline Result write (const WriteBufferDesc& buffer) noexcept;
|
||||
inline Result seek (SeekMode mode, int64_t bytes) const noexcept;
|
||||
inline Result tell () const noexcept;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
private:
|
||||
template <size_t size>
|
||||
inline Result swapAndWrite (const uint8_t* buffer) noexcept;
|
||||
inline void swap (uint8_t* buffer, uint64_t size) const noexcept;
|
||||
Steinberg::IBStream& stream;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
using LittleEndianStream = ByteOrderStream<kLittleEndian>;
|
||||
using BigEndianStream = ByteOrderStream<kBigEndian>;
|
||||
using NativeEndianStream = ByteOrderStream<BYTEORDER>;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <uint32_t StreamByteOrder>
|
||||
inline Result ByteOrderStream<StreamByteOrder>::read (const ReadBufferDesc& buffer) const noexcept
|
||||
{
|
||||
if (buffer.bytes > static_cast<uint64_t> (std::numeric_limits<int32_t>::max ()))
|
||||
return Result (Error::BufferToBig);
|
||||
Steinberg::int32 readBytes = 0;
|
||||
auto tres = stream.read (buffer.ptr, static_cast<Steinberg::int32> (buffer.bytes), &readBytes);
|
||||
if (tres != Steinberg::kResultTrue)
|
||||
return Result (Error::Unknown);
|
||||
assert (readBytes >= 0);
|
||||
return Result {Error::NoError, static_cast<uint64_t> (readBytes)};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <uint32_t StreamByteOrder>
|
||||
inline Result ByteOrderStream<StreamByteOrder>::write (const WriteBufferDesc& buffer) noexcept
|
||||
{
|
||||
if (buffer.bytes > static_cast<uint64_t> (std::numeric_limits<int32_t>::max ()))
|
||||
return Result (Error::BufferToBig);
|
||||
Steinberg::int32 writtenBytes = 0;
|
||||
auto tres = stream.write (const_cast<void*> (buffer.ptr),
|
||||
static_cast<Steinberg::int32> (buffer.bytes), &writtenBytes);
|
||||
if (tres != Steinberg::kResultTrue)
|
||||
return Result (Error::Unknown);
|
||||
assert (writtenBytes >= 0);
|
||||
return Result {Error::NoError, static_cast<uint64_t> (writtenBytes)};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <uint32_t StreamByteOrder>
|
||||
inline Result ByteOrderStream<StreamByteOrder>::seek (SeekMode mode, int64_t bytes) const noexcept
|
||||
{
|
||||
Steinberg::int32 seekMode = 0;
|
||||
switch (mode)
|
||||
{
|
||||
case SeekMode::Set: seekMode = Steinberg::IBStream::kIBSeekSet; break;
|
||||
case SeekMode::Current: seekMode = Steinberg::IBStream::kIBSeekCur; break;
|
||||
case SeekMode::End: seekMode = Steinberg::IBStream::kIBSeekEnd; break;
|
||||
}
|
||||
Steinberg::int64 seekRes = 0;
|
||||
auto tres = stream.seek (static_cast<Steinberg::int64> (bytes), seekMode, &seekRes);
|
||||
if (tres != Steinberg::kResultTrue || seekRes < 0)
|
||||
return Result {Error::Unknown};
|
||||
return Result (Error::NoError, static_cast<uint64_t> (seekRes));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <uint32_t StreamByteOrder>
|
||||
inline Result ByteOrderStream<StreamByteOrder>::tell () const noexcept
|
||||
{
|
||||
Steinberg::int64 tellRes = 0;
|
||||
auto tres = stream.tell (&tellRes);
|
||||
if (tres != Steinberg::kResultTrue || tellRes < 0)
|
||||
return Result {Error::Unknown};
|
||||
return Result {Error::NoError, static_cast<uint64_t> (tellRes)};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <uint32_t StreamByteOrder>
|
||||
inline Result ByteOrderStream<StreamByteOrder>::operator<< (const std::string& input) noexcept
|
||||
{
|
||||
auto res = *this << static_cast<uint64_t> (input.length ());
|
||||
if (!res)
|
||||
return res;
|
||||
res = stream.write (const_cast<void*> (static_cast<const void*> (input.data ())),
|
||||
static_cast<Steinberg::int32> (input.length ()));
|
||||
res.bytes += sizeof (uint64_t);
|
||||
return res;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <uint32_t StreamByteOrder>
|
||||
inline Result ByteOrderStream<StreamByteOrder>::operator>> (std::string& output) noexcept
|
||||
{
|
||||
uint64_t length;
|
||||
auto res = *this >> length;
|
||||
if (!res)
|
||||
return res;
|
||||
output.resize (length);
|
||||
if (length > 0)
|
||||
{
|
||||
res = stream.read (&output.front (), static_cast<Steinberg::int32> (length));
|
||||
res.bytes += sizeof (uint64_t);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <uint32_t StreamByteOrder>
|
||||
template <typename T>
|
||||
inline Result ByteOrderStream<StreamByteOrder>::operator<< (const T& input) noexcept
|
||||
{
|
||||
static_assert (std::is_standard_layout<T>::value, "Only standard layout types allowed");
|
||||
// with C++17: if constexpr (StreamByteOrder == BYTEORDER)
|
||||
if (constexpr bool tmp = (StreamByteOrder == BYTEORDER))
|
||||
return write (WriteBufferDesc {sizeof (T), static_cast<const void*> (&input)});
|
||||
|
||||
return swapAndWrite<sizeof (T)> (reinterpret_cast<const uint8_t*> (&input));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <uint32_t StreamByteOrder>
|
||||
template <typename T>
|
||||
inline Result ByteOrderStream<StreamByteOrder>::operator>> (T& output) const noexcept
|
||||
{
|
||||
static_assert (std::is_standard_layout<T>::value, "Only standard layout types allowed");
|
||||
auto res = read (ReadBufferDesc {sizeof (T), &output});
|
||||
// with C++17: if constexpr (StreamByteOrder == BYTEORDER)
|
||||
if (constexpr bool tmp = (StreamByteOrder == BYTEORDER))
|
||||
return res;
|
||||
|
||||
swap (reinterpret_cast<uint8_t*> (&output), res.bytes);
|
||||
return res;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <uint32_t StreamByteOrder>
|
||||
template <size_t _size>
|
||||
inline Result ByteOrderStream<StreamByteOrder>::swapAndWrite (const uint8_t* buffer) noexcept
|
||||
{
|
||||
// with C++17: if constexpr (_size > 1)
|
||||
if (constexpr bool tmp2 = (_size > 1))
|
||||
{
|
||||
int8_t tmp[_size];
|
||||
|
||||
constexpr auto halfSize = _size / 2;
|
||||
auto size = _size;
|
||||
auto low = buffer;
|
||||
auto high = buffer + size - 1;
|
||||
|
||||
while (size > halfSize)
|
||||
{
|
||||
tmp[size - 2] = buffer[(_size - size) + 1];
|
||||
tmp[(_size - size) + 1] = buffer[size - 2];
|
||||
tmp[_size - size] = *high;
|
||||
tmp[size - 1] = *low;
|
||||
low += 2;
|
||||
high -= 2;
|
||||
size -= 2;
|
||||
}
|
||||
return write (WriteBufferDesc {_size, tmp});
|
||||
}
|
||||
return write (WriteBufferDesc {1, buffer});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <uint32_t StreamByteOrder>
|
||||
inline void ByteOrderStream<StreamByteOrder>::swap (uint8_t* buffer, uint64_t size) const noexcept
|
||||
{
|
||||
if (size < 2)
|
||||
return;
|
||||
auto low = buffer;
|
||||
auto high = buffer + size - 1;
|
||||
while (size >= 2)
|
||||
{
|
||||
auto tmp = *low;
|
||||
*low = *high;
|
||||
*high = tmp;
|
||||
low += 2;
|
||||
high -= 2;
|
||||
size -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // IO
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
constexpr int32_t cMagic = 'CcnK';
|
||||
constexpr int32_t bankMagic = 'FxBk';
|
||||
constexpr int32_t privateChunkID = 'VstW';
|
||||
constexpr int32_t chunkBankMagic = 'FBCh';
|
||||
constexpr int32_t programMagic = 'FxCk';
|
||||
constexpr int32_t chunkProgramMagic = 'FPCh';
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
Optional<VST3::Vst2xProgram> loadProgram (const IO::BigEndianStream& state,
|
||||
const Optional<int32_t>& vst2xUniqueID)
|
||||
{
|
||||
Vst2xProgram program;
|
||||
int32_t id;
|
||||
if (!(state >> id))
|
||||
return {};
|
||||
if (id != cMagic)
|
||||
return {};
|
||||
int32_t bankSize;
|
||||
if (!(state >> bankSize))
|
||||
return {};
|
||||
int32_t fxMagic;
|
||||
if (!(state >> fxMagic))
|
||||
return {};
|
||||
if (!(fxMagic == programMagic || fxMagic == chunkProgramMagic))
|
||||
return {};
|
||||
int32_t formatVersion;
|
||||
if (!(state >> formatVersion))
|
||||
return {};
|
||||
int32_t fxId;
|
||||
if (!(state >> fxId))
|
||||
return {};
|
||||
if (vst2xUniqueID && fxId != *vst2xUniqueID)
|
||||
return {};
|
||||
int32_t fxVersion;
|
||||
if (!(state >> fxVersion))
|
||||
return {};
|
||||
int32_t numParams;
|
||||
if (!(state >> numParams))
|
||||
return {};
|
||||
if (numParams < 0)
|
||||
return {};
|
||||
char name[29];
|
||||
if (!state.read ({28, name}))
|
||||
return {};
|
||||
name[28] = 0;
|
||||
program.name = name;
|
||||
program.fxUniqueID = fxId;
|
||||
program.fxVersion = fxVersion;
|
||||
if (fxMagic == chunkProgramMagic)
|
||||
{
|
||||
uint32_t chunkSize;
|
||||
if (!(state >> chunkSize))
|
||||
return {};
|
||||
program.chunk.resize (chunkSize);
|
||||
if (!state.read ({chunkSize, program.chunk.data ()}))
|
||||
return {};
|
||||
}
|
||||
else
|
||||
{
|
||||
program.values.resize (numParams);
|
||||
float paramValue;
|
||||
for (int32_t i = 0; i < numParams; ++i)
|
||||
{
|
||||
if (!(state >> paramValue))
|
||||
return {};
|
||||
program.values[i] = paramValue;
|
||||
}
|
||||
}
|
||||
return {std::move (program)};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool loadPrograms (Steinberg::IBStream& stream, Vst2xState::Programs& programs,
|
||||
const Optional<int32_t>& vst2xUniqueID)
|
||||
{
|
||||
IO::BigEndianStream state (stream);
|
||||
|
||||
for (auto& program : programs)
|
||||
{
|
||||
if (auto prg = loadProgram (state, vst2xUniqueID))
|
||||
std::swap (program, *prg);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <typename SizeT, typename StreamT, typename Proc>
|
||||
IO::Error streamSizeWriter (StreamT& stream, Proc proc)
|
||||
{
|
||||
auto startPos = stream.tell ();
|
||||
if (startPos.error != IO::Error::NoError)
|
||||
return startPos.error;
|
||||
auto res = stream << static_cast<SizeT> (0); // placeholder
|
||||
if (!res)
|
||||
return res.error;
|
||||
auto procRes = proc ();
|
||||
if (procRes != IO::Error::NoError)
|
||||
return procRes;
|
||||
auto endPos = stream.tell ();
|
||||
if (endPos.error != IO::Error::NoError)
|
||||
return endPos.error;
|
||||
auto size = (endPos.bytes - startPos.bytes) - 4;
|
||||
auto typeSize = static_cast<SizeT> (size);
|
||||
if (size != static_cast<uint64_t> (typeSize))
|
||||
return IO::Error::Unknown;
|
||||
res = stream.seek (IO::SeekMode::Set, startPos.bytes);
|
||||
if (!res)
|
||||
return res.error;
|
||||
res = (stream << typeSize);
|
||||
if (!res)
|
||||
return res.error;
|
||||
res = stream.seek (IO::SeekMode::Set, endPos.bytes);
|
||||
return res.error;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <typename StreamT>
|
||||
IO::Error writePrograms (StreamT& stream, const Vst2xState::Programs& programs)
|
||||
{
|
||||
for (const auto& program : programs)
|
||||
{
|
||||
auto res = stream << cMagic;
|
||||
if (!res)
|
||||
return res.error;
|
||||
res = streamSizeWriter<int32_t> (stream, [&] () {
|
||||
bool writeChunk = !program.chunk.empty ();
|
||||
if (!(res = stream << (writeChunk ? chunkProgramMagic : programMagic)))
|
||||
return res.error;
|
||||
int32_t version = 1;
|
||||
if (!(res = stream << version))
|
||||
return res.error;
|
||||
if (!(res = stream << program.fxUniqueID))
|
||||
return res.error;
|
||||
int32_t fxVersion = program.fxVersion;
|
||||
if (!(res = stream << fxVersion))
|
||||
return res.error;
|
||||
uint32_t numParams = static_cast<uint32_t> (program.values.size ());
|
||||
if (!(res = stream << numParams))
|
||||
return res.error;
|
||||
auto programName = program.name;
|
||||
programName.resize (28);
|
||||
for (auto c : programName)
|
||||
{
|
||||
if (!(res = stream << c))
|
||||
return res.error;
|
||||
}
|
||||
if (writeChunk)
|
||||
{
|
||||
if (!(res = stream << static_cast<int32_t> (program.chunk.size ())))
|
||||
return res.error;
|
||||
if (!(res = stream.write ({program.chunk.size (), program.chunk.data ()})))
|
||||
return res.error;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto value : program.values)
|
||||
{
|
||||
if (!(res = stream << value))
|
||||
return res.error;
|
||||
}
|
||||
}
|
||||
return IO::Error::NoError;
|
||||
});
|
||||
if (res.error != IO::Error::NoError)
|
||||
return res.error;
|
||||
}
|
||||
return IO::Error::NoError;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // anonymous
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
Optional<Vst2xState> tryVst2StateLoad (Steinberg::IBStream& stream,
|
||||
Optional<int32_t> vst2xUniqueID) noexcept
|
||||
{
|
||||
Vst2xState result;
|
||||
|
||||
IO::BigEndianStream state (stream);
|
||||
int32_t version;
|
||||
int32_t size;
|
||||
int32_t id;
|
||||
if (!(state >> id))
|
||||
return {};
|
||||
if (id == privateChunkID)
|
||||
{
|
||||
if (!(state >> size))
|
||||
return {};
|
||||
if (!(state >> version))
|
||||
return {};
|
||||
int32_t bypass;
|
||||
if (!(state >> bypass))
|
||||
return {};
|
||||
result.isBypassed = bypass ? true : false;
|
||||
if (!(state >> id))
|
||||
return {};
|
||||
}
|
||||
if (id != cMagic)
|
||||
return {};
|
||||
int32_t bankSize;
|
||||
if (!(state >> bankSize))
|
||||
return {};
|
||||
int32_t fxMagic;
|
||||
if (!(state >> fxMagic))
|
||||
return {};
|
||||
if (!(fxMagic == bankMagic || fxMagic == chunkBankMagic))
|
||||
return {};
|
||||
int32_t bankVersion;
|
||||
if (!(state >> bankVersion))
|
||||
return {};
|
||||
int32_t fxId;
|
||||
if (!(state >> fxId))
|
||||
return {};
|
||||
if (vst2xUniqueID && fxId != *vst2xUniqueID)
|
||||
return {};
|
||||
result.fxUniqueID = fxId;
|
||||
int32_t fxVersion;
|
||||
if (!(state >> fxVersion))
|
||||
return {};
|
||||
result.fxVersion = fxVersion;
|
||||
|
||||
int32_t numPrograms;
|
||||
if (!(state >> numPrograms))
|
||||
return {};
|
||||
if (numPrograms < 1 && fxMagic == bankMagic)
|
||||
return {};
|
||||
|
||||
int32_t currentProgram = 0;
|
||||
if (bankVersion >= 1)
|
||||
{
|
||||
if (!(state >> currentProgram))
|
||||
return {};
|
||||
state.seek (IO::SeekMode::Current, 124); // future
|
||||
}
|
||||
result.currentProgram = currentProgram;
|
||||
if (fxMagic == bankMagic)
|
||||
{
|
||||
result.programs.resize (numPrograms);
|
||||
if (!loadPrograms (stream, result.programs, vst2xUniqueID))
|
||||
return {};
|
||||
assert (static_cast<int32_t> (result.programs.size ()) > currentProgram);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t chunkSize;
|
||||
if (!(state >> chunkSize))
|
||||
return {};
|
||||
if (chunkSize == 0)
|
||||
return {};
|
||||
result.chunk.resize (chunkSize);
|
||||
if (!state.read ({chunkSize, result.chunk.data ()}))
|
||||
return {};
|
||||
}
|
||||
return {std::move (result)};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
bool writeVst2State (const Vst2xState& state, Steinberg::IBStream& _stream,
|
||||
bool writeBypassState) noexcept
|
||||
{
|
||||
IO::BigEndianStream stream (_stream);
|
||||
if (writeBypassState)
|
||||
{
|
||||
if (!(stream << privateChunkID))
|
||||
return false;
|
||||
if (streamSizeWriter<uint32_t> (stream, [&] () {
|
||||
uint32_t version = 1;
|
||||
auto res = (stream << version);
|
||||
if (!res)
|
||||
return res.error;
|
||||
int32_t bypass = state.isBypassed ? 1 : 0;
|
||||
return (stream << bypass).error;
|
||||
}) != IO::Error::NoError)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!(stream << cMagic))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (streamSizeWriter<int32_t> (stream, [&] () {
|
||||
bool writeChunk = !state.chunk.empty ();
|
||||
IO::Result res;
|
||||
if (!(res = (stream << (writeChunk ? chunkBankMagic : bankMagic))))
|
||||
return res.error;
|
||||
int32_t bankVersion = 2;
|
||||
if (!(res = (stream << bankVersion)))
|
||||
return res.error;
|
||||
if (!(res = (stream << state.fxUniqueID)))
|
||||
return res.error;
|
||||
if (!(res = (stream << state.fxVersion)))
|
||||
return res.error;
|
||||
int32_t numPrograms = writeChunk ? 1 : static_cast<int32_t> (state.programs.size ());
|
||||
if (!(res = (stream << numPrograms)))
|
||||
return res.error;
|
||||
if (bankVersion > 1)
|
||||
{
|
||||
if (!(res = (stream << state.currentProgram)))
|
||||
return res.error;
|
||||
// write 124 zero bytes
|
||||
uint8_t byte = 0;
|
||||
for (uint32_t i = 0; i < 124; ++i)
|
||||
if (!(res = (stream << byte)))
|
||||
return res.error;
|
||||
}
|
||||
if (writeChunk)
|
||||
{
|
||||
auto chunkSize = static_cast<uint32_t> (state.chunk.size ());
|
||||
if (!(res = (stream << chunkSize)))
|
||||
return res.error;
|
||||
stream.write ({state.chunk.size (), state.chunk.data ()});
|
||||
}
|
||||
else
|
||||
{
|
||||
writePrograms (stream, state.programs);
|
||||
}
|
||||
return IO::Error::NoError;
|
||||
}) != IO::Error::NoError)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
Optional<Vst2xProgram> tryVst2ProgramLoad (Steinberg::IBStream& stream,
|
||||
Optional<int32_t> vst2xUniqueID) noexcept
|
||||
{
|
||||
IO::BigEndianStream state (stream);
|
||||
return loadProgram (state, vst2xUniqueID);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // VST3
|
||||
@@ -0,0 +1,103 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Flags : clang-format SMTGSequencer
|
||||
// Project : VST3 SDK
|
||||
// Filename : public.sdk/source/vst/utility/vst2persistence.h
|
||||
// Created by : Steinberg, 12/2019
|
||||
// Description : vst2 persistence 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 "public.sdk/source/vst/utility/optional.h"
|
||||
#include "pluginterfaces/base/ibstream.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace VST3 {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
using Vst2xChunk = std::vector<int8_t>;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** structure holding the content of a vst2 fxp format stream
|
||||
*
|
||||
* either the values member is valid or the chunk member but not both
|
||||
*/
|
||||
struct Vst2xProgram
|
||||
{
|
||||
using ProgramValues = std::vector<float>;
|
||||
ProgramValues values;
|
||||
Vst2xChunk chunk;
|
||||
int32_t fxUniqueID {0};
|
||||
int32_t fxVersion {0};
|
||||
std::string name;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** structure holding the content of a vst2 fxb format stream
|
||||
*
|
||||
* either the programs member is valid or the chunk member but not both
|
||||
*/
|
||||
struct Vst2xState
|
||||
{
|
||||
using Programs = std::vector<Vst2xProgram>;
|
||||
Programs programs;
|
||||
Vst2xChunk chunk;
|
||||
|
||||
int32_t fxUniqueID {0};
|
||||
int32_t fxVersion {0};
|
||||
int32_t currentProgram {0};
|
||||
bool isBypassed {false};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Try loading the state from an old vst2 fxb format stream
|
||||
*
|
||||
* If successfully loaded, the state has either a chunk or programs but not both
|
||||
* The Vst2xState::isBypassed boolean will be set if a Steinberg host has written the state into a
|
||||
* project and the plug-in was bypassed.
|
||||
*
|
||||
* @param stream the input stream
|
||||
* @param vst2xUniqueID vst2 unique id expected to be stored in the stream [optional]. If present
|
||||
* the fxb unique id header entry must be the same as this otherwise the
|
||||
* return value is empty.
|
||||
* @return on success the optional has a Vst2xState object with the data
|
||||
*/
|
||||
Optional<Vst2xState> tryVst2StateLoad (Steinberg::IBStream& stream,
|
||||
Optional<int32_t> vst2xUniqueID = {}) noexcept;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Write a vst2 fxb stream
|
||||
*
|
||||
* Writes the state into stream as a vst2 fxb format
|
||||
*
|
||||
* @param state the state which should be written
|
||||
* @param stream the stream where the state should be written into
|
||||
* @param writeBypassState write extra chunk with bypass state
|
||||
* @return true on success
|
||||
*/
|
||||
bool writeVst2State (const Vst2xState& state, Steinberg::IBStream& stream,
|
||||
bool writeBypassState = true) noexcept;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
/** Try loading the state from on old vst2 fxp format stream
|
||||
*
|
||||
* If successfully loaded, the program has either a chunk or plain values but not both
|
||||
*
|
||||
* @param stream the input stream
|
||||
* @param vst2xUniqueID vst2 unique id expected to be stored in the stream
|
||||
* @return on success the optional has a Vst2xProgram object with the data
|
||||
*/
|
||||
Optional<Vst2xProgram> tryVst2ProgramLoad (Steinberg::IBStream& stream,
|
||||
Optional<int32_t> vst2xUniqueID) noexcept;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // VST3
|
||||
Reference in New Issue
Block a user