Search guides and the APICtrl K
  1. API reference
  2. Formats and wrappers

vst3_wrapper.h

The VST3 format wrapper.

#include <sonore/vst3_wrapper.h>
On this page

Design notes

Written against Steinberg's C ABI (vst3_c_api.h, BSD-3-Clause) rather than the C++ SDK. That choice matters: the C header is one file under a permissive licence, so everything below is OUR code and the plugin links no framework at all. The C++ SDK would drag in a build system, a class hierarchy, and the dual GPLv3/proprietary terms this SDK exists to avoid.

Include it AFTER clap_wrapper.h in the same translation unit. The CLAP wrapper owns the machinery both formats share (the DSP instance, parameters, state, the webview bridge), and this file adapts it to VST3's shape, so one source file builds a .clap and a .vst3 that cannot drift apart.

Three differences drive everything here:

1. VST3 parameters are NORMALISED 0..1; ours are plain values. Every
   crossing converts, and getting one direction wrong is silent: the knob
   simply lands somewhere other than where the user put it.
2. VST3 is COM-like: refcounted objects reached through queryInterface. One
   object implements IComponent + IAudioProcessor + IEditController (a
   "single component effect"), which is what modern hosts expect and which
   halves the state that would otherwise need syncing.
3. UI edits must reach the host through IComponentHandler on the MAIN
   thread, unlike CLAP, where the plugin emits them from the audio thread.

Types 18

IUnitInfo: how VST3 spells a parameter hierarchy. Hosts read it to build the folders their generic editor shows.

IMidiMapping: which parameter a MIDI controller, channel aftertouch or pitch bend drives, since VST3 delivers those as parameter changes rather than MIDI. Only handed out when wantsMidiMapping() says the plugin reads MIDI.

INoteExpressionController: the per-note expression types this plugin accepts (see NoteExpressionSpec). A host sends none until it has asked.

IProcessContextRequirements: the process-context fields the plugin reads, which are exactly the ones readTransport() uses.

ChannelContext::IInfoListener: the host telling the plugin about its track (name, colour), passed on to the DSP as TrackInfo.

IComponent: the plugin's bus layout, activation and state, as the host sees the processing half.

IAudioProcessor: setup, speaker arrangements, latency and tail, and process(), which is the call that arrives on the audio thread.

IEditController: parameters, their text, the controller's copy of the state, and the editor. The same Plugin answers it as answers the processor, so both halves read one set of values.

IPlugView: the editor window as the host drives it. Points at the View, not the Plugin, because a view can outlive the plugin it came from.

The scale interface is a SEPARATE object with its own vtable pointer, because a caller that queried for it will call through it as if it were an IPlugViewContentScaleSupport -- handing back the view itself would have it dispatch setContentScaleFactor into whatever IPlugView keeps at slot 3.

The editor. Its lifetime is INDEPENDENT of the plugin's: a host may create a view, close the window, and release it much later, so it holds a plain pointer back and is told when the plugin goes away.

One object, three interfaces.

An event list seen from one piece of a block: the host's events that fall in it, at offsets within it -- and, as the output list, what the DSP sends back, moved from the piece's time to the host's.

One parameter's points as one piece of the block sees them: count of the host's, from its point first on.

The host's parameter changes as one piece of the block sees them: each queue's points that fall in the piece, so the last of them -- the one process() applies -- is the value at that point of the block, and a queue with none there shows none (an earlier piece applied its value). Only those: a MIDI controller's every point is a message, and a point an earlier piece already played, shown again at a later piece's frame 0, was a second message. One queue object serves every index: process() reads each queue through before it asks for the next.

One note expression type as INoteExpressionController reports it: the VST3 type id, the names a host shows, and the default and polarity in VST3's normalised 0..1 domain. One row of noteExpressionTable().

struct Steinberg_Vst_NoteExpressionTypeInfo* info) { if (!info || busIndex != 0 || channel < 0 || channel >= kMidiChannels) return Steinberg_kInvalidArgument; int n = 0; const NoteExpressionSpec* table = noteExpressionTable(&n); if (index < 0 || index >= n) return Steinberg_kInvalidArgument;

No comment on this one in the header yet; the source says the rest.

The module's IPluginFactory2, the object GetPluginFactory() hands a host. One per module, a function-local static built on first use (factory()); it is never freed, so its reference count only keeps a host's bookkeeping honest. It describes the one class this module has and creates Plugin instances of it.

Functions 184

Constants 9

↑↓ to move↵ to openEvery public name in the headers, and every guide