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