Can’t Write/Restore Integer plugin parameter

I am trying to add an Integer Transpose parameter to the ArpeggiatorPluginDemo.

The code works in the AudioPluginHost and the values are indeed transposed. But when I write and restore a “preset”, the Transpose value does not restore to the correct value. What am I missing here?

Arpeggiator constructor:

{
    addParameter (speed = new AudioParameterFloat ("speed", "Arpeggiator Speed", 0.0, 1.0, 0.5));
    addParameter (transpose = new AudioParameterInt ("transpose", "Arpeggiator Transpose", -12, 12, 0));
}

Saving/Restoring:

    void getStateInformation (MemoryBlock& destData) override
    {
        MemoryOutputStream (destData, true).writeFloat (*speed);
        MemoryOutputStream (destData, true).writeInt (*transpose);
    }

    void setStateInformation (const void* data, int sizeInBytes) override
    {
        speed->setValueNotifyingHost (MemoryInputStream (data, static_cast<size_t> (sizeInBytes), false).readFloat());
        transpose->setValueNotifyingHost (MemoryInputStream (data, static_cast<size_t> (sizeInBytes), false).readInt());
    }


private:
    //==============================================================================
    AudioParameterFloat* speed;
    AudioParameterInt* transpose;

I believe you are using MemoryInputStream incorrectly. I didn’t test this, but I think you want something like:

void setStateInformation (const void* data, int sizeInBytes) override
{
    MemoryInputStream mis (data, static_cast<size_t> (sizeInBytes), false);
    speed->setValueNotifyingHost (mis.readFloat());
    transpose->setValueNotifyingHost (mis.readInt());
}

And you can probably do something similar with the MemoryOutputStream in getStateInformation. It might be working how you have it, but it will be more correct if you instantiate a MemoryOuputStream object that you then operate on for each of the pieces of data you write to it.

Thanks - that was my erroneous assumption on how it should work based on not having an example of multiple parameters.

I have searched and I can only find one example in class AUv3SynthProcessor .h:

AUv3SynthProcessor : public AudioProcessor
{
public:
    AUv3SynthProcessor ()
        : AudioProcessor (BusesProperties().withOutput ("Output", AudioChannelSet::stereo(), true)),
          currentRecording (1, 1), currentProgram (0)
    {
        // initialize parameters
        addParameter (isRecordingParam = new AudioParameterBool ("isRecording", "Is Recording", false));
        addParameter (roomSizeParam = new AudioParameterFloat ("roomSize", "Room Size", 0.0f, 1.0f, 0.5f));
	
	[…]

//==============================================================================
void getStateInformation (MemoryBlock& destData) override
{
    MemoryOutputStream stream (destData, true);

    stream.writeFloat (*isRecordingParam);
    stream.writeFloat (*roomSizeParam);
}

void setStateInformation (const void* data, int sizeInBytes) override
{
    MemoryInputStream stream (data, static_cast<size_t> (sizeInBytes), false);

    isRecordingParam->setValueNotifyingHost (stream.readFloat());
    roomSizeParam->setValueNotifyingHost (stream.readFloat());

}
private:
[…]

AudioParameterBool* isRecordingParam;
AudioParameterFloat* roomSizeParam;

What this shows is that, although two parameters are declared and added as a Bool and a Float, they are both written and read as Floats. Is that correct?

Anyway, I tried it both ways in my plugin and it still doesn’t work:

Reading/writing as different types:

[…]
{
    addParameter (speed = new AudioParameterFloat ("speed", "Arpeggiator Speed", 0.0, 1.0, 0.5));
    addParameter (transpose = new AudioParameterInt ("transpose", "Arpeggiator Transpose", -12, 12, 0));
}
[…]
//==============================================================================
void getStateInformation (MemoryBlock& destData) override
{
    MemoryOutputStream stream (destData, true);
    
    stream.writeFloat (*speed);
    stream.writeInt (*transpose);
}

void setStateInformation (const void* data, int sizeInBytes) override
{
    MemoryInputStream stream (data, static_cast<size_t> (sizeInBytes), false);

    speed->setValueNotifyingHost (stream.readFloat());
    transpose->setValueNotifyingHost (stream.readInt());
}

private:
//==============================================================================
AudioParameterFloat* speed;
AudioParameterInt* transpose;

Reading/Writing as Floats:

[…]
{
    addParameter (speed = new AudioParameterFloat ("speed", "Arpeggiator Speed", 0.0, 1.0, 0.5));
    addParameter (transpose = new AudioParameterInt ("transpose", "Arpeggiator Transpose", -12, 12, 0));
}
[…]
//==============================================================================
void getStateInformation (MemoryBlock& destData) override
{
    MemoryOutputStream stream (destData, true);
    
    stream.writeFloat (*speed);
    stream.writeFloat (*transpose);
}

void setStateInformation (const void* data, int sizeInBytes) override
{
    MemoryInputStream stream (data, static_cast<size_t> (sizeInBytes), false);

    speed->setValueNotifyingHost (stream.readFloat());
    transpose->setValueNotifyingHost (stream.readFloat());
}

private:
//==============================================================================
AudioParameterFloat* speed;
AudioParameterInt* transpose;

Neither of these work correctly. When saving and opening a preset in the AudioPluginHost, The speed value is restored correctly, the transpose value is not.

I understand there may be better ways of handling multiple parameters such as XML or ValueTree, but I’m proceeding step by step with the tutorials and would like to understand this, first. Thanks!

I haven’t done plugin work (ie. haven’t worked with the AudioParameterXX functions) in a while, so I don’t know what the issue might be. But, the code doesn’t have issues with the MemoryStreams any more. Hopefully someone with more plugin knowledge will step in to help now. :slight_smile:

Welcome to the JUCE learning curve.

The demo examples tend to be written far more tightly than they probably ought to be, as teaching examples; hence the first mistake you make by invoking the MemoryOutputStream constructor twice.

The bigger issue is that the JUCE documentation, though generally quite good, is often missing really important little nuggets, like the fact that AudioProcessorParameter::setValueNotifyingHost() (which AudioProcessorInt inherits, and does not override in an integer-specific way, as one might expect) takes a float parameter which is normalized to the range [0.0f, 1.0], and you can invoke another inherited function, RangedAudioParameter::convertTo0to1() to do the required conversion.

The authors of the Arpeggiator demo were able to skirt all of that complexity by using exactly one float parameter, which, because it is already in the magic range [0.0f, 1.0f], does not need to be normalized/denormalized.

The resulting (working) code is:

void Arpeggiator::getStateInformation (MemoryBlock& destData)
{
    MemoryOutputStream stream(destData, false);
    stream.writeFloat(*speed);
    stream.writeInt(*transpose);
}

void Arpeggiator::setStateInformation (const void* data, int sizeInBytes)
{
    MemoryInputStream stream(data, static_cast<size_t> (sizeInBytes), false);

    float sp = stream.readFloat();
    int tr = stream.readInt();

    float sp01 = speed->convertTo0to1(sp);
    float tr01 = transpose->convertTo0to1(float(tr));

    speed->setValueNotifyingHost(sp01);
    transpose->setValueNotifyingHost(tr01);
}

As far as I know, none of this is actually written down anywhere. I had to figure it all out by trial and error.

2 Likes

Thanks! That works a treat. Would not have figured that out at this point. :slight_smile: