// yabridge: a Wine VST bridge // Copyright (C) 2020-2022 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 "vst2.h" #include #include // Generated inside of the build directory #include #include "../../common/communication/vst2.h" /** * A function pointer to what should be the entry point of a VST plugin. */ using VstEntryPoint = AEffect*(VST_CALL_CONV*)(audioMasterCallback); /** * If `plugin->ptr2` is set to this value, then we'll know that `plugin->ptr1` * is a valid pointer to a `Vst2Bridge` instance. This is needed for when one * instance of a plugin in a plugin group processes audio while another instance * of that plugin in the same plugin group is being initialized. In that * situation we cannot rely on just `current_bridge_instance`, and some plugins * don't zero initialize these pointers like they should so we also can't rely * on that. */ constexpr size_t yabridge_ptr2_magic = 0xdeadbeef + 420; /** * This ugly global is needed so we can get the instance of a `Vst2Bridge` class * from an `AEffect` when it performs a host callback during its initialization. * * We don't need any locking here because we can only initialize `Vst2Bridge` * from the main thread anyways. */ Vst2Bridge* current_bridge_instance = nullptr; /** * Callbacks (presumably made from the GUI thread) that may receive responses * that have to be handled from the same thread. If we don't do this, then those * respones might either cause a deadlock when the plugin uses recursive * mutexes, or it may result in some other thread safety issues. * * NOTE: This is needed for Voxengo VST2 plugins in Renoise. When * `effSetChunk()` is called from the GUI thread, Voxengo VST2 plugins * will (wrongly) call `audioMasterUpdateDisplay()` while handling that * call. Renoise then calls `effGetProgram()` while handling that which * shouldn't cause any issues, but the Voxengo plugins try to lock * recursive mutexes on both functions so `effGetProgram()` _has_ to be * called on the same thread that is currently calling * `audioMasterUpdateDisplay()`. * NOTE: Similarly, REAPER calls `effProgramName()` in response to * `audioMasterUpdateDisplay()`, and PG-8X also requires that to be called * from the same thread that called `audioMasterUpdateDisplay()`. */ static const std::unordered_set mutually_recursive_callbacks{ audioMasterUpdateDisplay}; /** * Opcodes that, when called on this plugin's dispatcher, have to be handled * mutually recursively, if possible. This means that the plugin makes a * callback using one of the functions defined in * `mutually_recursive_callbacks`, and when the host responds by calling one of * these functions, then that function should be handled on the same thread * where the plugin originally called the request on. If no mutually recursive * calling sequence is active while one of these functions is called, then we'll * just execute the function directly on the calling thread. See above for a * list of situations where this may be necessary. */ static const std::unordered_set safe_mutually_recursive_requests{ effGetProgram, effGetProgramName}; /** * Opcodes that should always be handled on the main thread because they may * involve GUI operations. * * NOTE: `effMainsChanged` is the odd one here. EZdrummer interacts with the * Win32 message loop while handling this function. If we don't execute * this from the main GUI thread, then EZdrummer won't produce any sound. * NOTE: `effSetChunk` and `effGetChunk` should be callable from any thread, but * Algonaut Atlas doesn't restore chunk data unless `effSetChunk` is run * from the GUI thread * NOTE: `effSetSampleRate` and `effSetBlockSize` really shouldn't be here, but * New Sonic Arts' Vice plugin spawns a new thread and calls drawing code * while changing sample rate and block size. We'll need to see if doing * this on the main thread introduces any regressions. */ static const std::unordered_set unsafe_requests{ effOpen, effClose, effEditGetRect, effEditOpen, effEditClose, effEditIdle, effEditTop, effMainsChanged, effGetChunk, effSetChunk, effSetSampleRate, effSetBlockSize}; /** * These opcodes from `unsafe_requests` should be run under realtime scheduling * so that if they spawn audio worker threads, those threads will also be run * with `SCHED_FIFO`. This is needed because unpatched Wine still does not * implement thread priorities. Normally these unsafe requests are run on the * main thread, which doesn't use realtime scheduling. */ static const std::unordered_set unsafe_requests_realtime{effOpen, effMainsChanged}; intptr_t VST_CALL_CONV host_callback_proxy(AEffect*, int, int, intptr_t, void*, float); /** * Fetch the Vst2Bridge instance stored in one of the two pointers reserved * for the host of the hosted VST plugin. This is sadly needed as a workaround * to avoid using globals since we need free function pointers to interface with * the VST C API. */ Vst2Bridge& get_bridge_instance(const AEffect* plugin) { if (plugin && reinterpret_cast(plugin->ptr2) == yabridge_ptr2_magic) [[likely]] { return *static_cast(plugin->ptr1); } // We can only set this pointer after the plugin has initialized, so when // the plugin performs a callback during its initialization we'll use the // current bridge instance set during the Vst2Bridge constructor. This is // thread safe because VST2 plugins have to be initialized on the main // thread. assert(current_bridge_instance); return *current_bridge_instance; } Vst2Bridge::Vst2Bridge(MainContext& main_context, // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) std::string plugin_dll_path, std::string endpoint_base_dir, pid_t parent_pid) : HostBridge(main_context, plugin_dll_path, parent_pid), logger_(generic_logger_), plugin_handle_(LoadLibrary(plugin_dll_path.c_str()), FreeLibrary), sockets_(main_context.context_, endpoint_base_dir, false) { if (!plugin_handle_) { throw std::runtime_error("Could not load the Windows .dll file at '" + plugin_dll_path + "'"); } // VST plugin entry point functions should be called `VSTPluginMain`, but // pre-VST2.4 `main` was also a valid name VstEntryPoint vst_entry_point = nullptr; for (auto name : {"VSTPluginMain", "main"}) { vst_entry_point = reinterpret_cast(reinterpret_cast( GetProcAddress(plugin_handle_.get(), name))); if (vst_entry_point) { break; } } if (!vst_entry_point) { throw std::runtime_error( "Could not find a valid VST entry point for '" + plugin_dll_path + "'."); } sockets_.connect(); // We'll try to do the same `get_bridge_instance()` trick as in //`plugin/bridges/vst2.cpp`, but since the plugin will probably call the // host callback while it's initializing we sadly have to use a global here. // Note that this reinterpret cast is not needed at all since the function // pointer types are exactly the same, but clangd will complain otherwise current_bridge_instance = this; // We'll also need to make sure that any audio worker threads created by the // plugin are running using realtime scheduling, since Wine doesn't fully // implement the Win32 process priority API yet. set_realtime_priority(true); plugin_ = vst_entry_point( reinterpret_cast(host_callback_proxy)); set_realtime_priority(false); if (!plugin_) { throw std::runtime_error("VST plugin at '" + plugin_dll_path + "' failed to initialize."); } // We use `plugin->ptr2` to identify plugins that have already been // initialized. Otherwise we can run into thread safety issues when a plugin // is processing audio while another plugin is being initialized. current_bridge_instance = nullptr; plugin_->ptr1 = this; plugin_->ptr2 = reinterpret_cast(yabridge_ptr2_magic); // Send the plugin's information to the Linux VST plugin. Any other updates // of this object will be sent over the `dispatcher()` socket. This would be // done after the host calls `effOpen()`, and when the plugin calls // `audioMasterIOChanged()`. We will also send along this host's version so // we can show a warning when the plugin's version doesn't match. sockets_.host_vst_control_.send( Vst2EventResult{.return_value = 0, .payload = *plugin_, .value_payload = yabridge_git_version}); // After sending the AEffect struct we'll receive this instance's // configuration as a response config_ = sockets_.host_vst_control_.receive_single(); // Allow this plugin to configure the main context's tick rate main_context.update_timer_interval(config_.event_loop_interval()); parameters_handler_ = Win32Thread([&]() { set_realtime_priority(true); pthread_setname_np(pthread_self(), "parameters"); sockets_.host_vst_parameters_.receive_multi( [&](Parameter& request, SerializationBufferBase& buffer) { // Both `getParameter` and `setParameter` functions are passed // through on this socket since they have a lot of overlap. The // presence of the `value` field tells us which one we're // dealing with. if (request.value) { // `setParameter` plugin_->setParameter(plugin_, request.index, *request.value); ParameterResult response{std::nullopt}; sockets_.host_vst_parameters_.send(response, buffer); } else { // `getParameter` float value = plugin_->getParameter(plugin_, request.index); ParameterResult response{value}; sockets_.host_vst_parameters_.send(response, buffer); } }); }); process_replacing_handler_ = Win32Thread([&]() { set_realtime_priority(true); pthread_setname_np(pthread_self(), "audio"); // Most plugins will already enable FTZ, but there are a handful of // plugins that don't that suffer from extreme DSP load increases when // they start producing denormals ScopedFlushToZero ftz_guard; sockets_.host_vst_process_replacing_.receive_multi< Vst2ProcessRequest>([&](Vst2ProcessRequest& process_request, SerializationBufferBase& buffer) { // Since the value cannot change during this processing cycle, // we'll send the current transport information as part of the // request so we prefetch it to avoid unnecessary callbacks from // the audio thread std::optional time_info_cache_guard = process_request.current_time_info ? std::optional(time_info_cache_.set( *process_request.current_time_info)) : std::nullopt; // We'll also prefetch the process level, since some plugins // will ask for this during every processing cycle decltype(process_level_cache_)::Guard process_level_cache_guard = process_level_cache_.set(process_request.current_process_level); // As suggested by Jack Winter, we'll synchronize this thread's // audio processing priority with that of the host's audio // thread every once in a while if (process_request.new_realtime_priority) { set_realtime_priority(true, *process_request.new_realtime_priority); } // Let the plugin process the MIDI events that were received // since the last buffer, and then clean up those events. This // approach should not be needed but Kontakt only stores // pointers to rather than copies of the events. std::lock_guard lock(next_buffer_midi_events_mutex_); // As an optimization we no don't pass the input audio along // with `Vst2ProcessRequest`, and instead we'll write it to a // shared memory object on the plugin side. We can then write // the output audio to the same shared memory object. Since the // host should only be calling one of `process()`, // processReplacing()` or `processDoubleReplacing()`, we can all // handle them all at once. We pick which one to call depending // on the type of data we got sent and the plugin's reported // support for these functions. auto do_process = [&](T) { // These were set up after the host called // `effMainsChanged()` with the correct size, so this // reinterpret cast is safe even if the host suddenly starts // sending 32-bit single precision audio after it set up // audio processing for double precision (not that the // Windows VST2 plugin would be able to handle that, // presumably) T** input_channel_pointers = reinterpret_cast( process_buffers_input_pointers_.data()); T** output_channel_pointers = reinterpret_cast( process_buffers_output_pointers_.data()); if constexpr (std::is_same_v) { // Any plugin made in the last fifteen years or so // should support `processReplacing`. In the off chance // it does not we can just emulate this behavior // ourselves. if (plugin_->processReplacing) { plugin_->processReplacing( plugin_, input_channel_pointers, output_channel_pointers, process_request.sample_frames); } else { // If we zero out this buffer then the behavior is // the same as `processReplacing` for (int channel = 0; channel < plugin_->numOutputs; channel++) { std::fill(output_channel_pointers[channel], output_channel_pointers[channel] + process_request.sample_frames, static_cast(0.0)); } plugin_->process(plugin_, input_channel_pointers, output_channel_pointers, process_request.sample_frames); } } else if (std::is_same_v) { plugin_->processDoubleReplacing( plugin_, input_channel_pointers, output_channel_pointers, process_request.sample_frames); } else { static_assert( std::is_same_v || std::is_same_v, "Audio processing only works with single and " "double precision floating point numbers"); } }; assert(process_buffers_); if (process_request.double_precision) { // XXX: Clangd doesn't let you specify template parameters // for templated lambdas. This argument should get // optimized out do_process(double()); } else { do_process(float()); } // We modified the buffers within the `process_response` object, // so we can just send that object back. Like on the plugin side // we cannot reuse the request object because a plugin may have // a different number of input and output channels sockets_.host_vst_process_replacing_.send(Ack{}, buffer); // See the docstrong on `should_clear_midi_events` for why we // don't just clear `next_buffer_midi_events` here should_clear_midi_events_ = true; }); }); } bool Vst2Bridge::inhibits_event_loop() noexcept { return !is_initialized_; } void Vst2Bridge::run() { set_realtime_priority(true); sockets_.host_vst_dispatch_.receive_events( std::nullopt, [&](Vst2Event& event, bool /*on_main_thread*/) -> Vst2EventResult { if (event.opcode == effProcessEvents) { // For 99% of the plugins we can just call // `effProcessReplacing()` and be done with it, but a select few // plugins (I could only find Kontakt that does this) don't // actually make copies of the events they receive and only // store pointers to those events, meaning that they have to // live at least until the next audio buffer gets processed. // We're not using `passthrough_events()` here directly because // we need to store a copy of the `DynamicVstEvents` struct // before passing the generated `VstEvents` object to the // plugin. std::lock_guard lock(next_buffer_midi_events_mutex_); // See the docstring on `should_clear_midi_events` for why we // only deallocate old MIDI events here instead of a at the end // of every processing cycle if (should_clear_midi_events_) { next_audio_buffer_midi_events_.clear(); should_clear_midi_events_ = false; } next_audio_buffer_midi_events_.push_back( std::get(event.payload)); DynamicVstEvents& events = next_audio_buffer_midi_events_.back(); // Exact same handling as in `passthrough_event()`, apart from // making a copy of the events first const intptr_t return_value = plugin_->dispatcher( plugin_, event.opcode, event.index, event.value, &events.as_c_events(), event.option); return Vst2EventResult{.return_value = return_value, .payload = nullptr, .value_payload = std::nullopt}; } Vst2EventResult result = passthrough_event( plugin_, [&](AEffect* plugin, int opcode, int index, intptr_t value, void* data, float option) -> intptr_t { // Certain functions will most definitely involve // the GUI or the Win32 message loop. These // functions have to be performed on the thread that // is running the IO context, since this is also // where the plugins were instantiated and where the // Win32 message loop is handled. if (unsafe_requests.contains(opcode)) { // Requests that potentially spawn an audio worker // thread should be run with `SCHED_FIFO` until Wine // implements the corresponding Windows API const bool is_realtime_request = unsafe_requests_realtime.contains(opcode); return main_context_ .run_in_context([&]() -> intptr_t { if (is_realtime_request) { set_realtime_priority(true); } const intptr_t result = dispatch_wrapper( plugin, opcode, index, value, data, option); if (is_realtime_request) { set_realtime_priority(false); } // The Win32 message loop will not be run up to // this point to prevent plugins with partially // initialized states from misbehaving if (opcode == effOpen) { is_initialized_ = true; } return result; }) .get(); } else if (safe_mutually_recursive_requests.contains( opcode)) { // If this function call is potentially in response to a // callback contained in `mutually_recursive_callbacks`, // then we should call it on the same thread that called // that callback if possible. This may be needed when // plugins use recursive mutexes, thus causing deadlocks // when the function is called from any other thread. return mutual_recursion_.handle([&]() { return dispatch_wrapper(plugin, opcode, index, value, data, option); }); } else { return dispatch_wrapper(plugin, opcode, index, value, data, option); } }, event); // We also need some special handling to set up audio processing. // After the plugin has finished setting up audio processing, we'll // initialize our shared audio buffers on this side and send the // configuration back to the native plugin so it can also connect to // the same buffers. We cannot use `Vst2Bridge::dispatch_wrapper()` // for this because we need to directly return payload data that // won't be visible to the plugin at all. // NOTE: Ardour will call `effMainsChanged()` with a value of 1 // unconditionally when unloading a plugin, even when audio // playback has never been initialized (and `effSetBlockSize` // has never been called) if (event.opcode == effMainsChanged && event.value == 1) { // Returning another result this way is a bit ugly, but sadly // optimizations have never made code nicer to read return Vst2EventResult{.return_value = result.return_value, .payload = setup_shared_audio_buffers(), .value_payload = std::nullopt}; } return result; }); } void Vst2Bridge::close_sockets() { sockets_.close(); } class HostCallbackDataConverter : public DefaultDataConverter { public: HostCallbackDataConverter( AEffect* plugin, VstTimeInfo& last_time_info, MutualRecursionHelper& mutual_recursion) noexcept : plugin_(plugin), last_time_info_(last_time_info), mutual_recursion_(mutual_recursion) {} Vst2Event::Payload read_data(const int opcode, const int index, const intptr_t value, const void* data) const override { switch (opcode) { case audioMasterGetTime: return WantsVstTimeInfo{}; break; case audioMasterIOChanged: // This is a helpful event that indicates that the VST // plugin's `AEffect` struct has changed. Writing these // results back is done inside of `passthrough_event()`. return AEffect(*plugin_); break; case audioMasterProcessEvents: return DynamicVstEvents(*static_cast(data)); break; // We detect whether an opcode should return a string by // checking whether there's a zeroed out buffer behind the void // pointer. This works for any host, but not all plugins zero // out their buffers. case audioMasterGetVendorString: case audioMasterGetProductString: return WantsString{}; break; // NOTE: DefaultDataConverter::read() should be able to handle all // of these 'simple' opcodes, but Plugsound Free by UVI passes // random garbage for their data argument. Because of that // `audioMasterWantMidi()` will segfault because when we'll // try to read that data as a string we'll start reading // unallocated memory. Even though no other plugins seem to do // this< we'll list all of these data-less opcodes just to be // sure. We're leaving out a few opcodes here, because I have // no clue whether some of the more obscure ones are supposed // to have an data argument or not. case audioMasterAutomate: case audioMasterVersion: case audioMasterCurrentId: case audioMasterIdle: case audioMasterWantMidi: case audioMasterSizeWindow: case audioMasterGetSampleRate: case audioMasterGetBlockSize: case audioMasterGetInputLatency: case audioMasterGetOutputLatency: case audioMasterGetCurrentProcessLevel: case audioMasterGetAutomationState: case audioMasterGetVendorVersion: case audioMasterGetLanguage: case audioMasterUpdateDisplay: case audioMasterBeginEdit: case audioMasterEndEdit: // NOTE: REAPER abuses the dispatcher to add their own opcodes // outside of `audioMasterVendorSpecific` case audioMasterDeadBeef: return nullptr; break; default: return DefaultDataConverter::read_data(opcode, index, value, data); break; } } std::optional read_value( const int opcode, const intptr_t value) const override { return DefaultDataConverter::read_value(opcode, value); } void write_data(const int opcode, void* data, const Vst2EventResult& response) const override { switch (opcode) { case audioMasterGetTime: // If the host returned a valid `VstTimeInfo` object, then we'll // keep track of it so we can return a pointer to it in the // function below if (std::holds_alternative(response.payload)) { last_time_info_ = std::get(response.payload); } break; default: DefaultDataConverter::write_data(opcode, data, response); break; } } intptr_t return_value(const int opcode, const intptr_t original) const override { switch (opcode) { case audioMasterGetTime: { // If the host returned a null pointer, then we'll do the same // thing here if (original == 0) { return 0; } else { return reinterpret_cast(&last_time_info_); } } break; default: return DefaultDataConverter::return_value(opcode, original); break; } } void write_value(const int opcode, intptr_t value, const Vst2EventResult& response) const override { return DefaultDataConverter::write_value(opcode, value, response); } Vst2EventResult send_event( boost::asio::local::stream_protocol::socket& socket, const Vst2Event& event, SerializationBufferBase& buffer) const override { if (mutually_recursive_callbacks.contains(event.opcode)) { return mutual_recursion_.fork([&]() { return DefaultDataConverter::send_event(socket, event, buffer); }); } else { return DefaultDataConverter::send_event(socket, event, buffer); } } private: AEffect* plugin_; VstTimeInfo& last_time_info_; MutualRecursionHelper& mutual_recursion_; }; intptr_t Vst2Bridge::host_callback(AEffect* effect, int opcode, int index, intptr_t value, void* data, float option) { switch (opcode) { // During a processing call we'll have already sent the current // transport information from the plugin side to avoid an unnecessary // callback case audioMasterGetTime: { const VstTimeInfo* cached_time_info = time_info_cache_.get(); if (cached_time_info) { // This cached value is temporary, so we'll still use the // regular time info storing mechanism last_time_info_ = *cached_time_info; const intptr_t result = reinterpret_cast(&last_time_info_); // Make sure that these cached events don't get lost in the logs logger_.log_event(false, opcode, index, value, WantsVstTimeInfo{}, option, std::nullopt); logger_.log_event_response(false, opcode, result, last_time_info_, std::nullopt, true); return result; } } break; // We also send the current process level for similar reasons case audioMasterGetCurrentProcessLevel: { const int* current_process_level = process_level_cache_.get(); if (current_process_level) { logger_.log_event(false, opcode, index, value, nullptr, option, std::nullopt); logger_.log_event_response(false, opcode, *current_process_level, nullptr, std::nullopt, true); return *current_process_level; } } break; // If the plugin changes its window size, we'll also resize the wrapper // window accordingly. case audioMasterSizeWindow: { if (editor_) { editor_->resize(index, value); } } break; } HostCallbackDataConverter converter(effect, last_time_info_, mutual_recursion_); return sockets_.vst_host_callback_.send_event( converter, std::nullopt, opcode, index, value, data, option); } intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin, int opcode, int index, intptr_t value, void* data, float option) { // We have to intercept GUI open calls since we can't use the X11 window // handle passed by the host. Keep in mind that in our `run()` function // above some of these events will be called on some arbitrary thread (where // we're running with realtime scheduling) and some might be called on the // main thread using `main_context.run_in_context()` (where we don't use // realtime scheduling). switch (opcode) { case effSetBlockSize: { // Used to initialize the shared audio buffers when handling // `effMainsChanged` in `Vst2Bridge::run()` max_samples_per_block_ = value; return plugin->dispatcher(plugin, opcode, index, value, data, option); } break; case effEditOpen: { // Create a Win32 window through Wine, embed it into the window // provided by the host, and let the plugin embed itself into // the Wine window const auto x11_handle = reinterpret_cast(data); Editor& editor_instance = editor_.emplace( main_context_, config_, generic_logger_, x11_handle, [plugin = plugin_]() { plugin->dispatcher(plugin, effEditIdle, 0, 0, nullptr, 0.0); }); const intptr_t result = plugin->dispatcher(plugin, opcode, index, value, editor_instance.get_win32_handle(), option); // Make sure the wrapper window has the correct initial size. The // plugin can later change this size using `audioMasterSizeWindow`. // NOTE: Every single plugin handles `effEditGetRect` before // `effEditOpen` fine. Except for this one single plugin: // https://codefn42.com/randarp/index.html VstRect* editor_rect = nullptr; plugin->dispatcher(plugin, effEditGetRect, 0, 0, &editor_rect, 0.0); if (editor_rect) { std::cerr << editor_rect->right << std::endl; std::cerr << editor_rect->bottom << std::endl; editor_->resize(editor_rect->right - editor_rect->left, editor_rect->bottom - editor_rect->top); } // NOTE: There's zero reason why the window couldn't already be // visible from the start, but Waves V13 VST3 plugins think it // would be a splendid idea to randomly dereference null // pointers when the window is already visible. Thanks Waves. editor_instance.show(); return result; } break; case effEditClose: { // Cleanup is handled through RAII const intptr_t return_value = plugin->dispatcher(plugin, opcode, index, value, data, option); editor_.reset(); return return_value; } break; case effSetProcessPrecision: { // Used to initialize the shared audio buffers when handling // `effMainsChanged` in `Vst2Bridge::run()` double_precision_ = value == kVstProcessPrecision64; return plugin->dispatcher(plugin, opcode, index, value, data, option); break; } default: { return plugin->dispatcher(plugin, opcode, index, value, data, option); break; } } } AudioShmBuffer::Config Vst2Bridge::setup_shared_audio_buffers() { // We'll first compute the size and channel offsets for our buffer based on // the information already passed to us by the host. The offsets for each // audio channel are in samples (since they'll be used with pointer // arithmetic in `AudioShmBuffer`), and we'll only use the first bus (since // VST2 plugins don't have multiple audio busses). assert(max_samples_per_block_); uint32_t current_offset = 0; std::vector input_channel_offsets(plugin_->numInputs); for (int channel = 0; channel < plugin_->numInputs; channel++) { input_channel_offsets[channel] = current_offset; current_offset += *max_samples_per_block_; } std::vector output_channel_offsets(plugin_->numOutputs); for (int channel = 0; channel < plugin_->numOutputs; channel++) { output_channel_offsets[channel] = current_offset; current_offset += *max_samples_per_block_; } // The size of the buffer is in bytes, and it will depend on whether the // host is going to pass 32-bit or 64-bit audio to the plugin const uint32_t buffer_size = current_offset * (double_precision_ ? sizeof(double) : sizeof(float)); // We'll set up these shared memory buffers on the Wine side first, and then // when this request returns we'll do the same thing on the native plugin // side AudioShmBuffer::Config buffer_config{ .name = sockets_.base_dir_.filename().string(), .size = buffer_size, .input_offsets = {std::move(input_channel_offsets)}, .output_offsets = {std::move(output_channel_offsets)}}; if (!process_buffers_) { process_buffers_.emplace(buffer_config); } else { process_buffers_->resize(buffer_config); } // The process functions expect a `T**` for their inputs and outputs, so // we'll also set those up right now process_buffers_input_pointers_.resize(plugin_->numInputs); for (int channel = 0; channel < plugin_->numInputs; channel++) { if (double_precision_) { process_buffers_input_pointers_[channel] = process_buffers_->input_channel_ptr(0, channel); } else { process_buffers_input_pointers_[channel] = process_buffers_->input_channel_ptr(0, channel); } } process_buffers_output_pointers_.resize(plugin_->numOutputs); for (int channel = 0; channel < plugin_->numOutputs; channel++) { if (double_precision_) { process_buffers_output_pointers_[channel] = process_buffers_->output_channel_ptr(0, channel); } else { process_buffers_output_pointers_[channel] = process_buffers_->output_channel_ptr(0, channel); } } return buffer_config; } intptr_t VST_CALL_CONV host_callback_proxy(AEffect* effect, int opcode, int index, intptr_t value, void* data, float option) { return get_bridge_instance(effect).host_callback(effect, opcode, index, value, data, option); }