How to save a file path as an AU/VST plugin parameter

I have as AU/VST plugin, that loads a midi file and plays it in a loop. My main purpose is to run this in Apple Mainstage. This already works properly. Now I want to save several parameters of the plugin, so if it is open again, the values will be “restored”.

This already works for integer or float values if I save the Concert in Mainstage but I don’t know, how to save the path to the midi file. I miss s.th. like AudioParameterString. I read in one or two threads, that s.th. can be transformed in floats and then restored again but to be honest, I do not understand this at all.

May you please give me a hint on how to do that or may you provide me a sample of how to save a file path using AudioProcessorValueTreeState. Or do I follow the wrong approach?

1 Like

You probably wouldn’t have this as an actual parameter, since it’s not something you can really automate anyway.

Since you’re using AudioProcessorValueTreeState you could simply use its ValueTree member, state. You can even use getPropertyAsValue() to get a Value object to easily read/write a property on a ValueTree.

Essentially you use your ValueTree state (or add a new child in it) and then you can take a property from it to get a Value object. Then when you set the Value to your string, it properly updates the underlying ValueTree property.

When you save your plugin’s state these kind of non-parameter ValueTrees still get written into the XML. I think the Value from getPropertyAsValue() will reflect the correct value as well when loading XML, but haven’t tried it out yet.

Quick example of what I mean in accessing the property:

Value midiFilePath = parameters.state.getPropertyAsValue("MIDI_FILEPATH", nullptr, true);
midiFilePath.setValue("~/foobar.midi"); // Writing the XML after this should show the "foobar.midi"

Thanks, @TonyAtHarrison for your support! This seems to be much easier than what I have seen before.

Just one more question.
Setting the property is quite simple. I just pass the string in the loadMIDIFile() function that is called from the editor.

void SimpleMidiplayerAudioProcessor::loadMIDIFile(File fileMIDI)
{
    midiFilePath.setValue(fileMIDI.getFullPathName()); // set file path in parameters
    
    // read file for further processing
    FileInputStream theStream(midiFile);
    inMidiFile.readFrom(theStream);
    ....
}

Reading the value from the property again is also simple

midiFilePath = parameters.state.getPropertyAsValue("midiFilePath", nullptr, true);

but reading the value is just the first step because I also have to load the file and read it for further processing. What is the right place to do this? I tried preparePlay() but this did not work.

What didn’t work about it?

Usually I see people talk about loading files on specialized threads, although that’s more in the case of something like an IR Reverb… But anyway, the idea there is that you shouldn’t be handling any I/O on the audio thread, so using a special thread for loading the file then atomically passing the data pointer off to the audio thread keeps your plugin/application running smoothly.

You haven’t once mentioned getStateInformation and setStateInformation…? I am assuming you have something implemented in those. Can you show the implementations?

Here is the code from the constructor where I read the parameters. I think it is not possible to do the processing of the midi file here:

SimpleMidiplayerAudioProcessor::SimpleMidiplayerAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", AudioChannelSet::stereo(), true)
                     #endif
                       ), parameters(*this, nullptr, Identifier ("MidiLooper"),
                                      {
                                          std::make_unique<AudioParameterInt> ("transpose",
                                                                             "Transpose",
                                                                             -24,
                                                                             24,
                                                                             1)
                                          
                                      })
#endif
{
    // read parameters
    transposeValue = (int) parameters.getParameterAsValue("transpose").getValue();
    midiFilePath = parameters.state.getPropertyAsValue("midiFilePath", nullptr, true);
    selectedTrack = parameters.state.getPropertyAsValue("selectedTrack", nullptr, true);
    numTracks.store(0);
}

And here is the code from getStateInformation and setStateInformation:

void SimpleMidiplayerAudioProcessor::getStateInformation (MemoryBlock& destData)
{
    auto state = parameters.copyState();
    std::unique_ptr<XmlElement> xml (state.createXml());
    copyXmlToBinary (*xml, destData);
}

void SimpleMidiplayerAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    std::unique_ptr<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
    
    if (xmlState.get() != nullptr)
        if (xmlState->hasTagName (parameters.state.getType()))
            parameters.replaceState (ValueTree::fromXml (*xmlState));
}

Right. It looks like nothing is handling the file name coming from the state? For things like that you need to manually handle updating the internal state in setStateInformation. (Only the plugin numerical parameters are handled “automagically”.) So, get the file name string from the tree, make a file object out of it and call your loadMIDIFile method.

OK, all code snippets I have posted before are still the same but I added now the following in perparePlay():

File midiFile(midiFilePath.getValue());
std::cout << "Midi file path from storage: " << midiFilePath.getValue().toString() << std::endl;
loadMIDIFile(midiFile);

I also set some breakpoints to check if the getStateInformation and setStateInformation functions are called. If I save the “project” in the Juce AudioPluginHost, the getStateInformation is called.
Even though I would expect it vice versa from the naming point of view, it looks like getStateInformation saves the parameters and properties.

That the information is correctly put to the property before is checked in loadMIDIFile:

midiFilePath.setValue(fileMIDI.getFullPathName());  // set file path in parameters
std::cout << "In load midi file: " << midiFilePath.getValue().toString() << std::endl;

This prints the path to the debugging console.

But if I stop the debugging and start it again, the output on the console from preparePlay() is empty and when loadMIDIFile is called, it runs into an error, because there is no file loaded. :thinking:

Have you checked the order in which prepareToPlay and setStateInformation are called by the host?

That’s the “call hierarchy”:

  1. Constructor
  2. setStateInformation
  3. perpareToPlay
  4. loadMIDIFile --> but currently crashes because of missing a real file

If I comment out loadMIDIFile call in perpareToPlay and load the file manually and save the project, getStateInformation is called.

1 Like

I am trying to implement what @TonyAtHarrison suggested to save a file path as a parameter. The parameter is being recalled when the DAW is open (i.e. urlParameter.getValue() returns to file path). However, when I reopen the DAW, urlParameter.getValue() returns nothing. Any suggestions?

Like was already discussed in the thread, something like strings/file paths can’t be handled as plugin parameters to begin with. (I am a bit confused how did you get your urlParameter to work at all if it is actually a plugin parameter…?) They need to be handled as custom data that you somehow deal with in the getStateInformation and setStateInformation calls of your plugin processor class. If you are already using AudioProcessorValueTreeState, that is going to be fairly easy, you can just store the path as a custom property in the ValueTree member (“state”) of the AudioProcessorValueTreeState.

Yes. I have created a custom property like so:

urlParameter = parameters.state.getPropertyAsValue("IR_FILEPATH", nullptr, true);

When I call the following when the DAW is running (even when I open and close the plugin):

DBG(urlParameter.getValue().toString());

Gives:

C:\Users\James\Downloads\ML Sound Lab - Freeman Cab Pack\44.1kHz-24bit\ML-FRMN-FREESTYLE.wav

Which is good!

However, when I save and close the DAW and open the DAW again

DBG(urlParameter.getValue().toString());

returns nothing.

My understanding is that if getStateInformation and setStateInformation calls are working when I close and open the plugin, then the parameter should be recalled when I also close and open the DAW. But this is where my problem lies. Any suggestions?

What do you mean by “closing and opening” the plugin? Closing and reopening the plugin GUI? That usually doesn’t destroy and recreate the whole plugin, it usually just destroys and recreates the GUI editor part of the plugin. The data in the plugin’s AudioProcessor part would stay intact.

How have you implemented your getStateInformation and setStateInformation methods?

Yes. I mean closing and opening the GUI. (I checked on another project that doesn’t have parameters, and you are correct. Closing and opening the GUI does not destroy the values)

Here are my getStateInformation and setStateInformation methods:

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

void GraphicalIrLoaderAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes));

if (xmlState.get() != nullptr)
    if (xmlState->hasTagName(parameters.state.getType()))
        parameters.replaceState(ValueTree::fromXml(*xmlState));

}

You need to change the setStateInformation to something like (not tested, the needed code might not be exactly like this) :

void GraphicalIrLoaderAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes));
if (xmlState.get() != nullptr)
    if (xmlState->hasTagName(parameters.state.getType()))
    {
        parameters.replaceState(ValueTree::fromXml(*xmlState));
        urlParameter.referTo(parameters.state.getPropertyAsValue("IR_FILEPATH", nullptr, true));
        
    } 


}

I’d suggest changing the name of urlParameter since it isn’t really a plugin parameter so it’s confusing to refer to it as a “parameter”.

Great! Thanks @Xenakios it works now.

I had called:

urlParameter = parameters.state.getPropertyAsValue(“IR_FILEPATH”, nullptr, true);

in the constructor previously, and my assumption was that

void GraphicalIrLoaderAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{

if (xmlState.get() != nullptr)
    if (xmlState->hasTagName(parameters.state.getType()))
        parameters.replaceState(ValueTree::fromXml(*xmlState));
}

would walk through the value tree and set the parameters.

Why does that not work in this case?

replaceState replaces the whole tree state and disconnects the Values that were previously attached to the tree. (In effect, without the referTo call, your urlParameter would still point to the data that was there before the setStateInformation call.)

2 Likes