Skip to content

VST3: optional self-contained VSTGUI editor#139

Open
edumeneses wants to merge 24 commits into
celtera:mainfrom
edumeneses:vst3-vstgui-editor
Open

VST3: optional self-contained VSTGUI editor#139
edumeneses wants to merge 24 commits into
celtera:mainfrom
edumeneses:vst3-vstgui-editor

Conversation

@edumeneses

Copy link
Copy Markdown

Hi @jcelerier — Claude here, assistant to Edu Meneses (SAT, Montréal). This adds an embedded VST3 editor built on VSTGUI (bundled with the VST3 SDK), so avnd VST3 plug-ins get a real GUI in any host, without Qt. Controller::createView was returning nullptr; this fills it in.

What it does

  • include/avnd/binding/vst3/vstgui_editor.hpp: a custom IPlugView hosting a VSTGUI CFrame, auto-laying-out one self-drawing widget per parameter (CKnob / CCheckBox / CTextLabel), bound through the standard IEditController + IComponentHandler API. No bitmaps, no .uidesc.
  • Controller<T>::createView returns it under AVND_VST3_VSTGUI.
  • avendish.vst3.cmake: AVND_ENABLE_VST3_VSTGUI option (default ON); links the vstgui target when the SDK is built with SMTG_ADD_VSTGUI=ON.
  • prototype.cpp.in: VSTGUI::init/exit in the module entries (Linux dlopen handle, macOS CFBundleRef, Windows module HINSTANCE).

Independent bug fix (cherry-pickable on its own — commit for "define DEVELOPMENT only in Debug")

DEVELOPMENT was defined unconditionally, which arms the VST3 SDK's FDebugBreak SIGTRAP assertions in release builds → crashes on teardown for all avnd VST3 plug-ins (hit this closing the editor). Now Debug-only.

Notes / for discussion

  • The editor needs RTTI + exceptions (VSTGUI uses dynamic_cast/throw), so the addon must set AVND_USES_EXCEPTIONS. Should the editor force that automatically when enabled?
  • VSTGUI's IRunLoop/IEventHandler/ITimerHandler are in VSTGUI::X11:: on 3.7.x (what I built against) but VSTGUI:: in newer VSTGUI — you'll know which VSTGUI avnd targets; happy to adjust the namespace.
  • Some MSVC glue (/Zc:preprocessor, a force-included vstgui_assert shim) works around VSTGUI+MSVC variadic-macro issues under the Visual Studio generator; open to a cleaner fix.
  • First cut is a generic auto-grid; honoring the halp struct ui layout and theming are natural follow-ups.

Tested

Builds on Linux/macOS/Windows CI. Editor open/close verified in Reaper (Linux) and via a minimal X11 host (attach → service run loop → teardown; no leaks or crashes).

Reference build & CI: https://github.com/edumeneses/TimeMachine

🤖 Generated with Claude Code

edumeneses added 16 commits July 3, 2026 08:45
Adds an embedded VST3 editor built on VSTGUI (bundled with the VST3 SDK),
so avnd VST3 plug-ins get a real GUI in any host without Qt.

- vstgui_editor.hpp: a custom IPlugView hosting a VSTGUI CFrame, auto-laying
  out one self-drawing widget per parameter (CKnob for continuous,
  CCheckBox for toggles, CTextLabel names), bound to the controller via the
  standard IEditController + IComponentHandler API. No bitmaps, no uidesc.
- Controller<T>::createView returns it when built with AVND_VST3_VSTGUI.
- avendish.vst3.cmake: AVND_ENABLE_VST3_VSTGUI option (default ON); links the
  `vstgui` target and defines AVND_VST3_VSTGUI when the VST3 SDK was
  configured with SMTG_ADD_VSTGUI=ON.

First cut: generic auto-layout. Honoring the halp `struct ui` layout and
theming are follow-ups.
VSTGUI::init = initPlatform + CFontDesc::init (global fonts). Calling only
initPlatform left kNormalFont null, crashing CParamDisplay's ctor at the
first label. Also init on macOS (bundleEntry's CFBundleRef) and Windows
(module HINSTANCE via GetModuleHandleEx), which never initialised VSTGUI.
@jcelerier

Copy link
Copy Markdown
Member

wops, we need to synchronize because I had an agent working on this but at a more general level than just VST / VST3

Steinberg::IPlugFrame* plugFrame{};
std::atomic<Steinberg::uint32> m_ref{1};

static constexpr int cols = 4;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very strange that this would be hardcoded, I think we have to make it so that things are flexible for the end user

Comment thread include/avnd/binding/vst3/vstgui_editor.hpp
extern "C" AVND_EXPORTED_SYMBOL bool bundleEntry(CFBundleRef ref)
{
#if defined(AVND_VST3_VSTGUI)
VSTGUI::init(ref);

@jcelerier jcelerier Jul 3, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to use the official vstgui backend from steinberg which is really terrible for making UIs (and won't help us when we want to use CLAP etc.)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real Edu here: What do you propose as a cross-platform UI tool for plugins?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, the decision was based on portability. The first TimeMachine UI indeed looks bad, but it is just a bunch of sliders for now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avendish shouldn't enforce a specific UI backend in its implementation, it should be entirely up to the end-user to build their UI with the lib that fits their requirements, as some people will prefer a slim and basic UI in C++, others a more complex one that uses a proper toolkit that has internationalization etc, others will prefer using a built-in web UI that uses a webview, etc.
For making a simple plug-in that will work across all backends the best way is to follow what DPF is doing : https://github.com/DISTRHO/DPF

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other good options for context usecase:
https://github.com/thorvg/thorvg https://github.com/sammycage/plutovg
my main requirement for the one I'm using for examples is that it can work all the way from esp32 to wasm as those are all avendish-supported platforms, so if someone wants to make "an ui for my algorithm" then the "default choice" should not cause build errors on any of those (which restricts to simple, pixel-graphics-only framework)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, that makes sense.

@edumeneses

Copy link
Copy Markdown
Author

@jcelerier, if you want, I can hold this until you finish the UI part on your end

edumeneses and others added 8 commits July 3, 2026 09:52
… knobs with labels

- Plugin name (avnd::get_name<T>) shown in a title bar on top.
- Toggles render as on/off text buttons instead of a tiny checkbox.
- Continuous params render as filled corona knobs (accent color, thicker
  handle) with the parameter name centered below.
Toggles reported stepCount=0 (no range.step), so hosts and the VSTGUI
editor drew them as continuous knobs. Set stepCount=1 for bool_parameter
controls -> host generic UIs show a checkbox and the editor a toggle.
…able

The on/off button used the default (light) gradient, so the light 'off'
label was invisible on a white fill. Set explicit dark (off) and accent
(on) gradients with contrasting text.
ParameterInfo has no notion of a momentary control, so the generic
VSTGUI editor drew every stepCount==1 parameter as a latched on/off
toggle. Add ControllerCommon::isMomentaryParameter(), overridden by the
typed Controller from the control's widget enumerators (pushbutton /
bang / impulse), and use it in the editor to pick CTextButton
kKickStyle: 1 while pressed, 0 on release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8rKjEZfGtrHLkU5iXR4gn
Adds a value readout label per continuous parameter (formatted via the
controller's getParamStringByValue), refreshed in valueChanged as the user
turns the knob.
processControl only applied the last point of each parameter queue.
VSTGUI's kick-style buttons emit press (1) and release (0) back-to-back
on mouse-up, so both landed in the same block and the processor only
ever saw 0 - momentary buttons never fired at all.

For bool parameters, apply the maximum point of the block's queue
instead (latching the press for the whole block) and, when the queue
ends released, defer the false assignment to the start of the next
block. Continuous parameters keep the last-point behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8rKjEZfGtrHLkU5iXR4gn
… disabled)

The control-object overload of map_control_to_01 gated itself on an
unqualified map_control_to_01(t.value), which has no viable overload (the
value overloads need explicit <T>), so the requires never held and the
overload was always disabled. Every caller (VST3 getParamNormalized, ...)
then got 0. Qualify the requires with <T>.
Use 6 columns instead of 4 when a plugin exposes more than 20
parameters, keeping the window height manageable (e.g. 40 parameters:
7 rows instead of 10).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K8rKjEZfGtrHLkU5iXR4gn
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.

2 participants