Allow resizing shared memory buffers in setActive

REAPER apparently changes bus arrangements between
`IAudioProcessor::setupProcessing()` and `IAudioProcessor::setActive()`.
This commit is contained in:
Robbert van der Helm
2022-05-18 17:46:51 +02:00
parent 5d9029d839
commit dedcdefebf
8 changed files with 95 additions and 20 deletions
+2
View File
@@ -69,6 +69,8 @@ Versioning](https://semver.org/spec/v2.0.0.html).
### Fixed ### Fixed
- Fixed manually changing channel counts with supported VST3 plugins in
**REAPER** not working.
- Fixed an obscure issue with VST3 plugins crashing in **Ardour** on - Fixed an obscure issue with VST3 plugins crashing in **Ardour** on
Arch/Manjaro because of misreported parameter queue lengths. Arch/Manjaro because of misreported parameter queue lengths.
- Some of yabridge's socket file names contained extremely aesthetically - Some of yabridge's socket file names contained extremely aesthetically
+13
View File
@@ -1897,6 +1897,19 @@ void Vst3Logger::log_response(
}); });
} }
void Vst3Logger::log_response(bool is_host_vst,
const YaComponent::SetActiveResponse& response) {
log_response_base(is_host_vst, [&](auto& message) {
message << response.result.string();
if (response.result == Steinberg::kResultOk &&
response.updated_audio_buffers_config) {
message << ", <new shared memory configuration for \""
<< response.updated_audio_buffers_config->name << "\", "
<< response.updated_audio_buffers_config->size << " bytes>";
}
});
}
void Vst3Logger::log_response( void Vst3Logger::log_response(
bool is_host_vst, bool is_host_vst,
const YaPrefetchableSupport::GetPrefetchableSupportResponse& response) { const YaPrefetchableSupport::GetPrefetchableSupportResponse& response) {
+1
View File
@@ -319,6 +319,7 @@ class Vst3Logger {
bool from_cache = false); bool from_cache = false);
void log_response(bool is_host_vst, void log_response(bool is_host_vst,
const YaComponent::GetRoutingInfoResponse&); const YaComponent::GetRoutingInfoResponse&);
void log_response(bool is_host_vst, const YaComponent::SetActiveResponse&);
void log_response( void log_response(
bool is_host_vst, bool is_host_vst,
const YaPrefetchableSupport::GetPrefetchableSupportResponse&); const YaPrefetchableSupport::GetPrefetchableSupportResponse&);
@@ -194,6 +194,11 @@ class YaAudioProcessor : public Steinberg::Vst::IAudioProcessor {
/** /**
* Message to pass through a call to * Message to pass through a call to
* `IAudioProcessor::setupProcessing(setup)` to the Wine plugin host. * `IAudioProcessor::setupProcessing(setup)` to the Wine plugin host.
*
* Here Wine plugin host will set up the shared memory buffers.
*
* NOTE: This process is repeated as part of `SetActive`. Apparently REAPER
* can change bus arrangements after the processing has been set up.
*/ */
struct SetupProcessing { struct SetupProcessing {
using Response = SetupProcessingResponse; using Response = SetupProcessingResponse;
@@ -18,6 +18,7 @@
#include <pluginterfaces/vst/ivstcomponent.h> #include <pluginterfaces/vst/ivstcomponent.h>
#include "../../../audio-shm.h"
#include "../../../bitsery/ext/in-place-optional.h" #include "../../../bitsery/ext/in-place-optional.h"
#include "../../common.h" #include "../../common.h"
#include "../base.h" #include "../base.h"
@@ -251,12 +252,32 @@ class YaComponent : public Steinberg::Vst::IComponent {
int32 index, int32 index,
TBool state) override = 0; TBool state) override = 0;
/**
* The response code and written state for a call to
* `IAudioProcessor::setupProcessing(setup)`.
*/
struct SetActiveResponse {
UniversalTResult result;
std::optional<AudioShmBuffer::Config> updated_audio_buffers_config;
template <typename S>
void serialize(S& s) {
s.object(result);
s.ext(updated_audio_buffers_config,
bitsery::ext::InPlaceOptional{});
}
};
/** /**
* Message to pass through a call to `IComponent::setActive(state)` to the * Message to pass through a call to `IComponent::setActive(state)` to the
* Wine plugin host. * Wine plugin host.
*
* NOTE: REAPER may change a plugin's bus arrangements after the processing
* has been set up, so we need to check for this on every
* `setActive()` call
*/ */
struct SetActive { struct SetActive {
using Response = UniversalTResult; using Response = SetActiveResponse;
native_size_t instance_id; native_size_t instance_id;
+22 -5
View File
@@ -416,14 +416,31 @@ Vst3PluginProxyImpl::activateBus(Steinberg::Vst::MediaType type,
tresult PLUGIN_API Vst3PluginProxyImpl::setActive(TBool state) { tresult PLUGIN_API Vst3PluginProxyImpl::setActive(TBool state) {
// HACK: Even though we initially implemented this cache specifically for // HACK: Even though we initially implemented this cache specifically for
// REAPER, REAPER doesn't use `IComponent::setProcessing` properly and // REAPER, REAPER doesn't use `IComponent::setProcessing()` properly
// calls it before doing setting up input and output busses. So now // and calls it before doing setting up input and output busses. So
// our workaround to get acceptable performance in REAPER needs a // now our workaround to get acceptable performance in REAPER needs a
// workaround of its ownn. Great! // workaround of its own. Great!
clear_bus_cache(); clear_bus_cache();
return bridge_.send_audio_processor_message( const SetActiveResponse response = bridge_.send_audio_processor_message(
YaComponent::SetActive{.instance_id = instance_id(), .state = state}); YaComponent::SetActive{.instance_id = instance_id(), .state = state});
// NOTE: REAPER may (and will) change a plugin's channel layout after
// calling `setupProcessing()`. Because of that, we need to test
// whether this has happened any time the plugin gets reactivated.
// It's technically legal, so we need to support it.
if (response.updated_audio_buffers_config) {
// The host should absolutely not call this function before
// `setupProcessing()` so this should already contain a value, but you
// never know...
if (!process_buffers_) {
process_buffers_.emplace(*response.updated_audio_buffers_config);
} else {
process_buffers_->resize(*response.updated_audio_buffers_config);
}
}
return response.result;
} }
tresult PLUGIN_API Vst3PluginProxyImpl::setState(Steinberg::IBStream* state) { tresult PLUGIN_API Vst3PluginProxyImpl::setState(Steinberg::IBStream* state) {
+24 -10
View File
@@ -1351,20 +1351,18 @@ Vst3Bridge::get_instance(size_t instance_id) noexcept {
object_instances_.at(instance_id), std::move(lock)); object_instances_.at(instance_id), std::move(lock));
} }
AudioShmBuffer::Config Vst3Bridge::setup_shared_audio_buffers( std::optional<AudioShmBuffer::Config> Vst3Bridge::setup_shared_audio_buffers(
size_t instance_id) { size_t instance_id) {
const auto& [instance, _] = get_instance(instance_id); const auto& [instance, _] = get_instance(instance_id);
// TODO: When also calling this in setActive, return a nullopt instead of
// asserting
assert(instance.process_buffers);
const Steinberg::IPtr<Steinberg::Vst::IComponent> component = const Steinberg::IPtr<Steinberg::Vst::IComponent> component =
instance.interfaces.component; instance.interfaces.component;
const Steinberg::IPtr<Steinberg::Vst::IAudioProcessor> audio_processor = const Steinberg::IPtr<Steinberg::Vst::IAudioProcessor> audio_processor =
instance.interfaces.audio_processor; instance.interfaces.audio_processor;
// TODO: When also calling this in setActive, return a nullopt instead of
// asserting if (!instance.process_setup || !component || !audio_processor) {
assert(component && audio_processor); return std::nullopt;
}
// We'll query the plugin for its audio bus layouts, and then create // We'll query the plugin for its audio bus layouts, and then create
// calculate the offsets in a large memory buffer for the different audio // calculate the offsets in a large memory buffer for the different audio
@@ -1575,7 +1573,7 @@ size_t Vst3Bridge::register_object_instance(
// buffers. // buffers.
instance.process_setup = request.setup; instance.process_setup = request.setup;
const AudioShmBuffer::Config audio_buffers_config = const AudioShmBuffer::Config audio_buffers_config =
setup_shared_audio_buffers(request.instance_id); *setup_shared_audio_buffers(request.instance_id);
return YaAudioProcessor::SetupProcessingResponse{ return YaAudioProcessor::SetupProcessingResponse{
.result = result, .result = result,
@@ -1739,12 +1737,28 @@ size_t Vst3Bridge::register_object_instance(
// deadlocks caused by mutually recursive function // deadlocks caused by mutually recursive function
// calls. // calls.
return do_mutual_recursion_on_off_thread( return do_mutual_recursion_on_off_thread(
[&]() -> tresult { [&]() -> YaComponent::SetActive::Response {
const auto& [instance, _] = const auto& [instance, _] =
get_instance(request.instance_id); get_instance(request.instance_id);
return instance.interfaces.component->setActive( const tresult result =
instance.interfaces.component->setActive(
request.state); request.state);
// NOTE: REAPER may change the bus layout after
// calling
// `IAudioProcessor::setupProcessing`. In
// that case we'll need to resize the
// shared memory buffers here.
const std::optional<AudioShmBuffer::Config>
updated_audio_buffers_config =
setup_shared_audio_buffers(
request.instance_id);
return YaComponent::SetActiveResponse{
.result = result,
.updated_audio_buffers_config = std::move(
updated_audio_buffers_config)};
}); });
}, },
[&](const YaPrefetchableSupport::GetPrefetchableSupport& [&](const YaPrefetchableSupport::GetPrefetchableSupport&
+5 -3
View File
@@ -456,10 +456,12 @@ class Vst3Bridge : public HostBridge {
* instance and return the configuration so the native plugin can connect to * instance and return the configuration so the native plugin can connect to
* it as well. * it as well.
* *
* This uses the `Vst3PluginInstance::process_setup` field, so that needs to * This returns a nullopt when `Vst3PluginInstance::process_setup` is not
* be set first or this function raise SIGABRT. * set, or when the object instance does not support the `IAudioProcessor`
* interface.
*/ */
AudioShmBuffer::Config setup_shared_audio_buffers(size_t instance_id); std::optional<AudioShmBuffer::Config> setup_shared_audio_buffers(
size_t instance_id);
/** /**
* Assign a unique identifier to an object and add it to * Assign a unique identifier to an object and add it to