aboutsummaryrefslogtreecommitdiffstats
path: root/utils/makehrtf.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/makehrtf.c')
-rw-r--r--utils/makehrtf.c2705
1 files changed, 2178 insertions, 527 deletions
diff --git a/utils/makehrtf.c b/utils/makehrtf.c
index 9f0a5d95..08861fb8 100644
--- a/utils/makehrtf.c
+++ b/utils/makehrtf.c
@@ -1,571 +1,1734 @@
-/**
+/*
* HRTF utility for producing and demonstrating the process of creating an
* OpenAL Soft compatible HRIR data set.
*
- * It can currently make use of the 44.1 KHz diffuse and compact KEMAR HRIRs
- * provided by MIT at:
+ * Copyright (C) 2011-2012 Christopher Fitzgerald
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * --------------------------------------------------------------------------
+ *
+ * A big thanks goes out to all those whose work done in the field of
+ * binaural sound synthesis using measured HRTFs makes this utility and the
+ * OpenAL Soft implementation possible.
+ *
+ * The algorithm for diffuse-field equalization was adapted from the work
+ * done by Rio Emmanuel and Larcher Veronique of IRCAM and Bill Gardner of
+ * MIT Media Laboratory. It operates as follows:
+ *
+ * 1. Take the FFT of each HRIR and only keep the magnitude responses.
+ * 2. Calculate the diffuse-field power-average of all HRIRs weighted by
+ * their contribution to the total surface area covered by their
+ * measurement.
+ * 3. Take the diffuse-field average and limit its magnitude range.
+ * 4. Equalize the responses by using the inverse of the diffuse-field
+ * average.
+ * 5. Reconstruct the minimum-phase responses.
+ * 5. Zero the DC component.
+ * 6. IFFT the result and truncate to the desired-length minimum-phase FIR.
+ *
+ * The spherical head algorithm for calculating propagation delay was adapted
+ * from the paper:
+ *
+ * Modeling Interaural Time Difference Assuming a Spherical Head
+ * Joel David Miller
+ * Music 150, Musical Acoustics, Stanford University
+ * December 2, 2001
*
- * http://sound.media.mit.edu/resources/KEMAR.html
*/
#include <stdio.h>
#include <stdlib.h>
-#include <math.h>
+#include <stdarg.h>
#include <string.h>
+#include <ctype.h>
+#include <math.h>
+
+// Needed for 64-bit unsigned integer.
+#include "config.h"
+// Rely (if naively) on OpenAL's header for the types used for serialization.
#include "AL/al.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
-#ifdef _MSC_VER
-static double round(double val)
-{
- if(val < 0.0)
- return ceil(val - 0.5);
- return floor(val + 0.5);
-}
-#endif
+// The epsilon used to maintain signal stability.
+#define EPSILON (1e-15)
+
+// Constants for accessing the token reader's ring buffer.
+#define TR_RING_BITS (16)
+#define TR_RING_SIZE (1 << TR_RING_BITS)
+#define TR_RING_MASK (TR_RING_SIZE - 1)
+
+// The token reader's load interval in bytes.
+#define TR_LOAD_SIZE (TR_RING_SIZE >> 2)
+
+// The maximum identifier length used when processing the data set
+// definition.
+#define MAX_IDENT_LEN (16)
+
+// The maximum path length used when processing filenames.
+#define MAX_PATH_LEN (256)
+
+// The limits for the sample 'rate' metric in the data set definition.
+#define MIN_RATE (32000)
+#define MAX_RATE (96000)
+
+// The limits for the HRIR 'points' metric in the data set definition.
+#define MIN_POINTS (16)
+#define MAX_POINTS (8192)
+
+// The limits to the number of 'azimuths' listed in the data set definition.
+#define MIN_EV_COUNT (5)
+#define MAX_EV_COUNT (128)
+
+// The limits for each of the 'azimuths' listed in the data set definition.
+#define MIN_AZ_COUNT (1)
+#define MAX_AZ_COUNT (128)
+
+// The limits for the listener's head 'radius' in the data set definition.
+#define MIN_RADIUS (0.05)
+#define MAX_RADIUS (0.15)
+
+// The limits for the 'distance' from source to listener in the definition
+// file.
+#define MIN_DISTANCE (0.5)
+#define MAX_DISTANCE (2.5)
+
+// The maximum number of channels that can be addressed for a WAVE file
+// source listed in the data set definition.
+#define MAX_WAVE_CHANNELS (65535)
+
+// The limits to the byte size for a binary source listed in the definition
+// file.
+#define MIN_BIN_SIZE (2)
+#define MAX_BIN_SIZE (4)
+
+// The minimum number of significant bits for binary sources listed in the
+// data set definition. The maximum is calculated from the byte size.
+#define MIN_BIN_BITS (16)
+
+// The limits to the number of significant bits for an ASCII source listed in
+// the data set definition.
+#define MIN_ASCII_BITS (16)
+#define MAX_ASCII_BITS (32)
+
+// The limits to the FFT window size override on the command line.
+#define MIN_FFTSIZE (512)
+#define MAX_FFTSIZE (16384)
+
+// The limits to the equalization range limit on the command line.
+#define MIN_LIMIT (2.0)
+#define MAX_LIMIT (120.0)
+
+// The limits to the truncation window size on the command line.
+#define MIN_TRUNCSIZE (8)
+#define MAX_TRUNCSIZE (128)
+
+// The truncation window size must be a multiple of the below value to allow
+// for vectorized convolution.
+#define MOD_TRUNCSIZE (8)
+
+// The defaults for the command line options.
+#define DEFAULT_EQUALIZE (1)
+#define DEFAULT_SURFACE (1)
+#define DEFAULT_LIMIT (24.0)
+#define DEFAULT_TRUNCSIZE (32)
+
+// The four-character-codes for RIFF/RIFX WAVE file chunks.
+#define FOURCC_RIFF (0x46464952) // 'RIFF'
+#define FOURCC_RIFX (0x58464952) // 'RIFX'
+#define FOURCC_WAVE (0x45564157) // 'WAVE'
+#define FOURCC_FMT (0x20746D66) // 'fmt '
+#define FOURCC_DATA (0x61746164) // 'data'
+#define FOURCC_LIST (0x5453494C) // 'LIST'
+#define FOURCC_WAVL (0x6C766177) // 'wavl'
+#define FOURCC_SLNT (0x746E6C73) // 'slnt'
+
+// The supported wave formats.
+#define WAVE_FORMAT_PCM (0x0001)
+#define WAVE_FORMAT_IEEE_FLOAT (0x0003)
+#define WAVE_FORMAT_EXTENSIBLE (0xFFFE)
+
+// The maximum propagation delay value supported by OpenAL Soft.
+#define MAX_HRTD (63.0)
-// The sample rate of the MIT HRIR data sets.
-#define MIT_IR_RATE (44100)
+// The OpenAL Soft HRTF format marker. It stands for minimum-phase head
+// response protocol 01.
+#define MHR_FORMAT ("MinPHR01")
+
+// Byte order for the serialization routines.
+enum ByteOrderT {
+ BO_NONE = 0,
+ BO_LITTLE ,
+ BO_BIG
+};
-// The total number of used impulse responses from the MIT HRIR data sets.
-#define MIT_IR_COUNT (828)
+// Source format for the references listed in the data set definition.
+enum SourceFormatT {
+ SF_NONE = 0,
+ SF_WAVE , // RIFF/RIFX WAVE file.
+ SF_BIN_LE , // Little-endian binary file.
+ SF_BIN_BE , // Big-endian binary file.
+ SF_ASCII // ASCII text file.
+};
-// The size (in samples) of each HRIR in the MIT data sets.
-#define MIT_IR_SIZE (128)
+// Element types for the references listed in the data set definition.
+enum ElementTypeT {
+ ET_NONE = 0,
+ ET_INT , // Integer elements.
+ ET_FP // Floating-point elements.
+};
-// The total number of elevations given a step of 10 degrees.
-#define MIT_EV_COUNT (19)
+// Desired output format from the command line.
+enum OutputFormatT {
+ OF_NONE = 0,
+ OF_MHR , // OpenAL Soft MHR data set file.
+ OF_TABLE // OpenAL Soft built-in table file (used when compiling).
+};
-// The first elevation that the MIT data sets have HRIRs for.
-#define MIT_EV_START (5)
+// Unsigned integer type.
+typedef unsigned int uint;
-// The head radius (in meters) used by the MIT data sets.
-#define MIT_RADIUS (0.09f)
+// Serialization types. The trailing digit indicates the number of bytes.
+typedef ALubyte uint1;
-// The source to listener distance (in meters) used by the MIT data sets.
-#define MIT_DISTANCE (1.4f)
+typedef ALint int4;
+typedef ALuint uint4;
-// The resulting size (in samples) of a mininum-phase reconstructed HRIR.
-#define MIN_IR_SIZE (32)
+#if defined (HAVE_STDINT_H)
+#include <stdint.h>
-// The size (in samples) of the real cepstrum used in reconstruction. This
-// needs to be large enough to reduce inaccuracy.
-#define CEP_SIZE (8192)
+typedef uint64_t uint8;
+#elif defined (HAVE___INT64)
+typedef unsigned __int64 uint8;
+#elif (SIZEOF_LONG == 8)
+typedef unsigned long uint8;
+#elif (SIZEOF_LONG_LONG == 8)
+typedef unsigned long long uint8;
+#endif
-// The OpenAL Soft HRTF format marker. It stands for minimum-phase head
-// response protocol 00.
-#define MHR_FORMAT ("MinPHR00")
+typedef enum ByteOrderT ByteOrderT;
+typedef enum SourceFormatT SourceFormatT;
+typedef enum ElementTypeT ElementTypeT;
+typedef enum OutputFormatT OutputFormatT;
-typedef struct ComplexT ComplexT;
+typedef struct TokenReaderT TokenReaderT;
+typedef struct SourceRefT SourceRefT;
typedef struct HrirDataT HrirDataT;
-// A complex number type.
-struct ComplexT {
- float mVec [2];
+// Token reader state for parsing the data set definition.
+struct TokenReaderT {
+ FILE * mFile;
+ const char * mName;
+ uint mLine,
+ mColumn;
+ char mRing [TR_RING_SIZE];
+ size_t mIn,
+ mOut;
};
-// The HRIR data definition. This can be used to add support for new HRIR
-// sources in the future.
+// Source reference state used when loading sources.
+struct SourceRefT {
+ SourceFormatT mFormat;
+ ElementTypeT mType;
+ uint mSize;
+ int mBits;
+ uint mChannel,
+ mSkip,
+ mOffset;
+ char mPath [MAX_PATH_LEN + 1];
+};
+
+// The HRIR metrics and data set used when loading, processing, and storing
+// the resulting HRTF.
struct HrirDataT {
- int mIrRate,
- mIrCount,
- mIrSize,
- mEvCount,
- mEvStart;
- const int * mEvOffset,
- * mAzCount;
- float mRadius,
+ uint mIrRate,
+ mIrCount;
+ size_t mIrSize,
+ mIrPoints,
+ mFftSize;
+ uint mEvCount,
+ mEvStart,
+ mAzCount [MAX_EV_COUNT],
+ mEvOffset [MAX_EV_COUNT];
+ double mRadius,
mDistance,
* mHrirs,
* mHrtds,
mMaxHrtd;
};
-// The linear index of the first HRIR for each elevation of the MIT data set.
-static const int MIT_EV_OFFSET [MIT_EV_COUNT] = {
- 0, 1, 13, 37, 73, 118, 174, 234, 306, 378, 450, 522, 594, 654, 710, 755, 791, 815, 827
-},
+/* Token reader routines for parsing text files. Whitespace is not
+ * significant. It can process tokens as identifiers, numbers (integer and
+ * floating-point), strings, and operators. Strings must be encapsulated by
+ * double-quotes and cannot span multiple lines.
+ */
-// The count of distinct azimuth steps for each elevation in the MIT data
-// set.
- MIT_AZ_COUNT [MIT_EV_COUNT] = {
- 1, 12, 24, 36, 45, 56, 60, 72, 72, 72, 72, 72, 60, 56, 45, 36, 24, 12, 1
-};
+// Setup the reader on the given file. The filename can be NULL if no error
+// output is desired.
+static void TrSetup (FILE * fp, const char * filename, TokenReaderT * tr) {
+ const char * name = NULL;
+ char ch;
+
+ tr -> mFile = fp;
+ name = filename;
+ // If a filename was given, store a pointer to the base name.
+ if (filename != NULL) {
+ while ((ch = (* filename)) != '\0') {
+ if ((ch == '/') || (ch == '\\'))
+ name = filename + 1;
+ filename ++;
+ }
+ }
+ tr -> mName = name;
+ tr -> mLine = 1;
+ tr -> mColumn = 1;
+ tr -> mIn = 0;
+ tr -> mOut = 0;
+}
-// Performs a forward Fast Fourier Transform.
-static void FftProc (int n, const ComplexT * fftIn, ComplexT * fftOut) {
- int m2, rk, k, m;
- float a, b;
- int i;
- float wx, wy;
- int j, km2;
- float tx, ty, wyd;
-
- // Data copy and bit-reversal ordering.
- m2 = (n >> 1);
- rk = 0;
- for (k = 0; k < n; k ++) {
- fftOut [rk] . mVec [0] = fftIn [k] . mVec [0];
- fftOut [rk] . mVec [1] = fftIn [k] . mVec [1];
- if (k < (n - 1)) {
- m = m2;
- while (rk >= m) {
- rk -= m;
- m >>= 1;
- }
- rk += m;
- }
+// Prime the reader's ring buffer, and return a result indicating that there
+// is text to process.
+static int TrLoad (TokenReaderT * tr) {
+ size_t toLoad, in, count;
+
+ toLoad = TR_RING_SIZE - (tr -> mIn - tr -> mOut);
+ if ((toLoad >= TR_LOAD_SIZE) && (! feof (tr -> mFile))) {
+ // Load TR_LOAD_SIZE (or less if at the end of the file) per read.
+ toLoad = TR_LOAD_SIZE;
+ in = tr -> mIn & TR_RING_MASK;
+ count = TR_RING_SIZE - in;
+ if (count < toLoad) {
+ tr -> mIn += fread (& tr -> mRing [in], 1, count, tr -> mFile);
+ tr -> mIn += fread (& tr -> mRing [0], 1, toLoad - count, tr -> mFile);
+ } else {
+ tr -> mIn += fread (& tr -> mRing [in], 1, toLoad, tr -> mFile);
+ }
+ if (tr -> mOut >= TR_RING_SIZE) {
+ tr -> mOut -= TR_RING_SIZE;
+ tr -> mIn -= TR_RING_SIZE;
+ }
+ }
+ if (tr -> mIn > tr -> mOut)
+ return (1);
+ return (0);
+}
+
+// Error display routine. Only displays when the base name is not NULL.
+static void TrErrorVA (const TokenReaderT * tr, uint line, uint column, const char * format, va_list argPtr) {
+ if (tr -> mName != NULL) {
+ fprintf (stderr, "Error (%s:%d:%d): ", tr -> mName, line, column);
+ vfprintf (stderr, format, argPtr);
+ }
+}
+
+// Used to display an error at a saved line/column.
+static void TrErrorAt (const TokenReaderT * tr, uint line, uint column, const char * format, ...) {
+ va_list argPtr;
+
+ va_start (argPtr, format);
+ TrErrorVA (tr, line, column, format, argPtr);
+ va_end (argPtr);
+}
+
+// Used to display an error at the current line/column.
+static void TrError (const TokenReaderT * tr, const char * format, ...) {
+ va_list argPtr;
+
+ va_start (argPtr, format);
+ TrErrorVA (tr, tr -> mLine, tr -> mColumn, format, argPtr);
+ va_end (argPtr);
+}
+
+// Skips to the next line.
+static void TrSkipLine (TokenReaderT * tr) {
+ char ch;
+
+ while (TrLoad (tr)) {
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ tr -> mOut ++;
+ if (ch == '\n') {
+ tr -> mLine ++;
+ tr -> mColumn = 1;
+ break;
+ }
+ tr -> mColumn ++;
+ }
+}
+
+// Skips to the next token.
+static int TrSkipWhitespace (TokenReaderT * tr) {
+ char ch;
+
+ while (TrLoad (tr)) {
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ if (isspace (ch)) {
+ tr -> mOut ++;
+ if (ch == '\n') {
+ tr -> mLine ++;
+ tr -> mColumn = 1;
+ } else {
+ tr -> mColumn ++;
+ }
+ } else if (ch == '#') {
+ TrSkipLine (tr);
+ } else {
+ return (1);
+ }
+ }
+ return (0);
+}
+
+// Get the line and/or column of the next token (or the end of input).
+static void TrIndication (TokenReaderT * tr, uint * line, uint * column) {
+ TrSkipWhitespace (tr);
+ if (line != NULL)
+ (* line) = tr -> mLine;
+ if (column != NULL)
+ (* column) = tr -> mColumn;
+}
+
+// Checks to see if a token is the given operator. It does not display any
+// errors and will not proceed to the next token.
+static int TrIsOperator (TokenReaderT * tr, const char * op) {
+ size_t out, len;
+ char ch;
+
+ if (! TrSkipWhitespace (tr))
+ return (0);
+ out = tr -> mOut;
+ len = 0;
+ while ((op [len] != '\0') && (out < tr -> mIn)) {
+ ch = tr -> mRing [out & TR_RING_MASK];
+ if (ch != op [len])
+ break;
+ len ++;
+ out ++;
+ }
+ if (op [len] == '\0')
+ return (1);
+ return (0);
+}
+
+/* The TrRead*() routines obtain the value of a matching token type. They
+ * display type, form, and boundary errors and will proceed to the next
+ * token.
+ */
+
+// Reads and validates an identifier token.
+static int TrReadIdent (TokenReaderT * tr, const size_t maxLen, char * ident) {
+ uint col;
+ size_t len;
+ char ch;
+
+ col = tr -> mColumn;
+ if (TrSkipWhitespace (tr)) {
+ col = tr -> mColumn;
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ if ((ch == '_') || isalpha (ch)) {
+ len = 0;
+ do {
+ if (len < maxLen)
+ ident [len] = ch;
+ len ++;
+ tr -> mOut ++;
+ if (! TrLoad (tr))
+ break;
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ } while ((ch == '_') || isdigit (ch) || isalpha (ch));
+ tr -> mColumn += len;
+ if (len > maxLen) {
+ TrErrorAt (tr, tr -> mLine, col, "Identifier is too long.\n");
+ return (0);
+ }
+ ident [len] = '\0';
+ return (1);
+ }
}
- // Perform the FFT.
- m2 = 1;
- for (m = 2; m <= n; m <<= 1) {
- a = sin (M_PI / m);
- a = 2.0f * a * a;
- b = sin (2.0f * M_PI / m);
- for (i = 0; i < n; i += m) {
- wx = 1.0f;
- wy = 0.0f;
- for (k = i, j = 0; j < m2; k ++, j ++) {
- km2 = k + m2;
- tx = (wx * fftOut [km2] . mVec [0]) - (wy * fftOut [km2] . mVec [1]);
- ty = (wx * fftOut [km2] . mVec [1]) + (wy * fftOut [km2] . mVec [0]);
- fftOut [km2] . mVec [0] = fftOut [k] . mVec [0] - tx;
- fftOut [km2] . mVec [1] = fftOut [k] . mVec [1] - ty;
- fftOut [k] . mVec [0] += tx;
- fftOut [k] . mVec [1] += ty;
- wyd = (a * wy) - (b * wx);
- wx -= (a * wx) + (b * wy);
- wy -= wyd;
+ TrErrorAt (tr, tr -> mLine, col, "Expected an identifier.\n");
+ return (0);
+}
+
+// Reads and validates (including bounds) an integer token.
+static int TrReadInt (TokenReaderT * tr, const int loBound, const int hiBound, int * value) {
+ uint col, digis;
+ size_t len;
+ char ch, temp [64 + 1];
+
+ col = tr -> mColumn;
+ if (TrSkipWhitespace (tr)) {
+ col = tr -> mColumn;
+ len = 0;
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ if ((ch == '+') || (ch == '-')) {
+ temp [len] = ch;
+ len ++;
+ tr -> mOut ++;
+ }
+ digis = 0;
+ while (TrLoad (tr)) {
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ if (! isdigit (ch))
+ break;
+ if (len < 64)
+ temp [len] = ch;
+ len ++;
+ digis ++;
+ tr -> mOut ++;
+ }
+ tr -> mColumn += len;
+ if ((digis > 0) && (ch != '.') && (! isalpha (ch))) {
+ if (len > 64) {
+ TrErrorAt (tr, tr -> mLine, col, "Integer is too long.");
+ return (0);
+ }
+ temp [len] = '\0';
+ (* value) = strtol (temp, NULL, 10);
+ if (((* value) < loBound) || ((* value) > hiBound)) {
+ TrErrorAt (tr, tr -> mLine, col, "Expected a value from %d to %d.\n", loBound, hiBound);
+ return (0);
+ }
+ return (1);
+ }
+ }
+ TrErrorAt (tr, tr -> mLine, col, "Expected an integer.\n");
+ return (0);
+}
+
+// Reads and validates (including bounds) a float token.
+static int TrReadFloat (TokenReaderT * tr, const double loBound, const double hiBound, double * value) {
+ uint col, digis;
+ size_t len;
+ char ch, temp [64 + 1];
+
+ col = tr -> mColumn;
+ if (TrSkipWhitespace (tr)) {
+ col = tr -> mColumn;
+ len = 0;
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ if ((ch == '+') || (ch == '-')) {
+ temp [len] = ch;
+ len ++;
+ tr -> mOut ++;
+ }
+ digis = 0;
+ while (TrLoad (tr)) {
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ if (! isdigit (ch))
+ break;
+ if (len < 64)
+ temp [len] = ch;
+ len ++;
+ digis ++;
+ tr -> mOut ++;
+ }
+ if (ch == '.') {
+ if (len < 64)
+ temp [len] = ch;
+ len ++;
+ tr -> mOut ++;
+ }
+ while (TrLoad (tr)) {
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ if (! isdigit (ch))
+ break;
+ if (len < 64)
+ temp [len] = ch;
+ len ++;
+ digis ++;
+ tr -> mOut ++;
+ }
+ if (digis > 0) {
+ if ((ch == 'E') || (ch == 'e')) {
+ if (len < 64)
+ temp [len] = ch;
+ len ++;
+ digis = 0;
+ tr -> mOut ++;
+ if ((ch == '+') || (ch == '-')) {
+ if (len < 64)
+ temp [len] = ch;
+ len ++;
+ tr -> mOut ++;
+ }
+ while (TrLoad (tr)) {
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ if (! isdigit (ch))
+ break;
+ if (len < 64)
+ temp [len] = ch;
+ len ++;
+ digis ++;
+ tr -> mOut ++;
+ }
+ }
+ tr -> mColumn += len;
+ if ((digis > 0) && (ch != '.') && (! isalpha (ch))) {
+ if (len > 64) {
+ TrErrorAt (tr, tr -> mLine, col, "Float is too long.");
+ return (0);
+ }
+ temp [len] = '\0';
+ (* value) = strtod (temp, NULL);
+ if (((* value) < loBound) || ((* value) > hiBound)) {
+ TrErrorAt (tr, tr -> mLine, col, "Expected a value from %f to %f.\n", loBound, hiBound);
+ return (0);
+ }
+ return (1);
+ }
+ } else {
+ tr -> mColumn += len;
+ }
+ }
+ TrErrorAt (tr, tr -> mLine, col, "Expected a float.\n");
+ return (0);
+}
+
+// Reads and validates a string token.
+static int TrReadString (TokenReaderT * tr, const size_t maxLen, char * text) {
+ uint col;
+ size_t len;
+ char ch;
+
+ col = tr -> mColumn;
+ if (TrSkipWhitespace (tr)) {
+ col = tr -> mColumn;
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ if (ch == '\"') {
+ tr -> mOut ++;
+ len = 0;
+ while (TrLoad (tr)) {
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ tr -> mOut ++;
+ if (ch == '\"')
+ break;
+ if (ch == '\n') {
+ TrErrorAt (tr, tr -> mLine, col, "Unterminated string at end of line.\n");
+ return (0);
}
- }
- m2 = m;
- }
-}
-
-// Performs an inverse Fast Fourier Transform.
-static void FftInvProc (int n, const ComplexT * fftIn, ComplexT * fftOut) {
- int m2, rk, k, m;
- float a, b;
- int i;
- float wx, wy;
- int j, km2;
- float tx, ty, wyd, invn;
-
- // Data copy and bit-reversal ordering.
- m2 = (n >> 1);
- rk = 0;
- for (k = 0; k < n; k ++) {
- fftOut [rk] . mVec [0] = fftIn [k] . mVec [0];
- fftOut [rk] . mVec [1] = fftIn [k] . mVec [1];
- if (k < (n - 1)) {
- m = m2;
- while (rk >= m) {
- rk -= m;
- m >>= 1;
- }
- rk += m;
- }
+ if (len < maxLen)
+ text [len] = ch;
+ len ++;
+ }
+ if (ch != '\"') {
+ tr -> mColumn += 1 + len;
+ TrErrorAt (tr, tr -> mLine, col, "Unterminated string at end of input.\n");
+ return (0);
+ }
+ tr -> mColumn += 2 + len;
+ if (len > maxLen) {
+ TrErrorAt (tr, tr -> mLine, col, "String is too long.\n");
+ return (0);
+ }
+ text [len] = '\0';
+ return (1);
+ }
+ }
+ TrErrorAt (tr, tr -> mLine, col, "Expected a string.\n");
+ return (0);
+}
+
+// Reads and validates the given operator.
+static int TrReadOperator (TokenReaderT * tr, const char * op) {
+ uint col;
+ size_t len;
+ char ch;
+
+ col = tr -> mColumn;
+ if (TrSkipWhitespace (tr)) {
+ col = tr -> mColumn;
+ len = 0;
+ while ((op [len] != '\0') && TrLoad (tr)) {
+ ch = tr -> mRing [tr -> mOut & TR_RING_MASK];
+ if (ch != op [len])
+ break;
+ len ++;
+ tr -> mOut ++;
+ }
+ tr -> mColumn += len;
+ if (op [len] == '\0')
+ return (1);
}
- // Perform the IFFT.
- m2 = 1;
- for (m = 2; m <= n; m <<= 1) {
- a = sin (M_PI / m);
- a = 2.0f * a * a;
- b = -sin (2.0f * M_PI / m);
- for (i = 0; i < n; i += m) {
- wx = 1.0f;
- wy = 0.0f;
- for (k = i, j = 0; j < m2; k ++, j ++) {
- km2 = k + m2;
- tx = (wx * fftOut [km2] . mVec [0]) - (wy * fftOut [km2] . mVec [1]);
- ty = (wx * fftOut [km2] . mVec [1]) + (wy * fftOut [km2] . mVec [0]);
- fftOut [km2] . mVec [0] = fftOut [k] . mVec [0] - tx;
- fftOut [km2] . mVec [1] = fftOut [k] . mVec [1] - ty;
- fftOut [k] . mVec [0] += tx;
- fftOut [k] . mVec [1] += ty;
- wyd = (a * wy) - (b * wx);
- wx -= (a * wx) + (b * wy);
- wy -= wyd;
+ TrErrorAt (tr, tr -> mLine, col, "Expected '%s' operator.\n", op);
+ return (0);
+}
+
+/* Performs a string substitution. Any case-insensitive occurrences of the
+ * pattern string are replaced with the replacement string. The result is
+ * truncated if necessary.
+ */
+static int StrSubst (const char * in, const char * pat, const char * rep, const size_t maxLen, char * out) {
+ size_t inLen, patLen, repLen;
+ size_t si, di;
+ int truncated;
+
+ inLen = strlen (in);
+ patLen = strlen (pat);
+ repLen = strlen (rep);
+ si = 0;
+ di = 0;
+ truncated = 0;
+ while ((si < inLen) && (di < maxLen)) {
+ if (patLen <= (inLen - si)) {
+ if (strncasecmp (& in [si], pat, patLen) == 0) {
+ if (repLen > (maxLen - di)) {
+ repLen = maxLen - di;
+ truncated = 1;
}
- }
- m2 = m;
+ strncpy (& out [di], rep, repLen);
+ si += patLen;
+ di += repLen;
+ }
+ }
+ out [di] = in [si];
+ si ++;
+ di ++;
}
- // Normalize the samples.
- invn = 1.0f / n;
- for (i = 0; i < n; i ++) {
- fftOut [i] . mVec [0] *= invn;
- fftOut [i] . mVec [1] *= invn;
+ if (si < inLen)
+ truncated = 1;
+ out [di] = '\0';
+ return (! truncated);
+}
+
+// Provide missing math routines for MSVC.
+#ifdef _MSC_VER
+static double round (double val) {
+ if (val < 0.0)
+ return (ceil (val - 0.5));
+ return (floor (val + 0.5));
+}
+
+static double fmin (double a, double b) {
+ return ((a < b) ? a : b);
+}
+
+static double fmax (double a, double b) {
+ return ((a > b) ? a : b);
+}
+#endif
+
+// Simple clamp routine.
+static double Clamp (const double val, const double lower, const double upper) {
+ return (fmin (fmax (val, lower), upper));
+}
+
+// Performs linear interpolation.
+static double Lerp (const double a, const double b, const double f) {
+ return (a + (f * (b - a)));
+}
+
+// Performs a high-passed triangular probability density function dither from
+// a double to an integer. It assumes the input sample is already scaled.
+static int HpTpdfDither (const double in, int * hpHist) {
+ const double PRNG_SCALE = 1.0 / RAND_MAX;
+ int prn;
+ double out;
+
+ prn = rand ();
+ out = round (in + (PRNG_SCALE * (prn - (* hpHist))));
+ (* hpHist) = prn;
+ return ((int) out);
+}
+
+// Allocates an array of doubles.
+static double * CreateArray (const size_t n) {
+ double * a = NULL;
+
+ a = (double *) calloc (n, sizeof (double));
+ if (a == NULL) {
+ fprintf (stderr, "Error: Out of memory.\n");
+ exit (-1);
}
+ return (a);
}
-// Complex absolute value.
-static void ComplexAbs (const ComplexT * in, ComplexT * out) {
- out -> mVec [0] = sqrt ((in -> mVec [0] * in -> mVec [0]) + (in -> mVec [1] * in -> mVec [1]));
- out -> mVec [1] = 0.0f;
+// Frees an array of doubles.
+static void DestroyArray (const double * a) {
+ free ((void *) a);
}
-// Complex logarithm.
-static void ComplexLog (const ComplexT * in, ComplexT * out) {
- float r, t;
+// Complex number routines. All outputs must be non-NULL.
+
+// Magnitude/absolute value.
+static double ComplexAbs (const double r, const double i) {
+ return (sqrt ((r * r) + (i * i)));
+}
- r = sqrt ((in -> mVec [0] * in -> mVec [0]) + (in -> mVec [1] * in -> mVec [1]));
- t = atan2 (in -> mVec [1], in -> mVec [0]);
- if (t < 0.0f)
- t += 2.0f * M_PI;
- out -> mVec [0] = log (r);
- out -> mVec [1] = t;
+// Multiply.
+static void ComplexMul (const double aR, const double aI, const double bR, const double bI, double * outR, double * outI) {
+ (* outR) = (aR * bR) - (aI * bI);
+ (* outI) = (aI * bR) + (aR * bI);
}
-// Complex exponent.
-static void ComplexExp (const ComplexT * in, ComplexT * out) {
- float e;
+// Base-e exponent.
+static void ComplexExp (const double inR, const double inI, double * outR, double * outI) {
+ double e;
- e = exp (in -> mVec [0]);
- out -> mVec [0] = e * cos (in -> mVec [1]);
- out -> mVec [1] = e * sin (in -> mVec [1]);
+ e = exp (inR);
+ (* outR) = e * cos (inI);
+ (* outI) = e * sin (inI);
}
-// Calculates the real cepstrum of a given impulse response. It currently
-// uses a fixed cepstrum size. To make this more robust, it should be
-// rewritten to handle a variable size cepstrum.
-static void RealCepstrum (int irSize, const float * ir, float cep [CEP_SIZE]) {
- ComplexT in [CEP_SIZE], out [CEP_SIZE];
- int index;
+/* Fast Fourier transform routines. The number of points must be a power of
+ * two. In-place operation is possible only if both the real and imaginary
+ * parts are in-place together.
+ */
- for (index = 0; index < irSize; index ++) {
- in [index] . mVec [0] = ir [index];
- in [index] . mVec [1] = 0.0f;
+// Performs bit-reversal ordering.
+static void FftArrange (const size_t n, const double * inR, const double * inI, double * outR, double * outI) {
+ size_t rk, k, m;
+ double tempR, tempI;
+
+ if ((inR == outR) && (inI == outI)) {
+ // Handle in-place arrangement.
+ rk = 0;
+ for (k = 0; k < n; k ++) {
+ if (rk > k) {
+ tempR = inR [rk];
+ tempI = inI [rk];
+ outR [rk] = inR [k];
+ outI [rk] = inI [k];
+ outR [k] = tempR;
+ outI [k] = tempI;
+ }
+ m = n;
+ while (rk & (m >>= 1))
+ rk &= ~m;
+ rk |= m;
+ }
+ } else {
+ // Handle copy arrangement.
+ rk = 0;
+ for (k = 0; k < n; k ++) {
+ outR [rk] = inR [k];
+ outI [rk] = inI [k];
+ m = n;
+ while (rk & (m >>= 1))
+ rk &= ~m;
+ rk |= m;
+ }
}
- for (; index < CEP_SIZE; index ++) {
- in [index] . mVec [0] = 0.0f;
- in [index] . mVec [1] = 0.0f;
+}
+
+// Performs the summation.
+static void FftSummation (const size_t n, const double s, double * re, double * im) {
+ double pi;
+ size_t m, m2;
+ double vR, vI, wR, wI;
+ size_t i, k, mk;
+ double tR, tI;
+
+ pi = s * M_PI;
+ for (m = 1, m2 = 2; m < n; m <<= 1, m2 <<= 1) {
+ // v = Complex (-2.0 * sin (0.5 * pi / m) * sin (0.5 * pi / m), -sin (pi / m))
+ vR = sin (0.5 * pi / m);
+ vR = -2.0 * vR * vR;
+ vI = -sin (pi / m);
+ // w = Complex (1.0, 0.0)
+ wR = 1.0;
+ wI = 0.0;
+ for (i = 0; i < m; i ++) {
+ for (k = i; k < n; k += m2) {
+ mk = k + m;
+ // t = ComplexMul (w, out [km2])
+ tR = (wR * re [mk]) - (wI * im [mk]);
+ tI = (wR * im [mk]) + (wI * re [mk]);
+ // out [mk] = ComplexSub (out [k], t)
+ re [mk] = re [k] - tR;
+ im [mk] = im [k] - tI;
+ // out [k] = ComplexAdd (out [k], t)
+ re [k] += tR;
+ im [k] += tI;
+ }
+ // t = ComplexMul (v, w)
+ tR = (vR * wR) - (vI * wI);
+ tI = (vR * wI) + (vI * wR);
+ // w = ComplexAdd (w, t)
+ wR += tR;
+ wI += tI;
+ }
}
- FftProc (CEP_SIZE, in, out);
- for (index = 0; index < CEP_SIZE; index ++) {
- ComplexAbs (& out [index], & out [index]);
- if (out [index] . mVec [0] < 0.000001f)
- out [index] . mVec [0] = 0.000001f;
- ComplexLog (& out [index], & in [index]);
+}
+
+// Performs a forward FFT.
+static void FftForward (const size_t n, const double * inR, const double * inI, double * outR, double * outI) {
+ FftArrange (n, inR, inI, outR, outI);
+ FftSummation (n, 1.0, outR, outI);
+}
+
+// Performs an inverse FFT.
+static void FftInverse (const size_t n, const double * inR, const double * inI, double * outR, double * outI) {
+ double f;
+ size_t i;
+
+ FftArrange (n, inR, inI, outR, outI);
+ FftSummation (n, -1.0, outR, outI);
+ f = 1.0 / n;
+ for (i = 0; i < n; i ++) {
+ outR [i] *= f;
+ outI [i] *= f;
}
- FftInvProc (CEP_SIZE, in, out);
- for (index = 0; index < CEP_SIZE; index ++)
- cep [index] = out [index] . mVec [0];
}
-// Reconstructs the minimum-phase impulse response for a given real cepstrum.
-// Like the above function, this should eventually be modified to handle a
-// variable size cepstrum.
-static void MinimumPhase (const float cep [CEP_SIZE], int irSize, float * mpIr) {
- ComplexT in [CEP_SIZE], out [CEP_SIZE];
- int index;
+/* Calculate the complex helical sequence (or discrete-time analytical
+ * signal) of the given input using the Hilbert transform. Given the
+ * negative natural logarithm of a signal's magnitude response, the imaginary
+ * components can be used as the angles for minimum-phase reconstruction.
+ */
+static void Hilbert (const size_t n, const double * in, double * outR, double * outI) {
+ size_t i;
+
+ if (in == outR) {
+ // Handle in-place operation.
+ for (i = 0; i < n; i ++)
+ outI [i] = 0.0;
+ } else {
+ // Handle copy operation.
+ for (i = 0; i < n; i ++) {
+ outR [i] = in [i];
+ outI [i] = 0.0;
+ }
+ }
+ FftForward (n, outR, outI, outR, outI);
+ /* Currently the Fourier routines operate only on point counts that are
+ * powers of two. If that changes and n is odd, the following conditional
+ * should be: i < (n + 1) / 2.
+ */
+ for (i = 1; i < (n / 2); i ++) {
+ outR [i] *= 2.0;
+ outI [i] *= 2.0;
+ }
+ // If n is odd, the following increment should be skipped.
+ i ++;
+ for (; i < n; i ++) {
+ outR [i] = 0.0;
+ outI [i] = 0.0;
+ }
+ FftInverse (n, outR, outI, outR, outI);
+}
+
+/* Calculate the magnitude response of the given input. This is used in
+ * place of phase decomposition, since the phase residuals are discarded for
+ * minimum phase reconstruction. The mirrored half of the response is also
+ * discarded.
+ */
+static void MagnitudeResponse (const size_t n, const double * inR, const double * inI, double * out) {
+ const size_t m = 1 + (n / 2);
+ size_t i;
+
+ for (i = 0; i < m; i ++)
+ out [i] = fmax (ComplexAbs (inR [i], inI [i]), EPSILON);
+}
- in [0] . mVec [0] = cep [0];
- for (index = 1; index < (CEP_SIZE / 2); index ++)
- in [index] . mVec [0] = 2.0f * cep [index];
- if ((CEP_SIZE % 2) != 1) {
- in [index] . mVec [0] = cep [index];
- index ++;
+/* Apply a range limit (in dB) to the given magnitude response. This is used
+ * to adjust the effects of the diffuse-field average on the equalization
+ * process.
+ */
+static void LimitMagnitudeResponse (const size_t n, const double limit, const double * in, double * out) {
+ const size_t m = 1 + (n / 2);
+ double halfLim;
+ size_t i, lower, upper;
+ double ave;
+
+ halfLim = limit / 2.0;
+ // Convert the response to dB.
+ for (i = 0; i < m; i ++)
+ out [i] = 20.0 * log10 (in [i]);
+ // Use six octaves to calculate the average magnitude of the signal.
+ lower = ((size_t) ceil (n / pow (2.0, 8.0))) - 1;
+ upper = ((size_t) floor (n / pow (2.0, 2.0))) - 1;
+ ave = 0.0;
+ for (i = lower; i <= upper; i ++)
+ ave += out [i];
+ ave /= upper - lower + 1;
+ // Keep the response within range of the average magnitude.
+ for (i = 0; i < m; i ++)
+ out [i] = Clamp (out [i], ave - halfLim, ave + halfLim);
+ // Convert the response back to linear magnitude.
+ for (i = 0; i < m; i ++)
+ out [i] = pow (10.0, out [i] / 20.0);
+}
+
+/* Reconstructs the minimum-phase component for the given magnitude response
+ * of a signal. This is equivalent to phase recomposition, sans the missing
+ * residuals (which were discarded). The mirrored half of the response is
+ * reconstructed.
+ */
+static void MinimumPhase (const size_t n, const double * in, double * outR, double * outI) {
+ const size_t m = 1 + (n / 2);
+ double * mags = NULL;
+ size_t i;
+ double aR, aI;
+
+ mags = CreateArray (n);
+ for (i = 0; i < m; i ++) {
+ mags [i] = fmax (in [i], EPSILON);
+ outR [i] = -log (mags [i]);
}
- for (; index < CEP_SIZE; index ++)
- in [index] . mVec [0] = 0.0f;
- for (index = 0; index < CEP_SIZE; index ++)
- in [index] . mVec [1] = 0.0f;
- FftProc (CEP_SIZE, in, out);
- for (index = 0; index < CEP_SIZE; index ++)
- ComplexExp (& out [index], & in [index]);
- FftInvProc (CEP_SIZE, in, out);
- for (index = 0; index < irSize; index ++)
- mpIr [index] = out [index] . mVec [0];
+ for (; i < n; i ++) {
+ mags [i] = mags [n - i];
+ outR [i] = outR [n - i];
+ }
+ Hilbert (n, outR, outR, outI);
+ // Remove any DC offset the filter has.
+ outR [0] = 0.0;
+ outI [0] = 0.0;
+ for (i = 1; i < n; i ++) {
+ ComplexExp (0.0, outI [i], & aR, & aI);
+ ComplexMul (mags [i], 0.0, aR, aI, & outR [i], & outI [i]);
+ }
+ DestroyArray (mags);
}
-// Calculate the left-ear time delay using a spherical head model.
-static float CalcLTD (float ev, float az, float rad, float dist) {
- float azp, dlp, l, al;
+// Read a binary value of the specified byte order and byte size from a file,
+// storing it as a 32-bit unsigned integer.
+static int ReadBin4 (FILE * fp, const char * filename, const ByteOrderT order, const uint bytes, uint4 * out) {
+ uint1 in [4];
+ uint4 accum;
+ uint i;
- azp = asin (cos (ev) * sin (az));
- dlp = sqrt ((dist * dist) + (rad * rad) + (2.0f * dist * rad * sin (azp)));
- l = sqrt ((dist * dist) - (rad * rad));
- al = (0.5f * M_PI) + azp;
- if (dlp > l)
- dlp = l + (rad * (al - acos (rad / dist)));
- return (dlp / 343.3f);
+ if (fread (in, 1, bytes, fp) != bytes) {
+ fprintf (stderr, "Error: Bad read from file '%s'.\n", filename);
+ return (0);
+ }
+ accum = 0;
+ switch (order) {
+ case BO_LITTLE :
+ for (i = 0; i < bytes; i ++)
+ accum = (accum << 8) | in [bytes - i - 1];
+ break;
+ case BO_BIG :
+ for (i = 0; i < bytes; i ++)
+ accum = (accum << 8) | in [i];
+ break;
+ default :
+ break;
+ }
+ (* out) = accum;
+ return (1);
}
-// Read a 16-bit little-endian integer from a file and convert it to a 32-bit
-// floating-point value in the range of -1.0 to 1.0.
-static int ReadInt16LeAsFloat32 (const char * fileName, FILE * fp, float * val) {
- ALubyte vb [2];
- ALushort vw;
+// Read a binary value of the specified byte order from a file, storing it as
+// a 64-bit unsigned integer.
+static int ReadBin8 (FILE * fp, const char * filename, const ByteOrderT order, uint8 * out) {
+ uint1 in [8];
+ uint8 accum;
+ uint i;
- if (fread (vb, 1, sizeof (vb), fp) != sizeof (vb)) {
- fclose (fp);
- fprintf (stderr, "Error reading from file, '%s'.\n", fileName);
+ if (fread (in, 1, 8, fp) != 8) {
+ fprintf (stderr, "Error: Bad read from file '%s'.\n", filename);
return (0);
}
- vw = (((unsigned short) vb [1]) << 8) | vb [0];
- (* val) = ((short) vw) / 32768.0f;
+ accum = 0ULL;
+ switch (order) {
+ case BO_LITTLE :
+ for (i = 0; i < 8; i ++)
+ accum = (accum << 8) | in [8 - i - 1];
+ break;
+ case BO_BIG :
+ for (i = 0; i < 8; i ++)
+ accum = (accum << 8) | in [i];
+ break;
+ default :
+ break;
+ }
+ (* out) = accum;
return (1);
}
-// Write a string to a file.
-static int WriteString (const char * val, const char * fileName, FILE * fp) {
+// Write an ASCII string to a file.
+static int WriteAscii (const char * out, FILE * fp, const char * filename) {
size_t len;
- len = strlen (val);
- if (fwrite (val, 1, len, fp) != len) {
+ len = strlen (out);
+ if (fwrite (out, 1, len, fp) != len) {
fclose (fp);
- fprintf (stderr, "Error writing to file, '%s'.\n", fileName);
+ fprintf (stderr, "Error: Bad write to file '%s'.\n", filename);
return (0);
}
return (1);
}
-// Write a 32-bit floating-point value in the range of -1.0 to 1.0 to a file
-// as a 16-bit little-endian integer.
-static int WriteFloat32AsInt16Le (float val, const char * fileName, FILE * fp) {
- ALshort vw;
- ALubyte vb [2];
-
- vw = (short) round (32767.0f * val);
- vb [0] = vw & 0x00FF;
- vb [1] = (vw >> 8) & 0x00FF;
- if (fwrite (vb, 1, sizeof (vb), fp) != sizeof (vb)) {
- fclose (fp);
- fprintf (stderr, "Error writing to file, '%s'.\n", fileName);
+// Write a binary value of the given byte order and byte size to a file,
+// loading it from a 32-bit unsigned integer.
+static int WriteBin4 (const ByteOrderT order, const uint bytes, const uint4 in, FILE * fp, const char * filename) {
+ uint1 out [4];
+ uint i;
+
+ switch (order) {
+ case BO_LITTLE :
+ for (i = 0; i < bytes; i ++)
+ out [i] = (in >> (i * 8)) & 0x000000FF;
+ break;
+ case BO_BIG :
+ for (i = 0; i < bytes; i ++)
+ out [bytes - i - 1] = (in >> (i * 8)) & 0x000000FF;
+ break;
+ default :
+ break;
+ }
+ if (fwrite (out, 1, bytes, fp) != bytes) {
+ fprintf (stderr, "Error: Bad write to file '%s'.\n", filename);
return (0);
}
return (1);
}
-// Write a 32-bit little-endian unsigned integer to a file.
-static int WriteUInt32Le (ALuint val, const char * fileName, FILE * fp) {
- ALubyte vb [4];
+/* Read a binary value of the specified type, byte order, and byte size from
+ * a file, converting it to a double. For integer types, the significant
+ * bits are used to normalize the result. The sign of bits determines
+ * whether they are padded toward the MSB (negative) or LSB (positive).
+ * Floating-point types are not normalized.
+ */
+static int ReadBinAsDouble (FILE * fp, const char * filename, const ByteOrderT order, const ElementTypeT type, const uint bytes, const int bits, double * out) {
+ union {
+ uint4 ui;
+ int4 i;
+ float f;
+ } v4;
+ union {
+ uint8 ui;
+ double f;
+ } v8;
+
+ (* out) = 0.0;
+ if (bytes > 4) {
+ if (! ReadBin8 (fp, filename, order, & v8 . ui))
+ return (0);
+ if (type == ET_FP)
+ (* out) = v8 . f;
+ } else {
+ if (! ReadBin4 (fp, filename, order, bytes, & v4 . ui))
+ return (0);
+ if (type == ET_FP) {
+ (* out) = (double) v4 . f;
+ } else {
+ if (bits > 0)
+ v4 . ui >>= (8 * bytes) - bits;
+ else
+ v4 . ui &= (0xFFFFFFFF >> (32 + bits));
+ if (v4 . ui & (1 << (abs (bits) - 1)))
+ v4 . ui |= (0xFFFFFFFF << abs (bits));
+ (* out) = v4 . i / ((double) (1 << (abs (bits) - 1)));
+ }
+ }
+ return (1);
+}
+
+/* Read an ascii value of the specified type from a file, converting it to a
+ * double. For integer types, the significant bits are used to normalize the
+ * result. The sign of the bits should always be positive. This also skips
+ * up to one separator character before the element itself.
+ */
+static int ReadAsciiAsDouble (TokenReaderT * tr, const char * filename, const ElementTypeT type, const uint bits, double * out) {
+ int v;
+
+ if (TrIsOperator (tr, ","))
+ TrReadOperator (tr, ",");
+ else if (TrIsOperator (tr, ":"))
+ TrReadOperator (tr, ":");
+ else if (TrIsOperator (tr, ";"))
+ TrReadOperator (tr, ";");
+ else if (TrIsOperator (tr, "|"))
+ TrReadOperator (tr, "|");
+ if (type == ET_FP) {
+ if (! TrReadFloat (tr, -1.0 / 0.0, 1.0 / 0.0, out)) {
+ fprintf (stderr, "Error: Bad read from file '%s'.\n", filename);
+ return (0);
+ }
+ } else {
+ if (! TrReadInt (tr, -(1 << (bits - 1)), (1 << (bits - 1)) - 1, & v)) {
+ fprintf (stderr, "Error: Bad read from file '%s'.\n", filename);
+ return (0);
+ }
+ (* out) = v / ((double) ((1 << (bits - 1)) - 1));
+ }
+ return (1);
+}
- vb [0] = val & 0x000000FF;
- vb [1] = (val >> 8) & 0x000000FF;
- vb [2] = (val >> 16) & 0x000000FF;
- vb [3] = (val >> 24) & 0x000000FF;
- if (fwrite (vb, 1, sizeof (vb), fp) != sizeof (vb)) {
- fclose (fp);
- fprintf (stderr, "Error writing to file, '%s'.\n", fileName);
+// Read the RIFF/RIFX WAVE format chunk from a file, validating it against
+// the source parameters and data set metrics.
+static int ReadWaveFormat (FILE * fp, const ByteOrderT order, const uint hrirRate, SourceRefT * src) {
+ uint4 fourCC, chunkSize;
+ uint4 format, channels, rate, dummy, block, size, bits;
+
+ chunkSize = 0;
+ do {
+ if (chunkSize > 0)
+ fseek (fp, chunkSize, SEEK_CUR);
+ if ((! ReadBin4 (fp, src -> mPath, BO_LITTLE, 4, & fourCC)) ||
+ (! ReadBin4 (fp, src -> mPath, order, 4, & chunkSize)))
+ return (0);
+ } while (fourCC != FOURCC_FMT);
+ if ((! ReadBin4 (fp, src -> mPath, order, 2, & format)) ||
+ (! ReadBin4 (fp, src -> mPath, order, 2, & channels)) ||
+ (! ReadBin4 (fp, src -> mPath, order, 4, & rate)) ||
+ (! ReadBin4 (fp, src -> mPath, order, 4, & dummy)) ||
+ (! ReadBin4 (fp, src -> mPath, order, 2, & block)))
+ return (0);
+ block /= channels;
+ if (chunkSize > 14) {
+ if (! ReadBin4 (fp, src -> mPath, order, 2, & size))
+ return (0);
+ size /= 8;
+ if (block > size)
+ size = block;
+ } else {
+ size = block;
+ }
+ if (format == WAVE_FORMAT_EXTENSIBLE) {
+ fseek (fp, 2, SEEK_CUR);
+ if (! ReadBin4 (fp, src -> mPath, order, 2, & bits))
+ return (0);
+ if (bits == 0)
+ bits = 8 * size;
+ fseek (fp, 4, SEEK_CUR);
+ if (! ReadBin4 (fp, src -> mPath, order, 2, & format))
+ return (0);
+ fseek (fp, chunkSize - 26, SEEK_CUR);
+ } else {
+ bits = 8 * size;
+ if (chunkSize > 14)
+ fseek (fp, chunkSize - 16, SEEK_CUR);
+ else
+ fseek (fp, chunkSize - 14, SEEK_CUR);
+ }
+ if ((format != WAVE_FORMAT_PCM) && (format != WAVE_FORMAT_IEEE_FLOAT)) {
+ fprintf (stderr, "Error: Unsupported WAVE format in file '%s'.\n", src -> mPath);
return (0);
}
+ if (src -> mChannel >= channels) {
+ fprintf (stderr, "Error: Missing source channel in WAVE file '%s'.\n", src -> mPath);
+ return (0);
+ }
+ if (rate != hrirRate) {
+ fprintf (stderr, "Error: Mismatched source sample rate in WAVE file '%s'.\n", src -> mPath);
+ return (0);
+ }
+ if (format == WAVE_FORMAT_PCM) {
+ if ((size < 2) || (size > 4)) {
+ fprintf (stderr, "Error: Unsupported sample size in WAVE file '%s'.\n", src -> mPath);
+ return (0);
+ }
+ if ((bits < 16) || (bits > (8 * size))) {
+ fprintf (stderr, "Error: Bad significant bits in WAVE file '%s'.\n", src -> mPath);
+ return (0);
+ }
+ src -> mType = ET_INT;
+ } else {
+ if ((size != 4) && (size != 8)) {
+ fprintf (stderr, "Error: Unsupported sample size in WAVE file '%s'.\n", src -> mPath);
+ return (0);
+ }
+ src -> mType = ET_FP;
+ }
+ src -> mSize = size;
+ src -> mBits = (int) bits;
+ src -> mSkip = channels;
return (1);
}
-// Write a 16-bit little-endian unsigned integer to a file.
-static int WriteUInt16Le (ALushort val, const char * fileName, FILE * fp) {
- ALubyte vb [2];
+// Read a RIFF/RIFX WAVE data chunk, converting all elements to doubles.
+static int ReadWaveData (FILE * fp, const SourceRefT * src, const ByteOrderT order, const size_t n, double * hrir) {
+ int pre, post, skip;
+ size_t i;
- vb [0] = val & 0x00FF;
- vb [1] = (val >> 8) & 0x00FF;
- if (fwrite (vb, 1, sizeof (vb), fp) != sizeof (vb)) {
- fclose (fp);
- fprintf (stderr, "Error writing to file, '%s'.\n", fileName);
+ pre = (int) (src -> mSize * src -> mChannel);
+ post = (int) (src -> mSize * (src -> mSkip - src -> mChannel - 1));
+ skip = 0;
+ for (i = 0; i < n; i ++) {
+ skip += pre;
+ if (skip > 0)
+ fseek (fp, skip, SEEK_CUR);
+ if (! ReadBinAsDouble (fp, src -> mPath, order, src -> mType, src -> mSize, src -> mBits, & hrir [i]))
+ return (0);
+ skip = post;
+ }
+ if (skip > 0)
+ fseek (fp, skip, SEEK_CUR);
+ return (1);
+}
+
+// Read the RIFF/RIFX WAVE list or data chunk, converting all elements to
+// doubles.
+static int ReadWaveList (FILE * fp, const SourceRefT * src, const ByteOrderT order, const size_t n, double * hrir) {
+ uint4 fourCC, chunkSize, listSize;
+ size_t block, skip, count, offset, i;
+ double lastSample;
+
+ for (;;) {
+ if ((! ReadBin4 (fp, src -> mPath, BO_LITTLE, 4, & fourCC)) ||
+ (! ReadBin4 (fp, src -> mPath, order, 4, & chunkSize)))
+ return (0);
+ if (fourCC == FOURCC_DATA) {
+ block = src -> mSize * src -> mSkip;
+ count = chunkSize / block;
+ if (count < (src -> mOffset + n)) {
+ fprintf (stderr, "Error: Bad read from file '%s'.\n", src -> mPath);
+ return (0);
+ }
+ fseek (fp, src -> mOffset * block, SEEK_CUR);
+ if (! ReadWaveData (fp, src, order, n, & hrir [0]))
+ return (0);
+ return (1);
+ } else if (fourCC == FOURCC_LIST) {
+ if (! ReadBin4 (fp, src -> mPath, BO_LITTLE, 4, & fourCC))
+ return (0);
+ chunkSize -= 4;
+ if (fourCC == FOURCC_WAVL)
+ break;
+ }
+ if (chunkSize > 0)
+ fseek (fp, chunkSize, SEEK_CUR);
+ }
+ listSize = chunkSize;
+ block = src -> mSize * src -> mSkip;
+ skip = src -> mOffset;
+ offset = 0;
+ lastSample = 0.0;
+ while ((offset < n) && (listSize > 8)) {
+ if ((! ReadBin4 (fp, src -> mPath, BO_LITTLE, 4, & fourCC)) ||
+ (! ReadBin4 (fp, src -> mPath, order, 4, & chunkSize)))
+ return (0);
+ listSize -= 8 + chunkSize;
+ if (fourCC == FOURCC_DATA) {
+ count = chunkSize / block;
+ if (count > skip) {
+ fseek (fp, skip * block, SEEK_CUR);
+ chunkSize -= skip * block;
+ count -= skip;
+ skip = 0;
+ if (count > (n - offset))
+ count = n - offset;
+ if (! ReadWaveData (fp, src, order, count, & hrir [offset]))
+ return (0);
+ chunkSize -= count * block;
+ offset += count;
+ lastSample = hrir [offset - 1];
+ } else {
+ skip -= count;
+ count = 0;
+ }
+ } else if (fourCC == FOURCC_SLNT) {
+ if (! ReadBin4 (fp, src -> mPath, order, 4, & count))
+ return (0);
+ chunkSize -= 4;
+ if (count > skip) {
+ count -= skip;
+ skip = 0;
+ if (count > (n - offset))
+ count = n - offset;
+ for (i = 0; i < count; i ++)
+ hrir [offset + i] = lastSample;
+ offset += count;
+ } else {
+ skip -= count;
+ count = 0;
+ }
+ }
+ if (chunkSize > 0)
+ fseek (fp, chunkSize, SEEK_CUR);
+ }
+ if (offset < n) {
+ fprintf (stderr, "Error: Bad read from file '%s'.\n", src -> mPath);
return (0);
}
return (1);
}
-// Write an 8-bit unsigned integer to a file.
-static int WriteUInt8 (ALubyte val, const char * fileName, FILE * fp) {
- if (fwrite (& val, 1, sizeof (val), fp) != sizeof (val)) {
- fclose (fp);
- fprintf (stderr, "Error writing to file, '%s'.\n", fileName);
+// Load a source HRIR from a RIFF/RIFX WAVE file.
+static int LoadWaveSource (FILE * fp, SourceRefT * src, const uint hrirRate, const size_t n, double * hrir) {
+ uint4 fourCC, dummy;
+ ByteOrderT order;
+
+ if ((! ReadBin4 (fp, src -> mPath, BO_LITTLE, 4, & fourCC)) ||
+ (! ReadBin4 (fp, src -> mPath, BO_LITTLE, 4, & dummy)))
+ return (0);
+ if (fourCC == FOURCC_RIFF) {
+ order = BO_LITTLE;
+ } else if (fourCC == FOURCC_RIFX) {
+ order = BO_BIG;
+ } else {
+ fprintf (stderr, "Error: No RIFF/RIFX chunk in file '%s'.\n", src -> mPath);
+ return (0);
+ }
+ if (! ReadBin4 (fp, src -> mPath, BO_LITTLE, 4, & fourCC))
+ return (0);
+ if (fourCC != FOURCC_WAVE) {
+ fprintf (stderr, "Error: Not a RIFF/RIFX WAVE file '%s'.\n", src -> mPath);
return (0);
}
+ if (! ReadWaveFormat (fp, order, hrirRate, src))
+ return (0);
+ if (! ReadWaveList (fp, src, order, n, hrir))
+ return (0);
return (1);
}
-// Load the MIT HRIRs. This loads the entire diffuse or compact set starting
-// counter-clockwise up at the bottom elevation and clockwise at the forward
-// azimuth.
-static int LoadMitHrirs (const char * baseName, HrirDataT * hData) {
- const int EV_ANGLE [MIT_EV_COUNT] = {
- -90, -80, -70, -60, -50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90
- };
- int e, a;
- char fileName [1024];
- FILE * fp = NULL;
- int j0, j1, i;
- float s;
-
- for (e = MIT_EV_START; e < MIT_EV_COUNT; e ++) {
- for (a = 0; a < MIT_AZ_COUNT [e]; a ++) {
- // The data packs the first 180 degrees in the left channel, and
- // the last 180 degrees in the right channel.
- if (round ((360.0f / MIT_AZ_COUNT [e]) * a) > 180.0f)
- break;
- // Determine which file to open.
- snprintf (fileName, 1023, "%s%d/H%de%03da.wav", baseName, EV_ANGLE [e], EV_ANGLE [e], (int) round ((360.0f / MIT_AZ_COUNT [e]) * a));
- if ((fp = fopen (fileName, "rb")) == NULL) {
- fprintf (stderr, "Could not open file, '%s'.\n", fileName);
+// Load a source HRIR from a binary file.
+static int LoadBinarySource (FILE * fp, const SourceRefT * src, const ByteOrderT order, const size_t n, double * hrir) {
+ size_t i;
+
+ fseek (fp, src -> mOffset, SEEK_SET);
+ for (i = 0; i < n; i ++) {
+ if (! ReadBinAsDouble (fp, src -> mPath, order, src -> mType, src -> mSize, src -> mBits, & hrir [i]))
+ return (0);
+ if (src -> mSkip > 0)
+ fseek (fp, src -> mSkip, SEEK_CUR);
+ }
+ return (1);
+}
+
+// Load a source HRIR from an ASCII text file containing a list of elements
+// separated by whitespace or common list operators (',', ';', ':', '|').
+static int LoadAsciiSource (FILE * fp, const SourceRefT * src, const size_t n, double * hrir) {
+ TokenReaderT tr;
+ size_t i, j;
+ double dummy;
+
+ TrSetup (fp, NULL, & tr);
+ for (i = 0; i < src -> mOffset; i ++) {
+ if (! ReadAsciiAsDouble (& tr, src -> mPath, src -> mType, src -> mBits, & dummy))
+ return (0);
+ }
+ for (i = 0; i < n; i ++) {
+ if (! ReadAsciiAsDouble (& tr, src -> mPath, src -> mType, src -> mBits, & hrir [i]))
+ return (0);
+ for (j = 0; j < src -> mSkip; j ++) {
+ if (! ReadAsciiAsDouble (& tr, src -> mPath, src -> mType, src -> mBits, & dummy))
return (0);
- }
- // Assuming they have not changed format, skip the .WAV header.
- fseek (fp, 44, SEEK_SET);
- // Map the left and right channels to their appropriate azimuth
- // offsets.
- j0 = (MIT_EV_OFFSET [e] + a) * MIT_IR_SIZE;
- j1 = (MIT_EV_OFFSET [e] + ((MIT_AZ_COUNT [e] - a) % MIT_AZ_COUNT [e])) * MIT_IR_SIZE;
- // Read in the data, converting it to floating-point.
- for (i = 0; i < MIT_IR_SIZE; i ++) {
- if (! ReadInt16LeAsFloat32 (fileName, fp, & s))
- return (0);
- hData -> mHrirs [j0 + i] = s;
- if (! ReadInt16LeAsFloat32 (fileName, fp, & s))
- return (0);
- hData -> mHrirs [j1 + i] = s;
- }
- fclose (fp);
}
}
return (1);
}
-// Performs the minimum phase reconstruction for a given HRIR data set. The
-// cepstrum size should be made configureable at some point in the future.
-static void ReconstructHrirs (int minIrSize, HrirDataT * hData) {
- int start, end, step, j;
- float cep [CEP_SIZE];
+// Load a source HRIR from a supported file type.
+static int LoadSource (SourceRefT * src, const uint hrirRate, const size_t n, double * hrir) {
+ FILE * fp = NULL;
+ int result;
+
+ if (src -> mFormat == SF_ASCII)
+ fp = fopen (src -> mPath, "r");
+ else
+ fp = fopen (src -> mPath, "rb");
+ if (fp == NULL) {
+ fprintf (stderr, "Error: Could not open source file '%s'.\n", src -> mPath);
+ return (0);
+ }
+ if (src -> mFormat == SF_WAVE)
+ result = LoadWaveSource (fp, src, hrirRate, n, hrir);
+ else if (src -> mFormat == SF_BIN_LE)
+ result = LoadBinarySource (fp, src, BO_LITTLE, n, hrir);
+ else if (src -> mFormat == SF_BIN_BE)
+ result = LoadBinarySource (fp, src, BO_BIG, n, hrir);
+ else
+ result = LoadAsciiSource (fp, src, n, hrir);
+ fclose (fp);
+ return (result);
+}
- start = hData -> mEvOffset [hData -> mEvStart];
- end = hData -> mIrCount;
- step = hData -> mIrSize;
- for (j = start; j < end; j ++) {
- RealCepstrum (step, & hData -> mHrirs [j * step], cep);
- MinimumPhase (cep, minIrSize, & hData -> mHrirs [j * minIrSize]);
+// Calculate the magnitude response of an HRIR and average it with any
+// existing responses for its elevation and azimuth.
+static void AverageHrirMagnitude (const double * hrir, const double f, const uint ei, const uint ai, const HrirDataT * hData) {
+ double * re = NULL, * im = NULL;
+ size_t n, m, i, j;
+
+ n = hData -> mFftSize;
+ re = CreateArray (n);
+ im = CreateArray (n);
+ for (i = 0; i < hData -> mIrPoints; i ++) {
+ re [i] = hrir [i];
+ im [i] = 0.0;
}
- hData -> mIrSize = minIrSize;
+ for (; i < n; i ++) {
+ re [i] = 0.0;
+ im [i] = 0.0;
+ }
+ FftForward (n, re, im, re, im);
+ MagnitudeResponse (n, re, im, re);
+ m = 1 + (n / 2);
+ j = (hData -> mEvOffset [ei] + ai) * hData -> mIrSize;
+ for (i = 0; i < m; i ++)
+ hData -> mHrirs [j + i] = Lerp (hData -> mHrirs [j + i], re [i], f);
+ DestroyArray (im);
+ DestroyArray (re);
}
-// Renormalize the entire HRIR data set, and attenutate it slightly.
-static void RenormalizeHrirs (const HrirDataT * hData) {
- int step, start, end;
- float norm;
- int j, i;
+/* Calculate the contribution of each HRIR to the diffuse-field average based
+ * on the area of its surface patch. All patches are centered at the HRIR
+ * coordinates on the unit sphere and are measured by solid angle.
+ */
+static void CalculateDfWeights (const HrirDataT * hData, double * weights) {
+ uint ei;
+ double evs, sum, ev, up_ev, down_ev, solidAngle;
+
+ evs = 90.0 / (hData -> mEvCount - 1);
+ sum = 0.0;
+ for (ei = hData -> mEvStart; ei < hData -> mEvCount; ei ++) {
+ // For each elevation, calculate the upper and lower limits of the
+ // patch band.
+ ev = -90.0 + (ei * 2.0 * evs);
+ if (ei < (hData -> mEvCount - 1))
+ up_ev = (ev + evs) * M_PI / 180.0;
+ else
+ up_ev = M_PI / 2.0;
+ if (ei > 0)
+ down_ev = (ev - evs) * M_PI / 180.0;
+ else
+ down_ev = -M_PI / 2.0;
+ // Calculate the area of the patch band.
+ solidAngle = 2.0 * M_PI * (sin (up_ev) - sin (down_ev));
+ // Each weight is the area of one patch.
+ weights [ei] = solidAngle / hData -> mAzCount [ei];
+ // Sum the total surface area covered by the HRIRs.
+ sum += solidAngle;
+ }
+ // Normalize the weights given the total surface coverage.
+ for (ei = hData -> mEvStart; ei < hData -> mEvCount; ei ++)
+ weights [ei] /= sum;
+}
+/* Calculate the diffuse-field average from the given magnitude responses of
+ * the HRIR set. Weighting can be applied to compensate for the varying
+ * surface area covered by each HRIR. The final average can then be limited
+ * by the specified magnitude range (in positive dB; 0.0 to skip).
+ */
+static void CalculateDiffuseFieldAverage (const HrirDataT * hData, const int weighted, const double limit, double * dfa) {
+ double * weights = NULL;
+ uint ei, ai;
+ size_t count, step, start, end, m, j, i;
+ double weight;
+
+ weights = CreateArray (hData -> mEvCount);
+ if (weighted) {
+ // Use coverage weighting to calculate the average.
+ CalculateDfWeights (hData, weights);
+ } else {
+ // If coverage weighting is not used, the weights still need to be
+ // averaged by the number of HRIRs.
+ count = 0;
+ for (ei = hData -> mEvStart; ei < hData -> mEvCount; ei ++)
+ count += hData -> mAzCount [ei];
+ for (ei = hData -> mEvStart; ei < hData -> mEvCount; ei ++)
+ weights [ei] = 1.0 / count;
+ }
+ ei = hData -> mEvStart;
+ ai = 0;
step = hData -> mIrSize;
- start = hData -> mEvOffset [hData -> mEvStart] * step;
+ start = hData -> mEvOffset [ei] * step;
end = hData -> mIrCount * step;
- norm = 0.0f;
+ m = 1 + (hData -> mFftSize / 2);
+ for (i = 0; i < m; i ++)
+ dfa [i] = 0.0;
for (j = start; j < end; j += step) {
- for (i = 0; i < step; i ++) {
- if (fabs (hData -> mHrirs [j + i]) > norm)
- norm = fabs (hData -> mHrirs [j + i]);
+ // Get the weight for this HRIR's contribution.
+ weight = weights [ei];
+ // Add this HRIR's weighted power average to the total.
+ for (i = 0; i < m; i ++)
+ dfa [i] += weight * hData -> mHrirs [j + i] * hData -> mHrirs [j + i];
+ // Determine the next weight to use.
+ ai ++;
+ if (ai >= hData -> mAzCount [ei]) {
+ ei ++;
+ ai = 0;
}
}
- if (norm > 0.000001f)
- norm = 1.0f / norm;
- norm *= 0.95f;
+ // Finish the average calculation and keep it from being too small.
+ for (i = 0; i < m; i ++)
+ dfa [i] = fmax (sqrt (dfa [i]), EPSILON);
+ // Apply a limit to the magnitude range of the diffuse-field average if
+ // desired.
+ if (limit > 0.0)
+ LimitMagnitudeResponse (hData -> mFftSize, limit, dfa, dfa);
+ DestroyArray (weights);
+}
+
+// Perform diffuse-field equalization on the magnitude responses of the HRIR
+// set using the given average response.
+static void DiffuseFieldEqualize (const double * dfa, const HrirDataT * hData) {
+ size_t step, start, end, m, j, i;
+
+ step = hData -> mIrSize;
+ start = hData -> mEvOffset [hData -> mEvStart] * step;
+ end = hData -> mIrCount * step;
+ m = 1 + (hData -> mFftSize / 2);
for (j = start; j < end; j += step) {
- for (i = 0; i < step; i ++)
- hData -> mHrirs [j + i] *= norm;
+ for (i = 0; i < m; i ++)
+ hData -> mHrirs [j + i] /= dfa [i];
}
}
-// Given an elevation offset and azimuth, calculates two offsets for
-// addressing the HRIRs buffer and their interpolation factor.
-static void CalcAzIndices (const HrirDataT * hData, int oi, float az, int * j0, int * j1, float * jf) {
- int ai;
+// Perform minimum-phase reconstruction using the magnitude responses of the
+// HRIR set.
+static void ReconstructHrirs (const HrirDataT * hData) {
+ double * re = NULL, * im = NULL;
+ size_t step, start, end, n, j, i;
- az = fmod ((2.0f * M_PI) + az, 2.0f * M_PI) * hData -> mAzCount [oi] / (2.0f * M_PI);
- ai = (int) az;
- az -= ai;
- (* j0) = hData -> mEvOffset [oi] + ai;
- (* j1) = hData -> mEvOffset [oi] + ((ai + 1) % hData -> mAzCount [oi]);
- (* jf) = az;
+ step = hData -> mIrSize;
+ start = hData -> mEvOffset [hData -> mEvStart] * step;
+ end = hData -> mIrCount * step;
+ n = hData -> mFftSize;
+ re = CreateArray (n);
+ im = CreateArray (n);
+ for (j = start; j < end; j += step) {
+ MinimumPhase (n, & hData -> mHrirs [j], re, im);
+ FftInverse (n, re, im, re, im);
+ for (i = 0; i < hData -> mIrPoints; i ++)
+ hData -> mHrirs [j + i] = re [i];
+ }
+ DestroyArray (im);
+ DestroyArray (re);
}
-// Perform a linear interpolation.
-static float Lerp (float a, float b, float f) {
- return (a + (f * (b - a)));
+/* Given an elevation index and an azimuth, calculate the indices of the two
+ * HRIRs that bound the coordinate along with a factor for calculating the
+ * continous HRIR using interpolation.
+ */
+static void CalcAzIndices (const HrirDataT * hData, const uint ei, const double az, uint * j0, uint * j1, double * jf) {
+ double af;
+ uint ai;
+
+ af = ((2.0 * M_PI) + az) * hData -> mAzCount [ei] / (2.0 * M_PI);
+ ai = ((uint) af) % hData -> mAzCount [ei];
+ af -= floor (af);
+ (* j0) = hData -> mEvOffset [ei] + ai;
+ (* j1) = hData -> mEvOffset [ei] + ((ai + 1) % hData -> mAzCount [ei]);
+ (* jf) = af;
}
-// Attempt to synthesize any missing HRIRs at the bottom elevations. Right
-// now this just blends the lowest elevation HRIRs together and applies some
-// attenuates and high frequency damping. It's not a realistic model to use,
-// but it is simple.
+/* Attempt to synthesize any missing HRIRs at the bottom elevations. Right
+ * now this just blends the lowest elevation HRIRs together and applies some
+ * attenuation and high frequency damping. It is a simple, if inaccurate
+ * model.
+ */
static void SynthesizeHrirs (HrirDataT * hData) {
- int step, oi, i, a, j, e;
- float of;
- int j0, j1;
- float jf;
- float lp [4], s0, s1;
+ uint oi, a, e;
+ size_t step, n, i, j;
+ double of;
+ size_t j0, j1;
+ double jf;
+ double lp [4], s0, s1;
if (hData -> mEvStart <= 0)
return;
step = hData -> mIrSize;
oi = hData -> mEvStart;
- for (i = 0; i < step; i ++)
- hData -> mHrirs [i] = 0.0f;
+ n = hData -> mIrPoints;
+ for (i = 0; i < n; i ++)
+ hData -> mHrirs [i] = 0.0;
for (a = 0; a < hData -> mAzCount [oi]; a ++) {
j = (hData -> mEvOffset [oi] + a) * step;
- for (i = 0; i < step; i ++)
+ for (i = 0; i < n; i ++)
hData -> mHrirs [i] += hData -> mHrirs [j + i] / hData -> mAzCount [oi];
}
for (e = 1; e < hData -> mEvStart; e ++) {
- of = ((float) e) / hData -> mEvStart;
+ of = ((double) e) / hData -> mEvStart;
for (a = 0; a < hData -> mAzCount [e]; a ++) {
j = (hData -> mEvOffset [e] + a) * step;
- CalcAzIndices (hData, oi, a * 2.0f * M_PI / hData -> mAzCount [e], & j0, & j1, & jf);
+ CalcAzIndices (hData, oi, a * 2.0 * M_PI / hData -> mAzCount [e], & j0, & j1, & jf);
j0 *= step;
j1 *= step;
- lp [0] = 0.0f;
- lp [1] = 0.0f;
- lp [2] = 0.0f;
- lp [3] = 0.0f;
- for (i = 0; i < step; i ++) {
+ lp [0] = 0.0;
+ lp [1] = 0.0;
+ lp [2] = 0.0;
+ lp [3] = 0.0;
+ for (i = 0; i < n; i ++) {
s0 = hData -> mHrirs [i];
s1 = Lerp (hData -> mHrirs [j0 + i], hData -> mHrirs [j1 + i], jf);
s0 = Lerp (s0, s1, of);
- lp [0] = Lerp (s0, lp [0], 0.15f - (0.15f * of));
- lp [1] = Lerp (lp [0], lp [1], 0.15f - (0.15f * of));
- lp [2] = Lerp (lp [1], lp [2], 0.15f - (0.15f * of));
- lp [3] = Lerp (lp [2], lp [3], 0.15f - (0.15f * of));
+ lp [0] = Lerp (s0, lp [0], 0.15 - (0.15 * of));
+ lp [1] = Lerp (lp [0], lp [1], 0.15 - (0.15 * of));
+ lp [2] = Lerp (lp [1], lp [2], 0.15 - (0.15 * of));
+ lp [3] = Lerp (lp [2], lp [3], 0.15 - (0.15 * of));
hData -> mHrirs [j + i] = lp [3];
}
}
}
- lp [0] = 0.0f;
- lp [1] = 0.0f;
- lp [2] = 0.0f;
- lp [3] = 0.0f;
- for (i = 0; i < step; i ++) {
+ lp [0] = 0.0;
+ lp [1] = 0.0;
+ lp [2] = 0.0;
+ lp [3] = 0.0;
+ for (i = 0; i < n; i ++) {
s0 = hData -> mHrirs [i];
- lp [0] = Lerp (s0, lp [0], 0.15f);
- lp [1] = Lerp (lp [0], lp [1], 0.15f);
- lp [2] = Lerp (lp [1], lp [2], 0.15f);
- lp [3] = Lerp (lp [2], lp [3], 0.15f);
+ lp [0] = Lerp (s0, lp [0], 0.15);
+ lp [1] = Lerp (lp [0], lp [1], 0.15);
+ lp [2] = Lerp (lp [1], lp [2], 0.15);
+ lp [3] = Lerp (lp [2], lp [3], 0.15);
hData -> mHrirs [i] = lp [3];
}
hData -> mEvStart = 0;
}
+// The following routines assume a full set of HRIRs for all elevations.
+
+// Normalize the HRIR set and slightly attenuate the result.
+static void NormalizeHrirs (const HrirDataT * hData) {
+ size_t step, end, n, j, i;
+ double maxLevel;
+
+ step = hData -> mIrSize;
+ end = hData -> mIrCount * step;
+ n = hData -> mIrPoints;
+ maxLevel = 0.0;
+ for (j = 0; j < end; j += step) {
+ for (i = 0; i < n; i ++)
+ maxLevel = fmax (fabs (hData -> mHrirs [j + i]), maxLevel);
+ }
+ maxLevel = 1.01 * maxLevel;
+ for (j = 0; j < end; j += step) {
+ for (i = 0; i < n; i ++)
+ hData -> mHrirs [j + i] /= maxLevel;
+ }
+}
+
+// Calculate the left-ear time delay using a spherical head model.
+static double CalcLTD (const double ev, const double az, const double rad, const double dist) {
+ double azp, dlp, l, al;
+
+ azp = asin (cos (ev) * sin (az));
+ dlp = sqrt ((dist * dist) + (rad * rad) + (2.0 * dist * rad * sin (azp)));
+ l = sqrt ((dist * dist) - (rad * rad));
+ al = (0.5 * M_PI) + azp;
+ if (dlp > l)
+ dlp = l + (rad * (al - acos (rad / dist)));
+ return (dlp / 343.3);
+}
+
// Calculate the effective head-related time delays for the each HRIR, now
// that they are minimum-phase.
static void CalculateHrtds (HrirDataT * hData) {
- float minHrtd, maxHrtd;
- int e, a, j;
- float t;
+ double minHrtd, maxHrtd;
+ uint e, a, j;
+ double t;
- minHrtd = 1000.0f;
- maxHrtd = -1000.0f;
+ minHrtd = 1000.0;
+ maxHrtd = -1000.0;
for (e = 0; e < hData -> mEvCount; e ++) {
for (a = 0; a < hData -> mAzCount [e]; a ++) {
j = hData -> mEvOffset [e] + a;
- t = CalcLTD ((-90.0f + (e * 180.0f / (hData -> mEvCount - 1))) * M_PI / 180.0f,
- (a * 360.0f / hData -> mAzCount [e]) * M_PI / 180.0f,
+ t = CalcLTD ((-90.0 + (e * 180.0 / (hData -> mEvCount - 1))) * M_PI / 180.0,
+ (a * 360.0 / hData -> mAzCount [e]) * M_PI / 180.0,
hData -> mRadius, hData -> mDistance);
hData -> mHrtds [j] = t;
- if (t > maxHrtd)
- maxHrtd = t;
- if (t < minHrtd)
- minHrtd = t;
+ maxHrtd = fmax (t, maxHrtd);
+ minHrtd = fmin (t, minHrtd);
}
}
maxHrtd -= minHrtd;
@@ -574,180 +1737,668 @@ static void CalculateHrtds (HrirDataT * hData) {
hData -> mMaxHrtd = maxHrtd;
}
-// Save the OpenAL Soft HRTF data set.
-static int SaveMhr (const HrirDataT * hData, const char * fileName) {
+// Store the OpenAL Soft HRTF data set.
+static int StoreMhr (const HrirDataT * hData, const char * filename) {
FILE * fp = NULL;
- int e, step, end, j, i;
+ uint e;
+ size_t step, end, n, j, i;
+ int hpHist, v;
- if ((fp = fopen (fileName, "wb")) == NULL) {
- fprintf (stderr, "Could not create file, '%s'.\n", fileName);
+ if ((fp = fopen (filename, "wb")) == NULL) {
+ fprintf (stderr, "Error: Could not open MHR file '%s'.\n", filename);
return (0);
}
- if (! WriteString (MHR_FORMAT, fileName, fp))
- return (0);
- if (! WriteUInt32Le ((ALuint) hData -> mIrRate, fileName, fp))
+ if (! WriteAscii (MHR_FORMAT, fp, filename))
return (0);
- if (! WriteUInt16Le ((ALushort) hData -> mIrCount, fileName, fp))
+ if (! WriteBin4 (BO_LITTLE, 4, (uint4) hData -> mIrRate, fp, filename))
return (0);
- if (! WriteUInt16Le ((ALushort) hData -> mIrSize, fileName, fp))
+ if (! WriteBin4 (BO_LITTLE, 1, (uint4) hData -> mIrPoints, fp, filename))
return (0);
- if (! WriteUInt8 ((ALubyte) hData -> mEvCount, fileName, fp))
+ if (! WriteBin4 (BO_LITTLE, 1, (uint4) hData -> mEvCount, fp, filename))
return (0);
for (e = 0; e < hData -> mEvCount; e ++) {
- if (! WriteUInt16Le ((ALushort) hData -> mEvOffset [e], fileName, fp))
+ if (! WriteBin4 (BO_LITTLE, 1, (uint4) hData -> mAzCount [e], fp, filename))
return (0);
}
step = hData -> mIrSize;
end = hData -> mIrCount * step;
+ n = hData -> mIrPoints;
+ srand (0x31DF840C);
for (j = 0; j < end; j += step) {
- for (i = 0; i < step; i ++) {
- if (! WriteFloat32AsInt16Le (hData -> mHrirs [j + i], fileName, fp))
+ hpHist = 0;
+ for (i = 0; i < n; i ++) {
+ v = HpTpdfDither (32767.0 * hData -> mHrirs [j + i], & hpHist);
+ if (! WriteBin4 (BO_LITTLE, 2, (uint4) v, fp, filename))
return (0);
}
}
for (j = 0; j < hData -> mIrCount; j ++) {
- i = (int) round (44100.0f * hData -> mHrtds [j]);
- if (i > 127)
- i = 127;
- if (! WriteUInt8 ((ALubyte) i, fileName, fp))
+// v = (int) fmin (round (44100.0 * hData -> mHrtds [j]), MAX_HRTD);
+v = fmin (hData -> mHrtds [j], MAX_HRTD);
+ if (! WriteBin4 (BO_LITTLE, 1, (uint4) v, fp, filename))
return (0);
}
fclose (fp);
return (1);
}
-// Save the OpenAL Soft built-in table.
-static int SaveTab (const HrirDataT * hData, const char * fileName) {
+// Store the OpenAL Soft built-in table.
+static int StoreTable (const HrirDataT * hData, const char * filename) {
FILE * fp = NULL;
- int step, end, j, i;
- char text [16];
+ size_t step, end, n, j, i;
+ int hpHist, v;
+ char text [128 + 1];
- if ((fp = fopen (fileName, "wb")) == NULL) {
- fprintf (stderr, "Could not create file, '%s'.\n", fileName);
+ if ((fp = fopen (filename, "wb")) == NULL) {
+ fprintf (stderr, "Error: Could not open table file '%s'.\n", filename);
return (0);
}
- if (! WriteString ("/* This data is Copyright 1994 by the MIT Media Laboratory. It is provided free\n"
- " * with no restrictions on use, provided the authors are cited when the data is\n"
- " * used in any research or commercial application. */\n"
- "/* Bill Gardner <[email protected]> and Keith Martin <[email protected]> */\n"
- "\n"
- " /* HRIR Coefficients */\n"
- " {\n", fileName, fp))
+ snprintf (text, 128, "/* Elevation metrics */\n"
+ "static const ALubyte defaultAzCount[%u] = { ", hData -> mEvCount);
+ if (! WriteAscii (text, fp, filename))
return (0);
+ for (i = 0; i < hData -> mEvCount; i ++) {
+ snprintf (text, 128, "%u, ", hData -> mAzCount [i]);
+ if (! WriteAscii (text, fp, filename))
+ return (0);
+ }
+ snprintf (text, 128, "};\n"
+ "static const ALushort defaultEvOffset[%u] = { ", hData -> mEvCount);
+ if (! WriteAscii (text, fp, filename))
+ return (0);
+ for (i = 0; i < hData -> mEvCount; i ++) {
+ snprintf (text, 128, "%u, ", hData -> mEvOffset [i]);
+ if (! WriteAscii (text, fp, filename))
+ return (0);
+ }
step = hData -> mIrSize;
end = hData -> mIrCount * step;
+ n = hData -> mIrPoints;
+ snprintf (text, 128, "};\n\n"
+ "/* HRIR Coefficients */\n"
+ "static const ALshort defaultCoeffs[%u] =\n{\n", end);
+ if (! WriteAscii (text, fp, filename))
+ return (0);
+ srand (0x31DF840C);
for (j = 0; j < end; j += step) {
- if (! WriteString (" { ", fileName, fp))
+ if (! WriteAscii (" ", fp, filename))
return (0);
- for (i = 0; i < step; i ++) {
- snprintf (text, 15, "%+d, ", (int) round (32767.0f * hData -> mHrirs [j + i]));
- if (! WriteString (text, fileName, fp))
+ hpHist = 0;
+ for (i = 0; i < n; i ++) {
+ v = HpTpdfDither (32767.0 * hData -> mHrirs [j + i], & hpHist);
+ snprintf (text, 128, "%+d, ", v);
+ if (! WriteAscii (text, fp, filename))
return (0);
}
- if (! WriteString ("},\n", fileName, fp))
+ if (! WriteAscii ("\n", fp, filename))
return (0);
}
- if (! WriteString (" },\n"
- "\n"
- " /* HRIR Delays */\n"
- " { ", fileName, fp))
+ snprintf (text, 128, "};\n\n"
+ "/* HRIR Delays */\n"
+ "static const ALubyte defaultDelays[%d] =\n{\n"
+ " ", hData -> mIrCount);
+ if (! WriteAscii (text, fp, filename))
return (0);
for (j = 0; j < hData -> mIrCount; j ++) {
- snprintf (text, 15, "%d, ", (int) round (44100.0f * hData -> mHrtds [j]));
- if (! WriteString (text, fileName, fp))
+ v = (int) fmin (round (44100.0 * hData -> mHrtds [j]), MAX_HRTD);
+ snprintf (text, 128, "%d, ", v);
+ if (! WriteAscii (text, fp, filename))
return (0);
}
- if (! WriteString ("}\n", fileName, fp))
+ if (! WriteAscii ("\n};\n\n"
+ "/* Default HRTF Definition */\n", fp, filename))
+ return (0);
+ snprintf (text, 128, "static const struct Hrtf DefaultHrtf = {\n"
+ " %u, %u, %u, defaultAzCount, defaultEvOffset,\n",
+ hData -> mIrRate, hData -> mIrPoints, hData -> mEvCount);
+ if (! WriteAscii (text, fp, filename))
+ return (0);
+ if (! WriteAscii (" defaultCoeffs, defaultDelays, NULL\n"
+ "};\n", fp, filename))
return (0);
fclose (fp);
return (1);
}
-// Loads and processes an MIT data set. At present, the HRIR and HRTD data
-// is loaded and processed in a static buffer. That should change to using
-// heap allocated memory in the future. A cleanup function will then be
-// required.
-static int MakeMit(const char *baseInName, HrirDataT *hData)
-{
- static float hrirs[MIT_IR_COUNT * MIT_IR_SIZE];
- static float hrtds[MIT_IR_COUNT];
-
- hData->mIrRate = MIT_IR_RATE;
- hData->mIrCount = MIT_IR_COUNT;
- hData->mIrSize = MIT_IR_SIZE;
- hData->mEvCount = MIT_EV_COUNT;
- hData->mEvStart = MIT_EV_START;
- hData->mEvOffset = MIT_EV_OFFSET;
- hData->mAzCount = MIT_AZ_COUNT;
- hData->mRadius = MIT_RADIUS;
- hData->mDistance = MIT_DISTANCE;
- hData->mHrirs = hrirs;
- hData->mHrtds = hrtds;
- fprintf(stderr, "Loading base HRIR data...\n");
- if(!LoadMitHrirs(baseInName, hData))
- return 0;
- fprintf(stderr, "Performing minimum phase reconstruction and truncation...\n");
- ReconstructHrirs(MIN_IR_SIZE, hData);
- fprintf(stderr, "Renormalizing minimum phase HRIR data...\n");
- RenormalizeHrirs(hData);
- fprintf(stderr, "Synthesizing missing elevations...\n");
- SynthesizeHrirs(hData);
- fprintf(stderr, "Calculating impulse delays...\n");
- CalculateHrtds(hData);
- return 1;
-}
-
-// Simple dispatch. Provided a command, the path to the MIT set of choice,
-// and an optional output filename, this will produce an OpenAL Soft
-// compatible HRTF set in the chosen format.
-int main(int argc, char *argv[])
-{
- char baseName[1024];
- const char *outName = NULL;
- HrirDataT hData;
-
- if(argc < 3 || strcmp(argv [1], "-h") == 0 || strcmp (argv [1], "--help") == 0)
- {
- fprintf(stderr, "Usage: %s <command> <path of MIT set> [ <output file> ]\n\n", argv[0]);
- fprintf(stderr, "Commands:\n");
- fprintf(stderr, " -m, --make-mhr Makes an OpenAL Soft compatible HRTF data set.\n");
- fprintf(stderr, " Defaults output to: ./oal_soft_hrtf_44100.mhr\n");
- fprintf(stderr, " -t, --make-tab Makes the built-in table used when compiling OpenAL Soft.\n");
- fprintf(stderr, " Defaults output to: ./hrtf_tables.inc\n");
- fprintf(stderr, " -h, --help Displays this help information.\n");
- return 0;
+// Process the data set definition to read and validate the data set metrics.
+static int ProcessMetrics (TokenReaderT * tr, const size_t fftSize, const size_t truncSize, HrirDataT * hData) {
+ char ident [MAX_IDENT_LEN + 1];
+ uint line, col;
+ int intVal;
+ size_t points;
+ double fpVal;
+ int hasRate = 0, hasPoints = 0, hasAzimuths = 0;
+ int hasRadius = 0, hasDistance = 0;
+
+ while (! (hasRate && hasPoints && hasAzimuths && hasRadius && hasDistance)) {
+ TrIndication (tr, & line, & col);
+ if (! TrReadIdent (tr, MAX_IDENT_LEN, ident))
+ return (0);
+ if (strcasecmp (ident, "rate") == 0) {
+ if (hasRate) {
+ TrErrorAt (tr, line, col, "Redefinition of 'rate'.\n");
+ return (0);
+ }
+ if (! TrReadOperator (tr, "="))
+ return (0);
+ if (! TrReadInt (tr, MIN_RATE, MAX_RATE, & intVal))
+ return (0);
+ hData -> mIrRate = intVal;
+ hasRate = 1;
+ } else if (strcasecmp (ident, "points") == 0) {
+ if (hasPoints) {
+ TrErrorAt (tr, line, col, "Redefinition of 'points'.\n");
+ return (0);
+ }
+ if (! TrReadOperator (tr, "="))
+ return (0);
+ TrIndication (tr, & line, & col);
+ if (! TrReadInt (tr, MIN_POINTS, MAX_POINTS, & intVal))
+ return (0);
+ points = (size_t) intVal;
+ if ((fftSize > 0) && (points > fftSize)) {
+ TrErrorAt (tr, line, col, "Value exceeds the overriden FFT size.\n");
+ return (0);
+ }
+ if (points < truncSize) {
+ TrErrorAt (tr, line, col, "Value is below the truncation size.\n");
+ return (0);
+ }
+ hData -> mIrPoints = points;
+ hData -> mFftSize = fftSize;
+ if (fftSize <= 0) {
+ points = 1;
+ while (points < (4 * hData -> mIrPoints))
+ points <<= 1;
+ hData -> mFftSize = points;
+ hData -> mIrSize = 1 + (points / 2);
+ } else {
+ hData -> mFftSize = fftSize;
+ hData -> mIrSize = 1 + (fftSize / 2);
+ if (points > hData -> mIrSize)
+ hData -> mIrSize = points;
+ }
+ hasPoints = 1;
+ } else if (strcasecmp (ident, "azimuths") == 0) {
+ if (hasAzimuths) {
+ TrErrorAt (tr, line, col, "Redefinition of 'azimuths'.\n");
+ return (0);
+ }
+ if (! TrReadOperator (tr, "="))
+ return (0);
+ hData -> mIrCount = 0;
+ hData -> mEvCount = 0;
+ hData -> mEvOffset [0] = 0;
+ for (;;) {
+ if (! TrReadInt (tr, MIN_AZ_COUNT, MAX_AZ_COUNT, & intVal))
+ return (0);
+ hData -> mAzCount [hData -> mEvCount] = intVal;
+ hData -> mIrCount += intVal;
+ hData -> mEvCount ++;
+ if (! TrIsOperator (tr, ","))
+ break;
+ if (hData -> mEvCount >= MAX_EV_COUNT) {
+ TrError (tr, "Exceeded the maximum of %d elevations.\n", MAX_EV_COUNT);
+ return (0);
+ }
+ hData -> mEvOffset [hData -> mEvCount] = hData -> mEvOffset [hData -> mEvCount - 1] + intVal;
+ TrReadOperator (tr, ",");
+ }
+ if (hData -> mEvCount < MIN_EV_COUNT) {
+ TrErrorAt (tr, line, col, "Did not reach the minimum of %d azimuth counts.\n", MIN_EV_COUNT);
+ return (0);
+ }
+ hasAzimuths = 1;
+ } else if (strcasecmp (ident, "radius") == 0) {
+ if (hasRadius) {
+ TrErrorAt (tr, line, col, "Redefinition of 'radius'.\n");
+ return (0);
+ }
+ if (! TrReadOperator (tr, "="))
+ return (0);
+ if (! TrReadFloat (tr, MIN_RADIUS, MAX_RADIUS, & fpVal))
+ return (0);
+ hData -> mRadius = fpVal;
+ hasRadius = 1;
+ } else if (strcasecmp (ident, "distance") == 0) {
+ if (hasDistance) {
+ TrErrorAt (tr, line, col, "Redefinition of 'distance'.\n");
+ return (0);
+ }
+ if (! TrReadOperator (tr, "="))
+ return (0);
+ if (! TrReadFloat (tr, MIN_DISTANCE, MAX_DISTANCE, & fpVal))
+ return (0);
+ hData -> mDistance = fpVal;
+ hasDistance = 1;
+ } else {
+ TrErrorAt (tr, line, col, "Expected a metric name.\n");
+ return (0);
}
+ TrSkipWhitespace (tr);
+ }
+ return (1);
+}
- snprintf(baseName, sizeof(baseName), "%s/elev", argv[2]);
- if(strcmp(argv[1], "-m") == 0 || strcmp(argv[1], "--make-mhr") == 0)
- {
- if(argc > 3)
- outName = argv[3];
- else
- outName = "./oal_soft_hrtf_44100.mhr";
- if(!MakeMit(baseName, &hData))
- return -1;
- fprintf(stderr, "Creating data set file...\n");
- if(!SaveMhr(&hData, outName))
- return -1;
- }
- else if(strcmp(argv[1], "-t") == 0 || strcmp(argv[1], "--make-tab") == 0)
- {
- if(argc > 3)
- outName = argv[3];
- else
- outName = "./hrtf_tables.inc";
- if(!MakeMit(baseName, &hData))
- return -1;
- fprintf(stderr, "Creating table file...\n");
- if(!SaveTab(&hData, outName))
- return -1;
+// Parse an index pair from the data set definition.
+static int ReadIndexPair (TokenReaderT * tr, const HrirDataT * hData, uint * ei, uint * ai) {
+ int intVal;
+
+ if (! TrReadInt (tr, 0, hData -> mEvCount, & intVal))
+ return (0);
+ (* ei) = (uint) intVal;
+ if (! TrReadOperator (tr, ","))
+ return (0);
+ if (! TrReadInt (tr, 0, hData -> mAzCount [(* ei)], & intVal))
+ return (0);
+ (* ai) = (uint) intVal;
+ return (1);
+}
+
+// Match the source format from a given identifier.
+static SourceFormatT MatchSourceFormat (const char * ident) {
+ if (strcasecmp (ident, "wave") == 0)
+ return (SF_WAVE);
+ else if (strcasecmp (ident, "bin_le") == 0)
+ return (SF_BIN_LE);
+ else if (strcasecmp (ident, "bin_be") == 0)
+ return (SF_BIN_BE);
+ else if (strcasecmp (ident, "ascii") == 0)
+ return (SF_ASCII);
+ return (SF_NONE);
+}
+
+// Match the source element type from a given identifier.
+static ElementTypeT MatchElementType (const char * ident) {
+ if (strcasecmp (ident, "int") == 0)
+ return (ET_INT);
+ else if (strcasecmp (ident, "fp") == 0)
+ return (ET_FP);
+ return (ET_NONE);
+}
+
+// Parse and validate a source reference from the data set definition.
+static int ReadSourceRef (TokenReaderT * tr, SourceRefT * src) {
+ uint line, col;
+ char ident [MAX_IDENT_LEN + 1];
+ int intVal;
+
+ TrIndication (tr, & line, & col);
+ if (! TrReadIdent (tr, MAX_IDENT_LEN, ident))
+ return (0);
+ src -> mFormat = MatchSourceFormat (ident);
+ if (src -> mFormat == SF_NONE) {
+ TrErrorAt (tr, line, col, "Expected a source format.\n");
+ return (0);
+ }
+ if (! TrReadOperator (tr, "("))
+ return (0);
+ if (src -> mFormat == SF_WAVE) {
+ if (! TrReadInt (tr, 0, MAX_WAVE_CHANNELS, & intVal))
+ return (0);
+ src -> mType = ET_NONE;
+ src -> mSize = 0;
+ src -> mBits = 0;
+ src -> mChannel = (uint) intVal;
+ src -> mSkip = 0;
+ } else {
+ TrIndication (tr, & line, & col);
+ if (! TrReadIdent (tr, MAX_IDENT_LEN, ident))
+ return (0);
+ src -> mType = MatchElementType (ident);
+ if (src -> mType == ET_NONE) {
+ TrErrorAt (tr, line, col, "Expected a source element type.\n");
+ return (0);
+ }
+ if ((src -> mFormat == SF_BIN_LE) || (src -> mFormat == SF_BIN_BE)) {
+ if (! TrReadOperator (tr, ","))
+ return (0);
+ if (src -> mType == ET_INT) {
+ if (! TrReadInt (tr, MIN_BIN_SIZE, MAX_BIN_SIZE, & intVal))
+ return (0);
+ src -> mSize = intVal;
+ if (TrIsOperator (tr, ",")) {
+ TrReadOperator (tr, ",");
+ TrIndication (tr, & line, & col);
+ if (! TrReadInt (tr, 0x80000000, 0x7FFFFFFF, & intVal))
+ return (0);
+ if ((abs (intVal) < MIN_BIN_BITS) || (abs (intVal) > (8 * src -> mSize))) {
+ TrErrorAt (tr, line, col, "Expected a value of (+/-) %d to %d.\n", MIN_BIN_BITS, 8 * src -> mSize);
+ return (0);
+ }
+ src -> mBits = intVal;
+ } else {
+ src -> mBits = 8 * src -> mSize;
+ }
+ } else {
+ TrIndication (tr, & line, & col);
+ if (! TrReadInt (tr, 0x80000000, 0x7FFFFFFF, & intVal))
+ return (0);
+ if ((intVal != 4) && (intVal != 8)) {
+ TrErrorAt (tr, line, col, "Expected a value of 4 or 8.\n");
+ return (0);
+ }
+ src -> mSize = (uint) intVal;
+ src -> mBits = 0;
+ }
+ } else if ((src -> mFormat == SF_ASCII) && (src -> mType == ET_INT)) {
+ if (! TrReadOperator (tr, ","))
+ return (0);
+ if (! TrReadInt (tr, MIN_ASCII_BITS, MAX_ASCII_BITS, & intVal))
+ return (0);
+ src -> mSize = 0;
+ src -> mBits = intVal;
+ } else {
+ src -> mSize = 0;
+ src -> mBits = 0;
+ }
+ if (TrIsOperator (tr, ";")) {
+ TrReadOperator (tr, ";");
+ if (! TrReadInt (tr, 0, 0x7FFFFFFF, & intVal))
+ return (0);
+ src -> mSkip = (uint) intVal;
+ } else {
+ src -> mSkip = 0;
+ }
+ }
+ if (! TrReadOperator (tr, ")"))
+ return (0);
+ if (TrIsOperator (tr, "@")) {
+ TrReadOperator (tr, "@");
+ if (! TrReadInt (tr, 0, 0x7FFFFFFF, & intVal))
+ return (0);
+ src -> mOffset = (uint) intVal;
+ } else {
+ src -> mOffset = 0;
+ }
+ if (! TrReadOperator (tr, ":"))
+ return (0);
+ if (! TrReadString (tr, MAX_PATH_LEN, src -> mPath))
+ return (0);
+ return (1);
+}
+
+// Process the list of sources in the data set definition.
+static int ProcessSources (TokenReaderT * tr, HrirDataT * hData) {
+ uint * setCount = NULL, * setFlag = NULL;
+ double * hrir = NULL;
+ uint line, col, ei, ai;
+ SourceRefT src;
+ double factor;
+
+ setCount = (uint *) calloc (hData -> mEvCount, sizeof (uint));
+ setFlag = (uint *) calloc (hData -> mIrCount, sizeof (uint));
+ hrir = CreateArray (hData -> mIrPoints);
+ while (TrIsOperator (tr, "[")) {
+ TrIndication (tr, & line, & col);
+ TrReadOperator (tr, "[");
+ if (ReadIndexPair (tr, hData, & ei, & ai)) {
+ if (TrReadOperator (tr, "]")) {
+ if (! setFlag [hData -> mEvOffset [ei] + ai]) {
+ if (TrReadOperator (tr, "=")) {
+ factor = 1.0;
+ for (;;) {
+ if (ReadSourceRef (tr, & src)) {
+ if (LoadSource (& src, hData -> mIrRate, hData -> mIrPoints, hrir)) {
+ AverageHrirMagnitude (hrir, 1.0 / factor, ei, ai, hData);
+ factor += 1.0;
+ if (! TrIsOperator (tr, "+"))
+ break;
+ TrReadOperator (tr, "+");
+ continue;
+ }
+ }
+ DestroyArray (hrir);
+ free (setFlag);
+ free (setCount);
+ return (0);
+ }
+ setFlag [hData -> mEvOffset [ei] + ai] = 1;
+ setCount [ei] ++;
+ continue;
+ }
+ } else {
+ TrErrorAt (tr, line, col, "Redefinition of source.\n");
+ }
+ }
}
- else
- {
- fprintf(stderr, "Invalid command '%s'\n", argv[1]);
- return -1;
+ DestroyArray (hrir);
+ free (setFlag);
+ free (setCount);
+ return (0);
+ }
+ ei = 0;
+ while ((ei < hData -> mEvCount) && (setCount [ei] < 1))
+ ei ++;
+ if (ei < hData -> mEvCount) {
+ hData -> mEvStart = ei;
+ while ((ei < hData -> mEvCount) && (setCount [ei] == hData -> mAzCount [ei]))
+ ei ++;
+ if (ei >= hData -> mEvCount) {
+ if (! TrLoad (tr)) {
+ DestroyArray (hrir);
+ free (setFlag);
+ free (setCount);
+ return (1);
+ } else {
+ TrError (tr, "Errant data at end of source list.\n");
+ }
+ } else {
+ TrError (tr, "Missing sources for elevation index %d.\n", ei);
+ }
+ } else {
+ TrError (tr, "Missing source references.\n");
+ }
+ DestroyArray (hrir);
+ free (setFlag);
+ free (setCount);
+ return (0);
+}
+
+/* Parse the data set definition and process the source data, storing the
+ * resulting data set as desired. If the input name is NULL it will read
+ * from standard input.
+ */
+static int ProcessDefinition (const char * inName, const size_t fftSize, const int equalize, const int surface, const double limit, const size_t truncSize, const OutputFormatT outFormat, const char * outName) {
+ FILE * fp = NULL;
+ TokenReaderT tr;
+ HrirDataT hData;
+ double * dfa = NULL;
+ char rateStr [8 + 1], expName [MAX_PATH_LEN];
+
+ hData.mIrRate = 0;
+ hData.mIrPoints = 0;
+ hData.mFftSize = 0;
+ hData.mIrSize = 0;
+ hData.mIrCount = 0;
+ hData.mEvCount = 0;
+ hData.mRadius = 0;
+ hData.mDistance = 0;
+
+ fprintf (stdout, "Reading HRIR definition...\n");
+ if (inName != NULL) {
+ fp = fopen (inName, "r");
+ if (fp == NULL) {
+ fprintf (stderr, "Error: Could not open definition file '%s'\n", inName);
+ return (0);
+ }
+ TrSetup (fp, inName, & tr);
+ } else {
+ fp = stdin;
+ TrSetup (fp, "<stdin>", & tr);
+ }
+ if (! ProcessMetrics (& tr, fftSize, truncSize, & hData)) {
+ if (inName != NULL)
+ fclose (fp);
+ return (0);
+ }
+ hData . mHrirs = CreateArray (hData . mIrCount * hData . mIrSize);
+ hData . mHrtds = CreateArray (hData . mIrCount);
+ if (! ProcessSources (& tr, & hData)) {
+ DestroyArray (hData . mHrtds);
+ DestroyArray (hData . mHrirs);
+ if (inName != NULL)
+ fclose (fp);
+ return (0);
+ }
+ if (inName != NULL)
+ fclose (fp);
+ if (equalize) {
+ dfa = CreateArray (1 + (hData . mFftSize / 2));
+ fprintf (stdout, "Calculating diffuse-field average...\n");
+ CalculateDiffuseFieldAverage (& hData, surface, limit, dfa);
+ fprintf (stdout, "Performing diffuse-field equalization...\n");
+ DiffuseFieldEqualize (dfa, & hData);
+ DestroyArray (dfa);
+ }
+ fprintf (stdout, "Performing minimum phase reconstruction...\n");
+ ReconstructHrirs (& hData);
+ fprintf (stdout, "Truncating minimum-phase HRIRs...\n");
+ hData . mIrPoints = truncSize;
+ fprintf (stdout, "Synthesizing missing elevations...\n");
+ SynthesizeHrirs (& hData);
+ fprintf (stdout, "Normalizing final HRIRs...\n");
+ NormalizeHrirs (& hData);
+ fprintf (stdout, "Calculating impulse delays...\n");
+ CalculateHrtds (& hData);
+ snprintf (rateStr, 8, "%u", hData . mIrRate);
+ StrSubst (outName, "%r", rateStr, MAX_PATH_LEN, expName);
+ switch (outFormat) {
+ case OF_MHR :
+ fprintf (stdout, "Creating MHR data set file...\n");
+ if (! StoreMhr (& hData, expName))
+ return (0);
+ break;
+ case OF_TABLE :
+ fprintf (stderr, "Creating OpenAL Soft table file...\n");
+ if (! StoreTable (& hData, expName))
+ return (0);
+ break;
+ default :
+ break;
+ }
+ DestroyArray (hData . mHrtds);
+ DestroyArray (hData . mHrirs);
+ return (1);
+}
+
+// Standard command line dispatch.
+int main (const int argc, const char * argv []) {
+ const char * inName = NULL, * outName = NULL;
+ OutputFormatT outFormat;
+ int argi;
+ size_t fftSize;
+ int equalize, surface;
+ double limit;
+ size_t truncSize;
+ char * end = NULL;
+
+ if (argc < 2) {
+ fprintf (stderr, "Error: No command specified. See '%s -h' for help.\n", argv [0]);
+ return (-1);
+ }
+ if ((strcmp (argv [1], "--help") == 0) || (strcmp (argv [1], "-h") == 0)) {
+ fprintf (stdout, "HRTF Processing and Composition Utility\n\n");
+ fprintf (stdout, "Usage: %s <command> [<option>...]\n\n", argv [0]);
+ fprintf (stdout, "Commands:\n");
+ fprintf (stdout, " -m, --make-mhr Makes an OpenAL Soft compatible HRTF data set.\n");
+ fprintf (stdout, " Defaults output to: ./oalsoft_hrtf_%%r.mhr\n");
+ fprintf (stdout, " -t, --make-tab Makes the built-in table used when compiling OpenAL Soft.\n");
+ fprintf (stdout, " Defaults output to: ./hrtf_tables.inc\n");
+ fprintf (stdout, " -h, --help Displays this help information.\n\n");
+ fprintf (stdout, "Options:\n");
+ fprintf (stdout, " -f=<points> Override the FFT window size (defaults to the first power-\n");
+ fprintf (stdout, " of-two that fits four times the number of HRIR points).\n");
+ fprintf (stdout, " -e={on|off} Toggle diffuse-field equalization (default: %s).\n", (DEFAULT_EQUALIZE ? "on" : "off"));
+ fprintf (stdout, " -s={on|off} Toggle surface-weighted diffuse-field average (default: %s).\n", (DEFAULT_SURFACE ? "on" : "off"));
+ fprintf (stdout, " -l={<dB>|none} Specify a limit to the magnitude range of the diffuse-field\n");
+ fprintf (stdout, " average (default: %.2f).\n", DEFAULT_LIMIT);
+ fprintf (stdout, " -w=<points> Specify the size of the truncation window that's applied\n");
+ fprintf (stdout, " after minimum-phase reconstruction (default: %d).\n", DEFAULT_TRUNCSIZE);
+ fprintf (stdout, " -i=<filename> Specify an HRIR definition file to use (defaults to stdin).\n");
+ fprintf (stdout, " -o=<filename> Specify an output file. Overrides command-selected default.\n");
+ fprintf (stdout, " Use of '%%r' will be substituted with the data set sample rate.\n");
+ return (0);
+ }
+ if ((strcmp (argv [1], "--make-mhr") == 0) || (strcmp (argv [1], "-m") == 0)) {
+ if (argc > 3)
+ outName = argv [3];
+ else
+ outName = "./oalsoft_hrtf_%r.mhr";
+ outFormat = OF_MHR;
+ } else if ((strcmp (argv [1], "--make-tab") == 0) || (strcmp (argv [1], "-t") == 0)) {
+ if (argc > 3)
+ outName = argv [3];
+ else
+ outName = "./hrtf_tables.inc";
+ outFormat = OF_TABLE;
+ } else {
+ fprintf (stderr, "Error: Invalid command '%s'.\n", argv [1]);
+ return (-1);
+ }
+ argi = 2;
+ fftSize = 0;
+ equalize = DEFAULT_EQUALIZE;
+ surface = DEFAULT_SURFACE;
+ limit = DEFAULT_LIMIT;
+ truncSize = DEFAULT_TRUNCSIZE;
+ while (argi < argc) {
+ if (strncmp (argv [argi], "-f=", 3) == 0) {
+ fftSize = strtoul (& argv [argi] [3], & end, 10);
+ if ((end [0] != '\0') || (fftSize & (fftSize - 1)) || (fftSize < MIN_FFTSIZE) || (fftSize > MAX_FFTSIZE)) {
+ fprintf (stderr, "Error: Expected a power-of-two value from %d to %d for '-f'.\n", MIN_FFTSIZE, MAX_FFTSIZE);
+ return (-1);
+ }
+ } else if (strncmp (argv [argi], "-e=", 3) == 0) {
+ if (strcmp (& argv [argi] [3], "on") == 0) {
+ equalize = 1;
+ } else if (strcmp (& argv [argi] [3], "off") == 0) {
+ equalize = 0;
+ } else {
+ fprintf (stderr, "Error: Expected 'on' or 'off' for '-e'.\n");
+ return (-1);
+ }
+ } else if (strncmp (argv [argi], "-s=", 3) == 0) {
+ if (strcmp (& argv [argi] [3], "on") == 0) {
+ surface = 1;
+ } else if (strcmp (& argv [argi] [3], "off") == 0) {
+ surface = 0;
+ } else {
+ fprintf (stderr, "Error: Expected 'on' or 'off' for '-s'.\n");
+ return (-1);
+ }
+ } else if (strncmp (argv [argi], "-l=", 3) == 0) {
+ if (strcmp (& argv [argi] [3], "none") == 0) {
+ limit = 0.0;
+ } else {
+ limit = strtod (& argv [argi] [3], & end);
+ if ((end [0] != '\0') || (limit < MIN_LIMIT) || (limit > MAX_LIMIT)) {
+ fprintf (stderr, "Error: Expected 'none' or a value from %.2f to %.2f for '-l'.\n", MIN_LIMIT, MAX_LIMIT);
+ return (-1);
+ }
+ }
+ } else if (strncmp (argv [argi], "-w=", 3) == 0) {
+ truncSize = strtoul (& argv [argi] [3], & end, 10);
+ if ((end [0] != '\0') || (truncSize < MIN_TRUNCSIZE) || (truncSize > MAX_TRUNCSIZE) || (truncSize % MOD_TRUNCSIZE)) {
+ fprintf (stderr, "Error: Expected a value from %d to %d in multiples of %d for '-w'.\n", MIN_TRUNCSIZE, MAX_TRUNCSIZE, MOD_TRUNCSIZE);
+ return (-1);
+ }
+ } else if (strncmp (argv [argi], "-i=", 3) == 0) {
+ inName = & argv [argi] [3];
+ } else if (strncmp (argv [argi], "-o=", 3) == 0) {
+ outName = & argv [argi] [3];
+ } else {
+ fprintf (stderr, "Error: Invalid option '%s'.\n", argv [argi]);
+ return (-1);
}
- fprintf(stderr, "Done.\n");
- return 0;
+ argi ++;
+ }
+ if (! ProcessDefinition (inName, fftSize, equalize, surface, limit, truncSize, outFormat, outName))
+ return (-1);
+ fprintf (stdout, "Operation completed.\n");
+ return (0);
}
+
+
+
+