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);
float32_t
float float32_t
Definition: ambix.h:41
ambix_matrix_init
AMBIX_API ambix_matrix_t * ambix_matrix_init(uint32_t rows, uint32_t cols, ambix_matrix_t *mtx)
Initialize a matrix.
ambix_t
struct ambix_t_struct ambix_t
Definition: ambix.h:74
ambix_info_t::samplerate
double samplerate
Definition: ambix.h:192
ambix_matrix_destroy
AMBIX_API void ambix_matrix_destroy(ambix_matrix_t *mtx)
Destroy a matrix.
AMBIX_BASIC
@ AMBIX_BASIC
Definition: ambix.h:117
ambix_info_t::fileformat
ambix_fileformat_t fileformat
Definition: ambix.h:209
ambix_info_t::ambichannels
uint32_t ambichannels
Definition: ambix.h:227
ambix_get_adaptormatrix
const AMBIX_API ambix_matrix_t * ambix_get_adaptormatrix(ambix_t *ambix)
Get the adaptor matrix.
ambix_close
AMBIX_API ambix_err_t ambix_close(ambix_t *ambix)
Close an ambix handle.
ambix_set_adaptormatrix
AMBIX_API ambix_err_t ambix_set_adaptormatrix(ambix_t *ambix, const ambix_matrix_t *matrix)
Set a matrix to be pre-multiplied.
AMBIX_NONE
@ AMBIX_NONE
Definition: ambix.h:115
ambix_readf_float32
AMBIX_API int64_t ambix_readf_float32(ambix_t *ambix, float32_t *ambidata, float32_t *otherdata, int64_t frames)
Read samples (as single precision floating point values) from the ambix file.
ambix_info_t
Definition: ambix.h:188
ambix_matrix_t
Definition: ambix.h:176
ambix_matrix_deinit
AMBIX_API void ambix_matrix_deinit(ambix_matrix_t *mtx)
De-initialize a matrix.
ambix_info_t::sampleformat
ambix_sampleformat_t sampleformat
Definition: ambix.h:194
ambix_open
AMBIX_API ambix_t * ambix_open(const char *path, const ambix_filemode_t mode, ambix_info_t *ambixinfo)
Open an ambix file.
AMBIX_EXTENDED
@ AMBIX_EXTENDED
Definition: ambix.h:119
ambix_info_t::extrachannels
uint32_t extrachannels
Definition: ambix.h:214
AMBIX_READ
@ AMBIX_READ
Definition: ambix.h:104
ambix_writef_float32
AMBIX_API int64_t ambix_writef_float32(ambix_t *ambix, const float32_t *ambidata, const float32_t *otherdata, int64_t frames)
Write (32bit floating point) samples to the ambix file.
ambix_info_t::frames
uint64_t frames
Definition: ambix.h:190
AMBIX_SAMPLEFORMAT_PCM24
@ AMBIX_SAMPLEFORMAT_PCM24
Definition: ambix.h:129
AMBIX_WRITE
@ AMBIX_WRITE
Definition: ambix.h:106
ambix_matrix_fill
AMBIX_API ambix_matrix_t * ambix_matrix_fill(ambix_matrix_t *matrix, ambix_matrixtype_t type)
Fill a matrix according to specs.