Learn Loading File Dependencies

Getting Started

Welcome to RNBO

Quickstart

RNBO Basics

Key Differences

Why We Made RNBO

Coding Resources

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

Special Topics

Sample Accurate Patching
Scala and Custom Tuning

RNBO and Max for Live

RNBO Raspberry Pi OSCQuery Runner

Metadata

Export Description

Raspberry Pi GPIO

Updating the RNBO Package

Loading File Dependencies

Parsing RNBO file dependencies and loading audio files

You can load audio data into a RNBO buffer using the setExternalData function. This function requires an ID for the buffer, an array of decoded audio data, and an object specifying the format of the data.

Parsing the Exported Dependencies File

When you configure your C++ export, any dependencies on files or URL resources will be collected into a dependencies.json file. In addition, if you enabled the "Copy Sample Dependencies" option, audio samples will also be copied to a folder called media.

loading-file-dependencies-01.png

The dependencies.json file contains information about sample dependencies in your patcher. For example, a patch containing an object like [buffer~ anton @file anton.aif] will generate the following dependency file:

[
  {
    "id": "anton",
    "file": "media/anton.aif"
  }
]

RNBO provides a convenience class for parsing this file called DataRefList. This class makes it easy to iterate over the data reference dependencies and pull out useful information about each one.

#include <iostream>
#include <unistd.h>
#include <fstream>
#include <sstream>

// Read in the dependencies.json file as a std::string
std::ifstream t("../dependencies.json");
std::stringstream buffer;
buffer << t.rdbuf();

// Parse dependencies into a RNBO DataRefList
DataRefList list(buffer.str());

for (int i = 0; i < list.size(); i++) {
  // Get parsed info about this data reference
  std::string idstr = list.datarefIdAtIndex(i);
  DataRefType type = list.datarefTypeAtIndex(i);
  std::string location = list.datarefLocationAtIndex(i);

  std::cout << idstr << " - " << type << " - " << location << "\n";
}

Decoding Audio Data

Unless it's stored in a raw format like .raw or .pcm, audio file data will have some encoding that must be decoded before it can be passed to setExternalData. RNBO does not include any convenience functions for loading and decoding files off of disk, or for streaming files from a remote endpoint. However, there are plenty of libraries that can provide this functionality. JUCE is one such framework. This example uses libsndfile, which is available under the Gnu Lesser General Public License.

First, open the audio file reading its format info.

// Assume this list is an instance of DataRefList
std::string filepath = list.datarefLocationAtIndex(0);

// Open the audio file
SF_INFO info;
info.format = 0;
SNDFILE *sf = sf_open(filepath.c_str(), SFM_READ, &info);

Next, fetch the audio samples themselves.

const uint32_t sampleBufferSize = sizeof(float) * info.frames * info.channels;
float *sampleBuffer = (float *) malloc(sampleBufferSize);
int readSamples = sf_read_float(sf, sampleBuffer, sampleBufferSize);

Finally, pass the sample data to setExternalData.

// since we used malloc to allocate our buffer, we pass a callback that uses
// free to release buffer memory when RNBO is done with it.
static void freeBuffer(RNBO::ExternalDataId id,char *data) {
  free(data);
}

// info is filled in by sf_open
RNBO::Float32AudioBuffer bufferType(info.channels, info.samplerate);

rnboObject.setExternalData(
  list.datarefIdAtIndex.c_str(),
  (char *) sampleBuffer,
  readSamples * sizeof(float) / sizeof(char),
  bufferType,
  &freeBuffer
);

CMake Configuration

If you're also using CMake with libsndfile, you could use a CMakeList.txt file like this one:

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_buffers.cpp rnbo/RNBO.cpp)

# Include the headers and library files for sndlib, to decode wav
target_link_libraries(RNBOCommandLine PUBLIC ${CMAKE_SOURCE_DIR}/libsndfile-1.0.28/src/.libs/libsndfile.a)
target_include_directories(RNBOCommandLine PUBLIC libsndfile-1.0.28/src)

# Needed to compile and run on os x
find_library(CORE_FOUNDATION Foundation)
find_library(APPLICATION_SERVICES ApplicationServices)
find_library(CORE_SERVICES CoreServices)

set(EXTRA_LIBS ${CORE_FOUNDATION} ${APPLICATION_SERVICES} ${CORE_SERVICES})

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

# Copy the executable up one level
add_custom_command(TARGET RNBOCommandLine 
    POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:RNBOCommandLine> ${CMAKE_SOURCE_DIR})

Note that this also copies the executable up one level.

Putting it All Together

The following code could be used to parse a dependencies.json file, iterate through it, load each file dependency, and load that data into a RNBO buffer. Note that this code ignores URL dependencies, which would have to be fetched from a remote endpoint using a network request. It also assumes that the current working directory of the program executable is the same as the folder into which you exported your RNBO patch.

#include <iostream>
#include <memory>
#include <atomic>
#include <RNBO.h>

#include <unistd.h>
#include <fstream>
#include <sstream>
#include <sndfile.h>

using namespace RNBO;

// since we used malloc to allocate our buffer, we pass a callback that uses
// free to release buffer memory when RNBO is done with it.
static void freeBuffer(ExternalDataId id, char *data) {
  std::cout << "--- Freed" << "\n";
  free(data);
}

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

  // Read in the dependencies.json file as a std::string
  std::ifstream t("dependencies.json");
  std::stringstream buffer;
  buffer << t.rdbuf();

  // Parse dependencies into a RNBO DataRefList
  DataRefList list(buffer.str());

  // Loop and load
  for (int i = 0; i < list.size(); i++) {
    // Get parsed info about this data reference
    std::string idstr = list.datarefIdAtIndex(i);
    DataRefType type = list.datarefTypeAtIndex(i);
    std::string location = list.datarefLocationAtIndex(i);

    // The type can be either URL or File
    if (type == DataRefType::File) {
      std::cout << "buffer id: " << idstr << "\n";
      std::cout << "file path: " << list.datarefLocationAtIndex(i) << "\n";

      std::string filepath = list.datarefLocationAtIndex(i);

      SF_INFO info;
      info.format = 0;
      SNDFILE *sf = sf_open(filepath.c_str(), SFM_READ, &info);

      // Use the file info to make a type
      RNBO::Float32AudioBuffer bufferType(info.channels, info.samplerate);

      if (sf) {
        // Make space to store the file
        const uint32_t sampleBufferSize = sizeof(float) * info.frames * info.channels;
        float *sampleBuffer = (float *) malloc(sampleBufferSize);

        int readSamples = sf_read_float(sf, (float *) sampleBuffer, sampleBufferSize);

        rnboObject.setExternalData(
          idstr.c_str(),
          (char *) sampleBuffer,
          readSamples * sizeof(float) / sizeof(char),
          bufferType,
          &freeBuffer
        );
        std::cout << "--- Success: Read " << readSamples << " samples" << "\n";

        sf_close(sf);
      } else {
        std::cout << "--- Failed" << "\n";
      }
    }
  }

  rnboObject.prepareToProcess(44100, 64);
  RNBO::SampleValue** inputs = nullptr;
  RNBO::SampleValue** outputs = new RNBO::SampleValue*[1];
  outputs[0] = new double[64];

  for (int i = 0; i < 100; i++) {
    rnboObject.process(inputs, 0, outputs, 1, 64);
  }

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

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

  return 0;
}

Materials in this article