Should be equivalent. The only reason why we use container2b in some
places is because strings based on `Steinberg::char16` arrays will have
an incorrect length on the Wine side, because character traits for
`wchar_t` is still reflects Linux instead of Windows there.
This basically changes the default small vectors during VST2 event
processing from 256 bytes to the size of a `DynamicVstEvents`
object (which also includes a small_vector to hold MIDI events without
allocating) and makes them thread local. We already have a similar
optimization for VST3. There it's a bit neater since we already had to
separate audio processing functions from non-time critical functions.
Here we don't have that separation, so we just made these buffers thread
local, large enough to hold our predefined number of events, and we then
just shrink them to fit if these buffers grow even more (which can only
happen after reading or writing chunk data).
The change doesn't specifically target `effProcessEvents()`, but that's
where you would see the differences. This is also relevant for
`audioMasterProcessEvents()`.
Apparently we also never did this for VST2 plugins, so this should be
safe. Filling the vectors with zeroes here had a non-negligible
performance impact according to perf.
I once read years ago somewhere on Stack Overflow that `std::vectors`
with that are preinitialized to a default size would allocate the
initial capacity on the stack. This of course doesn't make any
sense (run time sized stack allocations can cause all kinds of issues),
so we were still allocating with our default 64-byte sized buffers, but
just not as often.
C++ would always construct an `std::string` from the string constant
every iteration. Since this also happened when `YABRIDGE_DEBUG_LEVEL` is
not set to 2, this ended up causing unnecessary allocations.
Events, parameter changes, and the individual queues contained within
the parameter changes all use dynamic memory allocation. Preallocating
some memory for those things inside of the objects may prevent latency
spikes when they those objects are first filled. This is especially
useful for the parameter changes since there's no way to reserve memory
in a vector of vectors.
This prevents reinitializing `std::variant`s when the variant we want to
deserialize is already active. We store audio buffers in variants, so
reinitializing them results in a lot of unnecessary memory frees,
allocations and writes during every processing cycle.
I blindly assumed the original implementation also did this, but this
version `std::variant<Ts...>` objects from being reinitialized if we're
deserializing a variant that's also currently active in the object we're
deserializing into. For simple structs this won't make any difference,
but in yabridge we often use variants to differentiate between things
like single precision and double precision audio buffers. Those buffers
are allocated on the heap, so recreating the objects every time we
deserialize them adds a lot of unnecessary overhead.
We did a ton of work earlier to make sure we can reuse these objects,
but `auto` implies the type is never a reference type, and we were thus
unnecessarily creating copies every iteration, kind of defeating the
purpose of doing all of this in the first place. We could do some
template trickery here, but it's also safe to just make the persistent
object thread local since the actual objects aren't that large.
We don't need any special handling for this since our default argument
detection will handle strings, but it might be useful for log output if
a host ever uses this. At the moment there don't seem to be any hosts on
Linux that use this.
This does what we did for a few functions in the last few commits for
every function. We now use either the `std::invocable` concept or our
own `invocable_returning` concept wherever possible to make sure we pass
function types to these template functions, since constraint errors are
a lot more readable than template deduction errors. And instead of
having to specify the return type as a template argument, we now just
use `std::invoke_result_t<F>` instead. The VST3 message handling
functions are still using the good old `typename F` since those are
overloaded polymorphic functions. This was also a good moment to modify
`AdHocSocketHandler::send()` to allow functions returning void (this got
rid of an old fixme where we had to return some dummy value from a
function instead of just not returning anything).
As it turns out, we'll sadly also need this for VST2 plugins on the Wine
side, so we should probably finally encapsulate this instead of
duplicating it a third time.
For some reason ujam plugins (and other plugins made with the Gorilla
Engine, like the LoopCloud plugins) will throw a `JS_EXEC_FAILED` error
when trying to load the plugin while either of the STDOUT or STDERR
streams is pointing to a pipe. Simply redirecting the output to a file
fixes this. By default we'll write the output to
`<temporary_directory>/yabridge-plugin-output.log`, but you can also set
the new `disable_pipes` option to `"/dev/null"` to completely throw away
all output.
This addresses #47.
Apparently this can actually make a difference in some cases, and the
C++ Core Guideliens recommend doing this on all default constructors,
destructors, and all functions that can not throw (and thus also don't
allocate).
This is very ugly so hopefully I can think of a neater way, but now the
response object is just a set of pointers, so we can avoid all copies
and moves on the Wine side.
Making these thread local statics makes much more sense for their
purpose. The old approach technically wasn't thread safe (even if it was
never an issue) and this gets rid of a data structure.
We do this by using this new `MessageReference<T>` type to avoid copying
our `YaAudioProcessor::Process` struct and the contained `YaProcessData`
object. This is only part of the work, but this redesign lets us keep
the these objects alive on both the plugin and the host side. On the
plugin side, we'll simply serialize the data from the referred to object
without copying it. On the Wine side, we'll write the data to a
persistent thread local object, and then reassign the
`MessageReference<T>` to point to that object. This lets us serialize
'references', thus avoiding potentially expensive allocations. With
these last few changes alone VST3 plugins are already at the same
performance level as our optimized VST2 plugin groups.
This is kind of equivalent to `std::reference_wrapper`, but with default
initialization support (which is UB, but is required for serialization)
and a forward for `T::Response` as used by your sockets API.