Fundamentals
Export Targets
Code Export
Patcher UI
Special Topics
RNBO Raspberry Pi OSCQuery Runner
Working with Presets C++
If you define snapshots on a rnbo~ object, those snapshots will appear as presets in a presets.json
file along with your exported C++ source code. The contents of this file are not automatically loaded by the corresponding C++ source code. However, the RNBO::PresetList
object can be used to easily parse the file's contents.
std::string presetsAsRawText = somehowLoadAFile("presets.json");
RNBO::PresetList presetList(presetsAsRawText);
// Iterate through the preset names and print them
for (int i = 0; i < presetList.size(); i++) {
auto name = presetList.presetNameAtIndex(i);
std::cout << i << ": " << name << "\n";
}
// Load a preset with a given name
auto preset = presetList.presetWithName("funky");
rnboObject.setPreset(std::move(preset));
It's worth pointing out that the preset
returned from PresetList::presetWithName
and PresetList::presetAtIndex
is a UniquePresetPtr
— a copy of the preset in the list itself, wrapped in a unique pointer. That means that once you pass the pointer to the RNBO core object with setPreset
and std::move
, you don't need to worry about managing its memory anymore. One small caveat is that, unlike calling setParameterValue
, calling setPreset
doesn't update the interface value of a parameter until 15 milliseconds have passed. The preset is applied immediately, but if you want to read the preset value back, you'll need to wait until the preset value bubbles up to the interface layer.
coreObject.setParameterValue(0, 74);
coreObject.getParameterValue(0); // Parameter is now 74
coreObject.setPreset(presetInWhichTheParameterIsSeventyFive);
coreObject.getParamtereValue(0); // Parameter is still 74
// You need to wait 15 milliseconds for the value to bubble up after you apply a preset.
// Process again to bubble the parameter values through
for (int i = 0; i < 800; i += 64) { // this will be about 18 milliseconds at 44.1 kHz
rnboObject.process(inputs, 0, outputs.data(), outputs.size(), 64);
}
coreObject.getParameterValue(0); // Parameter is now 75
Storing Presets
The current state of a RNBO core object is also available at any time as a Preset
. There are two methods for getting a preset: getPreset
and getPresetSync
. The synchronous version returns its value immediately, but will block the process
function and thus potentially block the audio thread. Depending on your application, it might be better to use the asynchronous getPreset
function, which will copy the current state of the patcher and enter a callback function when it is safe to do so. Using the getPreset
function might look something like this:
#include <iostream>
#include "RNBO.h"
using namespace RNBO;
int main(int argc, const char * argv[]) {
const size_t nframes = 64;
CoreObject rnboObject;
std::array<SampleValue, nframes> channel;
std::array<SampleValue*, 1> outputs = { channel.data() };
rnboObject.prepareToProcess(44100, nframes);
// Print the values of all parameters before applying the preset
std::cout << "initial parameter states: \n";
for (int i = 0; i < rnboObject.getNumParameters(); i++) {
std::cout << i << ": " << rnboObject.getParameterId(i) << " " << rnboObject.getParameterValue(i) << "\n";
}
// Get the current state as a preset
ConstPresetPtr newPreset = rnboObject.getPresetSync();
// Alternatively, use the asynchronous preset getter, which requires a callback
// rnboObject.getPreset(callback);
// Make some changes to the parameter state
rnboObject.setParameterValue(0, 1);
// Process the change
rnboObject.process(nullptr, 0, outputs.data(), outputs.size(), nframes);
// See that the parameters have indeed changed
std::cout << "updated parameter states: \n";
for (int i = 0; i < rnboObject.getNumParameters(); i++) {
std::cout << i << ": " << rnboObject.getParameterId(i) << " " << rnboObject.getParameterValue(i) << "\n";
}
// Copy the preset and apply it
UniquePresetPtr uniquePreset = make_unique<Preset>();
copyPreset(*newPreset, *uniquePreset);
rnboObject.setPreset(std::move(uniquePreset));
// Process to handle the PresetEvent
rnboObject.process(nullptr, 0, outputs.data(), outputs.size(), nframes);
// As before, wait 15 milliseconds for the parameter change to bubble up to the interface layer
for (int i = 0; i < 800; i += nframes) { // This will be about 18 milliseconds at 44.1 kHz
rnboObject.process(nullptr, 0, outputs.data(), outputs.size(), nframes);
}
// See that the parameters have indeed changed back
std::cout << "restored parameter states: \n";
for (int i = 0; i < rnboObject.getNumParameters(); i++) {
std::cout << i << ": " << rnboObject.getParameterId(i) << " " << rnboObject.getParameterValue(i) << "\n";
}
return 0;
}
Creating Presets Programmatically
A RNBO::Preset
is also a RNBO::PatcherState
, and as long as you follow the internal conventions for representing PatcherState, it's perfectly possible to create a preset programmatically. A simple example might look like this:
#include <iostream>
#include "RNBO.h"
using namespace RNBO;
int main(int argc, const char * argv[]) {
const size_t nframes = 64;
CoreObject rnboObject;
SampleValue** inputs = nullptr;
std::array<SampleValue, nframes> channel;
std::array<SampleValue*, 1> outputs = { channel.data() };
rnboObject.prepareToProcess(44100, nframes);
// Create your own preset
PresetPtr customPreset = std::make_shared<Preset>();
// Print the values of all parameters before applying the preset
std::cout << "parameter values before applying preset: \n";
for (int i = 0; i < rnboObject.getNumParameters(); i++) {
std::cout << i << ": " << rnboObject.getParameterId(i) << " " << rnboObject.getParameterValue(i) << "\n";
}
// Set the value of a parameter in the preset
PatcherState& state = (*customPreset)["height"];
state["value"] = 9.0;
// Set the value of a parameter in a named subpatcher
PatcherState& subpatcherStateContainer = (*customPreset)["__sps"];
PatcherState& subpatcherState = subpatcherStateContainer["sub"];
PatcherState& weightParameterState = subpatcherState["weight"];
weightParameterState["value"] = 1.0;
// Send the preset to the core object
// Note you may want to make a copy here, since after the call to std::move, the core object will own the preset
auto uniquePreset = make_unique<Preset>();
RNBO::copyPreset(*customPreset, *uniquePreset);
rnboObject.setPreset(std::move(uniquePreset));
// Process once to apply the preset
rnboObject.process(inputs, 0, outputs.data(), outputs.size(), nframes);
// Secretly, RNBO must wait a few (15 as of now) milliseconds before pushing
// parameter values from the engine up to the interface. The preset is applied
// immediately, but we must wait at least 15 milliseconds to read the updated
// value back
// Process again to bubble the parameter values through
for (int i = 0; i < 800; i += nframes) {
rnboObject.process(inputs, 0, outputs.data(), outputs.size(), nframes);
}
// Print parameter values after applying preset
std::cout << "parameter values after applying preset: \n";
for (int i = 0; i < rnboObject.getNumParameters(); i++) {
std::cout << i << ": " << rnboObject.getParameterId(i) << " " << rnboObject.getParameterValue(i) << "\n";
}
return 0;
}