Embedding audio in get/setStateInformation


#1

Hey y’all. I am trying to embed a very small audio file in the plugin data that is stored and reloaded in get/savestateinformation. I’m converting the audio to a base 64 encoding, writing it to an attribute within the XML that contains my AudioProcessorValueTreeState, and then reloading it and converting it back from base 64. However, whenever I load the audio in setStateInformation, the AudioFormatReader reads that the audio is of 0 length, even though the size of the encoded strings, MemoryBlocks and the original AudioSampleBuffer are all consistent. I have also tried the Base64 standard encoding as well and I get the same results.
I am doing so the following way:

originalData is an AudioSampleBuffer containing the audio I would like to save

getStateInformation(MemoryBlock& destData) {
   XmlElement xml ("MYPLUGINSETTINGS");
   ...
   add other attributes
   ...
   MemoryOutputStream audioMemory (originalData->getNumSamples());
   if (AudioFormatWriter* writer = getCorrectAudioFormatWriter(&audioMemory)) {
       writer->writeFromAudioSampleBuffer(*originalData, 0, originalData->getNumSamples());
       MemoryBlock audioMemoryBlock = audioMemory.getMemoryBlock();
       String base64AudioData = audioMemoryBlock.toBase64Encoding();
       xml.setAttribute(settingAudioData, base64AudioData);
   }
   ...
   copyXmlToBinary (xml, destData);
}


setStateInformation (const void* data, int sizeInBytes) {
   ScopedPointer<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
   ...
   extract other attributes
   ...
   String retrievedAudioData = xmlState->getStringAttribute(settingAudioData);
   MemoryBlock retrievedAudioMemoryBlock;
   retrievedAudioMemoryBlock.fromBase64Encoding(retrievedAudioData);
   MemoryInputStream* stream  = new  
   MemoryInputStream(retrievedAudioMemoryBlock.getData(), 
   retrievedAudioMemoryBlock.getSize(),true);
   reloadSynth(false, stream);
}

Do y’all have any ideas? Thank you!


#2

Would you mind adding three backticks ``` before and after your code?
Then the code appears formatted and easier readable…

Thanks

And to your problem: I don’t know anything about your getCorrectAudioFormatWriter () function, but I assume it creates an AudioFormatWriter on the heap. This will write it’s length when calling flush or when the AudioFormatWriter is destroyed. Since you are leaking the writer, the length is never updated…

Just a guess…


#3

edited, thank you I’m still pretty new!


#4

Thanks, that looks better :slight_smile:

Ideally you put the AudioFormatWriter into a ScopedPointer and let it go out of scope before you call audioMemoryBlock.toBase64Encoding()


#5

I tried to make your suggested changes and now audioMemory is being deleted prematurely and I am getting ‘pointer being freed was not allocated’ in the console. I’ll include getCorrectAudioFormatWriter below:

audioFile is the File that corresponds with originalData

AudioFormatWriter* SMPLRAudioProcessor::getCorrectAudioFormatWriter(OutputStream* out)
{
    if (audioFile.hasFileExtension(".wav")) {
            WavAudioFormat wav;
            if (AudioFormatWriter* writer = wav.createWriterFor(out, getFileSampleRate(),
                (originalData->getNumChannels() == 2 ? AudioChannelSet::stereo() : AudioChannelSet::mono()), 16, nullptr, 0))
            {
                return writer;
            }
        }
    ...
    handle other extensions
    ...
}

Is this an incorrect way to generate an AudioFormatWriter? I think the error that is happening now is because writer is using audioMemory and when I delete writer and try to run audioMemoryBlock.toBase64Encoding(), it’s already deleted


#6

Indeed, the AudioFormatWriter docs say:

Parameters
destStream: the stream to write to - this will be deleted by this object when it is no longer needed

So you cannot use the MemoryOutputStream like that. In this case it has to be on the heap.

This should work (not tested):

MemoryBlock audioMemory;
{
    auto* output = new MemoryOutputStream (audioMemory);
    ScopedPointer<AudioFormatWriter> writer = getCorrectAudioFormatWriter (output);
    if (writer)
        writer->writeFromAudioSampleBuffer (*originalData, 0, originalData->getNumSamples());
    // closing the scope causes the writer to flush which will delete the MemoryOutputStream
}
xml.setAttribute (settingAudioData, audioMemory.toBase64Encoding());

Good luck

Edit: on a separate note: I think it is not necessary to put your plugin data in XML, it is handy if you want to inspect the data though. You could write directly into the block, since the host allows binary data here. It is up to you how you serialise your data… but XML is probably the safest way, it might just add some overhead…


#7

Heck yeah that worked!! Thank you so much @daniel I really appreciate your help. I owe you a beer :pray: