Skip to content

Releases: JaySNL/VSTForResolveLinux

v0.3.0.1 - the Fairlight freeze is a deadlock in Resolve

Choose a tag to compare

@JaySNL JaySNL released this 12 Sep 13:31

The Fairlight freeze is a deadlock inside Resolve, and this release repairs the lock order.

A tester's frozen Resolve was captured live with thread apply all bt: 468 threads, four waiting on a mutex, none running audio.

Function Takes first Then wants
BMDChainFX::ResetHistory this+0x568 this+0x218
BMDAudioPluginImpl::PreProcess this+0x218 this+0x568

Same object, opposite order. Each thread waits for the lock the other holds, nothing times out, and the trigger is a mouse drag on the timeline while audio runs.

The inversion is Blackmagic's and needs no third-party plugin. What hosting changes is how long the audio thread stands inside it: PreProcess holds 0x218 across the dispatch to the hosted plugin, so a plugin bridged through yabridge holds it for a Wine round trip rather than for microseconds. That turns a rare race into one you hit every session.

The fix

BMDChainFX::ResetHistory is wrapped so it takes this+0x218 first. Both mutexes are std::recursive_mutex, so the second take costs a counter increment. Every path then agrees on the order. The patch only ever adds a lock: it cannot introduce a data race, and it costs no latency.

Every offset is read out of the library's own instructions, never written down, and four checks must pass before anything is patched. A build that moved a member still works; a build that changed shape is left alone and says so in the log.

Off with FXBRIDGE_LOCKFIX=0.

Verified, and not

Verified on 21.1.0.0014: +0x218, +0x250, +0x568, the thunk adjusts 32, two vtable slots and one thunk slot rewritten, Resolve starts and runs. The 21.0.4 layout for that function is identical, checked by offset arithmetic against the tester's stack.

Not verified: that a ResetHistory has travelled through the wrapper, and that a session which used to freeze no longer does. The wrapper logs once the first time it runs:

[fxbridge] lockfix: a thunked ResetHistory came through the wrapper, mutex taken first

Install

Replace ~/.local/share/BMDAudioPlugins/libfxbridge.so and restart Resolve. On 21.0 nothing else changes. On 21.1 you also need libfxbridge-gate.so and resolve-with-fxbridge, and Resolve must be started through that launcher.

Corrections to earlier releases

The slow-plugin theory is dead: the v0.2.12 watchdog logged no slow block at all in 134 KB of log. And "it is a spin, not a deadlock, because the process sits at 145%" was a misreading of ps, whose %CPU column is a lifetime average.

Full analysis: docs/freeze-deadlock.md.

v0.3.0 - DaVinci Resolve 21.1 support

Choose a tag to compare

@JaySNL JaySNL released this 09 Sep 19:36

Resolve 21.1 will not load any external Fairlight plugin library, and says nothing about it. The effect list shows Resolve's own effects and none of yours. Nothing is wrong with your install: the configuration is read, the file is found, and the library is never opened.

What 21.1 changed

FLPluginHost::Initialize() is the function that reads BMDPlugins.Path. In 21.0 it does so immediately. In 21.1 it first asks Resolve's core interface a yes/no question keyed on the string "Debug", and returns without doing anything when the answer is no:

call  QString::fromAscii_helper("Debug", 5)
call  *0x1d0(%rbx)          ; a virtual on the core interface -> bool
test  %bpl,%bpl
je    +0xd4                 ; false: never read the setting, never load anything

That is a module gate, not a preference — the same virtual is called from 78 places in libFairlightPage.so, and the four that pass a literal pass "Studio", "ProxyGenerator", "RemoteControl" and "Debug". Writing a Debug row into configs/Fairlight/FLDebugSettings.csv was tried first and changes nothing. There is no setting to turn on.

No offset moved. All 32 vtable slots, the vtable size, the member offsets and the map node size are identical between 21.0.0.0048 and 21.1.0.0014.

The fix

~/.local/share/BMDAudioPlugins/resolve-with-fxbridge

The launcher preloads libfxbridge-gate.so, which replaces that one conditional jump with NOPs in memory, after Resolve has loaded the library and before it runs the function. Nothing on disk is modified, no root is needed, Blackmagic's files stay as they shipped, and not using the launcher undoes it. On 21.0 and earlier the gate is not there, and it logs one line saying so.

It anchors on the symbol _ZN12FLPluginHost10InitializeEv and requires the query and the gate to appear exactly once inside it — the gate's raw bytes occur twice in the library, so a plain signature search would be a coin toss. On any other shape it refuses and logs why.

Measured

21.0.0.0048 21.1.0.0014
without the preload 48 plugins listed nothing loaded
with the preload 48 plugins listed, "no gate" logged 48 plugins listed

tools/check-offsets.py now reports the gate as present or absent, so you can tell which Resolve you have without starting it.

Install

install -Dm755 libfxbridge.so         ~/.local/share/BMDAudioPlugins/libfxbridge.so
install -Dm755 fxbridge-scan          ~/.local/share/BMDAudioPlugins/fxbridge-scan
install -Dm755 libfxbridge-gate.so    ~/.local/share/BMDAudioPlugins/libfxbridge-gate.so
install -Dm755 resolve-with-fxbridge  ~/.local/share/BMDAudioPlugins/resolve-with-fxbridge

On 21.0 nothing changes: start Resolve as you always did. On 21.1, start it through resolve-with-fxbridge.

The bridge itself is unchanged from v0.2.12libfxbridge.so and fxbridge-scan are byte-identical to that release. Built in an Ubuntu 20.04 rootfs; floor GLIBC_2.16, CXXABI_1.3.9. Verify with sha256sum -c SHA256SUMS.

Not claimed

This clears one check that Blackmagic added. Whether they intended external Fairlight plugins to stay available on Linux is not something this repository knows.

v0.2.12 - a freeze now names the plugin that caused it

Choose a tag to compare

@JaySNL JaySNL released this 09 Sep 10:27

A slow plugin does not only drop audio, it freezes Resolve. This release measures that instead of guessing at it.

Resolve holds one lock per plugin for the whole of its processing call, and the same lock is taken when the playhead moves or the audio preview is cleared. So while a hosted plugin is working, the next thing you do that touches that effect waits behind it — and a plugin that takes seconds to answer looks exactly like a hung application.

Added

  • A watchdog on the hosted processing call. Every block is timed, and a block over 50 ms is written to the log by name:

    audio: SLOW - "MAutoDynamicEq" took 4231.077 ms for 1024 frames (block 1 past the limit, worst 4231.077 ms)
    

    FXBRIDGE_SLOW_MS moves the threshold. A 1024-frame block at 48 kHz is 21 ms of audio, so 50 ms has already missed the deadline twice over.

Nothing is aborted. A VST3 call through yabridge cannot be cancelled once it is in flight, and pretending otherwise would trade a freeze for a corrupted plugin. What the line buys is the name of the plugin to remove, which no crash dump carries.

Where this came from

A tester hit the same freeze four times on 2026-09-05, each with the identical stack:

ClearAudioPreview -> Previewer::Reset -> BMDChainFX::ResetHistory
  -> BMDAudioPluginImpl::ResetHistory+0x34    <- waiting on a lock

Reading libBMDAudioPlugins.so settles why that matters. Of the five functions that lock a plugin's own this+0x218, two are ResetHistory and PreProcess — and PreProcess is the call under which a hosted plugin runs.

Not fixed, and not claimed to be

Whether the hosted plugin is the slow party is still unmeasured. That is the point of the watchdog: the next freeze either names a plugin or clears the bridge. The frozen process was observed at 145% CPU, so it is a spin rather than a plain deadlock; where that CPU goes is not known.

Install

install -Dm755 libfxbridge.so ~/.local/share/BMDAudioPlugins/libfxbridge.so
install -Dm755 fxbridge-scan  ~/.local/share/BMDAudioPlugins/fxbridge-scan

Built in an Ubuntu 20.04 rootfs; floor is GLIBC_2.16, CXXABI_1.3.9. Verify with sha256sum -c SHA256SUMS.

v0.2.11 - NixOS support

Choose a tag to compare

@JaySNL JaySNL released this 04 Sep 23:35

NixOS support — and a tag that Nix can actually build.

If you are not on NixOS, skip this one

The library is unchanged. Nothing under src/ differs from v0.2.10, and the binaries here are
byte-identical — same sha256, verified by rebuilding from the new tag rather than copied
forward. There is no reason to re-download.

What is new

A flake, contributed by @mogorman in
#1:

nix build github:JaySNL/VSTForResolveLinux/v0.2.11
nix run  github:JaySNL/VSTForResolveLinux/v0.2.11#bmdaudioplugins-install

The first builds the bridge and the scanner. The second writes BMDPlugins.Path into
config-fairlight.dat for you, with a backup, the same way build.sh does elsewhere.

Third-party sources are pinned by revision and hash, matching this repo's own submodules, and the
Resolve path the bridge dlopens is rewritten to the Nix store path at build time.

Why it needed a version number

v0.2.10 was tagged before the flake was merged, so
nix build github:JaySNL/VSTForResolveLinux/v0.2.10 fails with no flake.nix. Without this tag a
Nix user has to pin main, which moves under them. That is the entire reason this release exists.

Corrections made to the contribution

Three, pushed on top and confirmed working by the contributor afterwards:

  • The checkPhase that refuses a library with unresolved symbols never ranstdenv only
    runs it when doCheck is set. That guard matters: this library links cleanly against a private
    ABI and can still carry symbols that fail only at dlopen.
  • aarch64-linux was advertised and cannot work. src/proxy.cpp carries hand-written x86-64
    assembly, and the Resolve vtable offsets the bridge patches are x86-64 layout.
  • The installer's "is Resolve running" guard matched /opt/resolve/bin/resolve — the exact path
    the flake rewrites because NixOS does not have it. So on NixOS it never fired, and Resolve
    rewrites its config on quit, which would have eaten the change silently.

Known, unchanged from v0.2.10

  • The locate forwarding is off by default. FXBRIDGE_RESET=1 re-enables it and can crash a
    VST2 through yabridge on project load.
  • A short silence when playback starts with a high-latency plugin. Exports are unaffected.
  • A plugin that changes its latency mid-session may not be re-compensated until the project is
    reopened.

The binaries

Built from the v0.2.11 tag in an Ubuntu 20.04 sandbox. libfxbridge.so floors at GLIBC_2.16.

8cb1a531772366d883f1ba8275e9da997f5dba61a0f284a9c1e0e1b9e569b014  libfxbridge.so
fcfcbb900031198219887721a3c40eee2cd01ae75b7d0215eceb1d5b3ac4ff90  fxbridge-scan

v0.2.10 - fixes a crash on project load in v0.2.9

Choose a tag to compare

@JaySNL JaySNL released this 03 Sep 12:50

v0.2.9 could crash Resolve while a project loaded. Install this instead.

If you are on v0.2.9 and a project with a Windows VST2 aborts on load — signal 6, terminate called without an active exception — that is this bug, and it is fixed here.

What happened

v0.2.9 added two things: telling Resolve how much latency a plugin has, and telling the plugin when
the playhead jumps. The first is fine and stays on. The second crashed.

The locate forwarding is now off by default. Confirmed by changing only that: with it off, the
same project opens, the same plugin loads, and delay compensation keeps working —
"PodcastPlugins TRACK" reports 5279 samples in the log.

Your sync stays fixed. What comes back is the older and much milder problem it replaced: after
a jump in the timeline, the first moments of audio may replay the tail of the previous position.
That is a monitoring annoyance. The crash was not.

Why it happened, since it is worth being straight about

The reset is deferred to the top of the next audio block on purpose — nothing is being processed
there, and CLAP names the audio thread as where a reset belongs. For VST2 that means dispatching
effStopProcess and effStartProcess from the audio thread into a Wine IPC bridge, and yabridge
expects most dispatcher traffic on the main thread.

The reasoning was right for VST3 and CLAP. It was generalised to VST2 without evidence for it, and
it was shipped. That is the mistake.

FXBRIDGE_RESET=1 turns it back on. That is how the real fix gets tested; it is not recommended
otherwise.

Also fixed

The Carla idle thread was created and never joined or detached, so its destructor ran at process
exit while still joinable — which aborts with the identical message a dying yabridge host
produces. Only reachable with the Carla loader selected, and not the crash above. Two sources of
the same abort text in one binary is a trap worth removing.

Install

curl -L -o ~/.local/share/BMDAudioPlugins/libfxbridge.so \
  https://github.com/JaySNL/VSTForResolveLinux/releases/latest/download/libfxbridge.so

Your cache, deny list and settings are untouched.

Known, unchanged from v0.2.9

  • A short silence when playback starts with a high-latency plugin. Exports are unaffected.
  • A plugin that changes its latency mid-session may not be re-compensated until the project is
    reopened.

The binaries

Built from the v0.2.10 tag in an Ubuntu 20.04 sandbox. libfxbridge.so floors at GLIBC_2.16.

8cb1a531772366d883f1ba8275e9da997f5dba61a0f284a9c1e0e1b9e569b014  libfxbridge.so
fcfcbb900031198219887721a3c40eee2cd01ae75b7d0215eceb1d5b3ac4ff90  fxbridge-scan

v0.2.9 - Resolve knows how much latency your plugins have

Choose a tag to compare

@JaySNL JaySNL released this 30 Aug 16:49

Resolve now knows how much latency your plugins have, and compensates for it.

Stacking effects used to push the audio out of sync with the video, and the first moments after a
jump in the timeline replayed the end of the previous position. Both are fixed, and both are
confirmed on the machine that reported them.

The bug, in one line

GetPluginLatency -> 0    plugin says 6144

Resolve asks each effect how much latency it has, once, at project load. The plugin knew — 6144
samples, 2048, 1080, 720. The bridge answered zero every time, so Resolve compensated for none
of it, and the error grew with every effect you stacked.

It now answers with the plugin's real figure. Only the answer is ours: the poll, the cache, the
mutex and Resolve's own delay compensation are untouched. That machinery was working correctly the
whole time, on an input of zero.

Confirmed from both directions — correct sync with the audio track back at zero offset, and the
cached value Resolve actually reads now carrying 1024, 6144, 2048, 720 and 1080 where every earlier
log showed zero.

And the jump

Resolve tells an effect when the playhead moves, a dozen times a session. The bridge dropped it, so
delay lines, lookahead buffers and reverb tails survived a locate — the first moments after a jump
were the tail of wherever you had been.

The plugin is now told: VST3 setProcessing off and on, VST2 stop and start process, CLAP
reset(). It happens at the top of the next audio block rather than the moment Resolve announces
it, so it can never land inside processing on another thread.

Known, and not fixed

  • A short silence when playback starts with a high-latency plugin loaded — a few hundred
    milliseconds. Exports are not affected; the rendered file is correct. It is not the reset:
    turning that off with FXBRIDGE_RESET=0 leaves the gap exactly as it was. Still being chased.
  • A plugin that changes its latency while the project is open may not be re-compensated until
    the project is reopened.
  • One instance in one session reported 768 samples to the plugin and zero to the cache, where every
    other instance agreed. Undiagnosed, 16 ms.

Install

curl -L -o ~/.local/share/BMDAudioPlugins/libfxbridge.so \
  https://github.com/JaySNL/VSTForResolveLinux/releases/latest/download/libfxbridge.so
curl -L -o ~/.local/share/BMDAudioPlugins/fxbridge-scan \
  https://github.com/JaySNL/VSTForResolveLinux/releases/latest/download/fxbridge-scan
chmod +x ~/.local/share/BMDAudioPlugins/fxbridge-scan

Your cache and deny list are kept. The log names each effect's latency:

[fxbridge] latency: "Acon Digital DeVerberate 3" reports 6144 samples

FXBRIDGE_LATENCY=0 and FXBRIDGE_RESET=0 turn the two halves off. They exist for narrowing down
a problem, not for daily use.

How this was found

Two instrumented builds that changed nothing and only wrote down what Resolve asked, before a line
of the fix was written. That is also how the first plan was caught being wrong: Resolve calls the
thunk entries, not the ones the analysis named, and a fix aimed where it was planned would have
changed nothing and looked like a dead theory.

The whole account, wrong turns included, is in
docs/latency-and-reset.md.

The binaries

Built from the v0.2.9 tag in an Ubuntu 20.04 sandbox, so they run on Ubuntu 20.04, Debian 11 and
Rocky 8. libfxbridge.so floors at GLIBC_2.16.

ab4936f505d50704a36c7da45608ecd031fcc1d74762bd59595794060c42afe9  libfxbridge.so
fcfcbb900031198219887721a3c40eee2cd01ae75b7d0215eceb1d5b3ac4ff90  fxbridge-scan

v0.2.8 - the scan moves out of Resolve and runs eight at a time

Choose a tag to compare

@JaySNL JaySNL released this 29 Aug 19:01

The slow first scan moves out of Resolve, gets a progress bar, and runs eight modules at a time.

fxbridge-scan — build the cache before you start Resolve

The scan was always going to be slow once: reading a plugin's name means opening it, and opening a
Windows VST3 through yabridge starts a Wine host. Until now that happened inside Resolve's splash
screen, with nothing to read and nothing to do.

  [##########..............]  42%  139/330  3m11s elapsed  ~4m22s left  MTurboDelayMB

build.sh builds it, installs it and runs it. It ships here beside the library. It is the same
code the bridge runs and writes the same cache to the same place, so Resolve then starts on that
cache and opens nothing.

Two things follow from it being a separate program:

  • A plugin that faults while being read takes down a command, not an edit session.
  • Ctrl-C is told apart from a plugin faulting. The interrupt deletes the in-flight note, so
    nothing is blamed for a run that was stopped on purpose. Verified both ways: SIGINT keeps the
    19 cached modules and blames nobody, SIGKILL leaves the two that were genuinely open.

Eight at a time

Measured before it was changed, because the fix follows the measurement:

phase cost
dlopen 0 ms
ModuleEntry 327–352 ms
ModuleExit 26–29 ms
dlclose 0 ms

All of the cost is a separate process starting — so there is nothing to shave off the work itself,
and separate processes start alongside each other. Two modules took 1196–1726 ms serially against
510–951 ms in parallel over three runs each. The whole scan here went from 2,910 s to 1,232 s,
on a set where only two of twenty-one modules are Windows plugins. A collection that is mostly
Windows plugins should do better than that.

FXBRIDGE_SCAN_THREADS sets the number. Default 8, capped at 16.

Bounded, and the bound is the point: the 336 Wine hosts that made this slow in the first place were
unbounded and permanent. Eight, for the length of one open, is a different thing.

A start that ended with modules open runs the next scan one at a time. Eight in flight means
eight stranded names and only one culprit, so a recovery run goes serial and the name that strands
again is the answer rather than a one-in-eight guess.

Also

The plugins' own output — yabridge writes a paragraph for every bundle that holds no Windows
module — now goes to ~/.local/share/BMDAudioPlugins/fxbridge-scan-errors.log instead of through
the progress bar it used to tear in half. It is kept rather than thrown away: if a plugin faults
while it is being read, its last words are in there.

Install

mkdir -p ~/.local/share/BMDAudioPlugins
curl -L -o ~/.local/share/BMDAudioPlugins/libfxbridge.so \
  https://github.com/JaySNL/VSTForResolveLinux/releases/latest/download/libfxbridge.so
curl -L -o ~/.local/share/BMDAudioPlugins/fxbridge-scan \
  https://github.com/JaySNL/VSTForResolveLinux/releases/latest/download/fxbridge-scan
chmod +x ~/.local/share/BMDAudioPlugins/fxbridge-scan

Then, with Resolve closed:

~/.local/share/BMDAudioPlugins/fxbridge-scan

Your existing cache is kept — only plugins it has not read yet are opened.

The binaries

Both built from the v0.2.8 tag in an Ubuntu 20.04 sandbox, so they run on Ubuntu 20.04,
Debian 11 and Rocky 8. libfxbridge.so floors at GLIBC_2.16, fxbridge-scan at GLIBC_2.14.

55056f305a59fc32e86f82478982cd0d92c345bca2d0845066a2f481d459437c  libfxbridge.so
0497b7a8c6e796d7021ba0c4f093f7a9eccc85719b5c3419318f1c0ac9df910d  fxbridge-scan

Known, and not done

Every non-shell module answers exactly one class, and its filename is already the plugin's name —
so a cold scan could open nothing and resolve the class when the plugin is actually loaded,
taking the first run from minutes to seconds. The obstacle is shells: one WaveShell holds 718
plugins behind a filename that names none of them. That needs designing, not typing, and it is not
in this release.

v0.2.4 - the editor opens when you ask for it

Choose a tag to compare

@JaySNL JaySNL released this 29 Aug 16:09

A plugin GUI opens when you click the effect, not when the project loads. On the test project that is eight windows at load down to zero.

This release is made of a tester's list. Four of the items turned out to be one cause.

The bridge opened every editor because it thought Resolve never would

A comment in the source said that with the stock panel suppressed Resolve never calls InitializeEffectEdit, so there would be no other way to open a window at all. It does call it. One click on the effect in the Audio FX panel logs:

editor: shown (AudioPlugin::InitializeEffectEdit)

The reading that said otherwise watched the primary slot; the one that fires is the AudioPlugin base. With a real open signal, three reported problems go away together.

Every plugin GUI constructed during a project load is the worst thing this bridge did to a large project. FXBRIDGE_OPEN_ON_CLAIM=1 brings the old behaviour back if a machine turns out to need it.

A window that came back after you closed the modal. A hide arriving with our window already gone used to be re-read as a request to show it. A hide is a hide now.

A deleted effect could leave its GUI on screen. The pump closes the editor of any effect whose vtable pointer is no longer ours.

Editor windows behave like windows

They were _NET_WM_WINDOW_TYPE_UTILITY, chosen so a tiling layout would not claim them — and KWin excludes utility windows from the switcher by type, so no amount of window-state juggling would have brought them back. They are dialogs now: a tiling layout floats a dialog for the same reason, and KWin lists it.

Three things were missing outright, which is why an editor also never took the keyboard:

  • WM_CLASS. A desktop environment labels a window by looking its class up in the installed desktop files, and a class of our own matched nothing, so KDE listed every editor as unknown. The class is Resolve's own now, which is also the honest description of what these windows are.
  • WM_HINTS with input = True.
  • An _NET_ACTIVE_WINDOW request, on the first map and on every reopen. A map alone leaves the window behind whatever you were on.

FXBRIDGE_WINDOW_TYPE accepts dialog (the default), utility and normal.

Unchanged from v0.2.3

Settings live in the Resolve project. See the v0.2.3 notes for what that does and what it still cannot do.

Install

See the readme. Every release and what changed in it is in CHANGELOG.md.

Not a supported product. It patches structures inside Resolve's process at run time and can take Resolve down mid-edit. Save often.

The binary

Built in an Ubuntu 20.04 rootfs, not on a current distribution. Floor GLIBC_2.16, CXXABI_1.3.9, GLIBCXX_3.4.22, and zero undefined symbols when checked inside that same old rootfs — which covers Ubuntu 18.04, Debian 9, Rocky 8 and Fedora 25 upward.

sha256  e499e71708fc8d5018a696fd9c41613aeaf176e17745413e7ae0f022a3ba58b1

That does not prove Resolve runs on a distribution that old. The bridge was tested there, the host application was not.

The build is scripted now: tools/release-sandbox.sh makes the rootfs, tools/release-build.sh v0.2.4 compiles the tag inside it and refuses to emit a binary with undefined symbols.

v0.2.3 - settings that live in the Resolve project

Choose a tag to compare

@JaySNL JaySNL released this 29 Aug 16:09

Your plugin settings are in the project now. Nothing to turn on.

A hosted plugin's own state travels inside the effect's AudioPluginPreset, so it belongs to that effect, it moves with the project, and it survives a chain that gets rearranged. The file store that did this job before is now a fallback, and still opt-in.

How it got here, and two things this project got wrong

The v0.2.1 notes said a project save asks the effect nothing, on the evidence that StorePreset and LoadPreset fired zero times across a session. That was measured on the wrong vtable slots — the primary pair. Resolve holds an AudioPlugin* and calls the thunks. Watching the right pair, LoadPreset fires once per effect on every project load and StorePreset fires on every save.

An engineering-log note also said the Linux build contains no VST host class. It contains 198 symbols of one. Reading it is what made this release possible, and both notes are retracted where they stand.

Riding in Resolve's own channel

Resolve's VST host does exactly this. VSTPlugin::StorePreset picks between two payload shapes on one AEffect flag — a parameter table when the plugin has no chunk, the plugin's opaque chunk when it has one — and both travel in the same pointer and length, with a type tag saying which. The serialised header carries that length and tag through the project file, with a uint32 ceiling.

The bridge leaves the tag alone and appends its chunk behind the carrier's own payload, behind a footer. On the way back in the length is set to the carrier's own for the stock call and restored afterwards, so Resolve's parser never sees a byte it did not write.

Also fixed

A crash on project load. GetControlType is the one accessor of twenty-nine in BMDAudioPluginImpl that indexes the control vector with no bounds check, and Fairlight calls it for every index while deserialising.

A fault handler. A crash now writes a named backtrace to ResolveDebug.txt, because systemd-coredump drops a process this size.

Measured

Eight effects on one timeline, file store switched off so nothing else could have supplied them: eight attached on save, eight restored on load, smartEQ4's 2,320,783-byte chunk included. Zero faults on either half.

Known: a plugin-only edit does not mark the project modified

Resolve serialises the effects model only when it already thinks the project changed, and a change made inside a plugin's own window is invisible to it. Nothing is lost — it is written on the next thing you do that Resolve can see. Measured: a plugin-only edit then Ctrl+S produced zero saves; a fader on a track carrying no effects at all produced eight, one for every effect in the project.

FXBRIDGE_STATE_STORE=1 covers a session that changes nothing else at all.

Install

See the readme. Every release and what changed in it is in CHANGELOG.md.

Not a supported product. It patches structures inside Resolve's process at run time and can take Resolve down mid-edit. Save often.

The binary

Built in an Ubuntu 20.04 rootfs, not on a current distribution. Floor GLIBC_2.16, CXXABI_1.3.9, GLIBCXX_3.4.22, and zero undefined symbols when checked inside that same old rootfs — which covers Ubuntu 18.04, Debian 9, Rocky 8 and Fedora 25 upward.

sha256  7b0c504364e635f166ed1fe0a475d93de93dfc2a262d68177d43ff6a4a4a9107

That does not prove Resolve runs on a distribution that old. The bridge was tested there, the host application was not.

The build is scripted now: tools/release-sandbox.sh makes the rootfs, tools/release-build.sh v0.2.3 compiles the tag inside it and refuses to emit a binary with undefined symbols.

v0.2.1 - settings per instance, and editor edits that reach the plugin

Choose a tag to compare

@JaySNL JaySNL released this 29 Aug 12:47

Correction, added 2026-08-29. The section "There is no save hook" below is wrong. It rested
on StorePreset and LoadPreset firing zero times on a save, and that was measured on the wrong
vtable slots — the primary pair. Resolve holds an AudioPlugin* and calls the thunks. On the
right pair, LoadPreset fires once per effect on every project load and StorePreset fires on
every save. A hosted plugin's settings live in the project from
v0.2.3 onward. The rest of
these notes still stands.

Delirio asked whether the same EQ twice in one chain could hold two different settings. It could not. Finding out why turned up four defects, three of them mine.

One file per instance, not per plugin

The same plugin twice in a chain shared one settings file, so both copies came back with whichever was touched last. An instance is now identified by its position among the live effects hosting that plugin, in Resolve's own load order.

Live is the load-bearing word. Nothing here is ever freed, so counting every effect ever claimed would give the second project's first EQ the number 1, and the same project reopened a third number again. EffectIsLive reads our vptr back off the instance through SafeRead: once Resolve frees it, that word is unmapped or somebody else's.

Editor edits never reached the plugin

performEdit was return V3_OK and nothing else, behind a single component handler shared by every effect — it could not have said which plugin was speaking. VST3 splits a plugin in two, and the host is the only thing connecting the editor to the processor. So the processor never heard about anything you did in a plugin's own window.

Each effect now owns its handler and queues the change, and the next process() call carries it over as a real parameter change.

Both halves of the state, not one

SaveState asked the component only. A VST3 keeps settings in two places and the SDK does not require them to share anything, so a plugin that keeps its values controller-side reported nothing at all. Both halves are now stored and restored in the SDK's order. Blobs written by v0.2.0 are still read.

Edits with the transport stopped

A parameter change only reaches the processor inside a process() call, and Resolve only makes those while audio runs — so anything you changed with playback stopped went nowhere. The host thread now issues the SDK's parameter flush: a process() call with no audio, carrying only the changes.

Measured, not assumed

Two Waves F6-RTA in one chain, set to different curves: edits routed per instance to different parameter ids, 5020 and 4892 bytes saved, both restored byte-exact across a full Resolve restart, and 84 changes delivered with the transport stopped.

Two things that are still true

There is no save hook. A project save asks the effect nothing — StorePreset, LoadPreset and GetParameterValue all fire zero times on Ctrl+S while other traced slots on the same object fire normally. Resolve persists the parameter values it already holds, which a hosted plugin's internal edits never touch. That is why the store runs on a timer and on every project load, and it is the reason the next real piece of work is publishing the plugin's parameters as the effect's own.

Some editors stay silent. Waves F6-RTA pushes all 84 parameters on a preset recall, and reported nothing for one band dragged by hand. Where the editor does not tell the host, nothing on this side can see it.

Settings are still opt-in: FXBRIDGE_STATE_STORE=1 /opt/resolve/bin/resolve

The binary

Built in an Ubuntu 20.04 sandbox. Floor GLIBC_2.16, CXXABI_1.3.9, GLIBCXX_3.4.22, zero undefined symbols when checked inside that same old rootfs. That does not prove Resolve runs on a distribution that old — the bridge was tested there, the host application was not.

sha256  80863b8eae1197e27cc456a7e8d60de2bf9a50caa4ecc3379d245ff669dab66d

Not a supported product. It patches structures inside Resolve's process at run time and can take Resolve down mid-edit. Save often.