558 lines
16 KiB
C
558 lines
16 KiB
C
|
|
#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 = (ALfloat)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_ambdec_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) WARN("Name not specified for speaker %u\n", cur+1);
|
|
else al_string_copy_cstr(&conf->Speakers[cur].Name, name);
|
|
if(!dist) WARN("Distance not specified for speaker %u\n", cur+1);
|
|
else read_float(&conf->Speakers[cur].Distance, dist);
|
|
if(!az) WARN("Azimuth not specified for speaker %u\n", cur+1);
|
|
else read_float(&conf->Speakers[cur].Azimuth, az);
|
|
if(!elev) WARN("Elevation not specified for speaker %u\n", cur+1);
|
|
else read_float(&conf->Speakers[cur].Elevation, elev);
|
|
if(!conn) TRACE("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_ambdec_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;
|
|
char *line;
|
|
FILE *f;
|
|
|
|
f = al_fopen(fname, "r");
|
|
if(!f)
|
|
{
|
|
ERR("Failed to open: %s\n", fname);
|
|
return 0;
|
|
}
|
|
|
|
while((line=read_clipped_line(f, &buffer, &maxlen)) != NULL)
|
|
{
|
|
char *saveptr;
|
|
char *command;
|
|
|
|
command = my_strtok_r(line, "/ \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_ambdec_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_ambdec_matrix(conf->HFOrderGain, conf->HFMatrix, conf->NumSpeakers,
|
|
f, &buffer, &maxlen, &saveptr))
|
|
goto fail;
|
|
}
|
|
else
|
|
{
|
|
if(strcmp(command, "lfmatrix") == 0)
|
|
{
|
|
if(!load_ambdec_matrix(conf->LFOrderGain, conf->LFMatrix, conf->NumSpeakers,
|
|
f, &buffer, &maxlen, &saveptr))
|
|
goto fail;
|
|
}
|
|
else if(strcmp(command, "hfmatrix") == 0)
|
|
{
|
|
if(!load_ambdec_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;
|
|
}
|