diff options
-rw-r--r-- | CMakeLists.txt | 23 | ||||
-rw-r--r-- | utils/MIT_KEMAR_sofa.def | 51 | ||||
-rw-r--r-- | utils/SCUT_KEMAR.def | 48 | ||||
-rw-r--r-- | utils/makehrtf.cpp | 693 |
4 files changed, 658 insertions, 157 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 0dc01a7b..16827594 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1515,19 +1515,20 @@ IF(ALSOFT_UTILS) TARGET_COMPILE_OPTIONS(openal-info PRIVATE ${C_FLAGS}) TARGET_LINK_LIBRARIES(openal-info PRIVATE ${LINKER_FLAGS} OpenAL) - SET(MAKEHRTF_SRCS utils/makehrtf.cpp) - IF(NOT HAVE_GETOPT) - SET(MAKEHRTF_SRCS ${MAKEHRTF_SRCS} utils/getopt.c utils/getopt.h) - ENDIF() - ADD_EXECUTABLE(makehrtf ${MAKEHRTF_SRCS}) - TARGET_COMPILE_DEFINITIONS(makehrtf PRIVATE ${CPP_DEFS}) - TARGET_COMPILE_OPTIONS(makehrtf PRIVATE ${C_FLAGS}) - IF(HAVE_LIBM) - TARGET_LINK_LIBRARIES(makehrtf PRIVATE ${LINKER_FLAGS} m) - ENDIF() - find_package(MySOFA) if(MYSOFA_FOUND) + set(MAKEHRTF_SRCS utils/makehrtf.cpp) + if(NOT HAVE_GETOPT) + set(MAKEHRTF_SRCS ${MAKEHRTF_SRCS} utils/getopt.c utils/getopt.h) + endif() + add_executable(makehrtf ${MAKEHRTF_SRCS}) + target_compile_definitions(makehrtf PRIVATE ${CPP_DEFS}) + target_include_directories(makehrtf PRIVATE ${MYSOFA_INCLUDE_DIRS}) + target_compile_options(makehrtf PRIVATE ${C_FLAGS}) + if(HAVE_LIBM) + target_link_libraries(makehrtf PRIVATE ${LINKER_FLAGS} ${MYSOFA_LIBRARIES} m) + endif() + set(SOFAINFO_SRCS utils/sofa-info.cpp) add_executable(sofa-info ${SOFAINFO_SRCS}) target_compile_definitions(sofa-info PRIVATE ${CPP_DEFS}) diff --git a/utils/MIT_KEMAR_sofa.def b/utils/MIT_KEMAR_sofa.def new file mode 100644 index 00000000..f20222b6 --- /dev/null +++ b/utils/MIT_KEMAR_sofa.def @@ -0,0 +1,51 @@ +# This is a makehrtf HRIR definition file. It is used to define the layout +# and source data to be processed into an OpenAL Soft compatible HRTF. +# +# This definition is used to transform the SOFA packaged KEMAR HRIRs +# originally provided by Bill Gardner <[email protected]> and Keith Martin +# <[email protected]> of MIT Media Laboratory. +# +# The SOFA conversion is available from: +# +# http://sofacoustics.org/data/database/mit/ +# +# The original data is available from: +# +# http://sound.media.mit.edu/resources/KEMAR.html +# +# It is copyrighted 1994 by MIT Media Laboratory, and provided free of charge +# with no restrictions on use so long as the authors (above) are cited. + +# Sampling rate of the HRIR data (in hertz). +rate = 44100 + +# The SOFA file is stereo, but the original data was mono. Channels are just +# mirrored by azimuth; so save some memory by allowing OpenAL Soft to mirror +# them at run time. +type = mono + +points = 512 + +radius = 0.09 + +# The MIT set has only one field with a distance of 1.4m. +distance = 1.4 + +# The MIT set varies the number of azimuths for each elevation to maintain +# an average distance between them. +azimuths = 1, 12, 24, 36, 45, 56, 60, 72, 72, 72, 72, 72, 60, 56, 45, 36, 24, 12, 1 + +# Normally the dataset would be composed manually by listing all necessary +# 'sofa' sources with the appropriate radius, elevation, azimuth (counter- +# clockwise for SOFA files) and receiver arguments: +# +# [ 5, 0 ] = sofa (1.4, -40.0, 0.0 : 0) : "./mit_kemar_normal_pinna.sofa" +# [ 5, 1 ] = sofa (1.4, -40.0, 353.6 : 0) : "./mit_kemar_normal_pinna.sofa" +# [ 5, 2 ] = sofa (1.4, -40.0, 347.1 : 0) : "./mit_kemar_normal_pinna.sofa" +# [ 5, 3 ] = sofa (1.4, -40.0, 340.7 : 0) : "./mit_kemar_normal_pinna.sofa" +# ... +# +# If HRIR composition isn't necessary, it's easier to just use the following: + +[ * ] = sofa : "./mit_kemar_normal_pinna.sofa" mono + diff --git a/utils/SCUT_KEMAR.def b/utils/SCUT_KEMAR.def new file mode 100644 index 00000000..b9bca7da --- /dev/null +++ b/utils/SCUT_KEMAR.def @@ -0,0 +1,48 @@ +# This is a makehrtf HRIR definition file. It is used to define the layout +# and source data to be processed into an OpenAL Soft compatible HRTF. +# +# This definition is used to transform the near-field KEMAR HRIRs provided by +# Bosun Xie <[email protected]> of the South China University of +# Technology, Guangzhou, China; and converted from SCUT to SOFA format by +# Piotr Majdak <[email protected]> of the Acoustics Research Institute, +# Austrian Academy of Sciences. +# +# A copy of the data (SCUT_KEMAR_radius_all.sofa) is available from: +# +# http://sofacoustics.org/data/database/scut/SCUT_KEMAR_radius_all.sofa +# +# It is provided under the Creative Commons CC 3.0 BY-SA-NC license: +# +# https://creativecommons.org/licenses/by-nc-sa/3.0/ + +rate = 44100 + +# While the SOFA file is stereo, doubling the size of the data set will cause +# the utility to exhaust its address space if compiled 32-bit. Since the +# dummy head is symmetric, the same results (ignoring variations caused by +# measurement error) can be obtained using mono channel processing. +type = mono + +points = 512 + +radius = 0.09 + +# This data set has 10 fields ranging from 0.2m to 1m. The layout was +# obtained using the sofa-info utility. +distance = 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0 + +azimuths = 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; + 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; + 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; + 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; + 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; + 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; + 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; + 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; + 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1; + 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1 + +# Given the above compatible layout, we can automatically process the entire +# data set. +[ * ] = sofa : "./SCUT_KEMAR_radius_all.sofa" mono + diff --git a/utils/makehrtf.cpp b/utils/makehrtf.cpp index bb42b2d1..7b5767ff 100644 --- a/utils/makehrtf.cpp +++ b/utils/makehrtf.cpp @@ -2,7 +2,7 @@ * HRTF utility for producing and demonstrating the process of creating an * OpenAL Soft compatible HRIR data set. * - * Copyright (C) 2011-2017 Christopher Fitzgerald + * Copyright (C) 2011-2019 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 @@ -33,7 +33,8 @@ * 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. + * measurement. This has since been modified to use coverage volume for + * multi-field HRIR data sets. * 3. Take the diffuse-field average and limit its magnitude range. * 4. Equalize the responses by using the inverse of the diffuse-field * average. @@ -85,6 +86,8 @@ #include <complex> #include <algorithm> +#include "mysofa.h" + #include "win_main_utf8.h" #ifndef M_PI @@ -231,16 +234,17 @@ enum ByteOrderT { // Source format for the references listed in the data set definition. enum SourceFormatT { SF_NONE, - SF_WAVE, // RIFF/RIFX WAVE file. + SF_ASCII, // ASCII text file. SF_BIN_LE, // Little-endian binary file. SF_BIN_BE, // Big-endian binary file. - SF_ASCII // ASCII text file. + SF_WAVE, // RIFF/RIFX WAVE file. + SF_SOFA // Spatially Oriented Format for Accoustics (SOFA) file. }; // Element types for the references listed in the data set definition. enum ElementTypeT { ET_NONE, - ET_INT, // Integer elements. + ET_INT, // Integer elements. ET_FP // Floating-point elements. }; @@ -276,6 +280,9 @@ struct SourceRefT { uint mSize; int mBits; uint mChannel; + double mAzimuth; + double mElevation; + double mRadius; uint mSkip; uint mOffset; char mPath[MAX_PATH_LEN+1]; @@ -408,7 +415,7 @@ static void TrErrorVA(const TokenReaderT *tr, uint line, uint column, const char { if(!tr->mName) return; - fprintf(stderr, "Error (%s:%u:%u): ", tr->mName, line, column); + fprintf(stderr, "\nError (%s:%u:%u): ", tr->mName, line, column); vfprintf(stderr, format, argPtr); } @@ -857,7 +864,7 @@ static inline uint dither_rng(uint *seed) static void TpdfDither(double *RESTRICT out, const double *RESTRICT in, const double scale, const int count, const int step, uint *seed) { - static constexpr double PRNG_SCALE = 1.0 / UINT_MAX; + static constexpr double PRNG_SCALE = 1.0 / std::numeric_limits<uint>::max(); for(int i{0};i < count;i++) { @@ -874,16 +881,14 @@ static void TpdfDither(double *RESTRICT out, const double *RESTRICT in, const do // Performs bit-reversal ordering. static void FftArrange(const uint n, complex_d *inout) { - uint rk, k, m; - // Handle in-place arrangement. - rk = 0; - for(k = 0;k < n;k++) + uint rk{0u}; + for(uint k{0u};k < n;k++) { if(rk > k) std::swap(inout[rk], inout[k]); - m = n; + uint m{n}; while(rk&(m >>= 1)) rk &= ~m; rk |= m; @@ -1103,7 +1108,7 @@ static uint Gcd(uint x, uint y) { while(y > 0) { - uint z = y; + uint z{y}; y = x % y; x = z; } @@ -1278,7 +1283,7 @@ static int ReadBin4(FILE *fp, const char *filename, const ByteOrderT order, cons if(fread(in, 1, bytes, fp) != bytes) { - fprintf(stderr, "Error: Bad read from file '%s'.\n", filename); + fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); return 0; } accum = 0; @@ -1309,7 +1314,7 @@ static int ReadBin8(FILE *fp, const char *filename, const ByteOrderT order, uint if(fread(in, 1, 8, fp) != 8) { - fprintf(stderr, "Error: Bad read from file '%s'.\n", filename); + fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); return 0; } accum = 0ULL; @@ -1398,7 +1403,7 @@ static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const Eleme if(!TrReadFloat(tr, -std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity(), out)) { - fprintf(stderr, "Error: Bad read from file '%s'.\n", filename); + fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); return 0; } } @@ -1407,7 +1412,7 @@ static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const Eleme int v; if(!TrReadInt(tr, -(1<<(bits-1)), (1<<(bits-1))-1, &v)) { - fprintf(stderr, "Error: Bad read from file '%s'.\n", filename); + fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); return 0; } *out = v / static_cast<double>((1<<(bits-1))-1); @@ -1469,29 +1474,29 @@ static int ReadWaveFormat(FILE *fp, const ByteOrderT order, const uint hrirRate, } if(format != WAVE_FORMAT_PCM && format != WAVE_FORMAT_IEEE_FLOAT) { - fprintf(stderr, "Error: Unsupported WAVE format in file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: 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); + fprintf(stderr, "\nError: 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); + fprintf(stderr, "\nError: 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); + fprintf(stderr, "\nError: 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); + fprintf(stderr, "\nError: Bad significant bits in WAVE file '%s'.\n", src->mPath); return 0; } src->mType = ET_INT; @@ -1500,7 +1505,7 @@ static int ReadWaveFormat(FILE *fp, const ByteOrderT order, const uint hrirRate, { if(size != 4 && size != 8) { - fprintf(stderr, "Error: Unsupported sample size in WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", src->mPath); return 0; } src->mType = ET_FP; @@ -1554,7 +1559,7 @@ static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, count = chunkSize / block; if(count < (src->mOffset + n)) { - fprintf(stderr, "Error: Bad read from file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath); return 0; } fseek(fp, static_cast<long>(src->mOffset * block), SEEK_CUR); @@ -1599,7 +1604,7 @@ static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, return 0; chunkSize -= count * block; offset += count; - lastSample = hrir [offset - 1]; + lastSample = hrir[offset - 1]; } else { @@ -1633,12 +1638,55 @@ static int ReadWaveList(FILE *fp, const SourceRefT *src, const ByteOrderT order, } if(offset < n) { - fprintf(stderr, "Error: Bad read from file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath); return 0; } 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 uint n, double *hrir) +{ + TokenReaderT tr; + uint i, j; + double dummy; + + TrSetup(fp, nullptr, &tr); + for(i = 0;i < src->mOffset;i++) + { + if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast<uint>(src->mBits), &dummy)) + return 0; + } + for(i = 0;i < n;i++) + { + if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast<uint>(src->mBits), &hrir[i])) + return 0; + for(j = 0;j < src->mSkip;j++) + { + if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast<uint>(src->mBits), &dummy)) + return 0; + } + } + return 1; +} + +// Load a source HRIR from a binary file. +static int LoadBinarySource(FILE *fp, const SourceRefT *src, const ByteOrderT order, const uint n, double *hrir) +{ + uint i; + + fseek(fp, static_cast<long>(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, static_cast<long>(src->mSkip), SEEK_CUR); + } + return 1; +} + // Load a source HRIR from a RIFF/RIFX WAVE file. static int LoadWaveSource(FILE *fp, SourceRefT *src, const uint hrirRate, const uint n, double *hrir) { @@ -1654,7 +1702,7 @@ static int LoadWaveSource(FILE *fp, SourceRefT *src, const uint hrirRate, const order = BO_BIG; else { - fprintf(stderr, "Error: No RIFF/RIFX chunk in file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: No RIFF/RIFX chunk in file '%s'.\n", src->mPath); return 0; } @@ -1662,7 +1710,7 @@ static int LoadWaveSource(FILE *fp, SourceRefT *src, const uint hrirRate, const return 0; if(fourCC != FOURCC_WAVE) { - fprintf(stderr, "Error: Not a RIFF/RIFX WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Not a RIFF/RIFX WAVE file '%s'.\n", src->mPath); return 0; } if(!ReadWaveFormat(fp, order, hrirRate, src)) @@ -1672,73 +1720,152 @@ static int LoadWaveSource(FILE *fp, SourceRefT *src, const uint hrirRate, const return 1; } -// Load a source HRIR from a binary file. -static int LoadBinarySource(FILE *fp, const SourceRefT *src, const ByteOrderT order, const uint n, double *hrir) +// Load a Spatially Oriented Format for Accoustics (SOFA) file. +static struct MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) { - uint i; + struct MYSOFA_EASY *sofa{mysofa_cache_lookup(src->mPath, (float)hrirRate)}; + if(sofa) return sofa; - fseek(fp, static_cast<long>(src->mOffset), SEEK_SET); - for(i = 0;i < n;i++) + sofa = static_cast<MYSOFA_EASY*>(calloc(1, sizeof(*sofa))); + if(sofa == nullptr) { - if(!ReadBinAsDouble(fp, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) - return 0; - if(src->mSkip > 0) - fseek(fp, static_cast<long>(src->mSkip), SEEK_CUR); + fprintf(stderr, "\nError: Out of memory.\n"); + return nullptr; } - return 1; + sofa->lookup = nullptr; + sofa->neighborhood = nullptr; + + int err; + sofa->hrtf = mysofa_load(src->mPath, &err); + if(!sofa->hrtf) + { + mysofa_close(sofa); + fprintf(stderr, "\nError: Could not load source file '%s'.\n", src->mPath); + return nullptr; + } + err = mysofa_check(sofa->hrtf); + if(err != MYSOFA_OK) +/* NOTE: Some valid SOFA files are failing this check. + { + mysofa_close(sofa); + fprintf(stderr, "\nError: Malformed source file '%s'.\n", src->mPath); + return nullptr; + }*/ + fprintf(stderr, "\nWarning: Supposedly malformed source file '%s'.\n", src->mPath); + if((src->mOffset + n) > sofa->hrtf->N) + { + mysofa_close(sofa); + fprintf(stderr, "\nError: Not enough samples in SOFA file '%s'.\n", src->mPath); + return nullptr; + } + if(src->mChannel >= sofa->hrtf->R) + { + mysofa_close(sofa); + fprintf(stderr, "\nError: Missing source receiver in SOFA file '%s'.\n", src->mPath); + return nullptr; + } + mysofa_tocartesian(sofa->hrtf); + sofa->lookup = mysofa_lookup_init(sofa->hrtf); + if(sofa->lookup == nullptr) + { + mysofa_close(sofa); + fprintf(stderr, "\nError: Out of memory.\n"); + return nullptr; + } + return mysofa_cache_store(sofa, src->mPath, (float)hrirRate); } -// 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 uint n, double *hrir) +// Copies the HRIR data from a particular SOFA measurement. +static void ExtractSofaHrir(const struct MYSOFA_EASY *sofa, const uint index, const uint channel, const uint offset, const uint n, double *hrir) { - TokenReaderT tr; - uint i, j; - double dummy; + for(uint i{0u};i < n;i++) + hrir[i] = sofa->hrtf->DataIR.values[(index*sofa->hrtf->R + channel)*sofa->hrtf->N + offset + i]; +} - TrSetup(fp, nullptr, &tr); - for(i = 0;i < src->mOffset;i++) +// Load a source HRIR from a Spatially Oriented Format for Accoustics (SOFA) +// file. +static int LoadSofaSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir) +{ + struct MYSOFA_EASY *sofa; + float target[3]; + int nearest; + float *coords; + + sofa = LoadSofaFile(src, hrirRate, n); + if(sofa == nullptr) + return 0; + + /* NOTE: At some point it may be benficial or necessary to consider the + various coordinate systems, listener/source orientations, and + direciontal vectors defined in the SOFA file. + */ + target[0] = src->mAzimuth; + target[1] = src->mElevation; + target[2] = src->mRadius; + mysofa_s2c(target); + + nearest = mysofa_lookup(sofa->lookup, target); + if(nearest < 0) { - if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast<uint>(src->mBits), &dummy)) - return 0; + fprintf(stderr, "\nError: Lookup failed in source file '%s'.\n", src->mPath); + return 0; } - for(i = 0;i < n;i++) + + coords = &sofa->hrtf->SourcePosition.values[3 * nearest]; + if(std::fabs(coords[0] - target[0]) > 0.001 || std::fabs(coords[1] - target[1]) > 0.001 || std::fabs(coords[2] - target[2]) > 0.001) { - if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast<uint>(src->mBits), &hrir[i])) - return 0; - for(j = 0;j < src->mSkip;j++) - { - if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast<uint>(src->mBits), &dummy)) - return 0; - } + fprintf(stderr, "\nError: No impulse response at coordinates (%.3fr, %.1fev, %.1faz) in file '%s'.\n", src->mRadius, src->mElevation, src->mAzimuth, src->mPath); + target[0] = coords[0]; + target[1] = coords[1]; + target[2] = coords[2]; + mysofa_c2s(target); + fprintf(stderr, " Nearest candidate at (%.3fr, %.1fev, %.1faz).\n", target[2], target[1], target[0]); + return 0; } - return 1; + + ExtractSofaHrir(sofa, nearest, src->mChannel, src->mOffset, n, hrir); + + return 1; } // Load a source HRIR from a supported file type. static int LoadSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir) { + FILE *fp{nullptr}; + if(src->mFormat != SF_SOFA) + { + if(src->mFormat == SF_ASCII) + fp = fopen(src->mPath, "r"); + else + fp = fopen(src->mPath, "rb"); + if(fp == nullptr) + { + fprintf(stderr, "\nError: Could not open source file '%s'.\n", src->mPath); + return 0; + } + } int result; - FILE *fp; - - if(src->mFormat == SF_ASCII) - fp = fopen(src->mPath, "r"); - else - fp = fopen(src->mPath, "rb"); - if(fp == nullptr) + switch(src->mFormat) { - fprintf(stderr, "Error: Could not open source file '%s'.\n", src->mPath); - return 0; + case SF_ASCII: + result = LoadAsciiSource(fp, src, n, hrir); + break; + case SF_BIN_LE: + result = LoadBinarySource(fp, src, BO_LITTLE, n, hrir); + break; + case SF_BIN_BE: + result = LoadBinarySource(fp, src, BO_BIG, n, hrir); + break; + case SF_WAVE: + result = LoadWaveSource(fp, src, hrirRate, n, hrir); + break; + case SF_SOFA: + result = LoadSofaSource(src, hrirRate, n, hrir); + break; + default: + result = 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); + if(fp) fclose(fp); return result; } @@ -1756,7 +1883,7 @@ static int WriteAscii(const char *out, FILE *fp, const char *filename) if(fwrite(out, 1, len, fp) != len) { fclose(fp); - fprintf(stderr, "Error: Bad write to file '%s'.\n", filename); + fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename); return 0; } return 1; @@ -1784,7 +1911,7 @@ static int WriteBin4(const ByteOrderT order, const uint bytes, const uint32_t in } if(fwrite(out, 1, bytes, fp) != bytes) { - fprintf(stderr, "Error: Bad write to file '%s'.\n", filename); + fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename); return 0; } return 1; @@ -1801,7 +1928,7 @@ static int StoreMhr(const HrirDataT *hData, const char *filename) if((fp=fopen(filename, "wb")) == nullptr) { - fprintf(stderr, "Error: Could not open MHR file '%s'.\n", filename); + fprintf(stderr, "\nError: Could not open MHR file '%s'.\n", filename); return 0; } if(!WriteAscii(MHR_FORMAT, fp, filename)) @@ -1889,18 +2016,25 @@ static int StoreMhr(const HrirDataT *hData, const char *filename) // timing for its field, elevation, azimuth, and ear. static double AverageHrirOnset(const uint rate, const uint n, const double *hrir, const double f, const double onset) { - double mag = 0.0; - uint i; + std::vector<double> upsampled(10 * n); + { + ResamplerT rs; + ResamplerSetup(&rs, rate, 10 * rate); + ResamplerRun(&rs, n, hrir, 10 * n, upsampled.data()); + } + + double mag{0.0}; + for(uint i{0u};i < 10*n;i++) + mag = std::max(std::abs(upsampled[i]), mag); - for(i = 0;i < n;i++) - mag = std::max(std::abs(hrir[i]), mag); mag *= 0.15; - for(i = 0;i < n;i++) + uint i{0u}; + for(;i < 10*n;i++) { - if(std::abs(hrir[i]) >= mag) + if(std::abs(upsampled[i]) >= mag) break; } - return Lerp(onset, static_cast<double>(i) / rate, f); + return Lerp(onset, static_cast<double>(i) / (10*rate), f); } // Calculate the magnitude response of an HRIR and average it with any @@ -1921,18 +2055,78 @@ static void AverageHrirMagnitude(const uint points, const uint n, const double * mag[i] = Lerp(mag[i], r[i], f); } +/* Balances the maximum HRIR magnitudes of multi-field data sets by + * independently normalizing each field in relation to the overall maximum. + * This is done to ignore distance attenuation. + */ +static void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, const uint m) +{ + double maxMags[MAX_FD_COUNT]; + uint fi, ei, ai, ti, i; + + double maxMag{0.0}; + for(fi = 0;fi < hData->mFdCount;fi++) + { + maxMags[fi] = 0.0; + + for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++) + { + for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + { + HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; + for(ti = 0;ti < channels;ti++) + { + for(i = 0;i < m;i++) + maxMags[fi] = std::max(azd->mIrs[ti][i], maxMags[fi]); + } + } + } + + maxMag = std::max(maxMags[fi], maxMag); + } + + for(fi = 0;fi < hData->mFdCount;fi++) + { + maxMags[fi] /= maxMag; + + for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++) + { + for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + { + HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; + for(ti = 0;ti < channels;ti++) + { + for(i = 0;i < m;i++) + azd->mIrs[ti][i] /= maxMags[fi]; + } + } + } + } +} + /* 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) { - double sum, evs, ev, upperEv, lowerEv, solidAngle; + double sum, innerRa, outerRa, evs, ev, upperEv, lowerEv; + double solidAngle, solidVolume; uint fi, ei; sum = 0.0; + // The head radius acts as the limit for the inner radius. + innerRa = hData->mRadius; for(fi = 0;fi < hData->mFdCount;fi++) { + // Each volume ends half way between progressive field measurements. + if((fi + 1) < hData->mFdCount) + outerRa = 0.5f * (hData->mFds[fi].mDistance + hData->mFds[fi + 1].mDistance); + // The final volume has its limit extended to some practical value. + // This is done to emphasize the far-field responses in the average. + else + outerRa = 10.0f; + evs = M_PI / 2.0 / (hData->mFds[fi].mEvCount - 1); for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++) { @@ -1941,18 +2135,19 @@ static void CalculateDfWeights(const HrirDataT *hData, double *weights) ev = hData->mFds[fi].mEvs[ei].mElevation; lowerEv = std::max(-M_PI / 2.0, ev - evs); upperEv = std::min(M_PI / 2.0, ev + evs); - // Calculate the area of the patch band. + // Calculate the surface area of the patch band. solidAngle = 2.0 * M_PI * (std::sin(upperEv) - std::sin(lowerEv)); - // Each weight is the area of one patch. - weights[(fi * MAX_EV_COUNT) + ei] = solidAngle / hData->mFds[fi].mEvs[ei].mAzCount; - // Sum the total surface area covered by the HRIRs of all fields. + // Then the volume of the extruded patch band. + solidVolume = solidAngle * (std::pow(outerRa, 3.0) - std::pow(innerRa, 3.0)) / 3.0; + // Each weight is the volume of one extruded patch. + weights[(fi * MAX_EV_COUNT) + ei] = solidVolume / hData->mFds[fi].mEvs[ei].mAzCount; + // Sum the total coverage volume of the HRIRs for all fields. sum += solidAngle; } + + innerRa = outerRa; } - /* TODO: It may be interesting to experiment with how a volume-based - weighting performs compared to the existing distance-indepenent - surface patches. - */ + for(fi = 0;fi < hData->mFdCount;fi++) { // Normalize the weights given the total surface coverage for all @@ -1964,8 +2159,8 @@ static void CalculateDfWeights(const HrirDataT *hData, double *weights) /* 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). + * coverage of 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 uint channels, const uint m, const int weighted, const double limit, double *dfa) { @@ -2138,9 +2333,10 @@ static void CalcAzIndices(const HrirDataT *hData, const uint fi, const uint ei, *af = f; } -// Synthesize any missing onset timings at the bottom elevations of each -// field. This just blends between slightly exaggerated known onsets (not -// an accurate model). +/* Synthesize any missing onset timings at the bottom elevations of each + * field. This just blends between slightly exaggerated known onsets (not + * an accurate model). + */ static void SynthesizeOnsets(HrirDataT *hData) { uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1; @@ -2253,16 +2449,18 @@ static void SynthesizeHrirs(HrirDataT *hData) // The following routines assume a full set of HRIRs for all elevations. -// Normalize the HRIR set and slightly attenuate the result. +// Normalize the HRIR set and slightly attenuate the result. This is done +// per-field since distance attenuation is ignored. static void NormalizeHrirs(const HrirDataT *hData) { uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1; uint n = hData->mIrPoints; uint ti, fi, ei, ai, i; - double maxLevel = 0.0; for(fi = 0;fi < hData->mFdCount;fi++) { + double maxLevel = 0.0; + for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) { for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) @@ -2275,10 +2473,9 @@ static void NormalizeHrirs(const HrirDataT *hData) } } } - } - maxLevel = 1.01 * maxLevel; - for(fi = 0;fi < hData->mFdCount;fi++) - { + + maxLevel = 1.01 * maxLevel; + for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) { for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) @@ -2310,58 +2507,63 @@ static double CalcLTD(const double ev, const double az, const double rad, const } // Calculate the effective head-related time delays for each minimum-phase -// HRIR. +// HRIR. This is done per-field since distance delay is ignored. static void CalculateHrtds(const HeadModelT model, const double radius, HrirDataT *hData) { uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1; - double minHrtd{std::numeric_limits<double>::infinity()}; - double maxHrtd{-minHrtd}; + double customRatio{radius / hData->mRadius}; uint ti, fi, ei, ai; - double t; - if(model == HM_DATASET) + if(model == HM_SPHERE) { for(fi = 0;fi < hData->mFdCount;fi++) { for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + HrirEvT *evd = &hData->mFds[fi].mEvs[ei]; + + for(ai = 0;ai < evd->mAzCount;ai++) { - HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; + HrirAzT *azd = &evd->mAzs[ai]; + for(ti = 0;ti < channels;ti++) - { - t = azd->mDelays[ti] * radius / hData->mRadius; - azd->mDelays[ti] = t; - maxHrtd = std::max(t, maxHrtd); - minHrtd = std::min(t, minHrtd); - } + azd->mDelays[ti] = CalcLTD(evd->mElevation, azd->mAzimuth, radius, hData->mFds[fi].mDistance); } } } } - else + else if(customRatio != 1.0) { for(fi = 0;fi < hData->mFdCount;fi++) { for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) { HrirEvT *evd = &hData->mFds[fi].mEvs[ei]; + for(ai = 0;ai < evd->mAzCount;ai++) { HrirAzT *azd = &evd->mAzs[ai]; for(ti = 0;ti < channels;ti++) - { - t = CalcLTD(evd->mElevation, azd->mAzimuth, radius, hData->mFds[fi].mDistance); - azd->mDelays[ti] = t; - maxHrtd = std::max(t, maxHrtd); - minHrtd = std::min(t, minHrtd); - } + azd->mDelays[ti] *= customRatio; } } } } + for(fi = 0;fi < hData->mFdCount;fi++) { + double minHrtd{std::numeric_limits<double>::infinity()}; + for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) + { + for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++) + { + HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; + + for(ti = 0;ti < channels;ti++) + minHrtd = std::min(azd->mDelays[ti], minHrtd); + } + } + for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++) { for(ti = 0;ti < channels;ti++) @@ -2701,14 +2903,16 @@ static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, // Match the source format from a given identifier. static SourceFormatT MatchSourceFormat(const char *ident) { - if(strcasecmp(ident, "wave") == 0) - return SF_WAVE; + if(strcasecmp(ident, "ascii") == 0) + return SF_ASCII; if(strcasecmp(ident, "bin_le") == 0) return SF_BIN_LE; if(strcasecmp(ident, "bin_be") == 0) return SF_BIN_BE; - if(strcasecmp(ident, "ascii") == 0) - return SF_ASCII; + if(strcasecmp(ident, "wave") == 0) + return SF_WAVE; + if(strcasecmp(ident, "sofa") == 0) + return SF_SOFA; return SF_NONE; } @@ -2727,6 +2931,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) { char ident[MAX_IDENT_LEN+1]; uint line, col; + double fpVal; int intVal; TrIndication(tr, &line, &col); @@ -2740,7 +2945,32 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) } if(!TrReadOperator(tr, "(")) return 0; - if(src->mFormat == SF_WAVE) + if(src->mFormat == SF_SOFA) + { + if(!TrReadFloat(tr, MIN_DISTANCE, MAX_DISTANCE, &fpVal)) + return 0; + src->mRadius = fpVal; + if(!TrReadOperator(tr, ",")) + return 0; + if(!TrReadFloat(tr, -90.0, 90.0, &fpVal)) + return 0; + src->mElevation = fpVal; + if(!TrReadOperator(tr, ",")) + return 0; + if(!TrReadFloat(tr, -360.0, 360.0, &fpVal)) + return 0; + src->mAzimuth = fpVal; + if(!TrReadOperator(tr, ":")) + return 0; + 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 if(src->mFormat == SF_WAVE) { if(!TrReadInt(tr, 0, MAX_WAVE_CHANNELS, &intVal)) return 0; @@ -2843,6 +3073,45 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) return 1; } +// Parse and validate a SOFA source reference from the data set definition. +static int ReadSofaRef(TokenReaderT *tr, SourceRefT *src) +{ + char ident[MAX_IDENT_LEN+1]; + uint line, col; + int intVal; + + TrIndication(tr, &line, &col); + if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + return 0; + src->mFormat = MatchSourceFormat(ident); + if(src->mFormat != SF_SOFA) + { + TrErrorAt(tr, line, col, "Expected the SOFA source format.\n"); + return 0; + } + + src->mType = ET_NONE; + src->mSize = 0; + src->mBits = 0; + src->mChannel = 0; + src->mSkip = 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; +} + // Match the target ear (index) from a given identifier. static int MatchTargetEar(const char *ident) { @@ -2872,6 +3141,134 @@ static int ProcessSources(const HeadModelT model, TokenReaderT *tr, HrirDataT *h TrIndication(tr, &line, &col); TrReadOperator(tr, "["); + + if(TrIsOperator(tr, "*")) + { + SourceRefT src; + struct MYSOFA_EASY *sofa; + uint si; + + TrReadOperator(tr, "*"); + if(!TrReadOperator(tr, "]") || !TrReadOperator(tr, "=")) + return 0; + + TrIndication(tr, &line, &col); + if(!ReadSofaRef(tr, &src)) + return 0; + + if(hData->mChannelType == CT_STEREO) + { + char type[MAX_IDENT_LEN+1]; + ChannelTypeT channelType; + + if(!TrReadIdent(tr, MAX_IDENT_LEN, type)) + return 0; + + channelType = MatchChannelType(type); + + switch(channelType) + { + case CT_NONE: + TrErrorAt(tr, line, col, "Expected a channel type.\n"); + return 0; + case CT_MONO: + src.mChannel = 0; + break; + case CT_STEREO: + src.mChannel = 1; + break; + } + } + else + { + char type[MAX_IDENT_LEN+1]; + ChannelTypeT channelType; + + if(!TrReadIdent(tr, MAX_IDENT_LEN, type)) + return 0; + + channelType = MatchChannelType(type); + if(channelType != CT_MONO) + { + TrErrorAt(tr, line, col, "Expected a mono channel type.\n"); + return 0; + } + src.mChannel = 0; + } + + sofa = LoadSofaFile(&src, hData->mIrRate, hData->mIrPoints); + if(!sofa) return 0; + + for(si = 0;si < sofa->hrtf->M;si++) + { + printf("\rLoading sources... %d of %d", si+1, sofa->hrtf->M); + fflush(stdout); + + float aer[3] = { + sofa->hrtf->SourcePosition.values[3*si], + sofa->hrtf->SourcePosition.values[3*si + 1], + sofa->hrtf->SourcePosition.values[3*si + 2] + }; + mysofa_c2s(aer); + + if(std::fabs(aer[1]) >= 89.999f) + aer[0] = 0.0f; + else + aer[0] = std::fmod(360.0f - aer[0], 360.0f); + + for(fi = 0;fi < hData->mFdCount;fi++) + { + double delta = aer[2] - hData->mFds[fi].mDistance; + if(std::abs(delta) < 0.001) + break; + } + if(fi >= hData->mFdCount) + continue; + + double ef{(90.0 + aer[1]) * (hData->mFds[fi].mEvCount - 1) / 180.0}; + ei = (int)std::round(ef); + ef = (ef - ei) * 180.0f / (hData->mFds[fi].mEvCount - 1); + if(std::abs(ef) >= 0.1) + continue; + + double af{aer[0] * hData->mFds[fi].mEvs[ei].mAzCount / 360.0f}; + ai = (int)std::round(af); + af = (af - ai) * 360.0f / hData->mFds[fi].mEvs[ei].mAzCount; + ai = ai % hData->mFds[fi].mEvs[ei].mAzCount; + if(std::abs(af) >= 0.1) + continue; + + HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; + + if(azd->mIrs[0] != nullptr) + { + TrErrorAt(tr, line, col, "Redefinition of source [ %d, %d, %d ].\n", fi, ei, ai); + return 0; + } + + ExtractSofaHrir(sofa, si, 0, src.mOffset, hData->mIrPoints, hrir.data()); + azd->mIrs[0] = &hrirs[hData->mIrSize * azd->mIndex]; + if(model == HM_DATASET) + azd->mDelays[0] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[0]); + AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir.data(), 1.0, azd->mIrs[0]); + + if(src.mChannel == 1) + { + ExtractSofaHrir(sofa, si, 1, src.mOffset, hData->mIrPoints, hrir.data()); + azd->mIrs[1] = &hrirs[hData->mIrSize * (hData->mIrCount + azd->mIndex)]; + if(model == HM_DATASET) + azd->mDelays[1] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[1]); + AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir.data(), 1.0, azd->mIrs[1]); + } + + // TODO: Since some SOFA files contain minimum phase HRIRs, + // it would be beneficial to check for per-measurement delays + // (when available) to reconstruct the HRTDs. + } + + continue; + } + if(!ReadIndexTriplet(tr, hData, &fi, &ei, &ai)) return 0; if(!TrReadOperator(tr, "]")) @@ -2990,9 +3387,13 @@ static int ProcessSources(const HeadModelT model, TokenReaderT *tr, HrirDataT *h } } if(!TrLoad(tr)) + { + mysofa_cache_release_all(); return 1; + } TrError(tr, "Errant data at end of source list.\n"); + mysofa_cache_release_all(); return 0; } @@ -3014,7 +3415,7 @@ static int ProcessDefinition(const char *inName, const uint outRate, const uint fp = fopen(inName, "r"); if(fp == nullptr) { - fprintf(stderr, "Error: Could not open definition file '%s'\n", inName); + fprintf(stderr, "\nError: Could not open definition file '%s'\n", inName); return 0; } TrSetup(fp, inName, &tr); @@ -3044,6 +3445,11 @@ static int ProcessDefinition(const char *inName, const uint outRate, const uint uint m = 1 + hData.mFftSize / 2; std::vector<double> dfa(c * m); + if(hData.mFdCount > 1) + { + fprintf(stdout, "Balancing field magnitudes...\n"); + BalanceFieldMagnitudes(&hData, c, m); + } fprintf(stdout, "Calculating diffuse-field average...\n"); CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa.data()); fprintf(stdout, "Performing diffuse-field equalization...\n"); @@ -3078,7 +3484,6 @@ static void PrintHelp(const char *argv0, FILE *ofile) { fprintf(ofile, "Usage: %s [<option>...]\n\n", argv0); fprintf(ofile, "Options:\n"); - fprintf(ofile, " -m Ignored for compatibility.\n"); fprintf(ofile, " -r <rate> Change the data set sample rate to the specified value and\n"); fprintf(ofile, " resample the HRIRs accordingly.\n"); fprintf(ofile, " -f <points> Override the FFT window size (default: %u).\n", DEFAULT_FFTSIZE); @@ -3090,7 +3495,7 @@ static void PrintHelp(const char *argv0, FILE *ofile) fprintf(ofile, " after minimum-phase reconstruction (default: %u).\n", DEFAULT_TRUNCSIZE); fprintf(ofile, " -d {dataset| Specify the model used for calculating the head-delay timing\n"); fprintf(ofile, " sphere} values (default: %s).\n", ((DEFAULT_HEAD_MODEL == HM_DATASET) ? "dataset" : "sphere")); - fprintf(ofile, " -c <size> Use a customized head radius measured ear-to-ear in meters.\n"); + fprintf(ofile, " -c <radius> Use a customized head radius measured to-ear in meters.\n"); fprintf(ofile, " -i <filename> Specify an HRIR definition file to use (defaults to stdin).\n"); fprintf(ofile, " -o <filename> Specify an output file. Use of '%%r' will be substituted with\n"); fprintf(ofile, " the data set sample rate.\n"); @@ -3128,19 +3533,15 @@ int main(int argc, char *argv[]) model = DEFAULT_HEAD_MODEL; radius = DEFAULT_CUSTOM_RADIUS; - while((opt=getopt(argc, argv, "mr:f:e:s:l:w:d:c:e:i:o:h")) != -1) + while((opt=getopt(argc, argv, "r:f:e:s:l:w:d:c:e:i:o:h")) != -1) { switch(opt) { - case 'm': - fprintf(stderr, "Ignoring unused command '-m'.\n"); - break; - case 'r': outRate = strtoul(optarg, &end, 10); if(end[0] != '\0' || outRate < MIN_RATE || outRate > MAX_RATE) { - fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, MIN_RATE, MAX_RATE); + fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, MIN_RATE, MAX_RATE); exit(EXIT_FAILURE); } break; @@ -3149,7 +3550,7 @@ int main(int argc, char *argv[]) fftSize = strtoul(optarg, &end, 10); if(end[0] != '\0' || (fftSize&(fftSize-1)) || fftSize < MIN_FFTSIZE || fftSize > MAX_FFTSIZE) { - fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected a power-of-two between %u to %u.\n", optarg, opt, MIN_FFTSIZE, MAX_FFTSIZE); + fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected a power-of-two between %u to %u.\n", optarg, opt, MIN_FFTSIZE, MAX_FFTSIZE); exit(EXIT_FAILURE); } break; @@ -3161,7 +3562,7 @@ int main(int argc, char *argv[]) equalize = 0; else { - fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt); + fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt); exit(EXIT_FAILURE); } break; @@ -3173,7 +3574,7 @@ int main(int argc, char *argv[]) surface = 0; else { - fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt); + fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt); exit(EXIT_FAILURE); } break; @@ -3186,7 +3587,7 @@ int main(int argc, char *argv[]) limit = strtod(optarg, &end); if(end[0] != '\0' || limit < MIN_LIMIT || limit > MAX_LIMIT) { - fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected between %.0f to %.0f.\n", optarg, opt, MIN_LIMIT, MAX_LIMIT); + fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %.0f to %.0f.\n", optarg, opt, MIN_LIMIT, MAX_LIMIT); exit(EXIT_FAILURE); } } @@ -3196,7 +3597,7 @@ int main(int argc, char *argv[]) truncSize = strtoul(optarg, &end, 10); if(end[0] != '\0' || truncSize < MIN_TRUNCSIZE || truncSize > MAX_TRUNCSIZE || (truncSize%MOD_TRUNCSIZE)) { - fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected multiple of %u between %u to %u.\n", optarg, opt, MOD_TRUNCSIZE, MIN_TRUNCSIZE, MAX_TRUNCSIZE); + fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected multiple of %u between %u to %u.\n", optarg, opt, MOD_TRUNCSIZE, MIN_TRUNCSIZE, MAX_TRUNCSIZE); exit(EXIT_FAILURE); } break; @@ -3208,7 +3609,7 @@ int main(int argc, char *argv[]) model = HM_SPHERE; else { - fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected dataset or sphere.\n", optarg, opt); + fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected dataset or sphere.\n", optarg, opt); exit(EXIT_FAILURE); } break; @@ -3217,7 +3618,7 @@ int main(int argc, char *argv[]) radius = strtod(optarg, &end); if(end[0] != '\0' || radius < MIN_CUSTOM_RADIUS || radius > MAX_CUSTOM_RADIUS) { - fprintf(stderr, "Error: Got unexpected value \"%s\" for option -%c, expected between %.2f to %.2f.\n", optarg, opt, MIN_CUSTOM_RADIUS, MAX_CUSTOM_RADIUS); + fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %.2f to %.2f.\n", optarg, opt, MIN_CUSTOM_RADIUS, MAX_CUSTOM_RADIUS); exit(EXIT_FAILURE); } break; |