Getting Started
VelociLoops is a static C library. It has no external dependencies beyond the C++ standard library and CMake 3.20+.
Building
# Configure (static library + demo executable)
cmake -B build .
# Build
cmake --build build
# Run the demo (extract slices from a .rx2 file)
./build/demo/velociloops_demo data/120Stereo.rx2 slices/
To open an Xcode project on macOS:
cmake -G Xcode -B build .
open build/VelociLoops.xcodeproj
Linking against VelociLoops
Add the repository as a CMake subdirectory and link against the static library:
add_subdirectory(VelociLoops)
target_link_libraries(my_app PRIVATE velociloops_library)
Reading a REX2 file
#include "velociloops.h"
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
VLError err;
VLFile file = vl_open("loop.rx2", &err);
if (!file)
{
fprintf(stderr, "open failed: %s\n", vl_error_string(err));
return 1;
}
/* File-level metadata */
VLFileInfo info;
vl_get_info(file, &info);
printf("channels=%d rate=%d tempo=%.3f BPM slices=%d\n",
info.channels, info.sample_rate,
info.tempo / 1000.0, info.slice_count);
/* Decode each slice to float buffers */
for (int i = 0; i < info.slice_count; ++i)
{
int32_t n = vl_get_slice_frame_count(file, i);
float *L = malloc(n * sizeof(float));
float *R = malloc(n * sizeof(float)); /* NULL for mono-only */
int32_t written;
VLError e = vl_decode_slice(file, i, L, R, 0, n, &written);
if (e == VL_OK)
printf(" slice %d: %d frames\n", i, written);
/* use L / R here … */
free(L);
free(R);
}
vl_close(file);
return 0;
}
Creating a REX2 file
#include "velociloops.h"
/* Assume `left` and `right` are float[frame_count] filled with audio data. */
void write_example(const float* left, const float* right, int32_t frame_count)
{
/* 1. Create handle: stereo, 44.1 kHz, 120 BPM */
VLError err;
VLFile file = vl_create_new(2, 44100, 120000, &err);
if (!file) return;
/* 2. Optionally set creator metadata before adding slices */
VLCreatorInfo ci = {0};
snprintf(ci.name, sizeof(ci.name), "My Plugin");
snprintf(ci.url, sizeof(ci.url), "https://example.com");
vl_set_creator_info(file, &ci);
/* 3. Add slices in ascending ppq_pos order */
/* ppq_pos=0 places the first slice at the very start of the loop */
int32_t idx = vl_add_slice(file, 0, left, right, frame_count);
if (idx < 0)
{
vl_close(file);
return;
}
/* 4. Save */
vl_save(file, "out.rx2");
/* 5. Clean up */
vl_close(file);
}
Error handling
Every function that can fail returns a VLError. Functions that return a
handle (vl_open, vl_create_new) signal failure by returning NULL and
optionally writing the error code to an err out-parameter.
VLError err;
VLFile f = vl_open("loop.rx2", &err);
if (!f)
{
/* vl_error_string() never returns NULL */
fprintf(stderr, "error %d: %s\n", (int)err, vl_error_string(err));
return;
}
VLFileInfo info;
VLError e = vl_get_info(f, &info);
if (e != VL_OK)
fprintf(stderr, "get_info: %s\n", vl_error_string(e));
vl_close(f);
Realtime-safe slice reads
After a file has been opened or authored, vl_get_slice_info(),
vl_get_slice_frame_count(), and vl_decode_slice() perform no heap
allocation. They are suitable for audio-thread use when output structs and
buffers are preallocated, capacity is at least the frame count remaining from
the requested frame_offset, and no other thread mutates or closes the same
VLFile concurrently.
VLError quick reference
Code |
Value |
Meaning |
|---|---|---|
|
0 |
Success |
|
-1 |
NULL or closed VLFile |
|
-2 |
NULL pointer or out-of-range number |
|
-3 |
Path missing or unreadable |
|
-4 |
Malformed IFF/DWOP structure |
|
-5 |
Heap allocation failed |
|
-6 |
Slice index out of range |
|
-7 |
Unsupported sample rate |
|
-8 |
Buffer < |
|
-9 |
CREI chunk absent |
|
-10 |
Feature not yet functional |
|
-11 |
Metadata set after slices added |
|
-12 |
Unsupported newer file version |
|
-13 |
No usable loop length |
|
-14 |
Invalid or inconsistent size fields |
|
-15 |
Invalid tempo field |
Loading from memory
For embedded contexts or network streams where the file is already in memory:
const uint8_t *buf = /* … */;
size_t size = /* … */;
VLError err;
VLFile file = vl_open_from_memory(buf, size, &err);
/* buf may be freed after this call */
Serialising to memory
Use the two-call pattern to write a REX2 file into a heap buffer:
size_t sz = 0;
vl_save_to_memory(file, NULL, &sz); /* query required size */
uint8_t *buf = malloc(sz);
vl_save_to_memory(file, buf, &sz); /* write */
/* transmit or store buf[0..sz-1] */
free(buf);