Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -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, &currentBlock);
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
}
};
//------------------------------------------------------------------------
}
@@ -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
@@ -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
@@ -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