Learn Working with Presets in C++

Getting Started

Welcome to RNBO

Quickstart

RNBO Basics

Key Differences

Why We Made RNBO

Fundamentals

Audio IO

Messages to rnbo~

Using Parameters

MIDI in RNBO

Messages and Ports

Polyphony and Voice Control

Audio Files in RNBO

Using Buffers

Using the FFT

Export Targets

Export Targets Overview

VST/AudioUnit
Max External Target
Raspberry Pi Target
The Web Export Target
The C++ Source Code Target

Code Export

Working with JavaScript
Working with C++

How to Include RNBO in Your C++ Project

RNBO Helper Types

Getting and Setting Parameters in C++

Sending and Receiving Messages in C++

Sending and Receiving MIDI in C++

Multiple RNBO Devices in one C++ Project

Working with Presets in C++

Loading File Dependencies

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;
}