ToxVox
February 9, 2022, 10:20am
1
I’m trying to figure out how to save and load a RackType with a te::SamplerPlugin including the audio files. I also checked the .trkpreset files from Waveform. If I understand it correctly it contains all resource files:
state="1279.CMlaKA...." base64:layout="131.LETVOUEU..P.Bj...
How can I prepare the state of RackType to archive this? Or do I have to manage all files myself?
Here is my code sketch (not tested yet):
Save a rack preset
auto valueTreeRackType = rackType->state;
auto xmlString = valueTreeRackType.toXmlString();
auto presetFile = juce::File::getCurrentWorkingDirectory().getChildFile ("PresetFile.trkpreset");
FileOutputStream output (presetFile);
if (output.openedOk())
{
output.setPosition (0);
output.truncate();
output.write ("<PRESET name=\"New Rack\" tags=\"Rack\">" + xmlString + "</PRESET>");
}
Load a rack preset
auto filePreset = File("PresetFile.trkpreset");
auto xmlPreset = filePreset.loadFileAsString();
auto valueTreePreset = juce::ValueTree::fromXml(xmlPreset);
auto valueTreeRack = valueTreePreset.getChildWithName(te::IDs::RACK);
te::EditItemID::remapIDs (valueTreeRack, nullptr, [&] { return edit.createNewItemID(); });
edit.getRackList().addRackTypeFrom(valueTreeRack);
auto rackType = edit.getRackList().addRackTypeFrom(valueTreeRack);
auto rackInstanceCreationInfo = te::RackInstance::create (*rackType);
auto rackPlugin = dynamic_cast<te::RackInstance*> (edit.getPluginCache().createNewPlugin (rackInstanceCreationInfo).get());
Here is the state of the RackType a want to save:
<RACK id="1094" name="New Rack">
<MACROPARAMETERS id="1095"/>
<WINDOWSTATE windowPos=""/>
<OUTPUT name="midi output"/>
<OUTPUT name="output 1 (left)"/>
<OUTPUT name="output 2 (right)"/>
<INPUT name="midi input"/>
<INPUT name="input 1 (left)"/>
<INPUT name="input 2 (right)"/>
<MODIFIERS/>
<PLUGININSTANCE x="1.0" y="1.0">
<PLUGIN type="sampler" windowLocked="1" id="1096" enabled="1">
<MACROPARAMETERS id="1097"/>
<SOUND source="/Users/halil/Library/BentoGrooveApp/Temporary/edit_0_27526ac/kick.ogg"
name="kick" startTime="0.0" length="0.0" keyNote="60" minNote="60"
maxNote="60" gainDb="1.0" pan="0.0"/>
<SOUND source="/Users/halil/Library/BentoGrooveApp/Temporary/edit_0_27526ac/snare.ogg"
name="snare" startTime="0.0" length="0.0" keyNote="61" minNote="61"
maxNote="61" gainDb="1.0" pan="0.0"/>
<SOUND source=".../Temporary/edit_0_27526ac/closed_hat.ogg"
name="closed_hat" startTime="0.0" length="0.0" keyNote="62" minNote="62"
maxNote="62" gainDb="1.0" pan="0.0"/>
<SOUND source=".../Temporary/edit_0_27526ac/open_hat.ogg"
name="open_hat" startTime="0.0" length="0.0" keyNote="63" minNote="63"
maxNote="63" gainDb="1.0" pan="0.0"/>
<SOUND source=".../Temporary/edit_0_27526ac/clap.ogg"
name="clap" startTime="0.0" length="0.0" keyNote="64" minNote="64"
maxNote="64" gainDb="1.0" pan="0.0"/>
<SOUND source=".../Temporary/edit_0_27526ac/low_tom.ogg"
name="low_tom" startTime="0.0" length="0.0" keyNote="65" minNote="65"
maxNote="65" gainDb="1.0" pan="0.0"/>
<SOUND source=".../Temporary/edit_0_27526ac/high_tom.ogg"
name="high_tom" startTime="0.0" length="0.0" keyNote="66" minNote="66"
maxNote="66" gainDb="1.0" pan="0.0"/>
<SOUND source=".../Temporary/edit_0_27526ac/ride.ogg"
name="ride" startTime="0.0" length="0.0" keyNote="67" minNote="67"
maxNote="67" gainDb="1.0" pan="0.0"/>
</PLUGIN>
</PLUGININSTANCE>
<CONNECTION src="0" dst="1096" srcPin="1" dstPin="1"/>
<CONNECTION src="0" dst="1096" srcPin="2" dstPin="2"/>
<CONNECTION src="1096" dst="0" srcPin="1" dstPin="1"/>
<CONNECTION src="1096" dst="0" srcPin="2" dstPin="2"/>
<CONNECTION src="0" dst="1096" srcPin="0" dstPin="0"/>
<CONNECTION src="1096" dst="0" srcPin="0" dstPin="0"/>
</RACK>
dave96
February 14, 2022, 11:14am
2
Yeah, unfortunately we don’t have an “engine” way of saving and loading complex presets like this as it’s really up to you app behaviour. You could copy all the samples in to a directory along with the preset and unpack them when the preset is loaded, or you could copy the audio files in to the preset as base64 encoded strings.
But how you do that and distribute it are really up to you I’m afraid.
You might also have extra meta data you want to store in the preset for populating your own browser etc. (this is what we do in Waveform).
ToxVox
February 21, 2022, 10:51am
3
Ok, I have decided to convert all files to base64. But after loading the preset file I’m getting error (bad access) when accessing the te::SamplerPlugin
. Seems like a dangling behaviour because the error is coming in different places.
Here is the "preset file"
<?xml version="1.0" encoding="UTF-8"?>
<RACK id="1094" name="PresetRack">
<MACROPARAMETERS id="1095"/>
<WINDOWSTATE windowPos=""/>
<OUTPUT name="midi output"/>
<OUTPUT name="output 1 (left)"/>
<OUTPUT name="output 2 (right)"/>
<INPUT name="midi input"/>
<INPUT name="input 1 (left)"/>
<INPUT name="input 2 (right)"/>
<MODIFIERS/>
<PLUGININSTANCE x="1.0" y="1.0">
<PLUGIN type="sampler" windowLocked="1" id="1096" enabled="1">
<MACROPARAMETERS id="1097"/>
<SOUND source="kick.ogg" name="kickPreset" startTime="0.0" length="0.1932426303854875"
keyNote="60" minNote="60" maxNote="60" gainDb="1.0" pan="0.0"
file="T2dnUwACAA...JDwA="/>
<SOUND source="snare.ogg" name="snarePreset" startTime="0.0" length="0.2158730158730159"
keyNote="61" minNote="61" maxNote="61" gainDb="1.0" pan="0.0"
file="T2dnUwACAA...JDwA="/>
<SOUND source="closed_hat.ogg" name="closed_hatPreset" startTime="0.0"
length="0.1983673469387755" keyNote="62" minNote="62" maxNote="62"
gainDb="1.0" pan="0.0" file="T2dnUwACAA...JDwA="/>
<SOUND source="open_hat.ogg" name="open_hatPreset" startTime="0.0"
length="0.2083673469387755" keyNote="63" minNote="63" maxNote="63"
gainDb="1.0" pan="0.0" file="T2dnUwACAA...JDwA="/>
<SOUND source="clap.ogg" name="clapPreset" startTime="0.0" length="0.1026984126984127"
keyNote="64" minNote="64" maxNote="64" gainDb="1.0" pan="0.0"
file="T2dnUwACAA...JDwA="/>
<SOUND source="low_tom.ogg" name="low_tomPreset" startTime="0.0"
length="0.2233560090702948" keyNote="65" minNote="65" maxNote="65"
gainDb="1.0" pan="0.0" file="T2dnUwACAA...JDwA="/>
<SOUND source="high_tom.ogg" name="high_tomPreset" startTime="0.0"
length="0.1820861678004535" keyNote="66" minNote="66" maxNote="66"
gainDb="1.0" pan="0.0" file="T2dnUwACAA...JDwA="/>
<SOUND source="ride.ogg" name="ridePreset" startTime="0.0" length="0.1990249433106576"
keyNote="67" minNote="67" maxNote="67" gainDb="1.0" pan="0.0"
file="T2dnUwACAA...JDwA="/>
</PLUGIN>
</PLUGININSTANCE>
<CONNECTION src="0" dst="1096" srcPin="1" dstPin="1"/>
<CONNECTION src="0" dst="1096" srcPin="2" dstPin="2"/>
<CONNECTION src="1096" dst="0" srcPin="1" dstPin="1"/>
<CONNECTION src="1096" dst="0" srcPin="2" dstPin="2"/>
<CONNECTION src="0" dst="1096" srcPin="0" dstPin="0"/>
<CONNECTION src="1096" dst="0" srcPin="0" dstPin="0"/>
</RACK>
The preset file loading function
te::RackInstance* createRackPluginFromPresetFile(juce::File presetFile) override
{
auto xmlPreset = presetFile.loadFileAsString();
auto valueTreePreset = juce::ValueTree::fromXml(xmlPreset);
for(auto child : valueTreePreset.getChildWithName(te::IDs::PLUGININSTANCE))
{
if(child.getType() == te::IDs::PLUGIN)
{
DBG("PLUGIN - type: " + child.getProperty(te::IDs::type).toString());
for(auto child2 : child)
{
if(child2.getType() == te::IDs::SOUND)
{
DBG("SOUND - name: " + child2.getProperty(te::IDs::name).toString());
if( child2.getProperty(te::IDs::source) != "")
{
// ToDo: Convert base64 to wave file
child2.removeProperty(te::IDs::file, nullptr) ;
}
}
}
}
}
auto rackType = edit->getRackList().addRackTypeFrom(valueTreePreset);
for(auto plugin : rackType->getPlugins())
{
DBG("Rack " + rackType->rackName + " with plugin: " + plugin->getName());
if(auto sampler = dynamic_cast<te::SamplerPlugin*>(plugin))
{
DBG("Plugin state dump: " + sampler->state.toXmlString());
}
}
auto rackInstanceCreationInfo = te::RackInstance::create (*rackType);
rackPlugin = dynamic_cast<te::RackInstance*> (edit->getPluginCache().createNewPlugin (rackInstanceCreationInfo).get());
return rackPlugin;
}
I add the rackPlugin to an AudioTrack
auto rackPlugin = instrument->createRackPluginFromPresetFile(presetFile);
audioTrack->pluginList.insertPlugin (rackPlugin, pluginIndex, nullptr);
Here is the debug output
PLUGIN - type: sampler
SOUND - name: kickPreset
SOUND - name: snarePreset
SOUND - name: closed_hatPreset
SOUND - name: open_hatPreset
SOUND - name: clapPreset
SOUND - name: low_tomPreset
SOUND - name: high_tomPreset
SOUND - name: ridePreset
Rack PresetRack V1.0 with plugin: Sampler
Plugin state dump: <?xml version="1.0" encoding="UTF-8"?>
<PLUGIN type="sampler" windowLocked="1" id="1096" enabled="1">
<MACROPARAMETERS id="1097"/>
<SOUND source="kick.ogg" name="kickPreset" startTime="0.0" length="0.1932426303854875"
keyNote="60" minNote="60" maxNote="60" gainDb="1.0" pan="0.0"/>
<SOUND source="snare.ogg" name="snarePreset" startTime="0.0" length="0.2158730158730159"
keyNote="61" minNote="61" maxNote="61" gainDb="1.0" pan="0.0"/>
<SOUND source="closed_hat.ogg" name="closed_hatPreset" startTime="0.0"
length="0.1983673469387755" keyNote="62" minNote="62" maxNote="62"
gainDb="1.0" pan="0.0"/>
<SOUND source="open_hat.ogg" name="open_hatPreset" startTime="0.0" length="0.2083673469387755"
keyNote="63" minNote="63" maxNote="63" gainDb="1.0" pan="0.0"/>
<SOUND source="clap.ogg" name="clapPreset" startTime="0.0" length="0.1026984126984127"
keyNote="64" minNote="64" maxNote="64" gainDb="1.0" pan="0.0"/>
<SOUND source="low_tom.ogg" name="low_tomPreset" startTime="0.0" length="0.2233560090702948"
keyNote="65" minNote="65" maxNote="65" gainDb="1.0" pan="0.0"/>
<SOUND source="high_tom.ogg" name="high_tomPreset" startTime="0.0" length="0.1820861678004535"
keyNote="66" minNote="66" maxNote="66" gainDb="1.0" pan="0.0"/>
<SOUND source="ride.ogg" name="ridePreset" startTime="0.0" length="0.1990249433106576"
keyNote="67" minNote="67" maxNote="67" gainDb="1.0" pan="0.0"/>
</PLUGIN>
Is there some additional triggers or something else to call?
dave96
February 21, 2022, 11:50am
4
There’s a bit too much going on there for me to follow. What is actually crashing? If you’re dereferencing a nullptr, where are you getting the pointer from? Have to stepped in to what is returning the pointer to see why it’s failing?
ToxVox
February 22, 2022, 9:30am
5
The crashes had other reasons. I was able to fix the crash behaviour.
My saving / loading preset functions for racks are working like I want:
te::RackInstance* createRackPluginFromPresetFile(juce::File presetFile) override
{
auto xmlPreset = presetFile.loadFileAsString();
auto valueTreePreset = juce::ValueTree::fromXml(xmlPreset);
auto tempDir = edit->getTempDirectory(true);
DBG("TempDir: " + tempDir.getFullPathName());
for(auto valueTreePlugins : valueTreePreset.getChildWithName(te::IDs::PLUGININSTANCE))
{
if(valueTreePlugins.getType() == te::IDs::PLUGIN)
{
DBG("PLUGIN - type: " + valueTreePlugins.getProperty(te::IDs::type).toString());
for(auto valueTreeSounds : valueTreePlugins)
{
if(valueTreeSounds.getType() == te::IDs::SOUND)
{
DBG("SOUND - name: " + valueTreeSounds.getProperty(te::IDs::name).toString());
if( valueTreeSounds.getProperty(te::IDs::source) != "")
{
juce::String fileBase64 = valueTreeSounds.getProperty(te::IDs::file).toString();
if (fileBase64.isNotEmpty())
{
MemoryOutputStream outputStream;
auto soundFile = juce::File(tempDir.getChildFile(valueTreeSounds.getProperty(te::IDs::source).toString()));
auto fileOutputStream = juce::FileOutputStream(soundFile);
if (Base64::convertFromBase64(outputStream, fileBase64))
{
fileOutputStream.write(outputStream.getData(), outputStream.getDataSize());
valueTreeSounds.setProperty(te::IDs::source, soundFile.getFullPathName(), nullptr);
DBG("Converting Success: " + soundFile.getFullPathName());
}
else
{
DBG("Converting Error: " + soundFile.getFullPathName());
}
}
valueTreeSounds.removeProperty(te::IDs::file, nullptr) ;
}
}
}
}
}
auto rackType = edit->getRackList().addRackTypeFrom(valueTreePreset);
auto rackInstanceCreationInfo = te::RackInstance::create (*rackType);
rackPlugin = dynamic_cast<te::RackInstance*> (edit->getPluginCache().createNewPlugin (rackInstanceCreationInfo).get());
return rackPlugin;
}
void saveAsPresetFile(juce::File presetFile) override
{
for ( auto rackType : edit->getRackList().getTypes() )
{
if(rackType == getRackType())
{
DBG("# Rack to save: " + rackType->rackName);
for(auto plugin : rackType->getPlugins())
{
DBG("## Plugin of the rack: " + plugin->getName());
for(auto child : plugin->state)
{
if(child.getType() == te::IDs::SOUND)
{
if( child.getProperty(te::IDs::source) != "")
{
auto source = edit->filePathResolver( child.getProperty(te::IDs::source) );
DBG( "### SourceFile: " + source.getFullPathName() );
child.setProperty ( te::IDs::source, source.getFileName(), nullptr);
std::unique_ptr<InputStream> inStream( source.createInputStream() );
MemoryBlock memoryBlock;
inStream->readIntoMemoryBlock(memoryBlock);
MemoryOutputStream memoryOutputStream;
Base64::convertToBase64(memoryOutputStream, memoryBlock.getData(), memoryBlock.getSize());
juce::String fileBase64 = memoryOutputStream.toString();
child.setProperty ( te::IDs::file, fileBase64, nullptr);
}
}
}
}
DBG("PresetXML - Save: " + rackType->state.toXmlString());
auto xmlFile = rackType->state.createXml();
xmlFile->writeTo(presetFile);
return;
}
}
}