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