diff options
-rw-r--r-- | Alc/ambdec.c | 555 | ||||
-rw-r--r-- | Alc/ambdec.h | 46 | ||||
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | OpenAL32/Include/alMain.h | 3 |
4 files changed, 604 insertions, 1 deletions
diff --git a/Alc/ambdec.c b/Alc/ambdec.c new file mode 100644 index 00000000..9b467648 --- /dev/null +++ b/Alc/ambdec.c @@ -0,0 +1,555 @@ + +#include "config.h" + +#include "ambdec.h" + +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +#include "compat.h" + + +static char *lstrip(char *line) +{ + while(isspace(line[0])) + line++; + return line; +} + +static char *rstrip(char *line) +{ + size_t len = strlen(line); + while(len > 0 && isspace(line[len-1])) + len--; + line[len] = 0; + return line; +} + +static int readline(FILE *f, char **output, size_t *maxlen) +{ + size_t len = 0; + int c; + + while((c=fgetc(f)) != EOF && (c == '\r' || c == '\n')) + ; + if(c == EOF) + return 0; + + do { + if(len+1 >= *maxlen) + { + void *temp = NULL; + size_t newmax; + + newmax = (*maxlen ? (*maxlen)<<1 : 32); + if(newmax > *maxlen) + temp = realloc(*output, newmax); + if(!temp) + { + ERR("Failed to realloc "SZFMT" bytes from "SZFMT"!\n", newmax, *maxlen); + return 0; + } + + *output = temp; + *maxlen = newmax; + } + (*output)[len++] = c; + (*output)[len] = '\0'; + } while((c=fgetc(f)) != EOF && c != '\r' && c != '\n'); + + return 1; +} + + +/* Custom strtok_r, since we can't rely on it existing. */ +static char *my_strtok_r(char *str, const char *delim, char **saveptr) +{ + /* Sanity check and update internal pointer. */ + if(!saveptr || !delim) return NULL; + if(str) *saveptr = str; + str = *saveptr; + + /* Nothing more to do with this string. */ + if(!str) return NULL; + + /* Find the first non-delimiter character. */ + while(*str != '\0' && strchr(delim, *str) != NULL) + str++; + if(*str == '\0') + { + /* End of string. */ + *saveptr = NULL; + return NULL; + } + + /* Find the next delimiter character. */ + *saveptr = strpbrk(str, delim); + if(*saveptr) *((*saveptr)++) = '\0'; + + return str; +} + +static char *read_uint(ALuint *num, const char *line, int base) +{ + char *end; + *num = strtoul(line, &end, base); + if(end && *end != '\0') + end = lstrip(end); + return end; +} + +static char *read_float(ALfloat *num, const char *line) +{ + char *end; +#ifdef HAVE_STRTOF + *num = strtof(line, &end); +#else + *num = strtod(line, &end); +#endif + if(end && *end != '\0') + end = lstrip(end); + return end; +} + + +char *read_clipped_line(FILE *f, char **buffer, size_t *maxlen) +{ + while(readline(f, buffer, maxlen)) + { + char *line, *comment; + + line = lstrip(*buffer); + comment = strchr(line, '#'); + if(comment) *(comment++) = 0; + + line = rstrip(line); + if(line[0]) return line; + } + return NULL; +} + +static int load_speakers(AmbDecConf *conf, FILE *f, char **buffer, size_t *maxlen, char **saveptr) +{ + ALuint cur = 0; + while(cur < conf->NumSpeakers) + { + const char *cmd = my_strtok_r(NULL, " \t", saveptr); + if(!cmd) + { + char *line = read_clipped_line(f, buffer, maxlen); + if(!line) + { + ERR("Unexpected end of file\n"); + return 0; + } + cmd = my_strtok_r(line, " \t", saveptr); + } + + if(strcmp(cmd, "add_spkr") == 0) + { + const char *name = my_strtok_r(NULL, " \t", saveptr); + const char *dist = my_strtok_r(NULL, " \t", saveptr); + const char *az = my_strtok_r(NULL, " \t", saveptr); + const char *elev = my_strtok_r(NULL, " \t", saveptr); + const char *conn = my_strtok_r(NULL, " \t", saveptr); + + if(!name) ERR("Name not specified for speaker %u\n", cur+1); + else al_string_copy_cstr(&conf->Speakers[cur].Name, name); + if(!dist) ERR("Distance not specified for speaker %u\n", cur+1); + else read_float(&conf->Speakers[cur].Distance, dist); + if(!az) ERR("Azimuth not specified for speaker %u\n", cur+1); + else read_float(&conf->Speakers[cur].Azimuth, az); + if(!elev) ERR("Elevation not specified for speaker %u\n", cur+1); + else read_float(&conf->Speakers[cur].Elevation, elev); + if(!conn) ERR("Connection not specified for speaker %u\n", cur+1); + else al_string_copy_cstr(&conf->Speakers[cur].Connection, conn); + cur++; + } + else + { + ERR("Unexpected speakers command: %s\n", cmd); + return 0; + } + + cmd = my_strtok_r(NULL, " \t", saveptr); + if(cmd) + { + ERR("Unexpected junk on line: %s\n", cmd); + return 0; + } + } + + return 1; +} + +static int load_matrix(ALfloat *gains, ALfloat (*matrix)[MAX_AMBI_COEFFS], ALuint maxrow, FILE *f, char **buffer, size_t *maxlen, char **saveptr) +{ + int gotgains = 0; + ALuint cur = 0; + while(cur < maxrow) + { + const char *cmd = my_strtok_r(NULL, " \t", saveptr); + if(!cmd) + { + char *line = read_clipped_line(f, buffer, maxlen); + if(!line) + { + ERR("Unexpected end of file\n"); + return 0; + } + cmd = my_strtok_r(line, " \t", saveptr); + } + + if(strcmp(cmd, "order_gain") == 0) + { + ALuint curgain = 0; + char *line; + while((line=my_strtok_r(NULL, " \t", saveptr)) != NULL) + { + ALfloat value; + line = read_float(&value, line); + if(line && *line != '\0') + { + ERR("Extra junk on gain %u: %s\n", curgain+1, line); + return 0; + } + if(curgain < MAX_AMBI_ORDER+1) + gains[curgain] = value; + curgain++; + } + while(curgain < MAX_AMBI_ORDER+1) + gains[curgain++] = 0.0f; + gotgains = 1; + } + else if(strcmp(cmd, "add_row") == 0) + { + ALuint curidx = 0; + char *line; + while((line=my_strtok_r(NULL, " \t", saveptr)) != NULL) + { + ALfloat value; + line = read_float(&value, line); + if(line && *line != '\0') + { + ERR("Extra junk on matrix element %ux%u: %s\n", cur, curidx, line); + return 0; + } + if(curidx < MAX_AMBI_COEFFS) + matrix[cur][curidx] = value; + curidx++; + } + while(curidx < MAX_AMBI_COEFFS) + matrix[cur][curidx++] = 0.0f; + cur++; + } + else + { + ERR("Unexpected speakers command: %s\n", cmd); + return 0; + } + + cmd = my_strtok_r(NULL, " \t", saveptr); + if(cmd) + { + ERR("Unexpected junk on line: %s\n", cmd); + return 0; + } + } + + if(!gotgains) + { + ERR("Matrix order_gain not specified\n"); + return 0; + } + + return 1; +} + +void ambdec_init(AmbDecConf *conf) +{ + ALuint i; + + memset(conf, 0, sizeof(*conf)); + AL_STRING_INIT(conf->Description); + for(i = 0;i < MAX_OUTPUT_CHANNELS;i++) + { + AL_STRING_INIT(conf->Speakers[i].Name); + AL_STRING_INIT(conf->Speakers[i].Connection); + } +} + +void ambdec_deinit(AmbDecConf *conf) +{ + ALuint i; + + al_string_deinit(&conf->Description); + for(i = 0;i < MAX_OUTPUT_CHANNELS;i++) + { + al_string_deinit(&conf->Speakers[i].Name); + al_string_deinit(&conf->Speakers[i].Connection); + } + memset(conf, 0, sizeof(*conf)); +} + +int ambdec_load(AmbDecConf *conf, const char *fname) +{ + char *buffer = NULL; + size_t maxlen = 0; + FILE *f; + + f = al_fopen(fname, "r"); + if(!f) + { + ERR("Failed to open: %s\n", fname); + return 0; + } + + while(read_clipped_line(f, &buffer, &maxlen)) + { + char *line, *saveptr; + char *command; + + command = my_strtok_r(buffer, "/ \t", &saveptr); + if(!command) + { + ERR("Malformed line: %s\n", line); + goto fail; + } + + if(strcmp(command, "description") == 0) + { + char *value = my_strtok_r(NULL, "", &saveptr); + al_string_copy_cstr(&conf->Description, lstrip(value)); + } + else if(strcmp(command, "version") == 0) + { + line = my_strtok_r(NULL, "", &saveptr); + line = read_uint(&conf->Version, line, 10); + if(line && *line != '\0') + { + ERR("Extra junk after version: %s\n", line); + goto fail; + } + if(conf->Version != 3) + { + ERR("Unsupported version: %u\n", conf->Version); + goto fail; + } + } + else if(strcmp(command, "dec") == 0) + { + const char *dec = my_strtok_r(NULL, "/ \t", &saveptr); + if(strcmp(dec, "chan_mask") == 0) + { + line = my_strtok_r(NULL, "", &saveptr); + line = read_uint(&conf->ChanMask, line, 16); + if(line && *line != '\0') + { + ERR("Extra junk after mask: %s\n", line); + goto fail; + } + } + else if(strcmp(dec, "freq_bands") == 0) + { + line = my_strtok_r(NULL, "", &saveptr); + line = read_uint(&conf->FreqBands, line, 10); + if(line && *line != '\0') + { + ERR("Extra junk after freq_bands: %s\n", line); + goto fail; + } + if(conf->FreqBands != 1 && conf->FreqBands != 2) + { + ERR("Invalid freq_bands value: %u\n", conf->FreqBands); + goto fail; + } + } + else if(strcmp(dec, "speakers") == 0) + { + line = my_strtok_r(NULL, "", &saveptr); + line = read_uint(&conf->NumSpeakers, line, 10); + if(line && *line != '\0') + { + ERR("Extra junk after speakers: %s\n", line); + goto fail; + } + if(conf->NumSpeakers > MAX_OUTPUT_CHANNELS) + { + ERR("Unsupported speaker count: %u\n", conf->NumSpeakers); + goto fail; + } + } + else if(strcmp(dec, "coeff_scale") == 0) + { + line = my_strtok_r(NULL, " \t", &saveptr); + if(strcmp(line, "n3d") == 0) + conf->CoeffScale = ADS_N3D; + else if(strcmp(line, "sn3d") == 0) + conf->CoeffScale = ADS_SN3D; + else if(strcmp(line, "fuma") == 0) + conf->CoeffScale = ADS_FuMa; + else + { + ERR("Unsupported coeff scale: %s\n", line); + goto fail; + } + } + else + { + ERR("Unexpected /dec option: %s\n", dec); + goto fail; + } + } + else if(strcmp(command, "opt") == 0) + { + const char *opt = my_strtok_r(NULL, "/ \t", &saveptr); + if(strcmp(opt, "xover_freq") == 0) + { + line = my_strtok_r(NULL, "", &saveptr); + line = read_float(&conf->XOverFreq, line); + if(line && *line != '\0') + { + ERR("Extra junk after xover_freq: %s\n", line); + goto fail; + } + } + else if(strcmp(opt, "xover_ratio") == 0) + { + line = my_strtok_r(NULL, "", &saveptr); + line = read_float(&conf->XOverRatio, line); + if(line && *line != '\0') + { + ERR("Extra junk after xover_ratio: %s\n", line); + goto fail; + } + } + else if(strcmp(opt, "input_scale") == 0 || strcmp(opt, "nfeff_comp") == 0 || + strcmp(opt, "delay_comp") == 0 || strcmp(opt, "level_comp") == 0) + { + /* Unused */ + my_strtok_r(NULL, " \t", &saveptr); + } + else + { + ERR("Unexpected /opt option: %s\n", opt); + goto fail; + } + } + else if(strcmp(command, "speakers") == 0) + { + const char *value = my_strtok_r(NULL, "/ \t", &saveptr); + if(strcmp(value, "{") != 0) + { + ERR("Expected { after speakers command, got %s\n", value); + goto fail; + } + if(!load_speakers(conf, f, &buffer, &maxlen, &saveptr)) + goto fail; + value = my_strtok_r(NULL, "/ \t", &saveptr); + if(!value) + { + line = read_clipped_line(f, &buffer, &maxlen); + if(!line) + { + ERR("Unexpected end of file\n"); + goto fail; + } + value = my_strtok_r(line, "/ \t", &saveptr); + } + if(strcmp(value, "}") != 0) + { + ERR("Expected } after speaker definitions, got %s\n", value); + goto fail; + } + } + else if(strcmp(command, "lfmatrix") == 0 || strcmp(command, "hfmatrix") == 0 || + strcmp(command, "matrix") == 0) + { + const char *value = my_strtok_r(NULL, "/ \t", &saveptr); + if(strcmp(value, "{") != 0) + { + ERR("Expected { after speakers command, got %s\n", value); + goto fail; + } + if(conf->FreqBands == 1) + { + if(strcmp(command, "matrix") != 0) + { + ERR("Unexpected \"%s\" type for a single-band decoder\n", command); + goto fail; + } + if(!load_matrix(conf->HFOrderGain, conf->HFMatrix, conf->NumSpeakers, + f, &buffer, &maxlen, &saveptr)) + goto fail; + } + else + { + if(strcmp(command, "lfmatrix") == 0) + { + if(!load_matrix(conf->LFOrderGain, conf->LFMatrix, conf->NumSpeakers, + f, &buffer, &maxlen, &saveptr)) + goto fail; + } + else if(strcmp(command, "hfmatrix") == 0) + { + if(!load_matrix(conf->HFOrderGain, conf->HFMatrix, conf->NumSpeakers, + f, &buffer, &maxlen, &saveptr)) + goto fail; + } + else + { + ERR("Unexpected \"%s\" type for a dual-band decoder\n", command); + goto fail; + } + } + value = my_strtok_r(NULL, "/ \t", &saveptr); + if(!value) + { + line = read_clipped_line(f, &buffer, &maxlen); + if(!line) + { + ERR("Unexpected end of file\n"); + goto fail; + } + value = my_strtok_r(line, "/ \t", &saveptr); + } + if(strcmp(value, "}") != 0) + { + ERR("Expected } after matrix definitions, got %s\n", value); + goto fail; + } + } + else if(strcmp(command, "end") == 0) + { + line = my_strtok_r(NULL, "/ \t", &saveptr); + if(line) + { + ERR("Unexpected junk on end: %s\n", line); + goto fail; + } + + fclose(f); + free(buffer); + return 1; + } + else + { + ERR("Unexpected command: %s\n", command); + goto fail; + } + + line = my_strtok_r(NULL, "/ \t", &saveptr); + if(line) + { + ERR("Unexpected junk on line: %s\n", line); + goto fail; + } + } + ERR("Unexpected end of file\n"); + +fail: + fclose(f); + free(buffer); + return 0; +} diff --git a/Alc/ambdec.h b/Alc/ambdec.h new file mode 100644 index 00000000..8a3befc1 --- /dev/null +++ b/Alc/ambdec.h @@ -0,0 +1,46 @@ +#ifndef AMBDEC_H +#define AMBDEC_H + +#include "alstring.h" +#include "alMain.h" + +/* Helpers to read .ambdec configuration files. */ + +enum AmbDecScaleType { + ADS_N3D, + ADS_SN3D, + ADS_FuMa, +}; +typedef struct AmbDecConf { + al_string Description; + ALuint Version; /* Must be 3 */ + + ALuint ChanMask; + ALuint FreqBands; /* Must be 1 or 2 */ + ALuint NumSpeakers; + enum AmbDecScaleType CoeffScale; + + ALfloat XOverFreq; + ALfloat XOverRatio; + + struct { + al_string Name; + ALfloat Distance; + ALfloat Azimuth; + ALfloat Elevation; + al_string Connection; + } Speakers[MAX_OUTPUT_CHANNELS]; + + /* Unused when FreqBands == 1 */ + ALfloat LFOrderGain[MAX_AMBI_ORDER+1]; + ALfloat LFMatrix[MAX_OUTPUT_CHANNELS][MAX_AMBI_COEFFS]; + + ALfloat HFOrderGain[MAX_AMBI_ORDER+1]; + ALfloat HFMatrix[MAX_OUTPUT_CHANNELS][MAX_AMBI_COEFFS]; +} AmbDecConf; + +void ambdec_init(AmbDecConf *conf); +void ambdec_deinit(AmbDecConf *conf); +int ambdec_load(AmbDecConf *conf, const char *fname); + +#endif /* AMBDEC_H */ diff --git a/CMakeLists.txt b/CMakeLists.txt index 33eb180e..d815c2aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -644,6 +644,7 @@ SET(ALC_OBJS Alc/ALc.c Alc/bsinc.c Alc/hrtf.c Alc/uhjfilter.c + Alc/ambdec.c Alc/panning.c Alc/mixer.c Alc/mixer_c.c diff --git a/OpenAL32/Include/alMain.h b/OpenAL32/Include/alMain.h index aae19b05..86311761 100644 --- a/OpenAL32/Include/alMain.h +++ b/OpenAL32/Include/alMain.h @@ -397,7 +397,8 @@ enum RenderMode { /* The maximum number of Ambisonics coefficients. For a given order (o), the * size needed will be (o+1)**2, thus zero-order has 1, first-order has 4, * second-order has 9, and third-order has 16. */ -#define MAX_AMBI_COEFFS 16 +#define MAX_AMBI_ORDER 3 +#define MAX_AMBI_COEFFS ((MAX_AMBI_ORDER+1) * (MAX_AMBI_ORDER+1)) typedef ALfloat ChannelConfig[MAX_AMBI_COEFFS]; |