Recall Plug-In Window Size from AudioProcessorValueTreeState

Hi,

I’m stuck!

I’m using a AudioProcessorValueTreeState for all my audio parameters and everything is working as expected with my sliders, combo boxes, Buttons, … with their respective attachments.

However I also need to store the plug-in window size and want to recall it with plug-in presets.
Storing the window width and height is working, but when recalling a preset the editor doesn’t get refreshed automatically, I have to close and reopen the window to have the window resized correctly.

I tried adding a AudioProcessorValueTreeState::Listener (which isn’t working at all) and also repaint() from AudioProcessor::setStateInformation which is working but also throwing JUCE assertions to the console.

JUCE Assertion failure in juce_Component.cpp:1839
JUCE Assertion failure in juce_Component.cpp:1068
2020-02-08 15:10:04.828033+0100 Pro Tools[38342:1143196] -[DFW_NSPanel displayIfNeeded:]: unrecognized selector sent to instance 0x600000423000
2020-02-08 15:10:04.828229+0100 Pro Tools[38342:1143196] [General] -[DFW_NSPanel displayIfNeeded:]: unrecognized selector sent to instance 0x600000423000

That doesn’t seem to be thread safe and therefore a really bad idea.

This is what I have so far:
AudioProcessor.h

// ...    
private:

AudioProcessorValueTreeState parameters {*this, nullptr, Identifier("Parameters"), {
    std::make_unique<AudioParameterInt>("windowWidth", "Window Width", 100, 1800, 460),
    std::make_unique<AudioParameterInt>("windowHeight", "Window Height", 100, 1800, 460)
}};

AudioProcessor.cpp

void AudioProcessor::getStateInformation (MemoryBlock& destData)
{
auto state = parameters.copyState();
auto xml(state.createXml());
copyXmlToBinary(*xml, destData);
    
}

void AudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    auto xmlState(getXmlFromBinary(data, sizeInBytes));
    if (xmlState.get() != nullptr && xmlState->hasTagName("Parameters")) parameters.replaceState(ValueTree::fromXml(*xmlState));
    
    int w = parameters.state.getProperty("windowWidth", 460);
    int h = parameters.state.getProperty("windowHeight", 460);

// this would work but also throwing a lot of JUCE Assertion failures    
//    if (auto editor = getActiveEditor()) {
//        editor->setSize(w, h);
//        editor->repaint();
//    }

}

AudioProcessorEditor.h

public:
   
   //... 
    virtual void parameterChanged (const String& parameterID, float newValue) override;
    
private:
    
    ValueTreePlugInTestAudioProcessor& processor;
    AudioProcessorValueTreeState& processorVts;

    Rectangle<int> windowRect {460, 460};

and finally AudioProcessorEditor.cpp

AudioProcessorEditor::AudioProcessorEditor (ValueTreePlugInTestAudioProcessor& p, AudioProcessorValueTreeState& vts)
    : AudioProcessorEditor (&p), processor (p), processorVts(vts)
{
  
    processorVts.addParameterListener("windowWidth", this);
    processorVts.addParameterListener("windowHeight", this);
    
    setResizable(true, true);
    setSize(processorVts.state.getProperty("windowWidth", 460), processorVts.state.getProperty("windowHeight", 460));
}

AudioProcessorEditor::~AudioProcessorEditor()
{
    processorVts.removeParameterListener("windowWidth", this);
    processorVts.removeParameterListener("windowHeight", this);
}

void ValueTreePlugInTestAudioProcessorEditor::resized()
{
   
    processorVts.state.setProperty("windowWidth", getWidth(), nullptr);
    processorVts.state.setProperty("windowHeight", getHeight(), nullptr);
    
}

// this won't ever get called
void ValueTreePlugInTestAudioProcessorEditor::parameterChanged (const String& parameterID, float newValue) {
    
    if (parameterID == "windowWidth") {
        windowRect.setWidth(newValue);
        setSize(windowRect.getWidth(), windowRect.getHeight());
    }
    else if (parameterID == "windowHeight") {
        windowRect.setHeight(newValue);
        setSize(windowRect.getWidth(), windowRect.getHeight());
    }
    
}

What am I doing wrong?
I’m sure the solution is very simple, but I’m running in circle for days now and don’t have a clue where to find a solution.

Thanks in advance.
Stefan

APVTS has great features. but it’s sadly only for ‘public’ state.
while you’d might want to store some parameters that aren’t exposed.

You can do this by having additional ValueTree for internalState (or similar).

During setStateInformation after you copyState(). add a child to (or from) the APVTS.

Druing getStateInformation decouple them.

If you need an example I’ll write a full code example.

this would work but also throwing a lot of JUCE Assertion failures

You state/get aren’t guaranteed to be from the main thread.
What you might wanna do is have a juce::Value (or some listeners on an internal settings ValueTree), then once notified use AsyncUpdater to setSize from the main thread.

Thanks ttg for your response.

In fact,I had this first but the parameters were not stored in the Plug-In preset. However, I only used a simple ValueTree and not an APTVS with an additional “internal” ValueTree. Maybe that was the issue.

Thank you so much for your kind offer. I think I should get this done by myself and don’t wanna waste your time on this.

That was also my assumption. That’s why I wanted to use the APVTS::Listener with parameterChanged() but it doesn’t do anything and I still don’t understand why.

In the meantime I solved my problem by creating two sliders for width and height and hide them from the GUI. Yes, I know that’s a bit hacky but I had to move on and it works very well. Preset recall is fine and you can even edit the parameters from a control surface (although I think no one would ever want to change the window size from a control surface …). To not get the parameters get be autmatable, I subclassed AudioParameterInt and let isAutomatable() returning false.
Next step is creating an InterprocessConnection where I don’t want to store the parameters in the Plug-In presets, so I’ll give your additional ValueTree way a try.

Thanks and best regards,
Stefan

That’s the way I think. The coupling and decoupling is between xmls, not between trees. Say you have

AudioProcessorValueTreeState publicState;
ValueTree internalState{ "my_internal_state" };

with all their properties set. To tell your state to the host:

void MyAudioProcessor::getStateInformation(MemoryBlock& destData)
{
    auto xml{ publicState.copyState().createXml() };
    xml->prependChildElement(internalState.createXml().release());
    copyXmlToBinary(*xml, destData);
}

To retrieve it:

void MyAudioProcessor::setStateInformation(const void* data, int sizeInBytes)
{
    if (auto xml{ getXmlFromBinary(data, sizeInBytes) })
    {
        if (auto internalXml{ xml->getChildByName(internalState.getType()) })
        {
            internalState.copyPropertiesFrom(ValueTree::fromXml(*internalXml), nullptr /* or your undo manager */);
            xml->removeChildElement(internalXml, true);
        }
        if (xml->hasTagName(publicState.state.getType()))
            publicState.replaceState(ValueTree::fromXml(*xml));
    }
}

To get notifications you can keep Value objects for each property using ValueTree::getPropertyAsValue() which lets you specify asynchronous updates, then make the editor listen those Values (for this to work you have to use copyPropertiesFrom instead of assigning the whole ValueTree to the new one).

Hi kamedin,

sorry for the late reply. Thanks for your suggestion. I’ll have a try with copyPropertiesFrom. That makes sense.

Thank you.
Stefan