Skip to content

VST3: implement setComponentState (fixes state restore in Ableton Live) - #476

Closed
olilarkin wants to merge 16 commits into
free-audio:nextfrom
olilarkin:vst3-set-component-state
Closed

VST3: implement setComponentState (fixes state restore in Ableton Live)#476
olilarkin wants to merge 16 commits into
free-audio:nextfrom
olilarkin:vst3-set-component-state

Conversation

@olilarkin

Copy link
Copy Markdown

Fixes #475.

Ableton Live restores a set by calling setState on the processor, then handing the same stream to the controller via setComponentState to initialize its parameter view. As a SingleComponentEffect the wrapper inherited EditController's kNotImplemented, so Live's parameter layer stayed at default values after a reload and wrote those defaults back over the plugin's restored state (Live Log.txt: couldn't initialize controller with processor state ... not implemented).

setComponentState now:

  • loads the handed state into the CLAP plugin — idempotent when setState already ran, and covers hosts that call the controller side first;
  • refreshes the VST3 parameter cache from the live CLAP values, the same sync param_rescan(CLAP_PARAM_RESCAN_VALUES) performs.

Tested:

  • Ableton Live 12.4 (macOS arm64): set reload now restores parameter values and plugin state correctly; device panel matches the plugin.
  • pluginval strictness 8 incl. GUI/state-restoration tests: SUCCESS (no regressions; note it passed before this fix too — it doesn't exercise setComponentState the way Live does).
  • clang-format clean against the repo .clang-format.

defiantnerd and others added 16 commits March 21, 2026 21:09
* fixing pointer alignment
…ee-audio#457)

- Delete gtkutils.h/.cpp and all CLAP_WRAPPER_HAS_GTK3 conditionals;
  Linux standalone now unconditionally uses the X11 path
- XInitThreads() and XOpenDisplay() null-check safety guards
- Guard MapNotify handler against null plugin / _ext._gui
- In X11Gui::shutdown(), explicitly call gui->destroy() before closing
  epoll and display so timer unregistration (triggered by guiDestroy)
  succeeds while the epoll fd is still open; set epoll_fd = -1 after
  close so the guard in unregister_timer fires correctly on any
  subsequent calls

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
free-audio#456)

* added line to vst wrapper cmake function to supress pragma-pack warnings in the vst3sdk when using clang on linux

* undoing whitespace changes made by overzealous code formatter

* putting no-pragma-pack flag behind if check as this flag is not compatible with MS cl

* using the correct cmake flag for windows (whoops)
Adding the AAX wrapper that will wrap CLAP plugins same way as VST3 or AUv2.
Note that there are some features of CLAP that can not be projected to AAX.
* Fix conversion of bundle version to hex version

The bundle version (1.2.3) needs to be converted to hex
(0x00102030) for the plist. The calculation which did this
truncated the last position inaccurately so led to point versions
not revving au versions, which causes a logic scan avoidance which
is painful

* format
* A variety of macOS Standalone GUI Fixes

- Add NSHighResolutionCapable to Info.plist.in so macOS opts the process
  into high-resolution mode, allowing plugins to render at Retina density
- Wire BUNDLE_VERSION through make_clapfirst and target_add_standalone_wrapper
  to MACOSX_BUNDLE_SHORT/LONG_VERSION_STRING and BUNDLE_VERSION properties;
  use @var@ substitution in Info.plist.in so the About screen shows the
  correct version instead of the hardcoded 1.0
- Add return-value checking after ui->create(), ui->get_size(), and
  ui->set_parent() with distinct user-facing NSAlert messages for each
  failure mode; validate get_size() output is non-zero and below 16384
- Switch on_main_thread callback timer from NSDefaultRunLoopMode to
  NSRunLoopCommonModes so callbacks fire during window resize and menu tracking
- These error paths can lead to a shutdown of audio which isn't started so
  guard against a null rtaDac in stopAudio

Assisted-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…io#463)

cmake_parse_arguments converts BUNDLE_IDENTIFIER named arg into
${prefix}_BUNDLE_IDENTIFIER. The VST3 wrapper path was reading the
typo'd ${C1ST_BUNDLE_IDENTIFER} which CMake silently treats as empty,
causing VST3 plugins to ship with bundle ID equal to literal ".vst3".
The CLAP path on line 132 already reads the correct variable name.
This fix aligns the VST3 path with the CLAP path.

Co-authored-by: Sheryar Hyatt <786shades@gmail.com>
BUNDLE_IDENTIFIER read IDENTIFER, causing VST3 and AUv2 bundles to have an empty argument and named ".vst3" or ".auv2" instead of "YourIDofchoice.vst3" and "YourIDofchoice.auv2"
… support for vst3, implement the 'audio-ports-config' extension for the clap-first-example plugin (free-audio#430)
…ree-audio#472)

* Fix Windows standalone opening off-screen and crashing on minimize

Restore a saved window position only when it still lands on a connected
monitor, so a stale off-screen value no longer brings the app up invisible
in the taskbar. Store window coordinates as signed values and only remember
the restored (non-minimized, non-maximized) geometry, so a minimized window's
sentinel position is never persisted as the next launch position.

Stop resizing the editor while the window is minimized, which drove
size-dependent drawing to zero dimensions and crashed.

Persist window settings when a move/resize finishes rather than on every
position change, and honor the launcher's requested window state instead of
always opening as a normal window.

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>

* win2022
This commit adds full AUv3 support to the clap-wrapper. Both MacOS and iOS/iPadOS are supported.
Live restores a set by calling setState on the processor, then hands the same
stream to the controller via setComponentState to initialize its parameter
view. As a SingleComponentEffect the wrapper inherited EditController's
kNotImplemented, so Live's parameter layer stayed at default values after a
reload and wrote those defaults back over the plugin's restored state.

setComponentState now loads the handed state into the CLAP plugin (idempotent
when setState already ran; covers hosts that call it first) and refreshes the
VST3 parameter cache from the live CLAP values, the same sync
param_rescan(CLAP_PARAM_RESCAN_VALUES) performs.
@olilarkin
olilarkin marked this pull request as draft July 9, 2026 16:59
@olilarkin

Copy link
Copy Markdown
Author

This issue and PR were entirely generated by fable. I'll look a bit more deeply into it next week.

@defiantnerd

Copy link
Copy Markdown
Collaborator

thanks. but probably Claude missed the macro redefinition of the function name when being compiled as single component.

@olilarkin

Copy link
Copy Markdown
Author

Good thought, but setComponentState isn't affected by that macro. The workaround in vstsinglecomponenteffect.h is:

// work around for the name clash of IComponent::setState and IEditController::setState
#define setState setEditorState
#define getState getEditorState
#include "public.sdk/source/vst/vsteditcontroller.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#undef setState
#undef getState

It only renames the setState/getState tokens (the controller's own state methods) during that include — setComponentState is a distinct token and keeps its name. Two pieces of evidence the override lands in the right vtable slot:

  1. It's declared with the override keyword, so a rename or signature mismatch would be a compile error.
  2. nm on the built plugin shows the expected three symbols side by side:
T ClapAsVst3::setComponentState(Steinberg::IBStream*)        <- this PR's override
T ClapAsVst3::setState(Steinberg::IBStream*)                 <- IComponent (processor) state
T Steinberg::Vst::EditController::setEditorState(...)        <- macro-renamed controller state, SDK default

The macro is relevant to the adjacent gap though: Live's log also prints couldn't get controller state: not implemented — that's IEditController::getState (= getEditorState under the macro), the controller's private state, which remains at the EditController default. That's a separate, harmless thing for a single-component wrapper (there's no controller-only state to persist) — probably why the setComponentState gap next to it went unnoticed.

Re-confirmed with the patched build in Ableton Live 12.4: state now transfers correctly (duplicating a device carries its state over; that goes through the same processor-state → controller handoff that was returning kNotImplemented).

@olilarkin

Copy link
Copy Markdown
Author

(says fable). It might be a Live bug. I'll check next week.

@CrushedPixel

Copy link
Copy Markdown

Can confirm this fixes the issue, but might be worth testing what @Quant1um posted in the issue:
#475 (comment)

@defiantnerd
defiantnerd deleted the branch free-audio:next July 18, 2026 09:26
@CrushedPixel

Copy link
Copy Markdown

@defiantnerd I disagree with closing this - we haven't conclusively resolved the issue yet. I am still investigating

@defiantnerd

Copy link
Copy Markdown
Collaborator

oops - sorry, that was by mistake.

@defiantnerd defiantnerd reopened this Jul 18, 2026
defiantnerd added a commit that referenced this pull request Aug 6, 2026
* adding update of parameter values after load
* fixing a small issue when param_rescan is called with other flags, de-duplified value update on two places
* replaces pr #476

Thanks to olilarkin for bringing it up.
@defiantnerd

Copy link
Copy Markdown
Collaborator

Implemented in #499 because because this one didn't merge anymore.

@defiantnerd defiantnerd closed this Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants