libambix
the AMBIsonics eXchange library
Usage

Reading ambix files

When opening a file for read, the ambix_info_t struct should be set to 0. On successful open, all fields are filled by the library. If the open fails, the state of the ambix_info_t fields is undefined.

The only exception to this is, if you want to force the ambix file to be read as either "BASIC" or "EXTENDED", e.g. because you don't want to care about adaptor matrices or because you do. In this case you must set the fileformat field the requested format.

Reading BASIC ambix files

You can read any ambix file as "BASIC" by setting the 'fileformat' member of the ambix_info_t struct to AMBIX_BASIC prior to opening the file. This will automatically do any conversion needed, by pre-multiplying the raw ambisonics data with an embedded adaptor matrix.

Real "BASIC" files lack extra audio channels. However, when opening a file that is not a "BASIC" ambix file (e.g. an "EXTENDED" ambix file) as a "BASIC" one, by forcing the 'fileformat' member to 'AMBIX_BASIC', extra channels might be readable.

ambix_t*ambix = NULL;
ambix_info_t*info = calloc(1, sizeof(ambix_info_t));
ambix->fileformat = AMBIX_BASIC;
ambix = ambix_open("ambixfile.caf", AMBIX_READ, info);
if(ambix) {
uint64_t frames = info->frames;
uint64_t blocksize = 1024;
float32_t*ambidata = calloc(info->ambichannels * blocksize, sizeof(float32_t));
float32_t*extradata = calloc(info->extrachannels * blocksize, sizeof(float32_t));
while(frames>blocksize) {
uint64_t blocks = ambix_readf_float32(ambix, ambidata, extradata, blocksize);
// process blocks frames of interleaved ambidata and interleaved extradata
// ...
frames-=blocks;
}
ambix_readf_float32(ambix, ambidata, extradata, frames);
// process last block of interleaved ambidata and interleaved extradata
// ...
ambix_close(ambix);
free(ambidata);
free(extradata);
}
free(info);

Reading EXTENDED ambix files

You can read an ambix file as "EXTENDED" by setting the 'fileformat' member of the ambix_info_t struct to AMBIX_EXTENDED prior to opening the file. You will then have to retrieve the adaptor matrix from the file, in order to be able to reconstruct the full ambisonics set. You can also analyze the matrix to make educated guesses about the original channel layout.

ambix_t*ambix = NULL;
ambix_info_t*info = calloc(1, sizeof(ambix_info_t));
/* setting the fileformat to AMBIX_EXTENDED forces the ambi data
* be delivered as stored in the file
*/
ambix->fileformat = AMBIX_EXTENDED;
ambix = ambix_open("ambixfile.caf", AMBIX_READ, info);
if(ambix) {
uint64_t frames = info->frames;
uint64_t blocksize = 1024;
float32_t*ambidata = calloc(info->ambichannels * blocksize, sizeof(float32_t));
float32_t*extradata = calloc(info->extrachannels * blocksize, sizeof(float32_t));
const ambix_matrix_t*adaptormatrix=ambix_get_adaptormatrix(ambix);
while(frames>blocksize) {
uint64_t blocks = ambix_readf_float32(ambix, ambidata, extradata, blocksize);
// process blocks frames of interleaved ambidata and interleaved extradata,
// using the adaptormatrix
// ...
frames-=blocks;
}
ambix_readf_float32(ambix, ambidata, extradata, frames);
// process last block of interleaved ambidata and interleaved extradata
// using the adaptormatrix
// ...
ambix_close(ambix);
free(ambidata);
free(extradata);
}
free(info);
*

Reading any ambix files

If you do not specify the format prior to opening, you can query the format of the file from the ambix_info_t struct.

ambix_t*ambix = NULL;
ambix_info_t*info = calloc(1, sizeof(ambix_info_t)); /* initialize the format field (among others) to 0 */
ambix = ambix_open("ambixfile.caf", AMBIX_READ, info);
if(ambix) {
switch(ambix->fileformat) {
case(AMBIX_BASIC):
printf("this file is ambix basic\n");
break;
printf("this file is ambix extended\n");
break;
case(AMBIX_NONE):
printf("this file is not an ambix file\n");
break;
default:
printf("this file is of unknown format...\n");
}
ambix_close(ambix);
}
free(info);

Writing ambix files

To write data to an ambix file, you have to open it with the AMBIX_WRITE flag. You also need to specify some global properties of the output data, namely the samplerate and the sampleformat, as well as the number of ambisonics channels and the number of extra channels that are physically stored on the disk.

Writing BASIC ambix files

You can write "BASIC" ambix files by setting the 'fileformat' member of the ambix_info_t struct to AMBIX_BASIC prior to opening the file.

You will need to provide a full set of ambisonics channels when writing data to the file, and must not set an adaptor matrix (see also Writing EXTENDED ambix files using the BASIC interface). A full set of ambisonics must always satisfy the formula \(channels=(order_{ambi}+1)^2\).

You cannot write extra audio channels into a "BASIC" ambix file.

ambix_t*ambix = NULL;
ambix_info_t*info = calloc(1, sizeof(ambix_info_t));
/* need to specify samplerate and sampleformat */
ambix->samplerate = 44100;
ambix->sampleformat = AMBIX_SAMPLEFORMAT_PCM24;
ambix->fileformat = AMBIX_BASIC;
ambix->ambichannels = 16; /* 16 channels means 3rd order ambisonics, according to L=(2N+1)^2 */
ambix = ambix_open("ambixfile.caf", AMBIX_WRITE, info);
if(ambix) {
uint64_t frames = info->frames;
uint64_t blocksize = 1024;
uint64_t block;
float32_t*ambidata = calloc(info->ambichannels * blocksize, sizeof(float32_t));
while(haveData) {
// acquire blocksize samples of a full set of 3rd order ambisonics data (16 channels)
// into ambidata (interleaved)
// ...
block = ambix_writef_float32(ambix, ambidata, NULL, blocksize);
}
ambix_close(ambix);
free(ambidata);
}
free(info);

Writing EXTENDED ambix files

You can write "EXTENDED" ambix files by setting the 'fileformat' member of the ambix_info_t struct to AMBIX_EXTENDED prior to opening the file.

You MUST set an adaptormatrix (to convert the reduced set to a full ambisonics set) using ambix_set_adaptormatrix(). It gets written to disk prior to writing any samples to the file (or closing the ambix file). It is an error to call ambix_set_adaptormatrix() after starting to write samples.

ambix_t*ambix = NULL;
ambix_info_t*info = calloc(1, sizeof(ambix_info_t));
/* need to specify samplerate and sampleformat */
ambix->samplerate = 44100;
ambix->sampleformat = AMBIX_SAMPLEFORMAT_PCM24;
ambix->fileformat = AMBIX_EXTENDED;
ambix->ambichannels = 8; /* a reduced ambisonics set */
ambix->extrachannels = 1; /* an extrachannel, e.g. click-track */
ambix = ambix_open("ambixfile.caf", AMBIX_WRITE, info);
if(ambix) {
uint64_t frames = info->frames;
uint64_t blocksize = 1024;
uint64_t block;
float32_t*ambidata = calloc(info->ambichannels * blocksize, sizeof(float32_t));
float32_t*extradata = calloc(info->extrachannels * blocksize, sizeof(float32_t));
/* create an adaptormatrix: */
ambix_matrix_t adaptormatrix = {0, 0, NULL};
ambix_matrix_init(16, 8, &adaptormatrix);
// fill the adaptormatrix, that expands our 8 channels to a full 3D 3rd-order set (16 channels)
// ...
ambix_set_adapatormatrix(ambix, &adaptormatrix);
ambix_write_header(ambix);
while(haveData) {
// acquire blocksize samples of a full set of reduced ambisonics data (8 channels)
// into ambidata (interleaved), and a some (1) extra channels
// ...
block = ambix_writef_float32(ambix, ambidata, extradata, blocksize);
}
ambix_close(ambix);
ambix_matrix_deinit(&adaptormatrix);
free(ambidata);
free(extradata);
}
free(info);

Writing EXTENDED ambix files using the BASIC interface

Finally, you can create "EXTENDED" ambix files from a full set of ambisonics channels and an adaptor matrix. This can be useful if you have a setup that works with full ambisonics sets (e.g. a DAW project) and you want to create a size-optimized ambix file that only stores a reduced set.

This can be achieved by setting the 'fileformat' member of the ambix_info_t struct to AMBIX_BASIC (because you will provide the full ambisonics set as if the file were opened in "BASIC" mode) and the 'ambichannels' member to the (reduced) number of ambisonics channel as will be written to the disk, and then setting an adaptor matrix ambix_set_adaptormatrix(), that will convert the reduced set back to the full set. libambix will internally reduce the full ambisonics set (as passed to ambix_writef()) using the (pseudo) inverse of the adaptor-matrix.

Note
You must ensure yourself that the adaptor matrix is inversible.

The adaptor matrix gets written to disk prior to writing any samples to the file (or closing the ambix file). It is an error to call ambix_set_adaptormatrix() after starting to write samples.

Note
If you pass an adaptor matrix that expands a reduced set to full ambisonics (e.g. converting from 1st order horizontal-only ambisonics set to a 2nd order fully periphonic set) the reconstructed ambisonics channels (when reading the ambix file later), might be different from the ambisonics channels you passed in when writing the file (e.g. channels that contain 3rd-order and/or Z-axis components will be muted).
ambix_t*ambix = NULL;
ambix_info_t*info = calloc(1, sizeof(ambix_info_t));
/* need to specify samplerate and sampleformat */
info->samplerate = 44100;
info->ambichannels = 7; /* 3rd-order horizontal-only(!) */
ambix = ambix_open("ambixfile.caf", AMBIX_WRITE, info);
if(ambix) {
uint64_t fullambichannels = 16;
uint64_t frames = info->frames;
uint64_t blocksize = 1024;
uint64_t block;
float32_t*ambidata = calloc(fullambichannels * blocksize, sizeof(float32_t));
/* on disk, the data is stored as 3rd-order horizontal-only Furse-Malham
* set (7 channels); we need to provide an adaptor matrix that converts
* from the 7 FuMa-channels to the 16 AMBIX channels
*/
ambix_matrix_t*mtx = NULL;
mtx = ambix_matrix_init(fullambichannels, info->ambichannels, mtx);
mtx = ambix_matrix_fill(mtx, ABIX_MATRIX_FUMA); /* fuma->ambix matrix [16x7] */
while(haveData) {
// acquire blocksize samples of a full set of 3rd order ambisonics data (16 channels)
// into ambidata (interleaved)
// ...
block = ambix_write_float32(ambix, ambidata, NULL, blocksize);
}
ambix_close(ambix);
free(ambidata);
}
free(info);