Can't call setStateInformation() from command-line plugin Host

Hey there!

I’m currently working on a command-line host for audio plugins using JUCE’s AudioPluginInstance to host VST2, VST3 and AU plugins without any GUI.

Within this context, I need to load the plugin’s state from an XML file. I want to use AudioProcessor::setStateInformation() for that, but when I call setStateInformation on my AudioPluginInstance, the call doesn’t seem to go through. Debugging yields that the setStateInformation() method is indeed not called and that I’m leaking 2 instances of class MessageListener.
I read through all other related posts, but couldn’t apply them to my own issue.

Here’s a minimum version of the components I’m using for reproducing this:

In the constructor of my plugin, I write my xml-file from the ValueTree of the plugin like this:

parameters_.getParameter("gain")->setValueNotifyingHost(0.125f);
auto state = parameters_.copyState();
std::unique_ptr<XmlElement> xml (state.createXml());
juce::File outputFile(xmlFilePath);
xml->writeTo(outputFile, {});

With the setStateInformation() method of the plugin looking like this:

void NewProjectAudioProcessor::setStateInformation(const void* data, int sizeInBytes)
{
    std::unique_ptr<XmlElement> xmlState(getXmlFromBinary(data, sizeInBytes));
    parameters_.replaceState(ValueTree::fromXml(*xmlState));
}

My command-line application then hosts the pugin using JUCE’s AudioPluginInstance and calls setStateInformation() on the plugin like this:

juce::File outputPresetFile(xmlFilePath);
juce::XmlDocument xmlDoc(outputPresetFile);
auto xml = xmlDoc.getDocumentElement();

juce::MemoryBlock block;
pluginInstance_->copyXmlToBinary(*xml, block);
pluginInstance_->setStateInformation(block.getData(), block.getSize());

When I run the above, I’m not arriving at my breakpoint set in the setStateInformation() method of the plugin. Executing until the end yields the following output:

JUCE v5.4.7
JUCE Assertion failure in juce_MessageManager.cpp:39
*** Leaked objects detected: 2 instance(s) of class MessageManager
JUCE Assertion failure in juce_LeakedObjectDetector.h:90

Is the leaking connected to setStateInformation not being called? Does anyone know how to correct my leaking MessageManager? Or am I missing something else entirely?

I’d appreciate any hint on what could be going wrong here!

Thanks,
Simon

Since you don’t use the JUCEApplication, you need to put the ScopedJuceInitialiser_GUI on the stack, that will do the cleanup when the console app finishes.

How you managed to have two MessageManagers leaking (it is a singleton) remains a mystery to me.

Also the lack of a MessageManager at the beginning might be the reason that async calls are not run.
Try creating one at the beginning by simply calling the accessor.

int main (int, char**)
{
    ScopedJuceInitialiser_GUI init;
    MessageManager::getInstance();
    // ...
    return 0;
}
1 Like

Hey daniel,

Thank you very much for the tip!
The initialisation of the JuceInitialiser_GUI and the MessageManager banished the Leaked objects successfully.
Unfortunately, it didn’t resolve the missing call to setStateInformation().

Also the lack of a MessageManager at the beginning might be the reason that async calls are not run.

Would that mean setStateInformation is the only async call ?
I’m asking because all other calls I’m issuing to the plugin from my host are going through without any problems.
(with “all other calls” I mean setParameter(), processBlock(), setCurrentProgram(), setChannelLayoutOfBus())

Seems you destroyed my hypothesis then :wink:

I think t your break point is outside the debugged application. Currently you are debugging your host, but the breakpoint is set in the plugin.
You should load the plugin in your IDE and set your command line host as executable. Then it should stop at the breakpoint.

Much easier is to DBG something in your plugin’s setStateInformation…

1 Like

Hey daniel!

Ok so when I load the plugin in my IDE and with the host set as my executable I can stop at breakpoints in every called method inside my plugin. However, the setStateInformation() method is the only one the debugger won’t stop at despite being called from the host. (tried both LLDB and GDB)
The MessageManager leaks now appear again too, despite intantiating ScopedJuceInitialiser_GUI in my main and getting an instance of MessageManager.
When setting up my breakpoints in the ctor, the process method and the dtor of the plugin the execution behaves like this:

  • stop at ctor
  • stop at process()
  • stop at dtor
  • failing the assert in line 39 of the MessageManager ctor

When I move the breakpoint from process() to setStateInformation() I get this:

  • stop at ctor
  • stop at dtor
  • failing the assert in line 39 of the MessageManager ctor

Both calls, to process() and to setStateInformation() use the same AudioPluginInstance member.
I can’t figure out what mechanic prevents setStateInformation() from being called here. Especially since I use the absolute minimum plugin for this test (gain example from the juce demos) and load it using the juce formatManager. :thinking:

Can you double check, if you are really overriding properly?
You have added the override keyword, there is no warning and you triple checked the spelling and arguments for the methods?

P.S. just checked the assert. It is not commented, but it seems that the MessageManager was never created or already deleted, since instance is the static of the singleton.
Don’t know, what to advise here

1 Like

Yes, the method is overriden correctly any without warnings and there are no syntax errors.

I just found something very interesting:
setStateInformation() is called when I add the getStateInformation right before like this:

juce::File outputPresetFile(xmlFilePath);
juce::XmlDocument xmlDoc(outputPresetFile);
auto xml = xmlDoc.getDocumentElement();

juce::MemoryBlock block;
pluginInstance_->getStateInformation(block);
pluginInstance_->setStateInformation(block.getData(), block.getSize());

However, I don’t want to do that as I’m trying to modify the MemoryBlock “block” with the contents of my XML file.

Couriously, if I do this:

juce::File outputPresetFile(filePath);
juce::XmlDocument xmlDoc(outputPresetFile);
auto xml = xmlDoc.getDocumentElement();

juce::MemoryBlock block;
pluginInstance_->getStateInformation(block);
pluginInstance_->copyXmlToBinary(*xml, block);
pluginInstance_->setStateInformation(block.getData(), block.getSize());

setStateInformation is again not called at all.
Is there some internal mechanic from JUCE that prevents directly setting the state from an xml file?

In light of this, the MessageManager leaks might actually be a separate issue?

So I just discovered the awesome pluginval project (https://github.com/Tracktion/pluginval).

In order to rule out that my way of setting up the host is responsible for the issue I’m having,
I added an additional test to the BasicTests.cpp of pluginval which also attempts to set the state of my plugin using the same XML file and setStateInformation().
It looks like this:

struct PluginStateFromXmlTest  : public PluginTest
{
  PluginStateFromXmlTest()
          : PluginTest ("Plugin xml state", 2)
  {
  }

  void runTest (PluginTests& ut, AudioPluginInstance& instance) override
  {
      auto r = ut.getRandom();

      // Read state
      MemoryBlock state;

      // get XML Element from file
      juce::File outputPresetFile(<pathToFile>);
      juce::XmlDocument xmlDoc(outputPresetFile);
      auto xml = xmlDoc.getDocumentElement();

      instance.copyXmlToBinary(*xml, state);

      // set the state using the modified block
      instance.setStateInformation (state.getData(), (int) state.getSize());

      // see if the parameter was set (xml file specifies the parameter at index 0 to be set to 0.0)
      auto parameters = getNonBypassAutomatableParameters (instance);
      ut.expectWithinAbsoluteError (parameters[0]->getValue(), 0.0f, 0.1f,
                  "Parameters not restored from XML file setStateInformation");
  }
};

static PluginStateFromXmlTest pluginStateFromXmlTest;

Unfortunately, this always results in

Starting test: pluginval / Plugin xml state...
!!! Test 1 failed: Parameters not restored from XML file setStateInformation -- Expected value  within 0.1 of: 0, Actual value: 0.397951

Time taken to run test: 0
FAILED!!  1 test failed, out of a total of 1

Suggesting that the parameter is not set to 0.0 as it should. Exactly like with my own plugin-host, when debugging and setting a breakpoint inside the Debug version of the tested plugin, I never arrive at the setStateInformation() method of my plugin. a print out in the setStateInformation() method also stays silent. As with my own host, setStateInformation() will be called when I leave out this line:

instance.copyXmlToBinary(*xml, state);

I also double checked the conversion back and forth between the XML file and the binary block.
Does any of you have any tips of how both my own and pluginval fail to call setStateInformation() in this case?
Would really appreciate if anyone has some hint for this, I’m kind of running out of options here :sweat_smile:

I m facing some error in this code

1 Like

I think you’re misunderstanding what you need to pass to setStateInformation, it needs to be a block of binary data exactly as was returned from getStateInformation.

I don’t think you’ve shown the way you’re creating the outputPresetFile here. If you’re using copyXmlToBinary to get the binary state out of the XML, are you sure you’re using getXmlFromBinary to get it in the first place?

Another easy test for this in pluginval would be to randomise the state, get it with getXmlFromBinary, randomise it again then restore it with copyXmlToBinary.

But you should probably step through setStateInformation a bit more to see where it’s failing.

1 Like

Hey alfred!

Are you trying to runthe code on your machine using pluginval? For that you would also need the plugin .vst3 file and the .xml file I’m using. If you really want to I could of corse send you these files.

Hey Dave,

Thanks alot for replying to this!
Yes, so I created the outputPresetFile from within my plugin like this:

parameters_.getParameter("gain")->setValueNotifyingHost(0.0f);
juce::MemoryBlock block;
getStateInformation(block);
auto xml = getXmlFromBinary(block.getData(), (int) block.getSize());
juce::File outputFile(<pathToFile>);
xml->writeTo(outputFile, {});

where <pathToFile> is the path to my xml file which is later used in the PluginStateFromXmlTest().

Thanks for your test-suggestion. I implemented this in my test like the following using pluginval (I oriented the test on the PluginStateTestRestauration)

struct PluginStateFromXmlTest  : public PluginTest
{
  PluginStateFromXmlTest()
          : PluginTest ("Plugin xml state", 2)
  {
  }

  void runTest (PluginTests& ut, AudioPluginInstance& instance) override
  {
      auto r = ut.getRandom();

      // Read state
      MemoryBlock originalState;

      // Check current sum of parameter values
      const float originalParamsSum = getParametersSum (instance);

      // Set random parameter values
      for (auto parameter : getNonBypassAutomatableParameters (instance))
          parameter->setValue (r.nextFloat());

      instance.getStateInformation(originalState);
      auto xml = instance.getXmlFromBinary(originalState.getData(), (int) originalState.getSize());

      // Set random parameter values again
      for (auto parameter : getNonBypassAutomatableParameters (instance))
          parameter->setValue (r.nextFloat());

      MemoryBlock newState;
      instance.copyXmlToBinary(*xml, newState);

      // set the state using the modified block
      instance.setStateInformation (newState.getData(), (int) newState.getSize());

      // Check parameter values return to original
      ut.expectWithinAbsoluteError (getParametersSum (instance), originalParamsSum, 0.1f,
              "Parameters not restored on setStateInformation");
  }
};

static PluginStateFromXmlTest pluginStateFromXmlTest;

Interestingly enough, this test won’t pass neither for my test plugin, nor for other third party plugins. :thinking:
I know that the setStateInformation() method is built to restore the data from the binary block coming from getStateInformation(), but surely that shouldn’t matter here because I’m only converting between XML and binary should it?

Are you sure your plugin and any others you’ve tested actually apply the state correctly?
It could be that there’s a problem in your plugin or some of the plugins you’re testing with?

Yeah, thats a good point, and also the reason why I built a simple plugin which only has a single gain parameter to reduce the complexity of the whole system. I’m certain that it applies the state correctly, because I can open it in a regular DAW (I’m using Reaper), set the gain parameter, save the Reaper session, close the session and open it again finding the gain parameter at the position where I left it.
Same with the other plugins. :frowning:

It uses only the following 2 lines in setStateInformation():

std::unique_ptr<XmlElement> xmlState(getXmlFromBinary(data, sizeInBytes));
parameters_.replaceState(ValueTree::fromXml(*xmlState));

done that, works fine now.