Initial release
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
# Welcome to the VST 3 Tutorials
|
||||
|
||||
Here you will find VST 3 tutorial projects
|
||||
|
||||
Please find the tutorials in the corresponding Readme's in the sub directories:
|
||||
|
||||
- [Advanced Techniques Tutorial](advanced-techniques-tutorial/)
|
||||
- [Audio Unit Tutorial](audiounit-tutorial/)
|
||||
- [Data Exchange Tutorial](dataexchange-tutorial/)
|
||||
|
||||
----
|
||||
Return to the [VST 3 SDK](../vst3sdk/)
|
||||
@@ -0,0 +1 @@
|
||||
build/
|
||||
@@ -0,0 +1,68 @@
|
||||
cmake_minimum_required(VERSION 3.14.0)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13 CACHE STRING "")
|
||||
|
||||
if(NOT vst3sdk_SOURCE_DIR)
|
||||
message(FATAL_ERROR "Path to VST3 SDK is empty! Please specify the vst3sdk_SOURCE_DIR cmake cache entry")
|
||||
endif()
|
||||
|
||||
project(advanced-techniques-tutorial
|
||||
# This is your plug-in version number. Change it here only.
|
||||
# Version number symbols usable in C++ can be found in
|
||||
# source/version.h and ${PROJECT_BINARY_DIR}/projectversion.h.
|
||||
VERSION 1.0.0.0
|
||||
DESCRIPTION "advanced-techniques-tutorial VST 3 Plug-in"
|
||||
)
|
||||
|
||||
set(SMTG_ENABLE_VST3_HOSTING_EXAMPLES 0)
|
||||
set(SMTG_ENABLE_VST3_PLUGIN_EXAMPLES 0)
|
||||
set(SMTG_ENABLE_VSTGUI_SUPPORT 0)
|
||||
|
||||
set(SMTG_VSTGUI_ROOT "${vst3sdk_SOURCE_DIR}")
|
||||
|
||||
add_subdirectory(${vst3sdk_SOURCE_DIR} ${PROJECT_BINARY_DIR}/vst3sdk)
|
||||
smtg_enable_vst3_sdk()
|
||||
|
||||
smtg_add_vst3plugin(advanced-techniques-tutorial
|
||||
README.md
|
||||
source/cids.h
|
||||
source/controller.cpp
|
||||
source/entry.cpp
|
||||
source/pids.h
|
||||
source/processor.cpp
|
||||
source/version.h
|
||||
)
|
||||
|
||||
target_compile_features(advanced-techniques-tutorial
|
||||
PUBLIC
|
||||
cxx_std_17
|
||||
)
|
||||
|
||||
target_link_libraries(advanced-techniques-tutorial
|
||||
PRIVATE
|
||||
sdk
|
||||
)
|
||||
|
||||
smtg_target_configure_version_file(advanced-techniques-tutorial)
|
||||
|
||||
if(SMTG_MAC)
|
||||
smtg_target_set_bundle(advanced-techniques-tutorial
|
||||
BUNDLE_IDENTIFIER com.steinberg.vst3.tutorial.dataexchange
|
||||
COMPANY_NAME "Steinberg Media Technologies"
|
||||
)
|
||||
smtg_target_set_debug_executable(advanced-techniques-tutorial
|
||||
"/Applications/VST3PluginTestHost.app"
|
||||
"--pluginfolder;$(BUILT_PRODUCTS_DIR)"
|
||||
)
|
||||
elseif(SMTG_WIN)
|
||||
target_sources(advanced-techniques-tutorial PRIVATE
|
||||
resource/win32resource.rc
|
||||
)
|
||||
if(MSVC)
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT advanced-techniques-tutorial)
|
||||
|
||||
smtg_target_set_debug_executable(advanced-techniques-tutorial
|
||||
"$(ProgramW6432)/Steinberg/VST3PluginTestHost/VST3PluginTestHost.exe"
|
||||
"--pluginfolder \"$(OutDir)/\""
|
||||
)
|
||||
endif()
|
||||
endif(SMTG_MAC)
|
||||
@@ -0,0 +1,432 @@
|
||||
# Advanced Techniques Tutorial Plug-in
|
||||
|
||||
---
|
||||
|
||||
## How to build
|
||||
|
||||
### macOS
|
||||
|
||||
mkdir build
|
||||
cmake -GXcode -Dvst3sdk_SOURCE_DIR="PATH_TO_YOUR_VST_SDK_FOLDER" ../
|
||||
cmake --build .
|
||||
|
||||
### Windows
|
||||
|
||||
mkdir build
|
||||
cmake -Dvst3sdk_SOURCE_DIR="PATH_TO_YOUR_VST_SDK_FOLDER" ..\
|
||||
cmake --build .
|
||||
|
||||
### Linux
|
||||
|
||||
mkdir build
|
||||
cmake -Dvst3sdk_SOURCE_DIR="PATH_TO_YOUR_VST_SDK_FOLDER" ../
|
||||
cmake --build .
|
||||
|
||||
---
|
||||
|
||||
## Tutorial - Advanced Techniques
|
||||
|
||||
In this tutorial you will learn:
|
||||
|
||||
- How to add nearly sample accurate parameter changes to an audio effect
|
||||
- Using C++ templates to write one algorithm supporting 32 bit and 64 bit audio processing
|
||||
- Setting the state of the audio effect in a thread safe manner
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Sample accurate parameter handling
|
||||
|
||||
We will start by looking at this process function:
|
||||
|
||||
``` c++
|
||||
void MyEffect::process (ProcessData& data)
|
||||
{
|
||||
handleParameterChanges (data.inputParameterChanges);
|
||||
|
||||
// get the gain value for this block
|
||||
ParamValue gain = gainParameter.getValue ();
|
||||
|
||||
// process audio
|
||||
AudioBusBuffers* inputs = data.inputs;
|
||||
AudioBusBuffers* outputs = data.outputs;
|
||||
for (auto channelIndex = 0; channelIndex < inputs[0].numChannels; ++channelIndex)
|
||||
{
|
||||
for (auto sampleIndex = 0; sampleIndex < data.numSamples; ++sampleIndex)
|
||||
{
|
||||
auto sample = inputs[0].channelBuffers32[channelIndex][sampleIndex];
|
||||
outputs[0].channelBuffers32[channelIndex][sampleIndex] = sample * gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is straight and simple, we handle the parameter changes in the function *handleParameterChanges*, which we will see in a moment. Then we get the last gain parameter value and iterate over the input buffers and copy the samples from there to the output buffers and apply the *gain* factor.
|
||||
|
||||
If we look at the handleParameterChanges function:
|
||||
|
||||
``` c++
|
||||
void MyEffect::handleParameterChanges (IParameterChanges*changes)
|
||||
{
|
||||
if (!changes)
|
||||
return;
|
||||
int32 changeCount = changes->getParameterCount ();
|
||||
for (auto i = 0; i < changeCount; ++i)
|
||||
{
|
||||
if (auto queue = changes->getParameterData (i))
|
||||
{
|
||||
auto paramID = queue->getParameterId ();
|
||||
if (paramID == ParameterID::Gain)
|
||||
{
|
||||
int32 pointCount = queue->getPointCount ();
|
||||
if (pointCount > 0)
|
||||
{
|
||||
int32 sampleOffset;
|
||||
ParamValue value;
|
||||
if (queue->getPoint (pointCount - 1, sampleOffset, value) == kResultTrue)
|
||||
gainParameter.setValue (value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We see that the *Gain* parameter only uses the last point for the gain value.
|
||||
|
||||
If we now want to use all points of the *Gain* parameter we can use two utility classes from the SDK.
|
||||
|
||||
The first one is the *ProcessDataSlicer* which slices the audio block into smaller peaces.
|
||||
|
||||
``` c++
|
||||
void MyEffect::process (ProcessData& data)
|
||||
{
|
||||
handleParameterChanges (data.inputParameterChanges);
|
||||
|
||||
ProcessDataSlicer slicer (8);
|
||||
|
||||
auto doProcessing = [this] (ProcessData& data) {
|
||||
// get the gain value for this block
|
||||
ParamValue gain = gainParameter.getValue ();
|
||||
|
||||
// process audio
|
||||
AudioBusBuffers* inputs = data.inputs;
|
||||
AudioBusBuffers* outputs = data.outputs;
|
||||
for (auto channelIndex = 0; channelIndex < inputs[0].numChannels; ++channelIndex)
|
||||
{
|
||||
for (auto sampleIndex = 0; sampleIndex < data.numSamples; ++sampleIndex)
|
||||
{
|
||||
auto sample = inputs[0].channelBuffers32[channelIndex][sampleIndex];
|
||||
outputs[0].channelBuffers32[channelIndex][sampleIndex] = sample * gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slicer.process<SymbolicSampleSizes::kSample32> (data, doProcessing);
|
||||
}
|
||||
```
|
||||
|
||||
As you see we have moved the algorithm part into a lambda *doProcessing* which is passed to *slicer.process*. This lambda is now called multiple times with a maximum of 8 samples per call until the whole buffer is processed. This doesn't give us yet a better parameter resolution, but we can now use the second utility class to handle this.
|
||||
|
||||
At first we now look at the type of the *gainParameter* variable as this is our next utility class:
|
||||
|
||||
``` c++
|
||||
SampleAccurate::Parameter gainParameter;
|
||||
```
|
||||
|
||||
We have to change the *handleParameterChanges* function to:
|
||||
|
||||
``` c++
|
||||
void MyEffect::handleParameterChanges (IParameterChanges*inputParameterChanges)
|
||||
{
|
||||
int32 changeCount = inputParameterChanges->getParameterCount ();
|
||||
for (auto i = 0; i < changeCount; ++i)
|
||||
{
|
||||
if (auto queue = changes->getParameterData (i))
|
||||
{
|
||||
auto paramID = queue->getParameterId ();
|
||||
if (paramID == ParameterID::Gain)
|
||||
{
|
||||
gainParameter.beginChanges (queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
in order to delegate the handling of the parameter changes to the *gainParameter* object.
|
||||
|
||||
Now we just need another small change in the process lambda to use the nearly sample accurate *gain* value. We have to call the *gainParameter* object to *advance* the parameter value:
|
||||
|
||||
``` c++
|
||||
auto doProcessing = [this] (ProcessData& data) {
|
||||
// get the gain value for this block
|
||||
ParamValue gain = gainParameter.advance (data.numSamples);
|
||||
|
||||
// process audio
|
||||
AudioBusBuffers* inputs = data.inputs;
|
||||
AudioBusBuffers* outputs = data.outputs;
|
||||
for (auto channelIndex = 0; channelIndex < inputs[0].numChannels; ++channelIndex)
|
||||
{
|
||||
for (auto sampleIndex = 0; sampleIndex < data.numSamples; ++sampleIndex)
|
||||
{
|
||||
auto sample = inputs[0].channelBuffers32[channelIndex][sampleIndex];
|
||||
outputs[0].channelBuffers32[channelIndex][sampleIndex] = sample * gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally we have to do some cleanup of the *gainParameter* at the end of the *process* function by calling *gainParameter.endChanges*.
|
||||
|
||||
``` c++
|
||||
void MyEffect::process (ProcessData& data)
|
||||
{
|
||||
handleParameterChanges (data.inputParameterChanges);
|
||||
|
||||
ProcessDataSlicer slicer (8);
|
||||
|
||||
auto doProcessing = [this] (ProcessData& data) {
|
||||
// get the gain value for this block
|
||||
ParamValue gain = gainParameter.advance (data.numSamples);
|
||||
|
||||
// process audio
|
||||
AudioBusBuffers* inputs = data.inputs;
|
||||
AudioBusBuffers* outputs = data.outputs;
|
||||
for (auto channelIndex = 0; channelIndex < inputs[0].numChannels; ++channelIndex)
|
||||
{
|
||||
for (auto sampleIndex = 0; sampleIndex < data.numSamples; ++sampleIndex)
|
||||
{
|
||||
auto sample = inputs[0].channelBuffers32[channelIndex][sampleIndex];
|
||||
outputs[0].channelBuffers32[channelIndex][sampleIndex] = sample * gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slicer.process<SymbolicSampleSizes::kSample32> (data, doProcessing);
|
||||
|
||||
gainParameter.endChanges ();
|
||||
}
|
||||
```
|
||||
|
||||
Now we have nearly sample accurate parameter changes support in this example. Every 8 samples the *gain* parameter will be updated to the correct value.
|
||||
|
||||
It's very simple to make this 100% sample accurate, check out the **AGain sample accurate** example in the SDK.
|
||||
|
||||
---
|
||||
|
||||
## Part 2: Adding 32 and 64 bit audio processing
|
||||
|
||||
The example currently only supports 32 bit processing. Now we will add 64 bit processing.
|
||||
|
||||
As you may have noticed above the *ProcessDataSlicer* uses a template parameter for its process function. This template parameter *SampleSize* defines the bit depth of the audio buffers in the *ProcessData* structure. This is currently hard-coded to be *SymbolicSampleSizes::kSample32*.
|
||||
|
||||
In order to support *SymbolicSampleSizes::kSample64* we only have to make a few changes to the code. First we adopt the algorithm part by introducing a new templated method to our effect:
|
||||
|
||||
``` c++
|
||||
template <SymbolicSampleSizes SampleSize>
|
||||
void MyEffect::process (ProcessData& data)
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
We mostly just move the code from the original process method to this one except the code for handling parameter changes:
|
||||
|
||||
``` c++
|
||||
template <SymbolicSampleSizes SampleSize>
|
||||
void MyEffect::process (ProcessData& data)
|
||||
{
|
||||
ProcessDataSlicer slicer (8);
|
||||
|
||||
auto doProcessing = [this] (ProcessData& data) {
|
||||
// get the gain value for this block
|
||||
ParamValue gain = gainParameter.advance (data.numSamples);
|
||||
|
||||
// process audio
|
||||
AudioBusBuffers* inputs = data.inputs;
|
||||
AudioBusBuffers* outputs = data.outputs;
|
||||
for (auto channelIndex = 0; channelIndex < inputs[0].numChannels; ++channelIndex)
|
||||
{
|
||||
for (auto sampleIndex = 0; sampleIndex < data.numSamples; ++sampleIndex)
|
||||
{
|
||||
auto sample = inputs[0].channelBuffers32[channelIndex][sampleIndex];
|
||||
outputs[0].channelBuffers32[channelIndex][sampleIndex] = sample * gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slicer.process<SampleSize> (data, doProcessing);
|
||||
}
|
||||
```
|
||||
|
||||
We just change the template parameter *SampleSize* of the process method of the *ProcessDataSlicer* to use the same template parameter as of our own process function.
|
||||
|
||||
This will not work correctly yet as we still work with the 32 bit audio buffers in our *doProcessing* lambda. In order to fix this we have to introduce two more templated functions *getChannelBuffers* that will choose the correct audio buffers depending on the *SampleSize* template parameter, which can either be *SymbolicSampleSizes::kSample32* or *SymbolicSampleSizes::kSample64*:
|
||||
|
||||
``` c++
|
||||
template <SymbolicSampleSizes SampleSize,
|
||||
typename std::enable_if<SampleSize == SymbolicSampleSizes::kSample32>::type* = nullptr>
|
||||
inline Sample32** getChannelBuffers (AudioBusBuffers& buffer)
|
||||
{
|
||||
return buffer.channelBuffers32;
|
||||
}
|
||||
|
||||
template <SymbolicSampleSizes SampleSize,
|
||||
typename std::enable_if<SampleSize == SymbolicSampleSizes::kSample64>::type* = nullptr>
|
||||
inline Sample64** getChannelBuffers (AudioBusBuffers& buffer)
|
||||
{
|
||||
return buffer.channelBuffers64;
|
||||
}
|
||||
```
|
||||
|
||||
Now we can change our *doProcessing* algorithm to use these functions:
|
||||
|
||||
``` c++
|
||||
template <SymbolicSampleSizes SampleSize>
|
||||
void MyEffect::process (ProcessData& data)
|
||||
{
|
||||
ProcessDataSlicer slicer (8);
|
||||
|
||||
auto doProcessing = [this] (ProcessData& data) {
|
||||
// get the gain value for this block
|
||||
ParamValue gain = gainParameter.advance (data.numSamples);
|
||||
|
||||
// process audio
|
||||
AudioBusBuffers* inputs = data.inputs;
|
||||
AudioBusBuffers* outputs = data.outputs;
|
||||
for (auto channelIndex = 0; channelIndex < inputs[0].numChannels; ++channelIndex)
|
||||
{
|
||||
auto inputBuffers = getChannelBuffers<SampleSize> (inputs[0])[channelIndex];
|
||||
auto outputBuffers = getChannelBuffers<SampleSize> (outputs[0])[channelIndex];
|
||||
for (auto sampleIndex = 0; sampleIndex < data.numSamples; ++sampleIndex)
|
||||
{
|
||||
auto sample = inputBuffers[sampleIndex];
|
||||
outputBuffers[sampleIndex] = sample * gain;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
slicer.process<SampleSize> (data, doProcessing);
|
||||
}
|
||||
```
|
||||
|
||||
As a final step we now need to call the templated *process<...>* function from the normal *process* function:
|
||||
|
||||
``` c++
|
||||
void MyEffect::process (ProcessData& data)
|
||||
{
|
||||
handleParameterChanges (data.inputParameterChanges);
|
||||
|
||||
if (processSetup.symbolicSampleSize == SymbolicSampleSizes::kSample32)
|
||||
process<SymbolicSampleSizes::kSample32> (data);
|
||||
else
|
||||
process<SymbolicSampleSizes::kSample64> (data);
|
||||
|
||||
gainParameter.endChanges ();
|
||||
}
|
||||
```
|
||||
|
||||
Depending on the *processSetup.symbolicSampleSize* we either call the 32 bit *process* function or the 64 bit *process* function.
|
||||
|
||||
We just have to inform the host that we can process 64 bit:
|
||||
|
||||
``` c++
|
||||
tresult PLUGIN_API MyEffect::canProcessSampleSize (int32symbolicSampleSize)
|
||||
{
|
||||
return (symbolicSampleSize == SymbolicSampleSizes::kSample32 ||
|
||||
symbolicSampleSize == SymbolicSampleSizes::kSample64) ?
|
||||
kResultTrue :
|
||||
kResultFalse;
|
||||
}
|
||||
```
|
||||
|
||||
Now we have sample accurate parameter changes and 32 and 64 bit audio processing.
|
||||
|
||||
---
|
||||
|
||||
## Part 3: Thread safe state changes
|
||||
|
||||
One common issue in this domain is that the plug-in state coming from a preset or a DAW project is set by the host from a non realtime thread.
|
||||
|
||||
If we want to change our internal data model to use this state we have to transfer this state to the realtime thread. This should be done in a realtime thread safe manner otherwise the model may not reflect the correct state as parameter changes dispatched in the realtime thread and the state data set on another thread will end in an undefined state.
|
||||
|
||||
For this case we have another utility class: *RTTransferT*
|
||||
|
||||
This class expects to have a template parameter *StateModel* describing the state data. We create a simple struct as data model:
|
||||
|
||||
``` c++
|
||||
struct StateModel
|
||||
{
|
||||
double gain;
|
||||
};
|
||||
|
||||
using RTTransfer = RTTransferT<StateModel>;
|
||||
```
|
||||
|
||||
We use *RTTransfer* now as a member for our *MyEffect* class:
|
||||
|
||||
``` c++
|
||||
class MyEffect : ....
|
||||
{
|
||||
RTTransfer stateTransfer;
|
||||
};
|
||||
```
|
||||
|
||||
If we now get a new *state* from the host, we create a *newStateModel* and write the *stateGain* value into *model->gain* andpass it to the utility class *stateTransfer*:
|
||||
|
||||
``` c++
|
||||
tresult PLUGIN_API MyEffect::setState (IBStream* state)
|
||||
{
|
||||
double stateGain = ... // read this out of the state stream
|
||||
|
||||
StateModel model = std::make_unique<StateModel> ();
|
||||
model->gain = stateGain;
|
||||
|
||||
stateTransfer.transferObject_ui (std::move (model));
|
||||
|
||||
return kResultTrue;
|
||||
}
|
||||
```
|
||||
|
||||
To get the *stateModel* into our realtime thread we have to change the *process* function like this:
|
||||
|
||||
``` c++
|
||||
void MyEffect::process (ProcessData& data)
|
||||
{
|
||||
stateTransfer.accessTransferObject_rt ([this] (const auto& stateModel) {
|
||||
gainParameter.setValue (stateModel.gain);
|
||||
});
|
||||
|
||||
handleParameterChanges (data.inputParameterChanges);
|
||||
|
||||
if (processSetup.symbolicSampleSize == SymbolicSampleSizes::kSample32)
|
||||
process<SymbolicSampleSizes::kSample32> (data);
|
||||
else
|
||||
process<SymbolicSampleSizes::kSample64> (data);
|
||||
|
||||
gainParameter.endChanges ();
|
||||
return kResultTrue;
|
||||
}
|
||||
```
|
||||
|
||||
The *accessTransferObject_rt* function will check if there is a new model state and will call the lambda if it is and then we can set our *gainParameter* to the value of *stateModel.gain*.
|
||||
|
||||
To free up the memory in the *stateTransfer* object we have to call the *clear_ui* method of it. In this case where we only have one double as state model it is OK to hold onto it until the next state is set or the effect is terminated. So we just add it to the *terminate* method of the plug-in:
|
||||
|
||||
``` c++
|
||||
tresult PLUGIN_API MyEffect::terminate ()
|
||||
{
|
||||
stateTransfer.clear_ui ();
|
||||
return AudioEffect::terminate ();
|
||||
}
|
||||
```
|
||||
|
||||
If the model data uses more memory and you want to get rid of it earlier you have to use a timer or similar to call the clear_ui method a little bit after the setState method was called. But this is not the scope of this tutorial.
|
||||
|
||||
If you want to use the utility classes, you will find them in the sdk at:
|
||||
|
||||
*public.sdk/source/vst/utility/processdataslicer.h*\
|
||||
*public.sdk/source/vst/utility/sampleaccurate.h*\
|
||||
*public.sdk/source/vst/utility/rttransfer.h*
|
||||
|
||||
That´s it!
|
||||
@@ -0,0 +1,19 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginterfaces/base/funknown.h"
|
||||
#include "pluginterfaces/vst/vsttypes.h"
|
||||
|
||||
namespace Steinberg::Tutorial {
|
||||
//------------------------------------------------------------------------
|
||||
static const FUID ProcessorUID (0xC18D3C1E, 0x719E4E29, 0x924D3ECA, 0xA5E4DA18);
|
||||
static const FUID ControllerUID (0xC244B7E6, 0x24084E20, 0xA24A8C43, 0xF84C8BE8);
|
||||
|
||||
#define DataExchangeVST3Category "Fx"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace Steinberg::Tutorial
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#include "pids.h"
|
||||
#include "public.sdk/source/vst/vsteditcontroller.h"
|
||||
#include "base/source/fstreamer.h"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg::Tutorial {
|
||||
|
||||
using namespace Steinberg::Vst;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
class Controller : public EditController
|
||||
{
|
||||
tresult PLUGIN_API initialize (FUnknown* context) SMTG_OVERRIDE;
|
||||
tresult PLUGIN_API setComponentState (IBStream* state) SMTG_OVERRIDE;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API Controller::initialize (FUnknown* context)
|
||||
{
|
||||
tresult result = EditController::initialize (context);
|
||||
if (result != kResultOk)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
parameters.addParameter (STR ("Gain"), STR ("%"), 0, 1., ParameterInfo::kCanAutomate,
|
||||
ParameterID::Gain);
|
||||
return kResultOk;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API Controller::setComponentState (IBStream* state)
|
||||
{
|
||||
if (!state)
|
||||
return kInvalidArgument;
|
||||
|
||||
IBStreamer streamer (state, kLittleEndian);
|
||||
|
||||
ParamValue value;
|
||||
if (!streamer.readDouble (value))
|
||||
return kResultFalse;
|
||||
|
||||
if (auto param = parameters.getParameter (ParameterID::Gain))
|
||||
param->setNormalized (value);
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
FUnknown* createControllerInstance (void*)
|
||||
{
|
||||
return static_cast<IEditController*> (new Controller);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Steinberg::Tutorial
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#include "cids.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "public.sdk/source/main/pluginfactory.h"
|
||||
#include "pluginterfaces/vst/ivsteditcontroller.h"
|
||||
#include "pluginterfaces/vst/ivstaudioprocessor.h"
|
||||
|
||||
#define stringPluginName "advanced-techniques-tutorial"
|
||||
|
||||
using namespace Steinberg;
|
||||
using namespace Steinberg::Vst;
|
||||
using namespace Steinberg::Tutorial;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg::Tutorial {
|
||||
FUnknown* createProcessorInstance (void*);
|
||||
FUnknown* createControllerInstance (void*);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// VST Plug-in Entry
|
||||
//------------------------------------------------------------------------
|
||||
// Windows: do not forget to include a .def file in your project to export
|
||||
// GetPluginFactory function!
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
BEGIN_FACTORY_DEF ("Steinberg Media Technologies",
|
||||
"https://steinberg.net",
|
||||
"mailto:info@steinberg.de")
|
||||
|
||||
//---First Plug-in included in this factory-------
|
||||
// its kVstAudioEffectClass component
|
||||
DEF_CLASS2 (INLINE_UID_FROM_FUID(ProcessorUID),
|
||||
PClassInfo::kManyInstances, // cardinality
|
||||
kVstAudioEffectClass, // the component category (do not changed this)
|
||||
stringPluginName, // here the Plug-in name (to be changed)
|
||||
Vst::kDistributable, // means that component and controller could be distributed on different computers
|
||||
DataExchangeVST3Category, // Subcategory for this Plug-in (to be changed)
|
||||
FULL_VERSION_STR, // Plug-in version (to be changed)
|
||||
kVstVersionString, // the VST 3 SDK version (do not changed this, use always this define)
|
||||
createProcessorInstance)// function pointer called when this component should be instantiated
|
||||
|
||||
// its kVstComponentControllerClass component
|
||||
DEF_CLASS2 (INLINE_UID_FROM_FUID (ControllerUID),
|
||||
PClassInfo::kManyInstances, // cardinality
|
||||
kVstComponentControllerClass,// the Controller category (do not changed this)
|
||||
stringPluginName "Controller", // controller name (could be the same than component name)
|
||||
0, // not used here
|
||||
"", // not used here
|
||||
FULL_VERSION_STR, // Plug-in version (to be changed)
|
||||
kVstVersionString, // the VST 3 SDK version (do not changed this, use always this define)
|
||||
createControllerInstance)// function pointer called when this component should be instantiated
|
||||
|
||||
//----for others Plug-ins contained in this factory, put like for the first Plug-in different DEF_CLASS2---
|
||||
|
||||
END_FACTORY
|
||||
@@ -0,0 +1,17 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg::Tutorial {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
enum ParameterID
|
||||
{
|
||||
Gain = 1,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Steinberg::Tutorial
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#include "cids.h"
|
||||
#include "pids.h"
|
||||
#include "public.sdk/source/vst/utility/audiobuffers.h"
|
||||
#include "public.sdk/source/vst/utility/processdataslicer.h"
|
||||
#include "public.sdk/source/vst/utility/rttransfer.h"
|
||||
#include "public.sdk/source/vst/utility/sampleaccurate.h"
|
||||
#include "public.sdk/source/vst/vstaudioeffect.h"
|
||||
#include "base/source/fstreamer.h"
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg::Tutorial {
|
||||
|
||||
using namespace Steinberg::Vst;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct StateModel
|
||||
{
|
||||
double gain;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct MyEffect : public AudioEffect
|
||||
{
|
||||
using RTTransfer = RTTransferT<StateModel>;
|
||||
|
||||
MyEffect ();
|
||||
tresult PLUGIN_API initialize (FUnknown* context) SMTG_OVERRIDE;
|
||||
tresult PLUGIN_API terminate () SMTG_OVERRIDE;
|
||||
tresult PLUGIN_API setState (IBStream* state) SMTG_OVERRIDE;
|
||||
tresult PLUGIN_API getState (IBStream* state) SMTG_OVERRIDE;
|
||||
tresult PLUGIN_API setBusArrangements (SpeakerArrangement* inputs, int32 numIns,
|
||||
SpeakerArrangement* outputs,
|
||||
int32 numOuts) SMTG_OVERRIDE;
|
||||
tresult PLUGIN_API canProcessSampleSize (int32 symbolicSampleSize) SMTG_OVERRIDE;
|
||||
tresult PLUGIN_API process (ProcessData& data) SMTG_OVERRIDE;
|
||||
|
||||
void handleParameterChanges (IParameterChanges* changes);
|
||||
|
||||
template <SymbolicSampleSizes SampleSize>
|
||||
void process (ProcessData& data);
|
||||
|
||||
SampleAccurate::Parameter gainParameter {ParameterID::Gain, 1.};
|
||||
RTTransfer stateTransfer;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
MyEffect::MyEffect ()
|
||||
{
|
||||
setControllerClass (ControllerUID);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API MyEffect::initialize (FUnknown* context)
|
||||
{
|
||||
auto result = AudioEffect::initialize (context);
|
||||
if (result == kResultTrue)
|
||||
{
|
||||
addAudioInput (STR ("Input"), SpeakerArr::kStereo);
|
||||
addAudioOutput (STR ("Output"), SpeakerArr::kStereo);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API MyEffect::terminate ()
|
||||
{
|
||||
stateTransfer.clear_ui ();
|
||||
return AudioEffect::terminate ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API MyEffect::setState (IBStream* state)
|
||||
{
|
||||
if (!state)
|
||||
return kInvalidArgument;
|
||||
|
||||
IBStreamer streamer (state, kLittleEndian);
|
||||
|
||||
uint32 numParams;
|
||||
if (streamer.readInt32u (numParams) == false)
|
||||
return kResultFalse;
|
||||
|
||||
auto model = std::make_unique<StateModel> ();
|
||||
|
||||
ParamValue value;
|
||||
if (!streamer.readDouble (value))
|
||||
return kResultFalse;
|
||||
|
||||
model->gain = value;
|
||||
|
||||
stateTransfer.transferObject_ui (std::move (model));
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API MyEffect::getState (IBStream* state)
|
||||
{
|
||||
if (!state)
|
||||
return kInvalidArgument;
|
||||
|
||||
IBStreamer streamer (state, kLittleEndian);
|
||||
streamer.writeDouble (gainParameter.getValue ());
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API MyEffect::setBusArrangements (SpeakerArrangement* inputs, int32 numIns,
|
||||
SpeakerArrangement* outputs, int32 numOuts)
|
||||
{
|
||||
if (numIns != 1 || numOuts != 1)
|
||||
return kResultFalse;
|
||||
if (SpeakerArr::getChannelCount (inputs[0]) == SpeakerArr::getChannelCount (outputs[0]))
|
||||
{
|
||||
getAudioInput (0)->setArrangement (inputs[0]);
|
||||
getAudioOutput (0)->setArrangement (outputs[0]);
|
||||
return kResultTrue;
|
||||
}
|
||||
return kResultFalse;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API MyEffect::canProcessSampleSize (int32 symbolicSampleSize)
|
||||
{
|
||||
return (symbolicSampleSize == SymbolicSampleSizes::kSample32 ||
|
||||
symbolicSampleSize == SymbolicSampleSizes::kSample64) ?
|
||||
kResultTrue :
|
||||
kResultFalse;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
template <SymbolicSampleSizes SampleSize>
|
||||
void MyEffect::process (ProcessData& data)
|
||||
{
|
||||
ProcessDataSlicer slicer (8);
|
||||
|
||||
auto doProcessing = [this] (ProcessData& data) {
|
||||
// get the gain value for this block
|
||||
ParamValue gain = gainParameter.advance (data.numSamples);
|
||||
|
||||
// process audio
|
||||
AudioBusBuffers* inputs = data.inputs;
|
||||
AudioBusBuffers* outputs = data.outputs;
|
||||
for (auto channelIndex = 0; channelIndex < inputs[0].numChannels; ++channelIndex)
|
||||
{
|
||||
auto inputBuffers = getChannelBuffers<SampleSize> (inputs[0])[channelIndex];
|
||||
auto outputBuffers = getChannelBuffers<SampleSize> (outputs[0])[channelIndex];
|
||||
for (auto sampleIndex = 0; sampleIndex < data.numSamples; ++sampleIndex)
|
||||
{
|
||||
auto sample = inputBuffers[sampleIndex];
|
||||
outputBuffers[sampleIndex] = sample * gain;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
slicer.process<SampleSize> (data, doProcessing);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void MyEffect::handleParameterChanges (IParameterChanges* changes)
|
||||
{
|
||||
if (!changes)
|
||||
return;
|
||||
int32 changeCount = changes->getParameterCount ();
|
||||
for (auto i = 0; i < changeCount; ++i)
|
||||
{
|
||||
if (auto queue = changes->getParameterData (i))
|
||||
{
|
||||
auto paramID = queue->getParameterId ();
|
||||
if (paramID == ParameterID::Gain)
|
||||
{
|
||||
gainParameter.beginChanges (queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API MyEffect::process (ProcessData& data)
|
||||
{
|
||||
stateTransfer.accessTransferObject_rt (
|
||||
[this] (const auto& stateModel) { gainParameter.setValue (stateModel.gain); });
|
||||
|
||||
handleParameterChanges (data.inputParameterChanges);
|
||||
|
||||
if (processSetup.symbolicSampleSize == SymbolicSampleSizes::kSample32)
|
||||
process<SymbolicSampleSizes::kSample32> (data);
|
||||
else
|
||||
process<SymbolicSampleSizes::kSample64> (data);
|
||||
|
||||
gainParameter.endChanges ();
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
FUnknown* createProcessorInstance (void*)
|
||||
{
|
||||
return static_cast<IAudioProcessor*> (new MyEffect);
|
||||
}
|
||||
|
||||
} // Steinberg::Tutorial
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginterfaces/base/fplatform.h"
|
||||
|
||||
// Plain project version file generated by cmake
|
||||
#include "projectversion.h"
|
||||
|
||||
#define stringOriginalFilename "advanced-techniques-tutorial.vst3"
|
||||
#if SMTG_PLATFORM_64
|
||||
#define stringFileDescription "advanced-techniques-tutorial VST3 (64Bit)"
|
||||
#else
|
||||
#define stringFileDescription "advanced-techniques-tutorial VST3"
|
||||
#endif
|
||||
#define stringCompanyName "Steinberg Media Technologies\0"
|
||||
#define stringLegalCopyright "Copyright(c) 2023 Steinberg Media Technologies."
|
||||
#define stringLegalTrademarks "VST is a trademark of Steinberg Media Technologies GmbH"
|
||||
@@ -0,0 +1 @@
|
||||
build/
|
||||
@@ -0,0 +1,114 @@
|
||||
cmake_minimum_required(VERSION 3.14.0)
|
||||
|
||||
option(SMTG_ENABLE_VST3_PLUGIN_EXAMPLES "Enable VST 3 Plug-in Examples" OFF)
|
||||
option(SMTG_ENABLE_VST3_HOSTING_EXAMPLES "Enable VST 3 Hosting Examples" OFF)
|
||||
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13 CACHE STRING "")
|
||||
|
||||
set(vst3sdk_SOURCE_DIR "../../")
|
||||
if(NOT vst3sdk_SOURCE_DIR)
|
||||
message(FATAL_ERROR "Path to VST3 SDK is empty!")
|
||||
endif()
|
||||
|
||||
project(VST3_AU_PlugIn
|
||||
# This is your plug-in version number. Change it here only.
|
||||
# Version number symbols usable in C++ can be found in
|
||||
# source/version.h and ${PROJECT_BINARY_DIR}/projectversion.h.
|
||||
VERSION 1.2.1.8
|
||||
DESCRIPTION "VST3_AU_PlugIn VST 3 Plug-in"
|
||||
)
|
||||
|
||||
# -- AudioUnitSDK --
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
AudioUnitSDK
|
||||
GIT_REPOSITORY https://github.com/apple/AudioUnitSDK.git
|
||||
GIT_TAG HEAD
|
||||
)
|
||||
FetchContent_MakeAvailable(AudioUnitSDK)
|
||||
FetchContent_GetProperties(
|
||||
AudioUnitSDK
|
||||
SOURCE_DIR SMTG_AUDIOUNIT_SDK_PATH
|
||||
)
|
||||
# -------------------
|
||||
|
||||
set(SMTG_VSTGUI_ROOT "${vst3sdk_SOURCE_DIR}")
|
||||
|
||||
add_subdirectory(${vst3sdk_SOURCE_DIR} ${PROJECT_BINARY_DIR}/vst3sdk)
|
||||
smtg_enable_vst3_sdk()
|
||||
|
||||
smtg_add_vst3plugin(VST3_AU_PlugIn
|
||||
source/version.h
|
||||
source/cids.h
|
||||
source/processor.h
|
||||
source/processor.cpp
|
||||
source/controller.h
|
||||
source/controller.cpp
|
||||
source/entry.cpp
|
||||
)
|
||||
|
||||
#- VSTGUI Wanted ----
|
||||
if(SMTG_ENABLE_VSTGUI_SUPPORT)
|
||||
target_sources(VST3_AU_PlugIn
|
||||
PRIVATE
|
||||
resource/editor.uidesc
|
||||
)
|
||||
target_link_libraries(VST3_AU_PlugIn
|
||||
PRIVATE
|
||||
vstgui_support
|
||||
)
|
||||
smtg_target_add_plugin_resources(VST3_AU_PlugIn
|
||||
RESOURCES
|
||||
"resource/editor.uidesc"
|
||||
)
|
||||
endif(SMTG_ENABLE_VSTGUI_SUPPORT)
|
||||
# -------------------
|
||||
|
||||
smtg_target_add_plugin_snapshots (VST3_AU_PlugIn
|
||||
RESOURCES
|
||||
resource/301DF339AFA3533FB5053B1B41367137_snapshot.png
|
||||
resource/301DF339AFA3533FB5053B1B41367137_snapshot_2.0x.png
|
||||
)
|
||||
|
||||
target_link_libraries(VST3_AU_PlugIn
|
||||
PRIVATE
|
||||
sdk
|
||||
)
|
||||
|
||||
smtg_target_configure_version_file(VST3_AU_PlugIn)
|
||||
|
||||
if(SMTG_MAC)
|
||||
smtg_target_set_bundle(VST3_AU_PlugIn
|
||||
BUNDLE_IDENTIFIER com.steinberg.vst3sdk.audiounit-tutorial
|
||||
COMPANY_NAME "Steinberg Media Technologies"
|
||||
)
|
||||
smtg_target_set_debug_executable(VST3_AU_PlugIn
|
||||
"/Applications/VST3PluginTestHost.app"
|
||||
"--pluginfolder;$(BUILT_PRODUCTS_DIR)"
|
||||
)
|
||||
elseif(SMTG_WIN)
|
||||
target_sources(VST3_AU_PlugIn PRIVATE
|
||||
resource/win32resource.rc
|
||||
)
|
||||
if(MSVC)
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT VST3_AU_PlugIn)
|
||||
|
||||
smtg_target_set_debug_executable(VST3_AU_PlugIn
|
||||
"$(ProgramW6432)/Steinberg/VST3PluginTestHost/VST3PluginTestHost.exe"
|
||||
"--pluginfolder \"$(OutDir)/\""
|
||||
)
|
||||
endif()
|
||||
endif(SMTG_MAC)
|
||||
|
||||
# -- Add the AUv2 target
|
||||
if (SMTG_MAC AND XCODE AND SMTG_ENABLE_AUV2_BUILDS)
|
||||
list(APPEND CMAKE_MODULE_PATH "${vst3sdk_SOURCE_DIR}/cmake/modules")
|
||||
include(SMTG_AddVST3AuV2)
|
||||
smtg_target_add_auv2(VST3_AU_PlugIn_AU
|
||||
BUNDLE_NAME audiounit_tutorial
|
||||
BUNDLE_IDENTIFIER com.steinberg.vst3sdk.audiounit_tutorial.audiounit
|
||||
INFO_PLIST_TEMPLATE ${CMAKE_CURRENT_SOURCE_DIR}/resource/au-info.plist
|
||||
VST3_PLUGIN_TARGET VST3_AU_PlugIn)
|
||||
smtg_target_set_debug_executable(VST3_AU_PlugIn_AU "/Applications/Reaper.app")
|
||||
endif(SMTG_MAC AND XCODE AND SMTG_ENABLE_AUV2_BUILDS)
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
# AudioUnit Version 2 Tutorial
|
||||
|
||||
In this tutorial you will learn how to add AudioUnit Version 2 support to your **VST 3** plug-in.
|
||||
|
||||
First of all, you need a **VST 3** plug-in project. For this tutorial we have generated one via the [VST 3 Project Generator](https://steinbergmedia.github.io/vst3_dev_portal/pages/What+is+the+VST+3+SDK/Project+Generator.html) from the SDK.
|
||||
|
||||
# Adding the AudioUnit Version 2 Target
|
||||
|
||||
## Obtaining the required AudioUnit SDK
|
||||
|
||||
The *AudioUnit Version 2* target needs the official *AudioUnit SDK* from Apple.
|
||||
As of this writing you can find it on GitHub: [https://github.com/apple/AudioUnitSDK](https://github.com/apple/AudioUnitSDK)
|
||||
|
||||
How you obtain and store the SDK is up to you, for the reproducibility of this tutorial, we will download it via *CMake* when generating the project.
|
||||
So we add the following text to the *CMakeLists.txt* directly before we include the **VST 3 SDK**.
|
||||
|
||||
```
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
AudioUnitSDK
|
||||
GIT_REPOSITORY https://github.com/apple/AudioUnitSDK.git
|
||||
GIT_TAG HEAD
|
||||
)
|
||||
FetchContent_MakeAvailable(AudioUnitSDK)
|
||||
FetchContent_GetProperties(
|
||||
AudioUnitSDK
|
||||
SOURCE_DIR SMTG_AUDIOUNIT_SDK_PATH
|
||||
)
|
||||
```
|
||||
|
||||
It is important to set the `SMTG_AUDIOUNIT_SDK_PATH` variable to tell the **VST 3 SDK** where to find the AudioUnit SDK.
|
||||
|
||||
## Creating the property list
|
||||
|
||||
For *AudioUnit Version 2* you need a manufacturer OSType registered with Apple.
|
||||
How to do this is out of the scope for this tutorial, please search the web on how this is done.
|
||||
|
||||
Besides the manufacturer OSType you also need a subtype OSType which you can choose by yourself.
|
||||
Both the manufacturer and subtype account for the uniqueness of your *AudioUnit Version 2*.
|
||||
|
||||
Now you can generate the required property list file:
|
||||
|
||||
```
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<string>yes</string>
|
||||
<key>AudioComponents</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>factoryFunction</key>
|
||||
<string>AUWrapperFactory</string>
|
||||
<key>description</key>
|
||||
<string>AudioUnit Tutorial</string>
|
||||
<key>manufacturer</key>
|
||||
<string>Stgb</string>
|
||||
<key>name</key>
|
||||
<string>Steinberg: AudioUnit Tutorial</string>
|
||||
<key>subtype</key>
|
||||
<string>0002</string>
|
||||
<key>type</key>
|
||||
<string>aufx</string>
|
||||
<key>version</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
Make sure to change the strings for `description`, `manufacturer`, `name`, `subtype` and `type`.
|
||||
The type must be one of:
|
||||
- aufx (Audio Effect)
|
||||
- aumu (Instrument)
|
||||
- aumf (Audio Effect with MIDI Input/Output)
|
||||
|
||||
If you build an audio effect you also need to add the supported channel layouts to the list:
|
||||
|
||||
```
|
||||
<key>AudioUnit SupportedNumChannels</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>Outputs</key>
|
||||
<string>2</string>
|
||||
<key>Inputs</key>
|
||||
<string>2</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Outputs</key>
|
||||
<string>2</string>
|
||||
<key>Inputs</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Outputs</key>
|
||||
<string>1</string>
|
||||
<key>Inputs</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</array>
|
||||
```
|
||||
|
||||
Save it to a file called `au-info.plist` inside the resource directory.
|
||||
|
||||
## Adding the AudioUnit Version 2 Target
|
||||
|
||||
Now you can add the AudioUnit target to the end of your *CMakeLists.txt* file:
|
||||
|
||||
```
|
||||
if (SMTG_MAC AND XCODE AND SMTG_COREAUDIO_SDK_PATH)
|
||||
list(APPEND CMAKE_MODULE_PATH "${vst3sdk_SOURCE_DIR}/cmake/modules")
|
||||
include(SMTG_AddVST3AuV2)
|
||||
smtg_target_add_auv2(VST3_AU_PlugIn_AU
|
||||
BUNDLE_NAME audiounit_tutorial
|
||||
BUNDLE_IDENTIFIER com.steinberg.vst3sdk.audiounit_tutorial.audiounit
|
||||
INFO_PLIST_TEMPLATE ${CMAKE_CURRENT_SOURCE_DIR}/resource/au-info.plist
|
||||
VST3_PLUGIN_TARGET VST3_AU_PlugIn)
|
||||
smtg_target_set_debug_executable(VST3_AU_PlugIn_AU "/Applications/Reaper.app")
|
||||
endif(SMTG_MAC AND XCODE AND SMTG_COREAUDIO_SDK_PATH)
|
||||
```
|
||||
|
||||
Now after generating and building the project the "audiounit_tutorial" plug-in should be available in
|
||||
any AudioUnit host.
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<string>yes</string>
|
||||
<key>AudioComponents</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>factoryFunction</key>
|
||||
<string>AUWrapperFactory</string>
|
||||
<key>description</key>
|
||||
<string>AudioUnit Tutorial</string>
|
||||
<key>manufacturer</key>
|
||||
<string>Stgb</string>
|
||||
<key>name</key>
|
||||
<string>Steinberg: AudioUnit Tutorial</string>
|
||||
<key>subtype</key>
|
||||
<string>0002</string>
|
||||
<key>type</key>
|
||||
<string>aufx</string>
|
||||
<key>version</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>AudioUnit SupportedNumChannels</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>Outputs</key>
|
||||
<string>2</string>
|
||||
<key>Inputs</key>
|
||||
<string>2</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Outputs</key>
|
||||
<string>2</string>
|
||||
<key>Inputs</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Outputs</key>
|
||||
<string>1</string>
|
||||
<key>Inputs</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<vstgui-ui-description version="1">
|
||||
<fonts>
|
||||
</fonts>
|
||||
<colors>
|
||||
</colors>
|
||||
<template background-color="~ BlackCColor" background-color-draw-style="filled and stroked" class="CViewContainer" mouse-enabled="true" name="view" opacity="1" origin="0, 0" size="300, 300" transparent="false" wants-focus="false"/>
|
||||
<bitmaps/>
|
||||
</vstgui-ui-description>
|
||||
@@ -0,0 +1,44 @@
|
||||
#include <windows.h>
|
||||
#include "../source/version.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Version
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT
|
||||
PRODUCTVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x40004L
|
||||
FILETYPE 0x1L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040004e4"
|
||||
BEGIN
|
||||
VALUE "FileVersion", FULL_VERSION_STR"\0"
|
||||
VALUE "ProductVersion", FULL_VERSION_STR"\0"
|
||||
VALUE "OriginalFilename", stringOriginalFilename"\0"
|
||||
VALUE "FileDescription", stringFileDescription"\0"
|
||||
VALUE "InternalName", stringFileDescription"\0"
|
||||
VALUE "ProductName", stringFileDescription"\0"
|
||||
VALUE "CompanyName", stringCompanyName"\0"
|
||||
VALUE "LegalCopyright", stringLegalCopyright"\0"
|
||||
VALUE "LegalTrademarks", stringLegalTrademarks"\0"
|
||||
//VALUE "PrivateBuild", " \0"
|
||||
//VALUE "SpecialBuild", " \0"
|
||||
//VALUE "Comments", " \0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x400, 1252
|
||||
END
|
||||
END
|
||||
@@ -0,0 +1,18 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2024 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginterfaces/base/funknown.h"
|
||||
#include "pluginterfaces/vst/vsttypes.h"
|
||||
|
||||
namespace Steinberg::Vst {
|
||||
//------------------------------------------------------------------------
|
||||
static const Steinberg::FUID kVST3AUPlugInProcessorUID (0x301DF339, 0xAFA3533F, 0xB5053B1B, 0x41367137);
|
||||
static const Steinberg::FUID kVST3AUPlugInControllerUID (0x473E512F, 0x68875B49, 0xB2EEFB2D, 0x886C33C6);
|
||||
|
||||
#define VST3AUPlugInVST3Category "Fx"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace Steinberg::Vst
|
||||
@@ -0,0 +1,82 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2024 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#include "controller.h"
|
||||
#include "cids.h"
|
||||
#include "vstgui/plugin-bindings/vst3editor.h"
|
||||
|
||||
using namespace Steinberg;
|
||||
|
||||
namespace Steinberg::Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// VST3AUPlugInController Implementation
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInController::initialize (FUnknown* context)
|
||||
{
|
||||
// Here the Plug-in will be instantiated
|
||||
|
||||
//---do not forget to call parent ------
|
||||
tresult result = EditControllerEx1::initialize (context);
|
||||
if (result != kResultOk)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Here you could register some parameters
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInController::terminate ()
|
||||
{
|
||||
// Here the Plug-in will be de-instantiated, last possibility to remove some memory!
|
||||
|
||||
//---do not forget to call parent ------
|
||||
return EditControllerEx1::terminate ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInController::setComponentState (IBStream* state)
|
||||
{
|
||||
// Here you get the state of the component (Processor part)
|
||||
if (!state)
|
||||
return kResultFalse;
|
||||
|
||||
return kResultOk;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInController::setState (IBStream* state)
|
||||
{
|
||||
// Here you get the state of the controller
|
||||
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInController::getState (IBStream* state)
|
||||
{
|
||||
// Here you are asked to deliver the state of the controller (if needed)
|
||||
// Note: the real state of your plug-in is saved in the processor
|
||||
|
||||
return kResultTrue;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
IPlugView* PLUGIN_API VST3AUPlugInController::createView (FIDString name)
|
||||
{
|
||||
// Here the Host wants to open your editor (if you have one)
|
||||
if (FIDStringsEqual (name, Vst::ViewType::kEditor))
|
||||
{
|
||||
// create your editor here and return a IPlugView ptr of it
|
||||
auto* view = new VSTGUI::VST3Editor (this, "view", "editor.uidesc");
|
||||
return view;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace Steinberg::Vst
|
||||
@@ -0,0 +1,49 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2024 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "public.sdk/source/vst/vsteditcontroller.h"
|
||||
|
||||
namespace Steinberg::Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// VST3AUPlugInController
|
||||
//------------------------------------------------------------------------
|
||||
class VST3AUPlugInController : public Steinberg::Vst::EditControllerEx1
|
||||
{
|
||||
public:
|
||||
//------------------------------------------------------------------------
|
||||
VST3AUPlugInController () = default;
|
||||
~VST3AUPlugInController () SMTG_OVERRIDE = default;
|
||||
|
||||
// Create function
|
||||
static Steinberg::FUnknown* createInstance (void* /*context*/)
|
||||
{
|
||||
return (Steinberg::Vst::IEditController*)new VST3AUPlugInController;
|
||||
}
|
||||
|
||||
//--- from IPluginBase -----------------------------------------------
|
||||
Steinberg::tresult PLUGIN_API initialize (Steinberg::FUnknown* context) SMTG_OVERRIDE;
|
||||
Steinberg::tresult PLUGIN_API terminate () SMTG_OVERRIDE;
|
||||
|
||||
//--- from EditController --------------------------------------------
|
||||
Steinberg::tresult PLUGIN_API setComponentState (Steinberg::IBStream* state) SMTG_OVERRIDE;
|
||||
Steinberg::IPlugView* PLUGIN_API createView (Steinberg::FIDString name) SMTG_OVERRIDE;
|
||||
Steinberg::tresult PLUGIN_API setState (Steinberg::IBStream* state) SMTG_OVERRIDE;
|
||||
Steinberg::tresult PLUGIN_API getState (Steinberg::IBStream* state) SMTG_OVERRIDE;
|
||||
|
||||
//---Interface---------
|
||||
DEFINE_INTERFACES
|
||||
// Here you can add more supported VST3 interfaces
|
||||
// DEF_INTERFACE (Vst::IXXX)
|
||||
END_DEFINE_INTERFACES (EditController)
|
||||
DELEGATE_REFCOUNT (EditController)
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
protected:
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace Steinberg::Vst
|
||||
@@ -0,0 +1,50 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2024 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#include "processor.h"
|
||||
#include "controller.h"
|
||||
#include "cids.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "public.sdk/source/main/pluginfactory.h"
|
||||
|
||||
#define stringPluginName "VST3 AU PlugIn"
|
||||
|
||||
using namespace Steinberg::Vst;
|
||||
using namespace Steinberg::Vst;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// VST Plug-in Entry
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
BEGIN_FACTORY_DEF ("Steinberg Media Technologies",
|
||||
"https://steinberg.net",
|
||||
"mailto:info@steinberg.de")
|
||||
|
||||
//---First Plug-in included in this factory-------
|
||||
// its kVstAudioEffectClass component
|
||||
DEF_CLASS2 (INLINE_UID_FROM_FUID(kVST3AUPlugInProcessorUID),
|
||||
PClassInfo::kManyInstances, // cardinality
|
||||
kVstAudioEffectClass, // the component category (do not changed this)
|
||||
stringPluginName, // here the Plug-in name (to be changed)
|
||||
Vst::kDistributable, // means that component and controller could be distributed on different computers
|
||||
VST3AUPlugInVST3Category, // Subcategory for this Plug-in (to be changed)
|
||||
FULL_VERSION_STR, // Plug-in version (to be changed)
|
||||
kVstVersionString, // the VST 3 SDK version (do not changed this, use always this define)
|
||||
VST3AUPlugInProcessor::createInstance) // function pointer called when this component should be instantiated
|
||||
|
||||
// its kVstComponentControllerClass component
|
||||
DEF_CLASS2 (INLINE_UID_FROM_FUID (kVST3AUPlugInControllerUID),
|
||||
PClassInfo::kManyInstances, // cardinality
|
||||
kVstComponentControllerClass,// the Controller category (do not changed this)
|
||||
stringPluginName "Controller", // controller name (could be the same than component name)
|
||||
0, // not used here
|
||||
"", // not used here
|
||||
FULL_VERSION_STR, // Plug-in version (to be changed)
|
||||
kVstVersionString, // the VST 3 SDK version (do not changed this, use always this define)
|
||||
VST3AUPlugInController::createInstance)// function pointer called when this component should be instantiated
|
||||
|
||||
//----for others Plug-ins contained in this factory, put like for the first Plug-in different DEF_CLASS2---
|
||||
|
||||
END_FACTORY
|
||||
@@ -0,0 +1,180 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2024 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#include "processor.h"
|
||||
#include "cids.h"
|
||||
|
||||
#include "base/source/fstreamer.h"
|
||||
#include "pluginterfaces/vst/ivstparameterchanges.h"
|
||||
|
||||
using namespace Steinberg;
|
||||
|
||||
namespace Steinberg::Vst {
|
||||
//------------------------------------------------------------------------
|
||||
// VST3AUPlugInProcessor
|
||||
//------------------------------------------------------------------------
|
||||
VST3AUPlugInProcessor::VST3AUPlugInProcessor ()
|
||||
{
|
||||
//--- set the wanted controller for our processor
|
||||
setControllerClass (kVST3AUPlugInControllerUID);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
VST3AUPlugInProcessor::~VST3AUPlugInProcessor ()
|
||||
{}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInProcessor::initialize (FUnknown* context)
|
||||
{
|
||||
// Here the Plug-in will be instantiated
|
||||
|
||||
//---always initialize the parent-------
|
||||
tresult result = AudioEffect::initialize (context);
|
||||
// if everything Ok, continue
|
||||
if (result != kResultOk)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
//--- create Audio IO ------
|
||||
addAudioInput (STR16 ("Stereo In"), Steinberg::Vst::SpeakerArr::kStereo);
|
||||
addAudioOutput (STR16 ("Stereo Out"), Steinberg::Vst::SpeakerArr::kStereo);
|
||||
|
||||
/* If you don't need an event bus, you can remove the next line */
|
||||
addEventInput (STR16 ("Event In"), 1);
|
||||
|
||||
return kResultOk;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInProcessor::terminate ()
|
||||
{
|
||||
// Here the Plug-in will be de-instantiated, last possibility to remove some memory!
|
||||
|
||||
//---do not forget to call parent ------
|
||||
return AudioEffect::terminate ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInProcessor::setActive (TBool state)
|
||||
{
|
||||
//--- called when the Plug-in is enable/disable (On/Off) -----
|
||||
return AudioEffect::setActive (state);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInProcessor::process (Vst::ProcessData& data)
|
||||
{
|
||||
//--- First : Read inputs parameter changes-----------
|
||||
|
||||
/*if (data.inputParameterChanges)
|
||||
{
|
||||
int32 numParamsChanged = data.inputParameterChanges->getParameterCount ();
|
||||
for (int32 index = 0; index < numParamsChanged; index++)
|
||||
{
|
||||
if (auto* paramQueue = data.inputParameterChanges->getParameterData (index))
|
||||
{
|
||||
Vst::ParamValue value;
|
||||
int32 sampleOffset;
|
||||
int32 numPoints = paramQueue->getPointCount ();
|
||||
switch (paramQueue->getParameterId ())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
//--- Here you have to implement your processing
|
||||
|
||||
if (data.numSamples > 0)
|
||||
{
|
||||
//--- ------------------------------------------
|
||||
// here as example a default implementation where we try to copy the inputs to the outputs:
|
||||
// if less input than outputs then clear outputs
|
||||
//--- ------------------------------------------
|
||||
|
||||
int32 minBus = std::min (data.numInputs, data.numOutputs);
|
||||
for (int32 i = 0; i < minBus; i++)
|
||||
{
|
||||
int32 minChan = std::min (data.inputs[i].numChannels, data.outputs[i].numChannels);
|
||||
for (int32 c = 0; c < minChan; c++)
|
||||
{
|
||||
// do not need to be copied if the buffers are the same
|
||||
if (data.outputs[i].channelBuffers32[c] != data.inputs[i].channelBuffers32[c])
|
||||
{
|
||||
memcpy (data.outputs[i].channelBuffers32[c], data.inputs[i].channelBuffers32[c],
|
||||
data.numSamples * sizeof (Vst::Sample32));
|
||||
}
|
||||
}
|
||||
data.outputs[i].silenceFlags = data.inputs[i].silenceFlags;
|
||||
|
||||
// clear the remaining output buffers
|
||||
for (int32 c = minChan; c < data.outputs[i].numChannels; c++)
|
||||
{
|
||||
// clear output buffers
|
||||
memset (data.outputs[i].channelBuffers32[c], 0,
|
||||
data.numSamples * sizeof (Vst::Sample32));
|
||||
|
||||
// inform the host that this channel is silent
|
||||
data.outputs[i].silenceFlags |= ((uint64)1 << c);
|
||||
}
|
||||
}
|
||||
// clear the remaining output buffers
|
||||
for (int32 i = minBus; i < data.numOutputs; i++)
|
||||
{
|
||||
// clear output buffers
|
||||
for (int32 c = 0; c < data.outputs[i].numChannels; c++)
|
||||
{
|
||||
memset (data.outputs[i].channelBuffers32[c], 0,
|
||||
data.numSamples * sizeof (Vst::Sample32));
|
||||
}
|
||||
// inform the host that this bus is silent
|
||||
data.outputs[i].silenceFlags = ((uint64)1 << data.outputs[i].numChannels) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return kResultOk;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInProcessor::setupProcessing (Vst::ProcessSetup& newSetup)
|
||||
{
|
||||
//--- called before any processing ----
|
||||
return AudioEffect::setupProcessing (newSetup);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInProcessor::canProcessSampleSize (int32 symbolicSampleSize)
|
||||
{
|
||||
// by default kSample32 is supported
|
||||
if (symbolicSampleSize == Vst::kSample32)
|
||||
return kResultTrue;
|
||||
|
||||
// disable the following comment if your processing support kSample64
|
||||
/* if (symbolicSampleSize == Vst::kSample64)
|
||||
return kResultTrue; */
|
||||
|
||||
return kResultFalse;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInProcessor::setState (IBStream* state)
|
||||
{
|
||||
// called when we load a preset, the model has to be reloaded
|
||||
IBStreamer streamer (state, kLittleEndian);
|
||||
|
||||
return kResultOk;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API VST3AUPlugInProcessor::getState (IBStream* state)
|
||||
{
|
||||
// here we need to save the model
|
||||
IBStreamer streamer (state, kLittleEndian);
|
||||
|
||||
return kResultOk;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace Steinberg::Vst
|
||||
@@ -0,0 +1,57 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2024 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "public.sdk/source/vst/vstaudioeffect.h"
|
||||
|
||||
namespace Steinberg::Vst {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// VST3AUPlugInProcessor
|
||||
//------------------------------------------------------------------------
|
||||
class VST3AUPlugInProcessor : public Steinberg::Vst::AudioEffect
|
||||
{
|
||||
public:
|
||||
VST3AUPlugInProcessor ();
|
||||
~VST3AUPlugInProcessor () SMTG_OVERRIDE;
|
||||
|
||||
// Create function
|
||||
static Steinberg::FUnknown* createInstance (void* /*context*/)
|
||||
{
|
||||
return (Steinberg::Vst::IAudioProcessor*)new VST3AUPlugInProcessor;
|
||||
}
|
||||
|
||||
//--- ---------------------------------------------------------------------
|
||||
// AudioEffect overrides:
|
||||
//--- ---------------------------------------------------------------------
|
||||
/** Called at first after constructor */
|
||||
Steinberg::tresult PLUGIN_API initialize (Steinberg::FUnknown* context) SMTG_OVERRIDE;
|
||||
|
||||
/** Called at the end before destructor */
|
||||
Steinberg::tresult PLUGIN_API terminate () SMTG_OVERRIDE;
|
||||
|
||||
/** Switch the Plug-in on/off */
|
||||
Steinberg::tresult PLUGIN_API setActive (Steinberg::TBool state) SMTG_OVERRIDE;
|
||||
|
||||
/** Will be called before any process call */
|
||||
Steinberg::tresult PLUGIN_API setupProcessing (Steinberg::Vst::ProcessSetup& newSetup) SMTG_OVERRIDE;
|
||||
|
||||
/** Asks if a given sample size is supported see SymbolicSampleSizes. */
|
||||
Steinberg::tresult PLUGIN_API canProcessSampleSize (Steinberg::int32 symbolicSampleSize) SMTG_OVERRIDE;
|
||||
|
||||
/** Here we go...the process call */
|
||||
Steinberg::tresult PLUGIN_API process (Steinberg::Vst::ProcessData& data) SMTG_OVERRIDE;
|
||||
|
||||
/** For persistence */
|
||||
Steinberg::tresult PLUGIN_API setState (Steinberg::IBStream* state) SMTG_OVERRIDE;
|
||||
Steinberg::tresult PLUGIN_API getState (Steinberg::IBStream* state) SMTG_OVERRIDE;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
protected:
|
||||
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace Steinberg::Vst
|
||||
@@ -0,0 +1,20 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2024 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginterfaces/base/fplatform.h"
|
||||
|
||||
// Plain project version file generated by cmake
|
||||
#include "projectversion.h"
|
||||
|
||||
#define stringOriginalFilename "VST3 AU PlugIn.vst3"
|
||||
#if SMTG_PLATFORM_64
|
||||
#define stringFileDescription "VST3 AU PlugIn VST3 (64Bit)"
|
||||
#else
|
||||
#define stringFileDescription "VST3 AU PlugIn VST3"
|
||||
#endif
|
||||
#define stringCompanyName "Steinberg Media Technologies\0"
|
||||
#define stringLegalCopyright "Copyright(c) 2024 Steinberg Media Technologies."
|
||||
#define stringLegalTrademarks "VST is a trademark of Steinberg Media Technologies GmbH"
|
||||
@@ -0,0 +1 @@
|
||||
build/
|
||||
@@ -0,0 +1,70 @@
|
||||
cmake_minimum_required(VERSION 3.14.0)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13 CACHE STRING "")
|
||||
|
||||
if(NOT vst3sdk_SOURCE_DIR)
|
||||
message(FATAL_ERROR "Path to VST3 SDK is empty! Please specify the vst3sdk_SOURCE_DIR cmake cache entry")
|
||||
endif()
|
||||
|
||||
project(dataexchange_tutorial
|
||||
# This is your plug-in version number. Change it here only.
|
||||
# Version number symbols usable in C++ can be found in
|
||||
# source/version.h and ${PROJECT_BINARY_DIR}/projectversion.h.
|
||||
VERSION 1.0.0.0
|
||||
DESCRIPTION "dataexchange_tutorial VST 3 Plug-in"
|
||||
)
|
||||
|
||||
set(SMTG_ENABLE_VST3_HOSTING_EXAMPLES 0)
|
||||
set(SMTG_ENABLE_VST3_PLUGIN_EXAMPLES 0)
|
||||
set(SMTG_ENABLE_VSTGUI_SUPPORT 0)
|
||||
|
||||
set(SMTG_VSTGUI_ROOT "${vst3sdk_SOURCE_DIR}")
|
||||
|
||||
add_subdirectory(${vst3sdk_SOURCE_DIR} ${PROJECT_BINARY_DIR}/vst3sdk)
|
||||
smtg_enable_vst3_sdk()
|
||||
|
||||
smtg_add_vst3plugin(dataexchange_tutorial
|
||||
README.md
|
||||
source/cids.h
|
||||
source/controller.cpp
|
||||
source/controller.h
|
||||
source/dataexchange.h
|
||||
source/entry.cpp
|
||||
source/processor.cpp
|
||||
source/processor.h
|
||||
source/version.h
|
||||
)
|
||||
|
||||
target_compile_features(dataexchange_tutorial
|
||||
PUBLIC
|
||||
cxx_std_17
|
||||
)
|
||||
|
||||
target_link_libraries(dataexchange_tutorial
|
||||
PRIVATE
|
||||
sdk
|
||||
)
|
||||
|
||||
smtg_target_configure_version_file(dataexchange_tutorial)
|
||||
|
||||
if(SMTG_MAC)
|
||||
smtg_target_set_bundle(dataexchange_tutorial
|
||||
BUNDLE_IDENTIFIER com.steinberg.vst3.tutorial.dataexchange
|
||||
COMPANY_NAME "Steinberg Media Technologies"
|
||||
)
|
||||
smtg_target_set_debug_executable(dataexchange_tutorial
|
||||
"/Applications/VST3PluginTestHost.app"
|
||||
"--pluginfolder;$(BUILT_PRODUCTS_DIR)"
|
||||
)
|
||||
elseif(SMTG_WIN)
|
||||
target_sources(dataexchange_tutorial PRIVATE
|
||||
resource/win32resource.rc
|
||||
)
|
||||
if(MSVC)
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT dataexchange_tutorial)
|
||||
|
||||
smtg_target_set_debug_executable(dataexchange_tutorial
|
||||
"$(ProgramW6432)/Steinberg/VST3PluginTestHost/VST3PluginTestHost.exe"
|
||||
"--pluginfolder \"$(OutDir)/\""
|
||||
)
|
||||
endif()
|
||||
endif(SMTG_MAC)
|
||||
@@ -0,0 +1,402 @@
|
||||
# Data Exchange Tutorial Plug-in
|
||||
|
||||
This tutorial shows how to use the *Data Exchange API* via the backwards compatible wrapper class which either uses the API directly if available or uses an alternative IMessage based method to provide the same functionality for hosts not implementing the API.
|
||||
|
||||
In this example the audio processor sends the samples it processes to the controller in 1 second big chunks.
|
||||
|
||||
---
|
||||
|
||||
## How to build
|
||||
|
||||
### macOS
|
||||
|
||||
mkdir build
|
||||
cmake -GXcode -Dvst3sdk_SOURCE_DIR="PATH_TO_YOUR_VST_SDK_FOLDER" ../
|
||||
cmake --build .
|
||||
|
||||
### Windows
|
||||
|
||||
mkdir build
|
||||
cmake -Dvst3sdk_SOURCE_DIR="PATH_TO_YOUR_VST_SDK_FOLDER" ..\
|
||||
cmake --build .
|
||||
|
||||
### Linux
|
||||
|
||||
mkdir build
|
||||
cmake -Dvst3sdk_SOURCE_DIR="PATH_TO_YOUR_VST_SDK_FOLDER" ../
|
||||
cmake --build .
|
||||
|
||||
---
|
||||
|
||||
## Tutorial - How to use the Data Exchange API
|
||||
|
||||
In this tutorial you learn how to use the *Data Exchange API* to send data from the realtime audio
|
||||
process method to the edit controller of your plug-in.
|
||||
|
||||
### Sending data from the audio processor
|
||||
|
||||
Let's send data from the processor to the controller.
|
||||
|
||||
First we need to add the required include so that we can use the wrapper class. Add the following
|
||||
include before you define your audio processor:
|
||||
|
||||
```c++
|
||||
#include "public.sdk/source/vst/utility/dataexchange.h"
|
||||
```
|
||||
|
||||
To prepare the AudioEffect class you need to overwrite the following methods:
|
||||
|
||||
```c++
|
||||
tresult PLUGIN_API initialize (FUnknown* context) override;
|
||||
tresult PLUGIN_API connect (Vst::IConnectionPoint* other) override;
|
||||
tresult PLUGIN_API disconnect (Vst::IConnectionPoint* other) override;
|
||||
tresult PLUGIN_API setActive (TBool state) override;
|
||||
tresult PLUGIN_API canProcessSampleSize (int32 symbolicSampleSize) override;
|
||||
tresult PLUGIN_API process (Vst::ProcessData& data) override;
|
||||
```
|
||||
|
||||
And we also have to add the wrapper class as a member of our audio processor class:
|
||||
|
||||
```c++
|
||||
std::unique_ptr<Vst::DataExchangeHandler> dataExchange;
|
||||
```
|
||||
|
||||
Now we can initialize and configure the dataExchange. We do this in:
|
||||
|
||||
```c++
|
||||
tresult PLUGIN_API DataExchangeProcessor::connect (Vst::IConnectionPoint* other)
|
||||
{
|
||||
auto result = Vst::AudioEffect::connect (other);
|
||||
if (result == kResultTrue)
|
||||
{
|
||||
auto configCallback = [this] (Vst::DataExchangeHandler::Config& config,
|
||||
const Vst::ProcessSetup& setup) {
|
||||
Vst::SpeakerArrangement arr;
|
||||
getBusArrangement (Vst::BusDirections::kInput, 0, arr);
|
||||
numChannels = static_cast<uint16_t> (Vst::SpeakerArr::getChannelCount (arr));
|
||||
auto sampleSize = sizeof (float);
|
||||
|
||||
config.blockSize = setup.sampleRate * numChannels * sampleSize + sizeof (DataBlock);
|
||||
config.numBlocks = 2;
|
||||
config.alignment = 32;
|
||||
config.userContextID = 0;
|
||||
return true;
|
||||
};
|
||||
|
||||
dataExchange = std::make_unique<Vst::DataExchangeHandler> (this, configCallback);
|
||||
dataExchange->onConnect (other, getHostContext ());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
The configration is done via the `configCallback`. In this example we configure the queue to have
|
||||
a block size to store exactly 1 second of audio data of all the channels of the configured speaker
|
||||
arrangement of the input bus. We choose two for `numBlocks` because we send one block per second
|
||||
and in this case two blocks should be enough. If the frequency you need to send the block is higher
|
||||
you need to increase this value to prevent data drop outs.
|
||||
The `configCallback` is called when the audio processor is activated.
|
||||
|
||||
The next thing we have to do is to call the dataExchange object when the edit controller is disconnected
|
||||
and release the memory:
|
||||
|
||||
```c++
|
||||
tresult PLUGIN_API DataExchangeProcessor::disconnect (Vst::IConnectionPoint* other)
|
||||
{
|
||||
if (dataExchange)
|
||||
{
|
||||
dataExchange->onDisconnect (other);
|
||||
dataExchange.reset ();
|
||||
}
|
||||
return AudioEffect::disconnect (other);
|
||||
}
|
||||
```
|
||||
|
||||
And we also have to call the dataExchange object when the processor's state changes:
|
||||
|
||||
```c++
|
||||
tresult PLUGIN_API DataExchangeProcessor::setActive (TBool state)
|
||||
{
|
||||
if (state)
|
||||
dataExchange->onActivate (processSetup);
|
||||
else
|
||||
dataExchange->onDeactivate ();
|
||||
return AudioEffect::setActive (state);
|
||||
}
|
||||
```
|
||||
|
||||
Now we prepare the data that we want to send to the controller. To make this a little bit easier we
|
||||
define a struct how this data should look like and move this into its own header "*dataexchange.h*":
|
||||
|
||||
```c++
|
||||
// dataexchange.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "public.sdk/source/vst/utility/dataexchange.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace Steinberg::Tutorial {
|
||||
|
||||
struct DataBlock
|
||||
{
|
||||
uint32_t sampleRate;
|
||||
uint16_t sampleSize;
|
||||
uint16_t numChannels;
|
||||
uint32_t numSamples;
|
||||
float samples[0];
|
||||
};
|
||||
|
||||
} // Steinberg::Tutorial
|
||||
```
|
||||
|
||||
So, we want to send the sample rate, size, the number of channels and the number of samples plus
|
||||
the actual samples to the controller.
|
||||
|
||||
To actually work with this `DataBlock` struct we introduce a little helper function we also add to
|
||||
this header:
|
||||
|
||||
```c++
|
||||
inline DataBlock* toDataBlock (const Vst::DataExchangeBlock& block)
|
||||
{
|
||||
if (block.blockID != Vst::InvalidDataExchangeBlockID)
|
||||
return reinterpret_cast<DataBlock*> (block.data);
|
||||
return nullptr;
|
||||
}
|
||||
```
|
||||
|
||||
One thing is left to do before we can implement the sending of the data and that is that we need a
|
||||
member variable of the Vst::DataExchangeBlock struct where we store the actual block we work with
|
||||
while processing the audio. So we add this to our processor definition:
|
||||
|
||||
|
||||
```c++
|
||||
class DataExchangeProcessor : public Vst::AudioEffect
|
||||
{
|
||||
private:
|
||||
Vst::DataExchangeBlock currentExchangeBlock {InvalidDataExchangeBlock};
|
||||
}
|
||||
```
|
||||
|
||||
Now lets start to implement the processing:
|
||||
|
||||
```c++
|
||||
tresult PLUGIN_API DataExchangeProcessor::process (Vst::ProcessData& processData)
|
||||
{
|
||||
if (processData.numSamples <= 0)
|
||||
return kResultTrue;
|
||||
return kResultTrue;
|
||||
}
|
||||
```
|
||||
|
||||
When there are no samples in the `processData` we jump out of the method directly.
|
||||
|
||||
Now the first thing we need to do is to acquire a new block from the `dataExchange` object:
|
||||
|
||||
```c++
|
||||
tresult PLUGIN_API DataExchangeProcessor::process (Vst::ProcessData& processData)
|
||||
{
|
||||
// ...
|
||||
if (currentExchangeBlock.blockID == Vst::InvalidDataExchangeBlockID)
|
||||
acquireNewExchangeBlock ();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
We only want to acquire a new block if we have't already. So we check for this first and then we
|
||||
call the `acquireNewExchangeBlock` method that does this:
|
||||
|
||||
```c++
|
||||
void DataExchangeProcessor::acquireNewExchangeBlock ()
|
||||
{
|
||||
currentExchangeBlock = dataExchange->getCurrentOrNewBlock ();
|
||||
if (auto block = toDataBlock (currentExchangeBlock))
|
||||
{
|
||||
block->sampleRate = static_cast<uint32_t> (processSetup.sampleRate);
|
||||
block->numChannels = numChannels;
|
||||
block->sampleSize = sizeof (float);
|
||||
block->numSamples = 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We ask the `dataExchange` object for a new block with `getCurrentOrNewBlock ()`, check if it is valid
|
||||
with a call to the previously defined function `toDataBlock` and fill it with the sample rate, the
|
||||
sample size and the number of channels.
|
||||
|
||||
Now back to our process function. We now write the samples from the input buffer into our block until
|
||||
the block is filled with 1 second of audio data:
|
||||
|
||||
```c++
|
||||
tresult PLUGIN_API DataExchangeProcessor::process (Vst::ProcessData& processData)
|
||||
{
|
||||
// ...
|
||||
auto input = processData.inputs[0];
|
||||
auto output = processData.outputs[0];
|
||||
|
||||
if (auto block = toDataBlock (currentExchangeBlock))
|
||||
{
|
||||
auto numSamples = static_cast<uint32> (processData.numSamples);
|
||||
while (numSamples > 0)
|
||||
{
|
||||
uint32 numSamplesFreeInBlock = block->sampleRate - block->numSamples;
|
||||
uint32 numSamplesToCopy = std::min<uint32> (numSamplesFreeInBlock, numSamples);
|
||||
for (auto channel = 0; channel < input.numChannels; ++channel)
|
||||
{
|
||||
auto blockChannelData = &block->samples[0] + block->numSamples;
|
||||
auto inputChannel =
|
||||
input.channelBuffers32[channel] + (processData.numSamples - numSamples);
|
||||
memcpy (blockChannelData, inputChannel, numSamplesToCopy * sizeof (float));
|
||||
}
|
||||
block->numSamples += numSamplesToCopy;
|
||||
if (block->numSamples == block->sampleRate)
|
||||
{
|
||||
dataExchange->sendCurrentBlock ();
|
||||
acquireNewExchangeBlock ();
|
||||
block = toDataBlock (currentExchangeBlock);
|
||||
if (block == nullptr)
|
||||
break;
|
||||
}
|
||||
numSamples -= numSamplesToCopy;
|
||||
}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
When it is filled with 1 second of audio data we send the block with `dataExchange->sendCurrentBlock()`
|
||||
and directly acquire a new block afterwards.
|
||||
|
||||
Finally we need to copy back the input audio buffers to the output audio buffers. The whole method
|
||||
looks like this then:
|
||||
|
||||
```c++
|
||||
tresult PLUGIN_API DataExchangeProcessor::process (Vst::ProcessData& processData)
|
||||
{
|
||||
if (processData.numSamples <= 0)
|
||||
return kResultTrue;
|
||||
|
||||
if (currentExchangeBlock.blockID == Vst::InvalidDataExchangeBlockID)
|
||||
acquireNewExchangeBlock ();
|
||||
|
||||
auto input = processData.inputs[0];
|
||||
auto output = processData.outputs[0];
|
||||
|
||||
if (auto block = toDataBlock (currentExchangeBlock))
|
||||
{
|
||||
auto numSamples = static_cast<uint32> (processData.numSamples);
|
||||
while (numSamples > 0)
|
||||
{
|
||||
uint32 numSamplesFreeInBlock = block->sampleRate - block->numSamples;
|
||||
uint32 numSamplesToCopy = std::min<uint32> (numSamplesFreeInBlock, numSamples);
|
||||
for (auto channel = 0; channel < input.numChannels; ++channel)
|
||||
{
|
||||
auto blockChannelData = &block->samples[0] + block->numSamples;
|
||||
auto inputChannel =
|
||||
input.channelBuffers32[channel] + (processData.numSamples - numSamples);
|
||||
memcpy (blockChannelData, inputChannel, numSamplesToCopy * sizeof (float));
|
||||
}
|
||||
block->numSamples += numSamplesToCopy;
|
||||
if (block->numSamples == block->sampleRate)
|
||||
{
|
||||
dataExchange->sendCurrentBlock ();
|
||||
acquireNewExchangeBlock ();
|
||||
block = toDataBlock (currentExchangeBlock);
|
||||
if (block == nullptr)
|
||||
break;
|
||||
}
|
||||
numSamples -= numSamplesToCopy;
|
||||
}
|
||||
}
|
||||
for (auto channel = 0; channel < input.numChannels; ++channel)
|
||||
{
|
||||
if (output.channelBuffers32[channel] != input.channelBuffers32[channel])
|
||||
{
|
||||
memcpy (output.channelBuffers32[channel], input.channelBuffers32[channel],
|
||||
processData.numSamples * sizeof (float));
|
||||
}
|
||||
output.silenceFlags = input.silenceFlags;
|
||||
}
|
||||
|
||||
return kResultOk;
|
||||
}
|
||||
```
|
||||
|
||||
### Receiving data in the edit controller
|
||||
|
||||
We prepare our edit controller by adding the inheritance of Vst::IDataExchangeReceiver and by
|
||||
providing a few methods and adding a new Vst::DataExchangeReceiverHandler member:
|
||||
|
||||
```c++
|
||||
class DataExchangeController : public Vst::EditControllerEx1,
|
||||
public Vst::IDataExchangeReceiver
|
||||
{
|
||||
public:
|
||||
// ...
|
||||
tresult PLUGIN_API notify (Vst::IMessage* message) override;
|
||||
void PLUGIN_API queueOpened (Vst::DataExchangeUserContextID userContextID,
|
||||
uint32 blockSize,
|
||||
TBool& dispatchOnBackgroundThread) override;
|
||||
void PLUGIN_API queueClosed (Vst::DataExchangeUserContextID userContextID) override;
|
||||
void PLUGIN_API onDataExchangeBlocksReceived (Vst::DataExchangeUserContextID userContextID,
|
||||
uint32 numBlocks,
|
||||
Vst::DataExchangeBlock* blocks,
|
||||
TBool onBackgroundThread) override;
|
||||
|
||||
DEFINE_INTERFACES
|
||||
DEF_INTERFACE (Vst::IDataExchangeReceiver)
|
||||
END_DEFINE_INTERFACES (EditController)
|
||||
DELEGATE_REFCOUNT (EditController)
|
||||
private:
|
||||
Vst::DataExchangeReceiverHandler dataExchange {this};
|
||||
}
|
||||
```
|
||||
|
||||
First we need to forward messages to the `DataExchangeReceiverHandler` so that it can process the
|
||||
data exchange messages when the host does not support the native API:
|
||||
|
||||
```c++
|
||||
tresult PLUGIN_API DataExchangeController::notify (Vst::IMessage* message)
|
||||
{
|
||||
if (dataExchange.onMessage (message))
|
||||
return kResultTrue;
|
||||
return EditControllerEx1::notify (message);
|
||||
}
|
||||
```
|
||||
|
||||
And next we can implement the `IDataExchangeReceiver` methods:
|
||||
|
||||
```c++
|
||||
void PLUGIN_API DataExchangeController::queueOpened (Vst::DataExchangeUserContextID userContextID,
|
||||
uint32 blockSize,
|
||||
TBool& dispatchOnBackgroundThread)
|
||||
{
|
||||
FDebugPrint ("Data Exchange Queue opened.\n");
|
||||
}
|
||||
|
||||
void PLUGIN_API DataExchangeController::queueClosed (Vst::DataExchangeUserContextID userContextID)
|
||||
{
|
||||
FDebugPrint ("Data Exchange Queue closed.\n");
|
||||
}
|
||||
|
||||
void PLUGIN_API DataExchangeController::onDataExchangeBlocksReceived (
|
||||
Vst::DataExchangeUserContextID userContextID, uint32 numBlocks, Vst::DataExchangeBlock* blocks,
|
||||
TBool onBackgroundThread)
|
||||
{
|
||||
for (auto index = 0u; index < numBlocks; ++index)
|
||||
{
|
||||
auto dataBlock = toDataBlock (blocks[index]);
|
||||
FDebugPrint (
|
||||
"Received Data Block: SampleRate: %d, SampleSize: %d, NumChannels: %d, NumSamples: %d\n",
|
||||
dataBlock->sampleRate, static_cast<uint32_t> (dataBlock->sampleSize),
|
||||
static_cast<uint32_t> (dataBlock->numChannels),
|
||||
static_cast<uint32_t> (dataBlock->numSamples));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `onDataExchangeBlocksReceived()` method will be called whenever the processor has send a block.
|
||||
You can now do whatever you want with the data.
|
||||
|
||||
If you want the data to be dispatched on a background thread you need to set the
|
||||
`dispatchOnBackgroundThread` variable to true in the `queueOpened` method.
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
#include <windows.h>
|
||||
#include "../source/version.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Version
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT
|
||||
PRODUCTVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x40004L
|
||||
FILETYPE 0x1L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040004e4"
|
||||
BEGIN
|
||||
VALUE "FileVersion", FULL_VERSION_STR"\0"
|
||||
VALUE "ProductVersion", FULL_VERSION_STR"\0"
|
||||
VALUE "OriginalFilename", stringOriginalFilename"\0"
|
||||
VALUE "FileDescription", stringFileDescription"\0"
|
||||
VALUE "InternalName", stringFileDescription"\0"
|
||||
VALUE "ProductName", stringFileDescription"\0"
|
||||
VALUE "CompanyName", stringCompanyName"\0"
|
||||
VALUE "LegalCopyright", stringLegalCopyright"\0"
|
||||
VALUE "LegalTrademarks", stringLegalTrademarks"\0"
|
||||
//VALUE "PrivateBuild", " \0"
|
||||
//VALUE "SpecialBuild", " \0"
|
||||
//VALUE "Comments", " \0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x400, 1252
|
||||
END
|
||||
END
|
||||
@@ -0,0 +1,18 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginterfaces/base/funknown.h"
|
||||
#include "pluginterfaces/vst/vsttypes.h"
|
||||
|
||||
namespace Steinberg::Tutorial {
|
||||
//------------------------------------------------------------------------
|
||||
static const Steinberg::FUID kDataExchangeProcessorUID (0xE35B96A9, 0x9BB353A9, 0x910130C1, 0x2C55EC26);
|
||||
static const Steinberg::FUID kDataExchangeControllerUID (0xADFD02D2, 0x20525504, 0x90D96683, 0xFD2DDF86);
|
||||
|
||||
#define DataExchangeVST3Category "Fx"
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace Steinberg::Tutorial
|
||||
@@ -0,0 +1,51 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#include "cids.h"
|
||||
#include "controller.h"
|
||||
|
||||
namespace Steinberg::Tutorial {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// DataExchangeController Implementation
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API DataExchangeController::notify (Vst::IMessage* message)
|
||||
{
|
||||
if (dataExchange.onMessage (message))
|
||||
return kResultTrue;
|
||||
return EditControllerEx1::notify (message);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void PLUGIN_API DataExchangeController::queueOpened (Vst::DataExchangeUserContextID userContextID,
|
||||
uint32 blockSize,
|
||||
TBool& dispatchOnBackgroundThread)
|
||||
{
|
||||
FDebugPrint ("Data Exchange Queue opened.\n");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void PLUGIN_API DataExchangeController::queueClosed (Vst::DataExchangeUserContextID userContextID)
|
||||
{
|
||||
FDebugPrint ("Data Exchange Queue closed.\n");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void PLUGIN_API DataExchangeController::onDataExchangeBlocksReceived (
|
||||
Vst::DataExchangeUserContextID userContextID, uint32 numBlocks, Vst::DataExchangeBlock* blocks,
|
||||
TBool onBackgroundThread)
|
||||
{
|
||||
for (auto index = 0u; index < numBlocks; ++index)
|
||||
{
|
||||
auto dataBlock = toDataBlock (blocks[index]);
|
||||
FDebugPrint (
|
||||
"Received Data Block: SampleRate: %d, SampleSize: %d, NumChannels: %d, NumSamples: %d\n",
|
||||
dataBlock->sampleRate, static_cast<uint32_t> (dataBlock->sampleSize),
|
||||
static_cast<uint32_t> (dataBlock->numChannels),
|
||||
static_cast<uint32_t> (dataBlock->numSamples));
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace Steinberg::Tutorial
|
||||
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dataexchange.h"
|
||||
#include "public.sdk/source/vst/vsteditcontroller.h"
|
||||
|
||||
namespace Steinberg::Tutorial {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// DataExchangeController
|
||||
//------------------------------------------------------------------------
|
||||
class DataExchangeController : public Vst::EditControllerEx1, public Vst::IDataExchangeReceiver
|
||||
{
|
||||
public:
|
||||
//------------------------------------------------------------------------
|
||||
// Create function
|
||||
static FUnknown* createInstance (void* /*context*/)
|
||||
{
|
||||
return (Vst::IEditController*)new DataExchangeController;
|
||||
}
|
||||
|
||||
// EditController
|
||||
tresult PLUGIN_API notify (Vst::IMessage* message) override;
|
||||
|
||||
// IDataExchangeReceiver
|
||||
void PLUGIN_API queueOpened (Vst::DataExchangeUserContextID userContextID, uint32 blockSize,
|
||||
TBool& dispatchOnBackgroundThread) override;
|
||||
void PLUGIN_API queueClosed (Vst::DataExchangeUserContextID userContextID) override;
|
||||
void PLUGIN_API onDataExchangeBlocksReceived (Vst::DataExchangeUserContextID userContextID,
|
||||
uint32 numBlocks, Vst::DataExchangeBlock* blocks,
|
||||
TBool onBackgroundThread) override;
|
||||
//---Interface---------
|
||||
DEFINE_INTERFACES
|
||||
// Here you can add more supported VST3 interfaces
|
||||
DEF_INTERFACE (Vst::IDataExchangeReceiver)
|
||||
END_DEFINE_INTERFACES (EditController)
|
||||
DELEGATE_REFCOUNT (EditController)
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
private:
|
||||
Vst::DataExchangeReceiverHandler dataExchange {this};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace Steinberg::Tutorial
|
||||
@@ -0,0 +1,32 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "public.sdk/source/vst/utility/dataexchange.h"
|
||||
#include <cstdint>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg::Tutorial {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct DataBlock
|
||||
{
|
||||
uint32_t sampleRate;
|
||||
uint16_t sampleSize;
|
||||
uint16_t numChannels;
|
||||
uint32_t numSamples;
|
||||
float samples[0];
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
inline DataBlock* toDataBlock (const Vst::DataExchangeBlock& block)
|
||||
{
|
||||
if (block.blockID != Vst::InvalidDataExchangeBlockID)
|
||||
return reinterpret_cast<DataBlock*> (block.data);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Steinberg::Tutorial
|
||||
@@ -0,0 +1,53 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#include "processor.h"
|
||||
#include "controller.h"
|
||||
#include "cids.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "public.sdk/source/main/pluginfactory.h"
|
||||
|
||||
#define stringPluginName "dataexchange-tutorial"
|
||||
|
||||
using namespace Steinberg::Vst;
|
||||
using namespace Steinberg::Tutorial;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// VST Plug-in Entry
|
||||
//------------------------------------------------------------------------
|
||||
// Windows: do not forget to include a .def file in your project to export
|
||||
// GetPluginFactory function!
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
BEGIN_FACTORY_DEF ("Steinberg Media Technologies",
|
||||
"https://steinberg.net",
|
||||
"mailto:info@steinberg.de")
|
||||
|
||||
//---First Plug-in included in this factory-------
|
||||
// its kVstAudioEffectClass component
|
||||
DEF_CLASS2 (INLINE_UID_FROM_FUID(kDataExchangeProcessorUID),
|
||||
PClassInfo::kManyInstances, // cardinality
|
||||
kVstAudioEffectClass, // the component category (do not changed this)
|
||||
stringPluginName, // here the Plug-in name (to be changed)
|
||||
Vst::kDistributable, // means that component and controller could be distributed on different computers
|
||||
DataExchangeVST3Category, // Subcategory for this Plug-in (to be changed)
|
||||
FULL_VERSION_STR, // Plug-in version (to be changed)
|
||||
kVstVersionString, // the VST 3 SDK version (do not changed this, use always this define)
|
||||
DataExchangeProcessor::createInstance) // function pointer called when this component should be instantiated
|
||||
|
||||
// its kVstComponentControllerClass component
|
||||
DEF_CLASS2 (INLINE_UID_FROM_FUID (kDataExchangeControllerUID),
|
||||
PClassInfo::kManyInstances, // cardinality
|
||||
kVstComponentControllerClass,// the Controller category (do not changed this)
|
||||
stringPluginName "Controller", // controller name (could be the same than component name)
|
||||
0, // not used here
|
||||
"", // not used here
|
||||
FULL_VERSION_STR, // Plug-in version (to be changed)
|
||||
kVstVersionString, // the VST 3 SDK version (do not changed this, use always this define)
|
||||
DataExchangeController::createInstance)// function pointer called when this component should be instantiated
|
||||
|
||||
//----for others Plug-ins contained in this factory, put like for the first Plug-in different DEF_CLASS2---
|
||||
|
||||
END_FACTORY
|
||||
@@ -0,0 +1,168 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#include "cids.h"
|
||||
#include "processor.h"
|
||||
|
||||
#include "base/source/fstreamer.h"
|
||||
#include "pluginterfaces/vst/ivstparameterchanges.h"
|
||||
|
||||
namespace Steinberg::Tutorial {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static constexpr Vst::DataExchangeBlock kInvalidDataExchangeBlock = {
|
||||
nullptr, 0, Vst::InvalidDataExchangeBlockID};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// DataExchangeProcessor
|
||||
//------------------------------------------------------------------------
|
||||
DataExchangeProcessor::DataExchangeProcessor ()
|
||||
{
|
||||
setControllerClass (kDataExchangeControllerUID);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
DataExchangeProcessor::~DataExchangeProcessor () = default;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API DataExchangeProcessor::initialize (FUnknown* context)
|
||||
{
|
||||
tresult result = AudioEffect::initialize (context);
|
||||
if (result != kResultOk)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
addAudioInput (STR16 ("Stereo In"), Steinberg::Vst::SpeakerArr::kStereo);
|
||||
addAudioOutput (STR16 ("Stereo Out"), Steinberg::Vst::SpeakerArr::kStereo);
|
||||
|
||||
return kResultOk;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API DataExchangeProcessor::connect (Vst::IConnectionPoint* other)
|
||||
{
|
||||
auto result = Vst::AudioEffect::connect (other);
|
||||
if (result == kResultTrue)
|
||||
{
|
||||
auto configCallback = [this] (Vst::DataExchangeHandler::Config& config,
|
||||
const Vst::ProcessSetup& setup) {
|
||||
Vst::SpeakerArrangement arr;
|
||||
getBusArrangement (Vst::BusDirections::kInput, 0, arr);
|
||||
numChannels = static_cast<uint16_t> (Vst::SpeakerArr::getChannelCount (arr));
|
||||
auto sampleSize = sizeof (float);
|
||||
|
||||
config.blockSize = setup.sampleRate * numChannels * sampleSize + sizeof (DataBlock);
|
||||
config.numBlocks = 2;
|
||||
config.alignment = 32;
|
||||
config.userContextID = 0;
|
||||
return true;
|
||||
};
|
||||
|
||||
dataExchange = std::make_unique<Vst::DataExchangeHandler> (this, configCallback);
|
||||
dataExchange->onConnect (other, getHostContext ());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API DataExchangeProcessor::disconnect (Vst::IConnectionPoint* other)
|
||||
{
|
||||
if (dataExchange)
|
||||
{
|
||||
dataExchange->onDisconnect (other);
|
||||
dataExchange.reset ();
|
||||
}
|
||||
return AudioEffect::disconnect (other);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API DataExchangeProcessor::setActive (TBool state)
|
||||
{
|
||||
if (state)
|
||||
dataExchange->onActivate (processSetup);
|
||||
else
|
||||
{
|
||||
dataExchange->onDeactivate ();
|
||||
currentExchangeBlock = kInvalidDataExchangeBlock;
|
||||
}
|
||||
|
||||
return AudioEffect::setActive (state);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API DataExchangeProcessor::canProcessSampleSize (int32 symbolicSampleSize)
|
||||
{
|
||||
if (symbolicSampleSize == Vst::kSample32)
|
||||
return kResultTrue;
|
||||
return kResultFalse;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void DataExchangeProcessor::acquireNewExchangeBlock ()
|
||||
{
|
||||
currentExchangeBlock = dataExchange->getCurrentOrNewBlock ();
|
||||
if (auto block = toDataBlock (currentExchangeBlock))
|
||||
{
|
||||
block->sampleRate = static_cast<uint32_t> (processSetup.sampleRate);
|
||||
block->numChannels = numChannels;
|
||||
block->sampleSize = sizeof (float);
|
||||
block->numSamples = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
tresult PLUGIN_API DataExchangeProcessor::process (Vst::ProcessData& processData)
|
||||
{
|
||||
if (processData.numSamples <= 0)
|
||||
return kResultTrue;
|
||||
|
||||
if (currentExchangeBlock.blockID == Vst::InvalidDataExchangeBlockID)
|
||||
acquireNewExchangeBlock ();
|
||||
|
||||
auto input = processData.inputs[0];
|
||||
auto output = processData.outputs[0];
|
||||
|
||||
if (auto block = toDataBlock (currentExchangeBlock))
|
||||
{
|
||||
auto numSamples = static_cast<uint32> (processData.numSamples);
|
||||
while (numSamples > 0)
|
||||
{
|
||||
uint32 numSamplesFreeInBlock = block->sampleRate - block->numSamples;
|
||||
uint32 numSamplesToCopy = std::min<uint32> (numSamplesFreeInBlock, numSamples);
|
||||
for (auto channel = 0; channel < input.numChannels; ++channel)
|
||||
{
|
||||
const auto channelOffset = channel * block->sampleRate;
|
||||
auto blockChannelData = &block->samples[0] + block->numSamples + channelOffset;
|
||||
auto inputChannel =
|
||||
input.channelBuffers32[channel] + (processData.numSamples - numSamples);
|
||||
memcpy (blockChannelData, inputChannel, numSamplesToCopy * sizeof (float));
|
||||
}
|
||||
block->numSamples += numSamplesToCopy;
|
||||
if (block->numSamples == block->sampleRate)
|
||||
{
|
||||
dataExchange->sendCurrentBlock ();
|
||||
acquireNewExchangeBlock ();
|
||||
block = toDataBlock (currentExchangeBlock);
|
||||
if (block == nullptr)
|
||||
break;
|
||||
}
|
||||
numSamples -= numSamplesToCopy;
|
||||
}
|
||||
}
|
||||
for (auto channel = 0; channel < input.numChannels; ++channel)
|
||||
{
|
||||
if (output.channelBuffers32[channel] != input.channelBuffers32[channel])
|
||||
{
|
||||
memcpy (output.channelBuffers32[channel], input.channelBuffers32[channel],
|
||||
processData.numSamples * sizeof (float));
|
||||
}
|
||||
output.silenceFlags = input.silenceFlags;
|
||||
}
|
||||
|
||||
return kResultOk;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace Steinberg::Tutorial
|
||||
@@ -0,0 +1,46 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dataexchange.h"
|
||||
#include "public.sdk/source/vst/vstaudioeffect.h"
|
||||
|
||||
namespace Steinberg::Tutorial {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
static constexpr Vst::DataExchangeBlock InvalidDataExchangeBlock = {
|
||||
nullptr, 0, Vst::InvalidDataExchangeBlockID};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// DataExchangeProcessor
|
||||
//------------------------------------------------------------------------
|
||||
class DataExchangeProcessor : public Vst::AudioEffect
|
||||
{
|
||||
public:
|
||||
DataExchangeProcessor ();
|
||||
~DataExchangeProcessor () override;
|
||||
|
||||
static FUnknown* createInstance (void* /*context*/)
|
||||
{
|
||||
return (Vst::IAudioProcessor*)new DataExchangeProcessor;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API initialize (FUnknown* context) override;
|
||||
tresult PLUGIN_API connect (Vst::IConnectionPoint* other) override;
|
||||
tresult PLUGIN_API disconnect (Vst::IConnectionPoint* other) override;
|
||||
tresult PLUGIN_API setActive (TBool state) override;
|
||||
tresult PLUGIN_API canProcessSampleSize (int32 symbolicSampleSize) override;
|
||||
tresult PLUGIN_API process (Vst::ProcessData& data) override;
|
||||
//------------------------------------------------------------------------
|
||||
protected:
|
||||
void acquireNewExchangeBlock ();
|
||||
|
||||
std::unique_ptr<Vst::DataExchangeHandler> dataExchange;
|
||||
Vst::DataExchangeBlock currentExchangeBlock {InvalidDataExchangeBlock};
|
||||
uint16_t numChannels {0};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // namespace Steinberg::Tutorial
|
||||
@@ -0,0 +1,20 @@
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright(c) 2023 Steinberg Media Technologies.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pluginterfaces/base/fplatform.h"
|
||||
|
||||
// Plain project version file generated by cmake
|
||||
#include "projectversion.h"
|
||||
|
||||
#define stringOriginalFilename "dataexchange-tutorial.vst3"
|
||||
#if SMTG_PLATFORM_64
|
||||
#define stringFileDescription "dataexchange-tutorial VST3 (64Bit)"
|
||||
#else
|
||||
#define stringFileDescription "dataexchange-tutorial VST3"
|
||||
#endif
|
||||
#define stringCompanyName "Steinberg Media Technologies\0"
|
||||
#define stringLegalCopyright "Copyright(c) 2023 Steinberg Media Technologies."
|
||||
#define stringLegalTrademarks "VST is a trademark of Steinberg Media Technologies GmbH"
|
||||
Reference in New Issue
Block a user