Files
2026-08-16 18:24:52 +07:00

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