From c463543ac97a1ee5048dddb37f28f9df3698b60c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 00:02:31 +0100 Subject: [PATCH] Implement a serializable vector based IBStream We can now use this for implementing reading and writing preset data. --- src/common/serialization/vst2.h | 6 +- src/common/serialization/vst3/base.cpp | 137 +++++++++++++++++++++++++ src/common/serialization/vst3/base.h | 63 +++++++++++- 3 files changed, 202 insertions(+), 4 deletions(-) diff --git a/src/common/serialization/vst2.h b/src/common/serialization/vst2.h index eae8f1d2..e44dfc16 100644 --- a/src/common/serialization/vst2.h +++ b/src/common/serialization/vst2.h @@ -53,9 +53,9 @@ constexpr size_t max_midi_events = max_buffer_size / sizeof(size_t); [[maybe_unused]] constexpr size_t max_string_length = 64; /** - * The size for a buffer in which we're receiving chunks. Allow for up to 50 MB - * chunks. Hopefully no plugin will come anywhere near this limit, but it will - * add up when plugins start to audio samples in their presets. + * The maximum size for the buffer we're receiving chunks in. Allows for up to + * 50 MB chunks. Hopefully no plugin will come anywhere near this limit, but it + * will add up when plugins start to audio include samples in their presets. */ constexpr size_t binary_buffer_size = 50 << 20; diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index 24ef9618..1145dfb0 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -14,6 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include +#include + #include "base.h" UniversalTResult::UniversalTResult() : universal_result(Value::kResultFalse) {} @@ -122,3 +125,137 @@ UniversalTResult::Value UniversalTResult::to_universal_result( break; } } + +VectorStream::VectorStream(){FUNKNOWN_CTOR} + +VectorStream::VectorStream(Steinberg::IBStream* stream) { + FUNKNOWN_CTOR + + if (!stream) { + throw std::runtime_error("Null pointer passed to VectorStream()"); + } + + if (stream->seek(0, Steinberg::IBStream::IStreamSeekMode::kIBSeekEnd) != + Steinberg::kResultOk) { + throw std::runtime_error( + "IBStream passed to VectorStream() does not suport seeking to end"); + } + + // Now that we're at the end of the stream we know how large the buffer + // should be + int64 size; + assert(stream->tell(&size) == Steinberg::kResultOk); + + int32 num_bytes_read; + buffer.resize(size); + assert(stream->seek(0, Steinberg::IBStream::IStreamSeekMode::kIBSeekSet) != + Steinberg::kResultOk); + assert(stream->read(buffer.data(), size, &num_bytes_read) == + Steinberg::kResultOk); + assert(num_bytes_read == 0 || num_bytes_read == size); +} + +VectorStream::~VectorStream() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(VectorStream) +#pragma GCC diagnostic pop + +tresult PLUGIN_API VectorStream::queryInterface(Steinberg::FIDString _iid, + void** obj) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, Steinberg::IBStream) + QUERY_INTERFACE(_iid, obj, Steinberg::IBStream::iid, Steinberg::IBStream) + QUERY_INTERFACE(_iid, obj, Steinberg::ISizeableStream::iid, + Steinberg::ISizeableStream) + + *obj = nullptr; + return Steinberg::kNoInterface; +} + +tresult PLUGIN_API VectorStream::read(void* buffer, + int32 numBytes, + int32* numBytesRead) { + if (!buffer || numBytes < 0) { + return Steinberg::kInvalidArgument; + } + + size_t bytes_to_read = std::min(static_cast(numBytes), + this->buffer.size() - seek_position); + + std::copy_n(&this->buffer[seek_position], bytes_to_read, + reinterpret_cast(buffer)); + + seek_position += bytes_to_read; + if (numBytesRead) { + *numBytesRead = bytes_to_read; + } + + return Steinberg::kResultOk; +} + +tresult PLUGIN_API VectorStream::write(void* buffer, + int32 numBytes, + int32* numBytesWritten) { + if (!buffer || numBytes < 0) { + return Steinberg::kInvalidArgument; + } + + if (seek_position + numBytes > this->buffer.size()) { + this->buffer.resize(seek_position + numBytes); + } + + std::copy_n(reinterpret_cast(buffer), numBytes, + &this->buffer[seek_position]); + + seek_position += numBytes; + if (numBytesWritten) { + *numBytesWritten = numBytes; + } + + return Steinberg::kResultOk; +} + +tresult PLUGIN_API VectorStream::seek(int64 pos, int32 mode, int64* result) { + switch (mode) { + case kIBSeekSet: + seek_position = pos; + break; + case kIBSeekCur: + seek_position += pos; + break; + case kIBSeekEnd: + seek_position = this->buffer.size() + pos; + break; + default: + return Steinberg::kInvalidArgument; + break; + } + + if (result) { + *result = seek_position; + } + + return Steinberg::kResultOk; +} + +tresult PLUGIN_API VectorStream::tell(int64* pos) { + if (pos) { + *pos = seek_position; + return Steinberg::kResultOk; + } else { + return Steinberg::kInvalidArgument; + } +} + +tresult PLUGIN_API VectorStream::getStreamSize(int64& size) { + size = seek_position; + return Steinberg::kResultOk; +} + +tresult PLUGIN_API VectorStream::setStreamSize(int64 size) { + buffer.resize(size); + return Steinberg::kResultOk; +} diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index f1e4a5fc..1cdeccae 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -18,14 +18,17 @@ #include #include +#include #include #include +#include // Yet Another layer of includes, but these are some VST3-specific typedefs that // we'll need for all of our interfaces -using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::tresult; +using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::int64, + Steinberg::tresult; /** * Both `TUID` (`int8_t[16]`) and `FIDString` (`char*`) are hard to work with @@ -36,6 +39,13 @@ using ArrayUID = std::array< std::remove_reference_t()[0])>, std::extent_v>; +/** + * The maximum size for an `IBStream` we can serialize. Allows for up to 50 MB + * of preset data. Hopefully no plugin will come anywhere near this limit, but + * it will add up when plugins start to include audio samples in their presets. + */ +constexpr size_t max_vector_stream_size = 50 << 20; + /** * Empty struct for when we have send a response to some operation without any * result values. @@ -101,3 +111,54 @@ class UniversalTResult { Value universal_result; }; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Serialize an `IBStream` into an `std::vector`, and allow the + * receiving side to use it as an `IBStream` again. `ISizeableStream` is defined + * but then for whatever reason never used, but we'll implement it anyways. + */ +class VectorStream : public Steinberg::IBStream, + public Steinberg::ISizeableStream { + public: + VectorStream(); + + /** + * Read an existing stream. + */ + VectorStream(Steinberg::IBStream* stream); + + virtual ~VectorStream(); + + DECLARE_FUNKNOWN_METHODS + + // From `IBstream` + tresult PLUGIN_API read(void* buffer, + int32 numBytes, + int32* numBytesRead = nullptr) override; + tresult PLUGIN_API write(void* buffer, + int32 numBytes, + int32* numBytesWritten = nullptr) override; + tresult PLUGIN_API seek(int64 pos, + int32 mode, + int64* result = nullptr) override; + tresult PLUGIN_API tell(int64* pos) override; + + // From `ISizeableStream` + tresult PLUGIN_API getStreamSize(int64& size) override; + tresult PLUGIN_API setStreamSize(int64 size) override; + + template + void serialize(S& s) { + s.container1b(buffer, max_vector_stream_size); + // The seek position should always be initialized at 0 + } + + private: + std::vector buffer; + size_t seek_position; +}; + +#pragma GCC diagnostic pop