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.

i have just read that you planning to create a wrapper for soul. on the github i found something called: SOULPatchHostDemo. is that what you were talking about? i have problems running it. i downloaded all files (there was no option to save the at once) and tried to open theSOULPatchHostDemo.jucer… when ever i try this projucer is crashing. any advice?

Do you mean you’re trying to run it in the projucer’s live code engine? That’s not going to get very far, you need to actually compile and run it as a project.

ok.i feel a bit lost now.trying to get my head around JUCE for the last 3 days. tried a few tutorials on the website. i have build some Reaktor and Max/MSP projects and have knowledge in a couple of web-based programming languages (javascript, php) and a basic understanding of OPP and Classes. but still it seems that i dont have enough C++ knowledge to understand what i am doing. i saw quiet often ppl mentioning that i dont get very far without it when i want to learn juce. then i saw that there is something like SOUL which looked way more accesible for me and now i am trying to figure out how to get a smple SOUL patch into a JUCE project and compile at as a VST3. i like to try that so i can see what the workflow is. then i want to try out more and more functions. i also mentioned in my question your VST Wrapper because of your comment: "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… "

If you just want to write and play with some soul code, you can do that in Tracktion, or using the soul command-line tools, or on soul.dev, and you don’t need C++ for that.

But if you want to build a real plugin as an actual product and distribute it, then the tool you need for that (the soul -> full juce project generator) isn’t quite finished yet. When that’s done (probably within a couple of months) then it’ll spit out a whole juce project that you can compile into a native VST… but it’s still a C++ project, and you’ll still need enough C++ know-how to at least understand how to use a C++ build chain, how to run, debug it. And you’re probably going to want to write a GUI for it, and that’s going to take grown-up C++ knowledge, much more than writing some DSP code.

In the long-term when SOUL is established and enough hosts can load it, then it’ll get a lot easier and you’ll be able to release pure soul + javascript GUI patches without needing to go near a native compiler, but that’s a way off right now!

1 Like

thanks a lot for the infos

would it possible to look at your code? i would like to try SOUL with a JUCE project and creat a vst3 or vst plugin.