I’m writing a minimal host for VST3 plugins.
I’m doing initialization following the VST3 host workflow:
https://steinbergmedia.github.io/vst3_doc/vstinterfaces/workflow.html
However, while LABS loads and processes, Helm (based on JUCE) crashes at the following line of code.
vst_plugin->processor->setProcessing(true)
As far as I know I’ve followed the bare basics of what VST3 requires me to provide to the plugin, is there anything I’m missing, or does helm require more than just this?
The full code of my VST3 host is below. This is compiled to a DLL and loaded from another host program that provides input/output buffers and copies the result to device output. process
is called from a separate processing thread.
#include <stdio.h>
#include <stdexcept>
#include <memory>
#include <vst/hosting/module.h>
#include <vst/hosting/plugprovider.h>
#include <vst/hosting/hostclasses.h>
#include <pluginterfaces/base/funknown.h>
#include <pluginterfaces/vst/ivstcomponent.h>
#include <pluginterfaces/vst/ivstaudioprocessor.h>
extern "C" {
typedef struct RdPlugin RdPlugin;
typedef struct {
RdPlugin* (*create)(double_t sample_rate);
void (*destroy)(RdPlugin *plugin);
void (*process)(RdPlugin *plugin, float_t **inputs, float_t **outputs);
} RdPluginInterface;
}
namespace Steinberg {
Steinberg::FUnknown *gStandardPluginContext = nullptr;
}
struct VstPlugin {
Steinberg::IPtr<Steinberg::Vst::IComponent> component;
Steinberg::IPtr<Steinberg::Vst::IAudioProcessor> processor;
bool processing_is_set = false;
};
RdPlugin* create(double_t sample_rate) {
// TODO: This currently has various memory leaks in error conditions
Steinberg::gStandardPluginContext = new Steinberg::Vst::HostApplication();
auto plugin = new VstPlugin();
std::string err;
auto module = VST3::Hosting::Module::create("C:\\Program Files\\Common Files\\VST3\\Helm\\helm64.vst3", err);
if (!module) {
printf("Failed to load VST module: %s\n", err.c_str());
return nullptr;
}
printf("Loaded VST module\n");
auto factory = module->getFactory();
for (auto &class_info : factory.classInfos())
{
if (class_info.category() == kVstAudioEffectClass)
{
printf("Found processor: %s\n", class_info.name().c_str());
plugin->component = factory.createInstance<Steinberg::Vst::IComponent>(class_info.ID());
break;
}
}
if (!plugin->component) {
printf("Couldn't initialize component\n");
return nullptr;
}
printf("Created component\n");
plugin->component->initialize(Steinberg::gStandardPluginContext);
printf("Initialized component\n");
Steinberg::Vst::IAudioProcessor *processor_ptr = nullptr;
if (plugin->component->queryInterface(Steinberg::Vst::IAudioProcessor::iid, (void **)&processor_ptr) != Steinberg::kResultOk
|| !processor_ptr) {
printf("Component does not implement IAudioProcessor interface\n");
return nullptr;
}
plugin->processor = Steinberg::shared(processor_ptr);
printf("Retrieved IAudioProcessor\n");
Steinberg::Vst::ProcessSetup setup; // { kRealtime, kSample32, blockSize, sampleRate };
setup.processMode = Steinberg::Vst::kRealtime;
setup.symbolicSampleSize = Steinberg::Vst::kSample32;
setup.maxSamplesPerBlock = 512;
setup.sampleRate = sample_rate;
if (plugin->processor->setupProcessing(setup) != Steinberg::kResultOk) {
printf("Failed to setup processing");
return nullptr;
}
if (plugin->component->setActive(true) != Steinberg::kResultOk) {
printf("Failed to set component active");
return nullptr;
}
plugin->processing_is_set = false;
return (RdPlugin*)plugin;
}
void destroy(RdPlugin *plugin) {
VstPlugin *vst_plugin = (VstPlugin*)plugin;
printf("Cleaning up\n");
delete plugin;
delete Steinberg::gStandardPluginContext;
}
void process(RdPlugin *plugin, float_t **inputs, float_t **outputs) {
VstPlugin *vst_plugin = (VstPlugin*)plugin;
printf("1\n");
if (!vst_plugin->processing_is_set) {
printf("Setting processing to true\n");
if (vst_plugin->processor->setProcessing(true) != Steinberg::kResultOk) {
printf("Failed to set processing to true\n");
return;
}
vst_plugin->processing_is_set = true;
}
printf("2\n");
Steinberg::Vst::ProcessData data;
data.processMode = Steinberg::Vst::ProcessModes::kRealtime;
data.symbolicSampleSize = Steinberg::Vst::kSample32;
data.numSamples = 512;
data.numInputs = 1;
data.numOutputs = 1;
printf("3\n");
Steinberg::Vst::AudioBusBuffers input;
input.numChannels = 2;
input.silenceFlags = 0;
input.channelBuffers32 = (Steinberg::Vst::Sample32 **)inputs;
data.inputs = &input;
Steinberg::Vst::AudioBusBuffers output;
output.numChannels = 2;
output.silenceFlags = 0;
output.channelBuffers32 = (Steinberg::Vst::Sample32 **)outputs;
data.outputs = &output;
printf("4\n");
vst_plugin->processor->process(data);
printf("5\n");
}
#define DLL_EXPORT __declspec(dllexport)
extern "C" {
DLL_EXPORT bool rd_plugin_get_interface(RdPluginInterface *interface) {
interface->create = create;
interface->destroy = destroy;
interface->process = process;
return true;
}
}