VST Plugin Host: Loadet Plugins wont add the effect to the output buffer

Hello Folks,

I have a weird problem with my VST host:

After loading a plugin with my host script, audio data is copied into IAudioClient::Buffers buffers.inputs[0] and buffers.inputs[1] in my audio callback.

After calling AudioClient::process(Buffers& buffers, int64_t continuousFrames), which under the hood calls IAudioProcessor processor->process(processData), the audio data in buffers.outputs[0] and buffers.outputs[1] after processing is an exact copy of the input buffer arrays.

In other words, the loaded plugin processes the audio data (original audio hearable) but does not actually apply the effect (e.g., reverb) to the audio output buffer arrays.

I am doing the exact same initialization and process calls as the “audiohost” example from the VST SDK. The only difference in my host implementation is that I assign my own buffers to IAudioClient::Buffers buffers instead of using the JACK interface and buffers.

Troubleshooting so far:

  • I checked if my buffer pointers are set correctly.
  • There are no errors during initialization.
  • This issue happens with any loaded plugin.
  • I am very confident that the plugin is not in bypass mode, because when using the “audiohost” example with JACK, the effect is applied correctly and hearable. Since my initialization is the same as in the example, no parameters should be different.

Can this be related to when the callback is slightly unstable (without dropouts) in timing?
Does anyone have an idea what might be going wrong?

Thanks!

Here is the code:


Note: I don’t use the AudioClient and AudioHost classes from the example anymore; rather, I use a “stand-alone” implementation. There was too much going on in the example host, and it didn’t help in identifying the problem. This implementation makes it much clearer to see what’s going on.

However, the results are the same here too. The plugin doesn’t add the effect—it’s only passing a copy of the input buffer.


VstHost class header:


class VstHost {
public:
	VstHost();
	~VstHost();

	bool init(const std::string& path, int sampleRate, int maxBlockSize, int symbolicSampleSize, bool realtime);
	void destroy();

	Steinberg::Vst::ProcessContext* processContext();
	void setProcessing(bool processing);
	bool process(int numSamples);

	const Steinberg::Vst::BusInfo* busInfo(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection direction, int which);
	int numBuses(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection direction);
	void setBusActive(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection direction, int which, bool active);

	Steinberg::Vst::Sample32* channelBuffer32(Steinberg::Vst::BusDirection direction, int which);
	Steinberg::Vst::Sample64* channelBuffer64(Steinberg::Vst::BusDirection direction, int which);

	Steinberg::Vst::EventList* eventList(Steinberg::Vst::BusDirection direction, int which);
	Steinberg::Vst::ParameterChanges* parameterChanges(Steinberg::Vst::BusDirection direction, int which);


	const std::string& name();

private:
	void _destroy(bool decrementRefCount);

	void _printDebug(const std::string& info);
	void _printError(const std::string& error);

	std::vector _inAudioBusInfos, _outAudioBusInfos;
	int _numInAudioBuses = 0, _numOutAudioBuses = 0;

	std::vector _inEventBusInfos, _outEventBusInfos;
	int _numInEventBuses = 0, _numOutEventBuses = 0;

	std::vector _inSpeakerArrs, _outSpeakerArrs;

	VST3::Hosting::Module::Ptr _module = nullptr;
	Steinberg::IPtr _plugProvider = nullptr;

	Steinberg::IPtr _vstPlug = nullptr;
	Steinberg::IPtr _audioEffect = nullptr;
	Steinberg::IPtr _editController = nullptr;
	Steinberg::Vst::HostProcessData _processData = {};
	Steinberg::Vst::ProcessSetup _processSetup = {};
	Steinberg::Vst::ProcessContext _processContext = {};

	Steinberg::IPtr _view = nullptr;

	int _sampleRate = 0, _maxBlockSize = 0, _symbolicSampleSize = 0;
	bool _realtime = false;

	std::string _path;
	std::string _name;

	static Steinberg::Vst::HostApplication* _standardPluginContext;
	static int _standardPluginContextRefCount;
};

For buffer communication between other programms used in main():
#pragma pack(push, 1)
struct VstStream {
	int needOutput;
	int outputReady;
	float inputBuffer[128]; // interleaved
	float outputBuffer[128]; // interleaved
};
#pragma pack(pop)
VstStream* connectSharedMemory() {
	HANDLE hFile = CreateFile(L"Z:\\tmp\\shared_memory", GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (hFile == INVALID_HANDLE_VALUE) {
		std::cerr << "Could not open shared file. Error: " << GetLastError() << "\n";
		return nullptr;
	}

	HANDLE hMapFile = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
	if (hMapFile == NULL) {
		std::cerr << "Could not create file mapping. Error: " << GetLastError() << "\n";
		CloseHandle(hFile);
		return nullptr;
	}

	struct VstStream* vstStream = (struct VstStream*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
	if (!vstStream) {
		std::cerr << "Could not map view of file. Error: " << GetLastError() << "\n";
		CloseHandle(hMapFile);
		CloseHandle(hFile);
		return nullptr;
	}

         std::cout << "Connected to shared file successfully!\n";

	return vstStream;
}

The VST host implementation :
Steinberg::Vst::HostApplication* VstHost::_standardPluginContext = nullptr;
int VstHost::_standardPluginContextRefCount = 0;

using namespace Steinberg;
using namespace Steinberg::Vst;
VstHost::VstHost()
{}

VstHost::~VstHost()
{
	destroy();
}

bool VstHost::init(const std::string& path, int sampleRate, int maxBlockSize, int symbolicSampleSize, bool realtime)
{
	_destroy(false);

	++_standardPluginContextRefCount;
	if (!_standardPluginContext) {
		_standardPluginContext = owned(new HostApplication());
		PluginContextFactory::instance().setPluginContext(_standardPluginContext);
	}

	_path = path;
	_sampleRate = sampleRate;
	_maxBlockSize = maxBlockSize;
	_symbolicSampleSize = symbolicSampleSize;
	_realtime = realtime;

	_processSetup.processMode = realtime;
	_processSetup.symbolicSampleSize = symbolicSampleSize;
	_processSetup.sampleRate = _sampleRate;
	_processSetup.maxSamplesPerBlock = _maxBlockSize;

	_processData.numSamples = 0;
	_processData.symbolicSampleSize = _symbolicSampleSize;
	_processData.processContext = &_processContext;

	std::string error;
	_module = VST3::Hosting::Module::create(path, error);
	if (!_module) {
		_printError(error);
		return false;
	}

	VST3::Hosting::PluginFactory factory = _module->getFactory();
	for (auto& classInfo : factory.classInfos())
	{
		if (classInfo.category() == kVstAudioEffectClass)
		{
			_plugProvider = owned(new PlugProvider(factory, classInfo, true));
			_name = classInfo.name();
			break;
		}
	}
	if (!_plugProvider) {
		_printError("No PlugProvider found");
		return false;
	}

	_vstPlug = _plugProvider->getComponent();

	_audioEffect = FUnknownPtr(_vstPlug);
	if (!_audioEffect) {
		_printError("Could not get audio processor from VST");
		return false;
	}

	_editController = _plugProvider->getController();

	FUnknownPtr contextRequirements(_audioEffect);
	if (contextRequirements) {
		auto flags = contextRequirements->getProcessContextRequirements();

#define PRINT_FLAG(x) if (flags & IProcessContextRequirements::Flags::x) { _printDebug(#x); }
		PRINT_FLAG(kNeedSystemTime)
			PRINT_FLAG(kNeedContinousTimeSamples)
			PRINT_FLAG(kNeedProjectTimeMusic)
			PRINT_FLAG(kNeedBarPositionMusic)
			PRINT_FLAG(kNeedCycleMusic)
			PRINT_FLAG(kNeedSamplesToNextClock)
			PRINT_FLAG(kNeedTempo)
			PRINT_FLAG(kNeedTimeSignature)
			PRINT_FLAG(kNeedChord)
			PRINT_FLAG(kNeedFrameRate)
			PRINT_FLAG(kNeedTransportState)
#undef PRINT_FLAG
	}

	_numInAudioBuses = _vstPlug->getBusCount(MediaTypes::kAudio, BusDirections::kInput);
	_numOutAudioBuses = _vstPlug->getBusCount(MediaTypes::kAudio, BusDirections::kOutput);
	_numInEventBuses = _vstPlug->getBusCount(MediaTypes::kEvent, BusDirections::kInput);
	_numOutEventBuses = _vstPlug->getBusCount(MediaTypes::kEvent, BusDirections::kOutput);

	std::ostringstream debugOss;
	debugOss << "Buses: " << _numInAudioBuses << " audio and " << _numInEventBuses << " event inputs; ";
	debugOss << _numOutAudioBuses << " audio and " << _numOutEventBuses <getBusInfo(kAudio, kInput, i, info);
		_inAudioBusInfos.push_back(info);
		setBusActive(kAudio, kInput, i, true);

		SpeakerArrangement speakerArr;
		_audioEffect->getBusArrangement(kInput, i, speakerArr);
		_inSpeakerArrs.push_back(speakerArr);
	}

	for (int i = 0; i < _numInEventBuses; ++i) {
		BusInfo info;
		_vstPlug->getBusInfo(kEvent, kInput, i, info);
		_inEventBusInfos.push_back(info);
		setBusActive(kEvent, kInput, i, false);
	}

	for (int i = 0; i < _numOutAudioBuses; ++i) {
		BusInfo info;
		_vstPlug->getBusInfo(kAudio, kOutput, i, info);
		_outAudioBusInfos.push_back(info);
		setBusActive(kAudio, kOutput, i, true);

		SpeakerArrangement speakerArr;
		_audioEffect->getBusArrangement(kOutput, i, speakerArr);
		_outSpeakerArrs.push_back(speakerArr);
	}

	for (int i = 0; i < _numOutEventBuses; ++i) {
		BusInfo info;
		_vstPlug->getBusInfo(kEvent, kOutput, i, info);
		_outEventBusInfos.push_back(info);
		setBusActive(kEvent, kOutput, i, false);
	}

	tresult res = _audioEffect->setBusArrangements(_inSpeakerArrs.data(), _numInAudioBuses, _outSpeakerArrs.data(), _numOutAudioBuses);
	if (res != kResultTrue) {
		_printError("Failed to set bus arrangements");
		return false;
	}

	res = _audioEffect->setupProcessing(_processSetup);
	if (res == kResultOk) {
		_processData.prepare(*_vstPlug, _maxBlockSize, _processSetup.symbolicSampleSize);
		if (_numInEventBuses > 0) {
			_processData.inputEvents = new EventList[_numInEventBuses];
		}
		if (_numOutEventBuses > 0) {
			_processData.outputEvents = new EventList[_numOutEventBuses];
		}
	}
	else {
		_printError("Failed to setup VST processing");
		return false;
	}

	if (_vstPlug->setActive(true) != kResultTrue) {
		_printError("Failed to activate VST component");
		return false;
	}

	return true;
}

void VstHost::destroy()
{
	_destroy(true);
}

bool VstHost::process(int numSamples)
{
	if (numSamples > _maxBlockSize) {
#ifdef _DEBUG
		_printError("numSamples > _maxBlockSize");
#endif
		numSamples = _maxBlockSize;
	}

	_processData.numSamples = numSamples;
	tresult result = _audioEffect->process(_processData);
	if (result != kResultOk) {
#ifdef _DEBUG
		std::cerr << "VST process failed" <activateBus(type, direction, which, active);
}

void VstHost::setProcessing(bool processing)
{
	_audioEffect->setProcessing(processing);
}

Steinberg::Vst::ProcessContext* VstHost::processContext()
{
	return &_processContext;
}

Steinberg::Vst::Sample32* VstHost::channelBuffer32(BusDirection direction, int which)
{
	if (direction == kInput) {
		return _processData.inputs->channelBuffers32[which];
	}
	else if (direction == kOutput) {
		return _processData.outputs->channelBuffers32[which];
	}
	else {
		return nullptr;
	}
}

Steinberg::Vst::Sample64* VstHost::channelBuffer64(BusDirection direction, int which)
{
	if (direction == kInput) {
		return _processData.inputs->channelBuffers64[which];
	}
	else if (direction == kOutput) {
		return _processData.outputs->channelBuffers64[which];
	}
	else {
		return nullptr;
	}
}

Steinberg::Vst::EventList* VstHost::eventList(Steinberg::Vst::BusDirection direction, int which)
{
	if (direction == kInput) {
		return static_cast(&_processData.inputEvents[which]);
	}
	else if (direction == kOutput) {
		return static_cast(&_processData.outputEvents[which]);
	}
	else {
		return nullptr;
	}
}

Steinberg::Vst::ParameterChanges* VstHost::parameterChanges(Steinberg::Vst::BusDirection direction, int which)
{
	if (direction == kInput) {
		return static_cast(&_processData.inputParameterChanges[which]);
	}
	else if (direction == kOutput) {
		return static_cast(&_processData.outputParameterChanges[which]);
	}
	else {
		return nullptr;
	}
}


const std::string& VstHost::name()
{
	return _name;
}

void VstHost::_destroy(bool decrementRefCount)
{
	

	_editController = nullptr;
	_audioEffect = nullptr;
	_vstPlug = nullptr;
	_plugProvider = nullptr;
	_module = nullptr;

	_inAudioBusInfos.clear();
	_outAudioBusInfos.clear();
	_numInAudioBuses = 0;
	_numOutAudioBuses = 0;

	_inEventBusInfos.clear();
	_outEventBusInfos.clear();
	_numInEventBuses = 0;
	_numOutEventBuses = 0;

	_inSpeakerArrs.clear();
	_outSpeakerArrs.clear();

	if (_processData.inputEvents) {
		delete[] static_cast(_processData.inputEvents);
	}
	if (_processData.outputEvents) {
		delete[] static_cast(_processData.outputEvents);
	}
	_processData.unprepare();
	_processData = {};

	_processSetup = {};
	_processContext = {};

	_sampleRate = 0;
	_maxBlockSize = 0;
	_symbolicSampleSize = 0;
	_realtime = false;

	_path = "";
	_name = "";

	if (decrementRefCount) {
		if (_standardPluginContextRefCount > 0) {
			--_standardPluginContextRefCount;
		}
		if (_standardPluginContext && _standardPluginContextRefCount == 0) {
			PluginContextFactory::instance().setPluginContext(nullptr);
			_standardPluginContext->release();
			delete _standardPluginContext;
			_standardPluginContext = nullptr;
		}
	}
}

void VstHost::_printDebug(const std::string& info)
{
	std::cout << "Debug info for VST3 plugin \"" << _path << "\": " << info << std::endl;
}

void VstHost::_printError(const std::string& error)
{
	std::cerr << "Error loading VST3 plugin \"" << _path << "\": " << error << std::endl;
}

Initialising and starting processing:
int main()
{
	std::string pathToPlugin = "Z:\\home\\patch\\ValhallaVintageVerb.vst3";

	int sampleRate = 48000;
	int maxBlockSize = 64; // Stands also for block size

	VstHost* vst = new VstHost();
	vst->init(pathToPlugin, sampleRate, maxBlockSize, kSample32, kRealtime);

	VstStream* vstStream = connectSharedMemory();

	Sample32* inL = vst->channelBuffer32(kInput, 0);
	Sample32* inR = vst->channelBuffer32(kInput, 1);

	Sample32* outL = vst->channelBuffer32(kOutput, 0);
	Sample32* outR = vst->channelBuffer32(kOutput, 1);


	vst->setProcessing(true);
	while (true) {

		// TODO -> implement better synchronization (this still works without dropouts)
		while (true) {
			std::this_thread::sleep_for(std::chrono::microseconds(100));
			if (vstStream->needOutput) {
				vstStream->needOutput = 0;
				break;
			}
		}

		for (unsigned int i = 0; i < 64; i++) {
			inL[i] = vstStream->inputBuffer[i * 2];
			inR[i] = vstStream->inputBuffer[i * 2 + 1];
		}

		vst->process(64);

		for (unsigned int i = 0; i < 64; i++) {
			vstStream->outputBuffer[i * 2] = outL[i];
			vstStream->outputBuffer[i * 2 + 1] = outR[i];
		}

		vstStream->outputReady = 1;
	}
	return 0;
}

did you try with VST3 plug-ins example from the VSTSDK ( VST 3 Plug-in Examples - VST 3 Developer Portal) or other plug-ins?

1 Like

Thank you Yvan for this hint.

I didnt try them but i have updates:

I downloaded more Plugins and testet them and it turned out that many of them work, i can hear the effect. Knowing this now is worth alot since i know that this implementation for processing the effect can work.

However the Valhalla Vintage Verb plugin has still this problem for not adding the effect. This is a good example since it works when loading it with the host example from the VST SDK.

My thoughts are now that this can be related to the Licencing mechanism of the plugin. I think the plugin is in a state where its asking the user to point to the licencing file and in this state its not processing (only bypass).
Since i dont display a gui i cant see if this is true.
But why is it working with the host example from the VST SDK, it also doesnt show the gui but immediately processes with the effect after loading.

Could this problem be realated to this? Is there a possibility that the plugin checks something in the operating System? For example if its not in the same system as it was installed originally? I made a copy of the .vst3 file to a different location and opened it with the Editor host example and the gui didnt show any licencing popups.

Im very curius why its processing the effect in the host example.