Skip to content

Commit

Permalink
Merge pull request #28 from AbletonAppDev/mtt-add-third-party-dev
Browse files Browse the repository at this point in the history
Add entries for third party plugin dev portal
  • Loading branch information
mtt-ableton authored Sep 23, 2024
2 parents 647f303 + 3acf80d commit c17d58f
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 0 deletions.
6 changes: 6 additions & 0 deletions _sass/base/_body-text.sass
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,9 @@ $bp-main-type-zoom: $bp-4
img
display: block
max-width: 100%
blockquote
background: #f9f9f9
border-left: 10px solid #ccc
margin: 1.5em 10px
padding: 1em 10px .1em
quotes: "\201C""\201D""\2018""\2019"
6 changes: 6 additions & 0 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ Ableton Live project files. Currently only ALSExportKit for iOS is available. It
governed by the [ALS Export SDK License](/export/assets/ALSExport_License_v1.0.pdf). To
get access to ALSExportKit please register
[here](https://www.ableton.com/en/link/sdk/license-request/).


[Third party plugin developers portal](/third_party_plugin_dev_portal/) gather
resources to help plugin manufacturers create products that are compatible with
Ableton products.

Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
layout: link
active_page: VST3presetSwitchingPerformEditAlternatives.
title: Recommendations to VST3 plugins manufacturers regarding bulk calls to performEdit
---

# Recommendations to VST3 plugins manufacturers regarding bulk calls to performEdit
## Introduction

For some VST3 plugins, Ableton receives reports of user interface lag when users switch presets from the plugin editor. After investigation, it seems that some VST3 manufacturers implement preset changes from the VST3 editor by calling performEdit for all parameters in order to notify both the DAW and the VST3 controller counterpart of its new state.

Live needs time to react to performEdit for our parameter mapping system.
Unfortunately, we receive calls from performEdit synchronously, and cannot optimize for situations where many successives calls of performEdit will happen.

## Steinberg’s recommendations: editor uses private communication to send new values to processor

This solution is about using private communication (for example sending a custom IMessage) from editor to component with all the new values to set before calling restartComponent and making sure that the controller will return the correct information before making a call to restartComponent(kParamValuesChanged).

See below some quotes from a Steinberg developer about it:

[https://forums.steinberg.net/t/how-to-use-restartcomponent-and-which-flags-are-the-right-one-when-changing-all-characteristics-parameters-except-size/202031/11](https://forums.steinberg.net/t/how-to-use-restartcomponent-and-which-flags-are-the-right-one-when-changing-all-characteristics-parameters-except-size/202031/11)

> Hi Erez, you don’t need to call for every parameter beginEdit, performEdit, endEdit. If you do this, then the parameter values are already known in the host and you don’t need to call restartComponent with kParamValuesChanged. If you do this only to tell your processor to use these new values, you can also send your processor a custom message with all the new values and set them yourself in the processor. You just have to make sure that before you make a call to restartComponent with `kParamTitlesChanged|kParamValuesChanged`, that your controller will return the correct information afterwards.
[https://forums.steinberg.net/t/how-to-use-restartcomponent-and-which-flags-are-the-right-one-when-changing-all-characteristics-parameters-except-size/202031/13](https://forums.steinberg.net/t/how-to-use-restartcomponent-and-which-flags-are-the-right-one-when-changing-all-characteristics-parameters-except-size/202031/13)

> If you now call restartComponent with `kParamTitlesChanged|kParamValuesChanged`, the host should have the new parameter names, flags and values. The host will not send the values to your processor in the process call this way. So if you need them there, but without going thru the beginEdit, performEdit, endEdit methods, you could allocate an IMessage with `IHostApplication::createInstance()` fill it with the necessary information and send it via `IConnectionPoint::notify` to your processor.
## Relevant VST3 documentation

### Optional separation of processor and edit controller

[https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/API+Documentation/Index.html#basic-concept](https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/API+Documentation/Index.html#basic-concept)

> The design of VST 3 suggests a complete separation of processor and edit controller by implementing two components. [] A plug-in that supports this separation has to set the Steinberg::Vst::kDistributable flag in the class info of the processor component (Steinberg::PClassInfo2::classFlags). Of course not every plug-in can support this, for example if it depends deeply on resources that cannot be moved easily to another computer. So when this flag is not set, the host must not try to separate the components in any way.
### private communication (custom IMessage)

[https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/API+Documentation/Index.html#private-communication](https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/API+Documentation/Index.html#private-communication)

> Data that is unknown to the host can be transmitted by means of messages. The communication interfaces are
> * Steinberg::Vst::IConnectionPoint: The host establishes a connection between processor and controller.
> * Steinberg::Vst::IMessage: Represents a message to send to the counterpart.
> * Steinberg::Vst::IAttributeList: A list of attributes belonging to a message.
>
> Please note that messages from the processor to the controller must not be sent during the process call, as this would not be fast enough and would break the real time processing. Such tasks should be handled in a separate timer thread.
## Implementation hints from open source frameworks
### VST3SDK 3.7.8
#### open303

In this example, the processor sends the preset (pattern) to the editor. The other way around should be relatively similar.

[https://github.com/scheffle/open303/blob/850c98186db28829f9ff6c8254966cdb6bf334e6/Source/VST3/o303processor.cpp#L185-L203](https://github.com/scheffle/open303/blob/850c98186db28829f9ff6c8254966cdb6bf334e6/Source/VST3/o303processor.cpp#L185-L203)

```cpp
void sendPatternToController (int patternIndex)
{
if (!peerConnection)
return;

const auto& pattern = open303Core.sequencer.getPattern (patternIndex);
assert (pattern != nullptr);

vst3utils::message msg (owned (allocateMessage ()));
if (!msg.is_valid ())
return;
msg.set_id (msgIDPattern);
auto attributes = msg.get_attributes ();
if (!attributes.is_valid ())
return;
PatternData data = toPatternData (*pattern);
attributes.set (msgIDPattern, data);
peerConnection->notify (msg);
}
```
### JUCE 7.0.12
#### Editor part
In JUCE, JuceVST3EditController is the Vst::IEditController
[https://github.com/juce-framework/JUCE/blob/4f43011b96eb0636104cb3e433894cda98243626/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp#L4246-L4249](https://github.com/juce-framework/JUCE/blob/4f43011b96eb0636104cb3e433894cda98243626/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp#L4246-L4249)
```cpp
return static_cast<Vst::IEditController*> (new JuceVST3EditController (h));
```


Then following Steinberg’s documentation

[https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/API+Documentation/Index.html#private-communication](https://steinbergmedia.github.io/vst3_dev_portal/pages/Technical+Documentation/API+Documentation/Index.html#private-communication)

```cpp
Vst::IEditController* editController = &myJuceVST3EditControllerInstance;
Vst::IConnectionPoint* iConnectionPointController = nullPtr;
editController->queryInterface (Vst::IConnectionPoint::iid, (void**)&iConnectionPointController);
```
Then the sending part should be similar to the 303 example, with peerConnection=editController
[https://github.com/scheffle/open303/blob/850c98186db28829f9ff6c8254966cdb6bf334e6/Source/VST3/o303processor.cpp#L185-L203](https://github.com/scheffle/open303/blob/850c98186db28829f9ff6c8254966cdb6bf334e6/Source/VST3/o303processor.cpp#L185-L203)
```cpp
void sendPatternToController (int patternIndex)
{
if (!peerConnection)
return;
const auto& pattern = open303Core.sequencer.getPattern (patternIndex);
assert (pattern != nullptr);
vst3utils::message msg (owned (allocateMessage ()));
if (!msg.is_valid ())
return;
msg.set_id (msgIDPattern);
auto attributes = msg.get_attributes ();
if (!attributes.is_valid ())
return;
PatternData data = toPatternData (*pattern);
attributes.set (msgIDPattern, data);
peerConnection->notify (msg);
}
```

#### Component part

Override in your own JuceVST3Component

[https://github.com/juce-framework/JUCE/blob/4f43011b96eb0636104cb3e433894cda98243626/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp#L2542-L2560](https://github.com/juce-framework/JUCE/blob/4f43011b96eb0636104cb3e433894cda98243626/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp#L2542-L2560)

```cpp
tresult PLUGIN_API notify (Vst::IMessage* message) override
```
## What about...
### surrounding bulk performEdit calls with start/finishGroupEdit
The documentation explains that groupEdit are about accurate timestamps, and thus not related to preset browsing.
[https://github.com/steinbergmedia/vst3_pluginterfaces/blob/37de655a51b28a558645621b39d48a00292be5e2/vst/ivsteditcontroller.h#L268-L271](https://github.com/steinbergmedia/vst3_pluginterfaces/blob/37de655a51b28a558645621b39d48a00292be5e2/vst/ivsteditcontroller.h#L268-L271)
```cpp
/** Starts the group editing (call before a \ref IComponentHandler::beginEdit),
the host will keep the current timestamp at this call and will use it for all
\ref IComponentHandler::beginEdit
```

### only calling restartComponent and letting the host transfer changes

A Steinberg developer explains that the host will not transfer changes from editor to component when calling restartComponent.

[https://forums.steinberg.net/t/how-to-use-restartcomponent-and-which-flags-are-the-right-one-when-changing-all-characteristics-parameters-except-size/202031/17](https://forums.steinberg.net/t/how-to-use-restartcomponent-and-which-flags-are-the-right-one-when-changing-all-characteristics-parameters-except-size/202031/17)

> A plug-in calls restartComponent (kParamValuesChanged) to let the host know about an internal change of its parameter values.[] So the host wont transfer changes when restartComponent (kParamValuesChanged) is called.
22 changes: 22 additions & 0 deletions third_party_plugin_dev_portal/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
layout: link
active_page: third_party_plugin_dev_portal
title: Third party plugin developers portal
---

# Third party plugin developers portal

## Lag or CPU spikes when changing presets in VST3 plugins

When a VST3 plugin editor needs to change many parameter values at once, for
example when changing presets, it should not call `performEdit` for each
parameter.


Instead, the editor should use private communication (for example sending
custom `IMessage`) to send all the new values to the controller. Then, the
controller shall be able to return the correct information before making a call
to `restartComponent(kParamValuesChanged)`.

More details at [performEdit alternatives](VST3presetSwitchingPerformEditAlternatives).

0 comments on commit c17d58f

Please sign in to comment.