Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"