From 9b603a964cd8ce92e80a87280a209d2e40aa46b3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 10 Jan 2021 15:57:49 +0100 Subject: [PATCH] Implement IStreamAttributes when reading from host This allows presets to contain meta data about file names and the type of preset. Even though the docs don't mention that this is also relevant for `getState()`, we should also implement it there so plugins can write their own meta data. --- src/common/logging/vst3.cpp | 48 ++++++++------ src/common/serialization/vst3/README.md | 20 +++--- src/common/serialization/vst3/bstream.cpp | 76 +++++++++++++++++++++++ src/common/serialization/vst3/bstream.h | 40 +++++++++++- 4 files changed, 153 insertions(+), 31 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 12538356..50b88d4d 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -22,6 +22,25 @@ #include "src/common/serialization/vst3.h" +/** + * Format a `YaBStream` object as a string so we don't have to repeat this + * everywhere. + */ +std::string format_bstream(const YaBStream& stream) { + std::ostringstream formatted; + formatted << ""; + + return formatted.str(); +} + Vst3Logger::Vst3Logger(Logger& generic_logger) : logger(generic_logger) {} void Vst3Logger::log_query_interface( @@ -87,8 +106,7 @@ bool Vst3Logger::log_request(bool is_host_vst, return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": {IComponent,IEditController}::setState(state = " - ")"; + << format_bstream(request.state) << ")"; }); } @@ -180,9 +198,8 @@ bool Vst3Logger::log_request( const YaEditController::SetComponentState& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id - << ": IEditController::setComponentState(state = )"; + << ": IEditController::setComponentState(state = " + << format_bstream(request.state) << ")"; }); } @@ -611,8 +628,7 @@ bool Vst3Logger::log_request(bool is_host_vst, message << "IProgramListData::setProgramData(listId = " << request.list_id << ", programIndex = " << request.program_index - << ", data = )"; + << ", data = " << format_bstream(request.data) << ")"; }); } @@ -635,8 +651,7 @@ bool Vst3Logger::log_request(bool is_host_vst, const YaUnitData::SetUnitData& request) { return log_request_base(is_host_vst, [&](auto& message) { message << "IUnitData::setUnitData(listId = " << request.unit_id - << ", data = )"; + << ", data = " << format_bstream(request.data) << ")"; }); } @@ -747,8 +762,7 @@ bool Vst3Logger::log_request(bool is_host_vst, << ": IUnitInfo::setUnitProgramData(listOrUnitId = " << request.list_or_unit_id << ", programIndex = " << request.program_index - << ", data = )"; + << ", data = " << format_bstream(request.data) << ")"; }); } @@ -1199,8 +1213,7 @@ void Vst3Logger::log_response( log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); if (response.result == Steinberg::kResultOk) { - message << ", "; + message << ", " << format_bstream(response.updated_state); } }); } @@ -1372,8 +1385,7 @@ void Vst3Logger::log_response( log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); if (response.result == Steinberg::kResultOk) { - message << ", "; + message << ", " << format_bstream(response.data); } }); } @@ -1383,8 +1395,7 @@ void Vst3Logger::log_response(bool is_host_vst, log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); if (response.result == Steinberg::kResultOk) { - message << ", "; + message << ", " << format_bstream(response.data); } }); } @@ -1469,8 +1480,7 @@ void Vst3Logger::log_response( log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); if (response.result == Steinberg::kResultOk) { - message << ", "; + message << ", " << format_bstream(response.stream); } }); } diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 1f2f192e..94925f93 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -55,16 +55,16 @@ VST3 host interfaces are implemented as follows: The following host interfaces are passed as function arguments and are thus also implemented for serialization purposes: -| yabridge class | Interfaces | Notes | -| --------------------- | ----------------------------- | --------------------------------------------------------------------- | -| `YaAttributeList` | `IAttributeList` | | -| `YaBStream` | `IBStream`, `ISizeableStream` | Used for serializing data streams | -| `YaContextMenuTarget` | `IContextMenuTarget` | Used in `YaContextMenu` to proxy specific menu items | -| `YaEventList` | `IEventList` | Comes with a lot of serialization wrappers around the related structs | -| `YaMessage` | `IMessage` | | -| `YaMessagePtr` | `IMessage` | Should be used in inter process communication to exchange messages | -| `YaParameterChanges` | `IParameterChanges` | | -| `YaParamValueQueue` | `IParamValueQueue` | | +| yabridge class | Interfaces | Notes | +| --------------------- | -------------------------------------------------- | --------------------------------------------------------------------- | +| `YaAttributeList` | `IAttributeList` | | +| `YaBStream` | `IBStream`, `ISizeableStream`, `IStreamAttributes` | Used for serializing data streams | +| `YaContextMenuTarget` | `IContextMenuTarget` | Used in `YaContextMenu` to proxy specific menu items | +| `YaEventList` | `IEventList` | Comes with a lot of serialization wrappers around the related structs | +| `YaMessage` | `IMessage` | | +| `YaMessagePtr` | `IMessage` | Should be used in inter process communication to exchange messages | +| `YaParameterChanges` | `IParameterChanges` | | +| `YaParamValueQueue` | `IParamValueQueue` | | And finally `YaProcessData` uses the above along with `YaAudioBusBuffers` to wrap around `ProcessData`. diff --git a/src/common/serialization/vst3/bstream.cpp b/src/common/serialization/vst3/bstream.cpp index dab57aba..844e08a9 100644 --- a/src/common/serialization/vst3/bstream.cpp +++ b/src/common/serialization/vst3/bstream.cpp @@ -16,6 +16,8 @@ #include "bstream.h" +#include + #include #include @@ -46,6 +48,50 @@ YaBStream::YaBStream(Steinberg::IBStream* stream) { assert(stream->read(buffer.data(), size, &num_bytes_read) == Steinberg::kResultOk); assert(num_bytes_read == 0 || num_bytes_read == size); + + // Starting at VST 3.6.0 streams provided by the host may contain context + // based meta data + if (Steinberg::FUnknownPtr + stream_attributes = stream) { + supports_stream_attributes = true; + + Steinberg::Vst::String128 vst_string{0}; + if (stream_attributes->getFileName(vst_string) == + Steinberg::kResultOk) { + file_name.emplace(tchar_pointer_to_u16string(vst_string)); + } + + // Copy over all predefined meta data keys. `IAttributeList` does not + // offer any interface to enumerate over the stored keys. + // TODO: There's also + // `Steinberg::Vst::PresetAttributes::kFilePathStringType` + // This would require translating between Windows and Unix style + // paths, which we can't easily do outside of Wine. If this ends + // up being important, then we'll have to shell out to `winepath` + // which is not ideal. On the Wine side we can just use the + // `wine_get_dos_file_name` and `wine_get_unix_file_name` + // functions instead. Requesting this should also use a 1024 + // character buffer. + attributes.emplace(); + Steinberg::IPtr stream_attributes_list = + stream_attributes->getAttributes(); + for (const auto& key : + {Steinberg::Vst::PresetAttributes::kPlugInName, + Steinberg::Vst::PresetAttributes::kPlugInCategory, + Steinberg::Vst::PresetAttributes::kInstrument, + Steinberg::Vst::PresetAttributes::kStyle, + Steinberg::Vst::PresetAttributes::kCharacter, + Steinberg::Vst::PresetAttributes::kStateType, + Steinberg::Vst::PresetAttributes::kName, + Steinberg::Vst::PresetAttributes::kFileName}) { + vst_string[0] = 0; + if (stream_attributes_list->getString(key, vst_string, + sizeof(vst_string)) == + Steinberg::kResultOk) { + attributes->setString(key, vst_string); + } + } + } } YaBStream::~YaBStream() { @@ -64,6 +110,12 @@ tresult PLUGIN_API YaBStream::queryInterface(Steinberg::FIDString _iid, QUERY_INTERFACE(_iid, obj, Steinberg::ISizeableStream::iid, Steinberg::ISizeableStream) + // TODO: We don't have any logging for this + if (supports_stream_attributes) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IStreamAttributes::iid, + Steinberg::Vst::IStreamAttributes) + } + *obj = nullptr; return Steinberg::kNoInterface; } @@ -83,6 +135,11 @@ tresult YaBStream::write_back(Steinberg::IBStream* stream) const { static_cast(num_bytes_written) == buffer.size()); } + // TODO: Can plugins write their own meta data, and should we write those + // back after `*::getState()`? I'd assume so, but the docs don't + // mention this. If so then we need to always store whether the host + // supports `IStreamAttributes`. + return Steinberg::kResultOk; } @@ -174,3 +231,22 @@ tresult PLUGIN_API YaBStream::setStreamSize(int64 size) { buffer.resize(size); return Steinberg::kResultOk; } + +tresult PLUGIN_API YaBStream::getFileName(Steinberg::Vst::String128 name) { + if (name && file_name) { + std::copy(file_name->begin(), file_name->end(), name); + name[file_name->size()] = 0; + + return Steinberg::kResultOk; + } else { + return Steinberg::kResultFalse; + } +} + +Steinberg::Vst::IAttributeList* PLUGIN_API YaBStream::getAttributes() { + if (attributes) { + return &*attributes; + } else { + return nullptr; + } +} diff --git a/src/common/serialization/vst3/bstream.h b/src/common/serialization/vst3/bstream.h index 3e6df193..cfcf67c9 100644 --- a/src/common/serialization/vst3/bstream.h +++ b/src/common/serialization/vst3/bstream.h @@ -16,8 +16,12 @@ #pragma once -#include +#include +#include +#include + +#include "attribute-list.h" #include "base.h" #pragma GCC diagnostic push @@ -27,9 +31,13 @@ * 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. + * + * If we're copying data from an existing `IBstream` and that stream supports + * VST 3.6.0 preset meta data, then we'll copy that meta data as well. */ class YaBStream : public Steinberg::IBStream, - public Steinberg::ISizeableStream { + public Steinberg::ISizeableStream, + public Steinberg::Vst::IStreamAttributes { public: YaBStream(); @@ -71,15 +79,43 @@ class YaBStream : public Steinberg::IBStream, tresult PLUGIN_API getStreamSize(int64& size) override; tresult PLUGIN_API setStreamSize(int64 size) override; + // From `IStreamAttributes` + tresult PLUGIN_API getFileName(Steinberg::Vst::String128 name) override; + Steinberg::Vst::IAttributeList* PLUGIN_API getAttributes() override; + template void serialize(S& s) { s.container1b(buffer, max_vector_stream_size); // The seek position should always be initialized at 0 + + s.value1b(supports_stream_attributes); + s.ext(file_name, bitsery::ext::StdOptional{}, + [](S& s, std::u16string& name) { + s.text2b(name, std::extent_v); + }); + s.ext(attributes, bitsery::ext::StdOptional{}); } + /** + * Whether this stream supports `IStreamAttributes`. This will be true if we + * copied a stream provided by the host that also supported meta data. + */ + bool supports_stream_attributes = false; + + /** + * The stream's name, if this stream supports stream attributes. + */ + std::optional file_name; + private: std::vector buffer; size_t seek_position = 0; + + /** + * The stream's meta data if we've copied from a stream that supports meta + * data. + */ + std::optional attributes; }; #pragma GCC diagnostic pop