Skip to content

Instantly share code, notes, and snippets.

@agrif
Last active January 2, 2016 11:59
Show Gist options
  • Select an option

  • Save agrif/8300247 to your computer and use it in GitHub Desktop.

Select an option

Save agrif/8300247 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <assert.h>
#include <portaudio.h>
#include <sndfile.h>
#define BUFSIZE 1024
#define CHANNELMAP "WXYZRSTUVKLMNOPQ"
#define MAXCHANNELS 16
static struct
{
const char* map;
const char* description;
} channelinfos[] = {
/* 0 */ {NULL, NULL},
/* 1 */ {"W", "mono"},
/* 2 */ {"WY", "mid-side"},
/* 3 */ {"WXY", "first-order horizontal"},
/* 4 */ {"WXYZ", "first-order 3D"},
/* 5 */ {"WXYUV", "second-order horizontal"},
/* 6 */ {"WXYZUV", "second-order horizontal + first-order 3D"},
/* 7 */ {"WXYUVPQ", "third-order horizontal"},
/* 8 */ {"WXYZUVPQ", "third-order horizontal + first-order 3D"},
/* 9 */ {"WXYZRSTUV", "second-order 3D"},
/* 10 */ {NULL, NULL},
/* 11 */ {"WXYZRSTUVPQ", "third-order horizontal + second-order 3D"},
/* 12 */ {NULL, NULL},
/* 13 */ {NULL, NULL},
/* 14 */ {NULL, NULL},
/* 15 */ {NULL, NULL},
/* 16 */ {"WXYZRSTUVKLMNOPQ", "third-order 3D"},
};
static inline const char* get_description(unsigned int num_channels)
{
if (num_channels >= sizeof(channelinfos) / sizeof(*channelinfos))
return NULL;
return channelinfos[num_channels].description;
}
static inline const char* get_channel_map(unsigned int num_channels)
{
if (num_channels >= sizeof(channelinfos) / sizeof(*channelinfos))
return NULL;
return channelinfos[num_channels].map;
}
typedef struct
{
float v[MAXCHANNELS];
} AmbiVector;
static inline AmbiVector* av_init(AmbiVector* av, const char* first, ...)
{
for (unsigned int i = 0; i < MAXCHANNELS; i++)
{
av->v[i] = 0.0f;
}
va_list ap;
va_start(ap, first);
const char* channelmap = CHANNELMAP;
const char* name = first;
while (name)
{
/* recall: floats are promoted to doubles, here */
float value = va_arg(ap, double);
const char* s = strstr(channelmap, name);
if (s == NULL)
{
fprintf(stderr, "invalid channel name: %s", name);
exit(1);
}
av->v[s - channelmap] = value;
name = va_arg(ap, const char*);
}
va_end(ap);
return av;
}
static inline AmbiVector* av_init_from_channels(AmbiVector* av, float* chans, unsigned int num_chans)
{
const char* channelmap = CHANNELMAP;
const char* map = get_channel_map(num_chans);
assert(map);
for (unsigned int i = 0; i < MAXCHANNELS; i++)
{
av->v[i] = 0.0f;
}
for (unsigned int c = 0; c < num_chans; c++)
{
const char* s = strchr(channelmap, map[c]);
assert(s);
av->v[s - channelmap] = chans[c];
}
return av;
}
static inline float av_dot(AmbiVector* a, AmbiVector* b)
{
float total = 0.0f;
for (unsigned int i = 0; i < MAXCHANNELS; i++)
{
total += a->v[i] * b->v[i];
}
return total;
}
static inline AmbiVector* av_normalize(AmbiVector* av)
{
float imag = 1.0f / sqrtf(av_dot(av, av));
for (unsigned int i = 0; i < MAXCHANNELS; i++)
{
av->v[i] *= imag;
}
return av;
}
static inline AmbiVector* av_renormalize(AmbiVector* av, const char* map)
{
const char* channelmap = CHANNELMAP;
for (unsigned int i = 0; i < MAXCHANNELS; i++)
{
if (strchr(map, channelmap[i]) == NULL)
av->v[i] = 0.0f;
}
return av_normalize(av);
}
typedef struct
{
unsigned int num_channels;
AmbiVector* channels;
SNDFILE* file;
SF_INFO info;
float* readbuf;
} CallbackClosure;
static int paCallback(const void* input_buffer, void* output_buffer, unsigned long frames_per_buffer, const PaStreamCallbackTimeInfo* time_info, PaStreamCallbackFlags status_flags, void* user_data)
{
CallbackClosure* data = user_data;
float* out = output_buffer;
/* read the samples */
assert(frames_per_buffer == BUFSIZE);
sf_count_t read = sf_readf_float(data->file, data->readbuf, BUFSIZE);
unsigned int i = 0;
for (; i < read; i++)
{
unsigned int start = i * data->info.channels;
AmbiVector sample;
av_init_from_channels(&sample, &(data->readbuf[start]), data->info.channels);
for (unsigned int c = 0; c < data->num_channels; c++)
{
*out++ = av_dot(&(data->channels[c]), &sample);
}
}
/* fill the rest, if we have to */
if (i < BUFSIZE)
{
for (; i < BUFSIZE; i++)
{
for (unsigned int c = 0; c < data->num_channels; c++)
*out++ = 0.0f;
}
return paComplete;
}
return paContinue;
}
static inline void pa_die(PaError err, const char* msg)
{
fprintf(stderr, "%s: %s\n", msg, Pa_GetErrorText(err));
exit(1);
}
static inline void sf_die(SNDFILE* f, const char* msg)
{
fprintf(stderr, "%s: %s\n", msg, sf_strerror(f));
exit(1);
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
fprintf(stderr, "usage: %s <ambfile>\n", argv[0]);
return 1;
}
unsigned int num_channels = 2;
AmbiVector left, right;
av_normalize(av_init(&left, "W", 1.0, "Y", -1.0, NULL));
av_normalize(av_init(&right, "W", 1.0, "Y", 1.0, NULL));
AmbiVector channels[] = {left, right};
/* initialize portaudio */
PaError err;
err = Pa_Initialize();
if (err != paNoError)
pa_die(err, "could not initialize portaudio");
/* open the input */
CallbackClosure closure = {num_channels, channels, NULL, {0}, NULL};
closure.file = sf_open(argv[1], SFM_READ, &closure.info);
if (!closure.file)
sf_die(NULL, "could not open input");
/* print stats */
const char* description = get_description(closure.info.channels);
const char* map = get_channel_map(closure.info.channels);
if (!map)
{
fprintf(stderr, "invalid number of channels: %i\n", closure.info.channels);
return 1;
}
printf("file type: %s\n", description);
/* renormalize the speakers to the available channels */
for (unsigned int c = 0; c < num_channels; c++)
{
av_renormalize(&(channels[c]), map);
}
/* create the readbuf */
float readbuf[BUFSIZE * closure.info.channels];
closure.readbuf = readbuf;
/* open our output */
PaStream* stream;
err = Pa_OpenDefaultStream(&stream,
0, /* input channels */
num_channels, /* output channels */
paFloat32, /* format */
44100, /* rate */
BUFSIZE, /* bufsize */
paCallback, /* callback + closure */
&closure);
if (err != paNoError)
pa_die(err, "could not open audio stream");
/* start the stream */
err = Pa_StartStream(stream);
if (err != paNoError)
pa_die(err, "could not start audio stream");
while (Pa_IsStreamActive(stream) != 0)
Pa_Sleep(100);
/* stop the stream */
err = Pa_StopStream(stream);
if (err != paNoError)
pa_die(err, "could not stop audio stream");
/* close the stream */
err = Pa_CloseStream(stream);
if (err != paNoError)
pa_die(err, "could not close audio stream");
/* close the input */
sf_close(closure.file);
/* deinitialize portaudio */
err = Pa_Terminate();
if (err != paNoError)
pa_die(err, "could not terminate portaudio");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment