SOUL inside of a VST3

Hello,

I’m trying to set up SOUL inside of a VST plugin template, I tried to call processBlock of the loaded plugin instance compiled from .soulpatch file, but it does not seem to change the buffer at all. It feels like the Plugin wrapper is one level more than I need, really I just want to parse the .soul files and play them back inside a processor.

Pseudocode below. Is this possible at all and if so - what I’m missing? An example would be very much appreciated

void DemoAudioProcessor::processBlock (AudioBuffer<float> &buffer, MidiBuffer &midiMessages)
{
	AudioPluginInstance * i = currentPlugin.get();
	i->processBlock(buffer, midiMessages);
}

Actually one of my next tasks is to build a generic VST wrapper that’ll do this, so you might want to wait for that… But yes, essentially just calling straight through to the methods of the soul AudioPluginInstance object is the general idea.

Sounds good. I would like to finish the plugin by new year, the only thing remaining is wiring it up to soul code I made, so I’m stuck.

If you could show a quick code example of how such a PluginInstance and call through would work that would be fantastic.

I tried many things but either I loose sound completely or buffer is not changed at all. Thanks

It’s impossible to guess what you might have done wrong, there are hundreds of possibilities! I’m aiming to do this as my next task though, so (no promises) should be able to push some code within a week or two.

1 Like

It might be that there’s an issue with AST parsing, any .soulpatch loaded into my program (I’m using examples on soul.dev) consistently errors out at runtime with the following two errors:

Exception thrown at 0x00007FFB2B945A29 in Plugin.exe: Microsoft C++ exception: `private: soul::pool_ptr<soul::AST::Expression> __cdecl soul::StructuralParser::tryToParseExpressionIgnoringErrors(void) __ptr64'::`2'::FailedParse at memory location 0x0000000903FFC560.
Exception thrown at 0x00007FFB2B945A29 in Plugin.exe: Microsoft C++ exception: `private: soul::pool_ptr<soul::AST::Expression> __cdecl soul::StructuralParser::tryToParseExpressionIgnoringErrors(void) __ptr64'::`2'::FailedParse at memory location 0x0000000903FFC240.

Hmm, that’s not actually an error - the FailedParse class is just something we throw internally as part of parsing. Maybe you’ve just got your debugger set up to stop at all exceptions, including ones that aren’t actually a problem?

Probably it’s nothing. I use the default debugging settings so it does not trigger a breakpoint, just reports an error in the log output of Visual Studio 2019.

I feel It must be something very simple I’m missing, maybe related to how I instantiate the plugin or maybe a missing setup call - this is how I’m instantiating the plugin into the audioProcessorGraph, is there anything here that catches your eye?

void SaltWaveAudioProcessor::loadPlugin(std::unique_ptr<juce::AudioPluginInstance> newPlugin, const juce::String& error) {
	currentPlugin = std::move(newPlugin);
}

void SaltWaveAudioProcessor::initializeGraph(double sampleRate, int samplesPerBlock) {
	graph->setPlayConfigDetails(getTotalNumInputChannels(), getTotalNumOutputChannels(), sampleRate, samplesPerBlock);
	graph->setProcessingPrecision(AudioProcessor::singlePrecision);
	graph->prepareToPlay(sampleRate, samplesPerBlock);
	audioInputNode = graph->addNode(std::make_unique<AudioGraphIOProcessor>(AudioGraphIOProcessor::audioInputNode), NodeID(1));
	audioOutputNode = graph->addNode(std::make_unique<AudioGraphIOProcessor>(AudioGraphIOProcessor::audioOutputNode), NodeID(2));
	midiInputNode = graph->addNode(std::make_unique<AudioGraphIOProcessor>(AudioGraphIOProcessor::midiInputNode), NodeID(3));
	midiOutputNode = graph->addNode(std::make_unique<AudioGraphIOProcessor>(AudioGraphIOProcessor::midiOutputNode), NodeID(4));
	// is this the right way to add a AudioPluginInstance to the graph?
	pluginNode = graph->addNode(std::move(currentPlugin), NodeID(100));
	pluginNode->getProcessor()->setPlayConfigDetails(getTotalNumInputChannels(), getTotalNumOutputChannels(), sampleRate, samplesPerBlock);
	connectAudioNodes();
	connectMidiNodes();
	for (auto node : graph->getNodes()) {
		node->getProcessor()->enableAllBuses();
	}
}

void SaltWaveAudioProcessor::connectAudioNodes()
{
	for (int channel = 0; channel < 2; ++channel) {
		graph->addConnection({{ audioInputNode->nodeID,  channel }, { pluginNode->nodeID, channel }});
		graph->addConnection({{ pluginNode->nodeID, channel }, { audioOutputNode->nodeID, channel }});
	}
}

void SaltWaveAudioProcessor::connectMidiNodes()
{
	graph->addConnection({ { midiInputNode->nodeID,  AudioProcessorGraph::midiChannelIndex },
						   { midiOutputNode->nodeID, AudioProcessorGraph::midiChannelIndex } }); }

void SaltWaveAudioProcessor::processBlock (AudioBuffer<float> &buffer, MidiBuffer &midiMessages)
{
	graph->processBlock(buffer, midiMessages);
	if(buffer.getNumChannels() > 0) {
		lcPeak.store(buffer.getMagnitude(0, 0, buffer.getNumSamples()));
		rcPeak.store(buffer.getMagnitude(1, 0, buffer.getNumSamples()));
	}
}

It’s not an error, and it has nothing to do with the way you’re setting things up - like I said, it’s just an internal exception that we deliberately throw and catch as part of normal parsing, and the only problem is that for some reason your debugger is reporting it. There’s nothing actually wrong with the code that’s running.

Right, but I’m getting no sound when I route the connections through the loaded SOUL plugin, otherwise if I connect input -> output directly, I can hear the sound just fine. Just trying to figure out why.

Right. I can’t see anything obvious in the code you posted, but that exception is definitely a red herring. Maybe a channel number mis-match somewhere?

More information about those two errors, seems they are triggered in patch->compileNewPlayer

Would it be possible for you to provide debug symbols for the SoulPatchLoader.dll?

I already explained what those exceptions are (twice!)

For the last time:
Ignore the exceptions!
They’re deliberately thrown and caught INTERNALLY inside the library!

If you want to understand, the code for them is here: https://github.com/soul-lang/SOUL/blob/05bc37aba4d358fcc46e19346a84fd295baeec29/source/modules/soul_core/compiler/soul_Parser.h#L1031
But please don’t mention them to me again, I’m not interested!

I don’t know why your code isn’t playing sound, but that is NOT the reason!

This is frustrating.

I’ve verified that my code and node setup works fine and can play sounds up to this point, please see the comments in green meant to illustrate what I’m swapping out in the screenshot below:


As soon as I swap out the osclilator node inside a graph (which makes sound) with the plugin instance produced by createInstanceFromDescription, the buffer returns 0.

I’m instantiating the plugin normally:


I’m also loading most basic patch (just out << in), also attached.

TestPatch.zip (608 Bytes)

Another interesting fact - calling bypass on the plugin directly renders audio just fine:
pluginNode->getProcessor()->processBlockBypassed(buffer, midiMessages);

While this renders 0 buffers:
pluginNode->getProcessor()->processBlock(buffer, midiMessages);

Okay, got it to work finally by adding a patch.prepareToPlay() wrapped inside of patch.isPlayable().

void MyAudioProcessor::patchUpdated(soul::patch::SOULPatchAudioProcessor& patch)
{
	patch.reinitialise();
	if (patch.isPlayable()) {
		patch.prepareToPlay(getSampleRate(), getBlockSize());
	} else {
		DBG(patch.getCompileError());
	}
	std::cout << patch.getName().isNotEmpty() ? (String("Loaded: ") + patch.getName()) : juce::String();
}

This starts the sound on already mounted AudioPluginInstance which you get by calling patchFormat->createPluginInstance(). Basically first plugin created by this call is an empty shell, does nothing, has no sound. The real one gets loaded into this empty shell by calling patch.reinitialise() once the soul file is ready to go. You then need to re-start processing by calling patch.prepareToPlay(getSampleRate(), getBlockSize());.

I guess API could be improved here to abstract this a bit better.