Learn Multiple RNBO Devices in one C++ Project

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

Multiple RNBO Devices in one C++ Project

Sometimes you need to work with multiple exported devices in one C++ project, or even one C++ file. We'll use CMake here for demonstration, but the steps are the same for any development environment. At a high level:

  • Use the Codegen: Classname configuration property of the C++ Source Code Export to choose a unique classname for each exported patcher.
  • Define the preprocessor macro RNBO_NO_PATCHERFACTORY to disable the declaration of the global patcher factory
  • Get a factory function for each device, which you can use to create a core object for each one.

First, when exporting your patch, use the Export Name and Codegen: Classname configuration options to choose a unique filename and classname for each exported patcher.

multiple-rnbo-devices-in-one-cpp-01.png

After exporting both files, you might have a folder structure like this one...

Now modify your CMakeLists.txt file to include both sources, and to define the RNBO_NO_PATCHERFACTORY preprocessor macro.

cmake_minimum_required(VERSION 3.10)

# Set the C++ standard to at least C++11, which is needed for RNBO
set (CMAKE_CXX_STANDARD 11)

# Set the project name
project(RNBOCommandLine)

# Add the main executable as well as the RNBO sources
add_executable(RNBOCommandLine main.cpp rnbo_effect.cpp rnbo_synth.cpp rnbo/RNBO.cpp)

# Add a preprocessor macro to disable the convenience definition of the global patcher factory
target_compile_definitions(RNBOCommandLine PRIVATE RNBO_NO_PATCHERFACTORY)

# Include the RNBO headers
target_include_directories(RNBOCommandLine PRIVATE rnbo rnbo/common)

Finally, create a main.cpp file that declares a factory function for each device class. The name of the factory function will be XXXFactoryFunction, where XXX is the value of the Codegen: Classname configuration option.

#include <iostream>
#include "RNBO.h"

using namespace RNBO;

// First you have to expose the factory functions with extern “C” declarations:
extern "C" PatcherFactoryFunctionPtr rnbosynthFactoryFunction(PlatformInterface* platformInterface);
extern "C" PatcherFactoryFunctionPtr rnboeffectFactoryFunction(PlatformInterface* platformInterface);

CoreObject rnboEffectObject;

int main(int argc, const char * argv[]) {

	auto synthPatcherInterface = rnbosynthFactoryFunction(Platform::get())();
	auto effectPatcherInterface = rnboeffectFactoryFunction(Platform::get())();

  // You can either construct the CoreObject directly with
  // the PatcherInterface you get from the factory function
	CoreObject rnboSynthObject((UniquePtr<PatcherInterface>(synthPatcherInterface)));
	rnboSynthObject.prepareToProcess(44100, 64);

  // Or you have the CoreObject already declared somewhere else
  // and call setPatcher with the PatcherInterface you get from
  // the factory function
	rnboEffectObject.setPatcher((UniquePtr<PatcherInterface>(effectPatcherInterface)));
	rnboEffectObject.prepareToProcess(44100, 64);

    // Make buffers to hold your inputs and outputs
	SampleValue** dummyInputs = nullptr;
	SampleValue** inputs = new SampleValue*[1];
    inputs[0] = new double[64];
	SampleValue** outputs = new SampleValue*[1];
    outputs[0] = new double[64];

    // Process the synth, feed it to the effect, print the result
    rnboSynthObject.process(dummyInputs, 0, inputs, 1, 64);
    rnboEffectObject.process(inputs, 1, outputs, 1, 64);

	for (int i = 0; i < 64; i++) {
		std::cout << outputs[0][i] << "\n";
	} 

    delete [] inputs[0];
    delete [] outputs[0];
    delete [] inputs;
    delete [] outputs;

    return 0;
}

You can simply use them to instantiate a core object. If you already have a core object, you can also call the setPatcher function, passing the PatcherInterface that comes from the factory function.

CoreObject rnboeffectFactoryFunction;
rnboeffectFactoryFunction.setPatcher((UniquePtr<PatcherInterface>(effectPatcherInterface)));