614 lines
18 KiB
C++
614 lines
18 KiB
C++
//------------------------------------------------------------------------
|
|
// 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
|