diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 608b71a9..e61e30b7 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -24,6 +24,18 @@ Vst3Logger::Vst3Logger(Logger& generic_logger) : logger(generic_logger) {} +void Vst3Logger::log_request(bool is_host_vst, const CreateInstaneIComponent&) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { + std::ostringstream message; + // TODO: Log the cid in some readable way, if possible + message << get_log_prefix(is_host_vst) + << " >> IPluginFactory::createComponent(cid, IComponent::iid, " + "&obj)"; + + log(message.str()); + } +} + void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { std::ostringstream message; @@ -53,6 +65,16 @@ void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { } } +void Vst3Logger::log_response(bool is_host_vst, const YaComponent&) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { + std::ostringstream message; + // TODO: Add the instance ID after we implement that + message << get_log_prefix(is_host_vst) << " "; + + log(message.str()); + } +} + void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory& factory) { if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 45a9511d..f4cf1699 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -45,10 +45,12 @@ class Vst3Logger { // flag here indicates whether the request was initiated on the host side // (what we'll call a control message). + void log_request(bool is_host_vst, const CreateInstaneIComponent&); void log_request(bool is_host_vst, const WantsConfiguration&); void log_request(bool is_host_vst, const WantsPluginFactory&); void log_response(bool is_host_vst, const Configuration&); + void log_response(bool is_host_vst, const YaComponent&); void log_response(bool is_host_vst, const YaPluginFactory&); Logger& logger; diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 092a41ee..ca22b365 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -23,6 +23,7 @@ #include "../configuration.h" #include "../utils.h" #include "common.h" +#include "vst3/component.h" #include "vst3/plugin-factory.h" // Event handling for our VST3 plugins works slightly different from how we @@ -37,6 +38,21 @@ // TODO: If this approach works, maybe we can also refactor the VST2 handling to // do this since it's a bit safer and easier to read +/** + * Request the Wine plugin host to instantiate a new IComponent to pass through + * a call to `IPluginFactory::createInstance(cid, IComponent::iid, ...)`. + */ +struct CreateInstaneIComponent { + using Response = YaComponent&; + + Steinberg::TUID cid; + + template + void serialize(S& s) { + s.container1b(cid); + } +}; + /** * Marker struct to indicate the other side (the plugin) should send a copy of * the configuration. @@ -64,7 +80,8 @@ struct WantsPluginFactory { * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Response`. */ -using ControlRequest = std::variant; +using ControlRequest = + std::variant; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index ab218c5d..2554f0b1 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -50,7 +50,7 @@ instantiated and managed by the host. The model works as follows: ## Plugin Factory -TODO: Explain how we implement `createInstance()` +TODO: Explain how we implement `createInstance()`, based on the todo comment there. ## Safety notes diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp new file mode 100644 index 00000000..efe19abf --- /dev/null +++ b/src/common/serialization/vst3/component.cpp @@ -0,0 +1,51 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "component.h" + +YaComponent::YaComponent(){FUNKNOWN_CTOR} + +YaComponent::YaComponent( + Steinberg::IPtr component) { + FUNKNOWN_CTOR + + // `IComponent::getControllerClassId` + component->getControllerClassId(edit_controller_cid); + + // Everything else is handled directly through callbacks to minimize the + // potential for errors +} + +YaComponent::~YaComponent() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(YaComponent) +#pragma GCC diagnostic pop + +tresult PLUGIN_API YaComponent::queryInterface(Steinberg::FIDString _iid, + void** obj) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, Steinberg::IPluginBase) + QUERY_INTERFACE(_iid, obj, Steinberg::IPluginBase::iid, + Steinberg::IPluginBase) + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, + Steinberg::Vst::IComponent) + + *obj = nullptr; + return Steinberg::kNoInterface; +} diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h new file mode 100644 index 00000000..e7ba26b7 --- /dev/null +++ b/src/common/serialization/vst3/component.h @@ -0,0 +1,93 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +using Steinberg::TBool, Steinberg::int32, Steinberg::tresult; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IComponent` for serialization purposes. See `README.md` for + * more information on how this works. On the Wine plugin host side this is only + * used for serialization, and on the plugin side have an implementation that + * can send control messages. + * + * We might be able to do some caching here with the buss infos, but since that + * sounds like a huge potential source of errors we'll just do pure callbacks + * for everything other than the edit controller's class ID. + */ +class YaComponent : public Steinberg::Vst::IComponent { + public: + YaComponent(); + + /** + * Create a copy of an existing component. + */ + explicit YaComponent(Steinberg::IPtr component); + + virtual ~YaComponent(); + + DECLARE_FUNKNOWN_METHODS + + // From `IPluginBase` + virtual tresult PLUGIN_API initialize(FUnknown* context) override = 0; + virtual tresult PLUGIN_API terminate() override = 0; + + // From `IComponent` + tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; + virtual tresult PLUGIN_API + setIoMode(Steinberg::Vst::IoMode mode) override = 0; + virtual int32 PLUGIN_API + getBusCount(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir) override = 0; + virtual tresult PLUGIN_API + getBusInfo(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::BusInfo& bus /*out*/) override = 0; + virtual tresult PLUGIN_API + getRoutingInfo(Steinberg::Vst::RoutingInfo& inInfo, + Steinberg::Vst::RoutingInfo& outInfo /*out*/) override = 0; + virtual tresult PLUGIN_API activateBus(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + TBool state) override = 0; + virtual tresult PLUGIN_API setActive(TBool state) override = 0; + virtual tresult PLUGIN_API + setState(Steinberg::IBStream* state) override = 0; + virtual tresult PLUGIN_API + getState(Steinberg::IBStream* state) override = 0; + + template + void serialize(S& s) { + s.container1b(edit_controller_cid); + } + + private: + /** + * The class ID of this component's corresponding editor controller. + */ + Steinberg::TUID edit_controller_cid; + + // TODO: As explained in a few other places, `YaComponent` objects should be + // assigned a unique ID for identification +}; + +#pragma GCC diagnostic pop diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 7fe9d555..a0e87daf 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -44,8 +44,7 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { YaPluginFactory(); /** - * Create a copy of an existing plugin factory. Depending on the - supported + * Create a copy of an existing plugin factory. Depending on the supported * interface function more or less of this struct will be left empty, and * `iid` will be set accordingly. */ @@ -61,17 +60,10 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { int32 PLUGIN_API countClasses() override; tresult PLUGIN_API getClassInfo(Steinberg::int32 index, Steinberg::PClassInfo* info) override; - // TODO: Figure out how to implement this. Some considerations: - // - We have to sent a control message to the Wine plugin host to ask - // it to create an instance of `_iid`. - // - We then create a `Ya*` implementation of the same interface on - // the plugin side. - // - These two should be wired up so that when the host calls a - // function on it, it should be sent to the instance on the Wine - // plugin host side with the same cid. - // - We should have a list of interfaces we support. When we receive a - // request to create an instance of something we don't support, then - // we should log that and then fail. + /** + * See the implementation in `YaPluginFactoryPluginImpl` for how this is + * handled. + */ virtual tresult PLUGIN_API createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, void** obj) override = 0; @@ -83,6 +75,10 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { // From `IPluginFactory3` tresult PLUGIN_API getClassInfoUnicode(int32 index, Steinberg::PClassInfoW* info) override; + /** + * We'll pass a `IHostApplication` to the Windows VST3 plugin's factory when + * this is called so it can send messages. + */ virtual tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override = 0; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 150cc16d..cd3e71d4 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -51,6 +51,33 @@ void Vst3Bridge::run() { sockets.host_vst_control.receive_messages( std::nullopt, overload{ + [&](const CreateInstaneIComponent& args) + -> CreateInstaneIComponent::Response { + Steinberg::IPtr component = + module->getFactory() + .createInstance(args.cid); + + // TODO: Next steps are: + // - Generate a new unique ID using an atomic size_t and + // fetch-and-add. + // - Add an `std::map` + // to this class and add `component` with the generated + // ID to that. + // - Add that ID to `YaComponent` and set it in the object + // we create here. + // - In case `factory` is a null pointer, allow returning + // `nullopt`. Not sure how that is going to work with + // the deserialization. + if (!component) { + // TODO: Handle + } + + // TODO: Implement `YaComponentHostImpl` and create an instance + // based on `component` + YaComponent* removeme = nullptr; + return *removeme; + }, [&](const WantsPluginFactory&) -> WantsPluginFactory::Response { return *plugin_factory; }});