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,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.
@@ -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"