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++

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 using the libsndfile C++ API
SndfileHandle sndfile(filepath);

Next, fetch the audio samples themselves.

// Make space to store the file
const uint32_t numItems = sndfile.frames() * sndfile.channels();
float* buffer = new float[numItems];

// Read the audio samples using libsndfile
sf_count_t readSamples = sndfile.readf(buffer, sndfile.frames());

Finally, pass the sample data to setExternalData.

// Use the file handle to initialize the buffer type struct
RNBO::Float32AudioBuffer bufferType(sndfile.channels(), sndfile.samplerate());

// Give the memory to RNBO
rnboObject.setExternalData(
  idstr.c_str(),
  reinterpret_cast<char *>(buffer),
  readSamples * sizeof(float),
  bufferType,
  [buffer] (RNBO::ExternalDataId, char*) {
    delete[] buffer; //release the memory
  }
);

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.14)

project(RNBOCommandLine LANGUAGES CXX)

# --------------------------------------------------------------------
# Tooling / IntelliSense
# --------------------------------------------------------------------
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# RNBO requires at least C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent)

# --------------------------------------------------------------------
# libsndfile via FetchContent
# --------------------------------------------------------------------
FetchContent_Declare(
    libsndfile
    GIT_REPOSITORY https://github.com/libsndfile/libsndfile.git
    GIT_TAG        1.2.2   # or any version you prefer
)

# Optional: disable features you don't need to reduce build time
set(BUILD_PROGRAMS OFF CACHE BOOL "" FORCE)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(ENABLE_EXTERNAL_LIBS OFF CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(libsndfile)

# libsndfile provides the target: sndfile
# --------------------------------------------------------------------

# Main executable
add_executable(RNBOCommandLine
    main.cpp
    export/sound-file-mixer.cpp
    export/rnbo/RNBO.cpp
)

# RNBO headers
target_include_directories(RNBOCommandLine
    PRIVATE
        export/rnbo
        export/rnbo/common
)

# Link against libsndfile
target_link_libraries(RNBOCommandLine
    PRIVATE
        sndfile
)

# --------------------------------------------------------------------
# macOS system frameworks
# --------------------------------------------------------------------
if(APPLE)
    find_library(CORE_FOUNDATION Foundation)
    find_library(APPLICATION_SERVICES ApplicationServices)
    find_library(CORE_SERVICES CoreServices)

    target_link_libraries(RNBOCommandLine
        PRIVATE
            ${CORE_FOUNDATION}
            ${APPLICATION_SERVICES}
            ${CORE_SERVICES}
    )
endif()

# --------------------------------------------------------------------
# Copy the executable up one level after build
# --------------------------------------------------------------------
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 "src/RNBO_CoreObject.h"
#include "src/RNBO_DataRefList.h"
#include <iostream>
#include <RNBO.h>

#include <unistd.h>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <sndfile.hh>

using RNBO::CoreObject;
using RNBO::DataRefList;
using RNBO::DataRefType;
using RNBO::ExternalDataId;

int main(int argc, const char * argv[]) {
    CoreObject rnboObject;
    
    // Read in the dependencies.json file as a std::string
    std::ifstream t("export/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";
            
            // Add the export prefix to the path
            std::string filepath = "export/" + list.datarefLocationAtIndex(i);

            SndfileHandle sndfile(filepath);
            
            if (sndfile) {
                // Use the file handle to make a type
                RNBO::Float32AudioBuffer bufferType(sndfile.channels(), sndfile.samplerate());

                // Make space to store the file
                const uint32_t numItems = sndfile.frames() * sndfile.channels();
                float* buffer = new float[numItems];
                
                // Read the audio samples in
                sf_count_t readSamples = sndfile.readf(buffer, sndfile.frames());
                
                // Give the memory to RNBO
                rnboObject.setExternalData(
                    idstr.c_str(),
                    reinterpret_cast<char *>(buffer),
                    readSamples * sizeof(float),
                    bufferType,
                    [buffer] (RNBO::ExternalDataId, char*) {
                        delete[] buffer; //release the memory
                    }
                );
                std::cout << "--- Success: Read " << readSamples << " samples" << "\n";
            } else {
                std::cout << "--- Failed" << "\n";
            }
        }
    }
    
    const int sampleRate = 44100;
    const int blockSize = 64;
    const int totalSamples = 44100; // 1 second of audio
    const int numBlocks = (totalSamples + blockSize - 1) / blockSize;

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

    // Buffer to accumulate all output samples
    std::vector<float> outputBuffer(totalSamples);
    int samplesWritten = 0;

    for (int i = 0; i < numBlocks && samplesWritten < totalSamples; i++) {
        rnboObject.process(inputs, 0, outputs, 1, blockSize);

        // Copy samples to output buffer
        int samplesToCopy = std::min(blockSize, totalSamples - samplesWritten);
        for (int j = 0; j < samplesToCopy; j++) {
            outputBuffer[samplesWritten + j] = static_cast<float>(outputs[0][j]);
        }
        samplesWritten += samplesToCopy;
    }

    // Write output to AIFF file
    SndfileHandle outFile("./output.aif", SFM_WRITE, SF_FORMAT_AIFF | SF_FORMAT_PCM_16, 1, sampleRate);
    if (outFile) {
        outFile.write(outputBuffer.data(), totalSamples);
        std::cout << "\nWrote " << totalSamples << " samples to ./output.aif\n";
    } else {
        std::cerr << "\nFailed to create output file\n";
    }

    delete [] outputs[0];
    delete [] outputs;
    
    return 0;
}

Materials in this article