Last active
January 2, 2016 11:59
-
-
Save agrif/8300247 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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