Fundamentals
Export Targets
Code Export
Patcher UI
Special Topics
RNBO Raspberry Pi OSCQuery Runner
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.
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