2012-09-11 01:59:42 -07:00
/*
2012-03-25 19:57:40 -07:00
* HRTF utility for producing and demonstrating the process of creating an
* OpenAL Soft compatible HRIR data set .
*
2019-01-22 11:13:14 -08:00
* Copyright ( C ) 2011 - 2019 Christopher Fitzgerald
2012-09-11 01:59:42 -07:00
*
* 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
2019-01-22 11:13:14 -08:00
* measurement . This has since been modified to use coverage volume for
* multi - field HRIR data sets .
2012-09-11 01:59:42 -07:00
* 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
2012-03-25 19:57:40 -07:00
*
2012-11-18 09:29:58 -08:00
* The formulae for calculating the Kaiser window metrics are from the
* the textbook :
*
* Discrete - Time Signal Processing
* Alan V . Oppenheim and Ronald W . Schafer
* Prentice - Hall Signal Processing Series
* 1999
2012-03-25 19:57:40 -07:00
*/
2019-07-28 14:55:02 -07:00
# define _UNICODE
2012-09-11 04:45:43 -07:00
# include "config.h"
2019-07-28 14:55:02 -07:00
# include "makemhr.h"
# include <algorithm>
# include <atomic>
# include <chrono>
# include <cmath>
# include <complex>
# include <cstdint>
2019-01-09 19:42:40 +01:00
# include <cstdio>
# include <cstdlib>
# include <cstring>
2019-07-28 14:55:02 -07:00
# include <functional>
2019-09-23 18:37:36 -07:00
# include <iostream>
2019-07-28 14:55:02 -07:00
# include <limits>
# include <memory>
# include <numeric>
# include <thread>
# include <utility>
# include <vector>
2017-08-20 04:30:53 -07:00
# ifdef HAVE_GETOPT
# include <unistd.h>
# else
2019-07-14 04:05:08 -07:00
# include "../getopt.h"
2017-08-20 04:30:53 -07:00
# endif
2012-09-11 01:59:42 -07:00
2019-09-23 18:37:36 -07:00
# include "alfstream.h"
2020-08-13 16:02:13 -07:00
# include "alspan.h"
2019-09-16 14:55:52 -07:00
# include "alstring.h"
2019-03-24 19:00:58 -07:00
# include "loaddef.h"
# include "loadsofa.h"
2018-12-31 19:54:34 -08:00
# include "win_main_utf8.h"
2012-03-25 19:57:40 -07:00
2019-07-28 14:55:02 -07:00
2019-03-05 06:21:09 -08:00
namespace {
using namespace std : : placeholders ;
} // namespace
2012-03-27 08:20:04 -07:00
# ifndef M_PI
2012-11-18 09:29:58 -08:00
# define M_PI (3.14159265358979323846)
2012-03-27 08:20:04 -07:00
# endif
2017-08-20 01:39:24 -07:00
2019-03-24 22:06:01 -07:00
// Head model used for calculating the impulse delays.
enum HeadModelT {
HM_NONE ,
HM_DATASET , // Measure the onset from the dataset.
HM_SPHERE // Calculate the onset using a spherical head model.
} ;
2012-09-11 01:59:42 -07:00
// The epsilon used to maintain signal stability.
2017-07-25 16:17:46 -07:00
# define EPSILON (1e-9)
2012-09-11 01:59:42 -07:00
// The limits to the FFT window size override on the command line.
2017-07-25 16:17:46 -07:00
# define MIN_FFTSIZE (65536)
# define MAX_FFTSIZE (131072)
2012-09-11 01:59:42 -07:00
// 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.
2017-07-25 16:17:46 -07:00
# define MIN_TRUNCSIZE (16)
2019-12-11 01:20:00 -08:00
# define MAX_TRUNCSIZE (128)
2012-09-11 01:59:42 -07:00
2014-01-11 02:24:20 -08:00
// The limits to the custom head radius on the command line.
# define MIN_CUSTOM_RADIUS (0.05)
# define MAX_CUSTOM_RADIUS (0.15)
2012-09-11 01:59:42 -07:00
// The defaults for the command line options.
2017-07-25 16:17:46 -07:00
# define DEFAULT_FFTSIZE (65536)
2012-09-11 01:59:42 -07:00
# define DEFAULT_EQUALIZE (1)
# define DEFAULT_SURFACE (1)
# define DEFAULT_LIMIT (24.0)
# define DEFAULT_TRUNCSIZE (32)
2013-12-18 12:43:59 -08:00
# define DEFAULT_HEAD_MODEL (HM_DATASET)
2014-01-11 02:24:20 -08:00
# define DEFAULT_CUSTOM_RADIUS (0.0)
2012-09-11 01:59:42 -07:00
// The maximum propagation delay value supported by OpenAL Soft.
# define MAX_HRTD (63.0)
2012-03-27 08:44:08 -07:00
2012-09-11 01:59:42 -07:00
// The OpenAL Soft HRTF format marker. It stands for minimum-phase head
2020-02-11 01:01:10 -08:00
// response protocol 03.
# define MHR_FORMAT ("MinPHR03")
2012-09-11 01:59:42 -07:00
2019-03-09 13:00:08 -08:00
/* Channel index enums. Mono uses LeftChannel only. */
enum ChannelIndex : uint {
LeftChannel = 0u ,
RightChannel = 1u
} ;
2012-03-25 19:57:40 -07:00
2012-09-11 01:59:42 -07:00
/* Performs a string substitution. Any case-insensitive occurrences of the
* pattern string are replaced with the replacement string . The result is
* truncated if necessary .
*/
2020-08-13 16:02:13 -07:00
static std : : string StrSubst ( al : : span < const char > in , const al : : span < const char > pat ,
const al : : span < const char > rep )
2016-02-18 22:55:03 -08:00
{
2020-08-13 16:02:13 -07:00
std : : string ret ;
ret . reserve ( in . size ( ) + pat . size ( ) ) ;
while ( in . size ( ) > = pat . size ( ) )
2016-02-18 22:55:03 -08:00
{
2020-08-13 16:02:13 -07:00
if ( al : : strncasecmp ( in . data ( ) , pat . data ( ) , pat . size ( ) ) = = 0 )
2016-02-18 22:55:03 -08:00
{
2020-08-13 16:02:13 -07:00
in = in . subspan ( pat . size ( ) ) ;
ret . append ( rep . data ( ) , rep . size ( ) ) ;
}
else
{
size_t endpos { 1 } ;
while ( endpos < in . size ( ) & & in [ endpos ] ! = pat . front ( ) )
+ + endpos ;
ret . append ( in . data ( ) , endpos ) ;
in = in . subspan ( endpos ) ;
2016-02-18 22:55:03 -08:00
}
}
2020-08-13 16:02:13 -07:00
ret . append ( in . data ( ) , in . size ( ) ) ;
return ret ;
2012-09-11 01:59:42 -07:00
}
2016-02-18 22:55:03 -08:00
/*********************
* * * Math routines * * *
* * * * * * * * * * * * * * * * * * * * */
2012-09-11 01:59:42 -07:00
// Simple clamp routine.
2016-02-18 22:55:03 -08:00
static double Clamp ( const double val , const double lower , const double upper )
{
2018-12-31 19:54:34 -08:00
return std : : min ( std : : max ( val , lower ) , upper ) ;
2012-09-11 01:59:42 -07:00
}
2017-08-01 23:43:25 -07:00
static inline uint dither_rng ( uint * seed )
2016-02-18 22:55:03 -08:00
{
2017-10-22 15:36:42 -07:00
* seed = * seed * 96314165 + 907633515 ;
2017-08-01 23:43:25 -07:00
return * seed ;
}
2017-10-22 15:36:42 -07:00
// Performs a triangular probability density function dither. The input samples
// should be normalized (-1 to +1).
2018-10-29 11:32:50 -07:00
static void TpdfDither ( double * RESTRICT out , const double * RESTRICT in , const double scale ,
2019-09-11 04:55:54 -07:00
const uint count , const uint step , uint * seed )
2017-08-01 23:43:25 -07:00
{
2019-01-22 11:13:14 -08:00
static constexpr double PRNG_SCALE = 1.0 / std : : numeric_limits < uint > : : max ( ) ;
2017-10-22 15:36:42 -07:00
2019-09-11 04:55:54 -07:00
for ( uint i { 0 } ; i < count ; i + + )
2017-10-22 15:36:42 -07:00
{
2018-12-31 19:54:34 -08:00
uint prn0 { dither_rng ( seed ) } ;
uint prn1 { dither_rng ( seed ) } ;
2019-09-11 04:55:54 -07:00
* out = std : : round ( * ( in + + ) * scale + ( prn0 * PRNG_SCALE - prn1 * PRNG_SCALE ) ) ;
out + = step ;
2017-10-22 15:36:42 -07:00
}
}
2012-03-25 19:57:40 -07:00
2018-05-29 22:15:49 -07:00
/* Fast Fourier transform routines. The number of points must be a power of
* two .
2012-09-11 01:59:42 -07:00
*/
2012-03-25 19:57:40 -07:00
2012-09-11 01:59:42 -07:00
// Performs bit-reversal ordering.
2018-12-31 19:54:34 -08:00
static void FftArrange ( const uint n , complex_d * inout )
2016-02-18 22:55:03 -08:00
{
2018-05-29 22:15:49 -07:00
// Handle in-place arrangement.
2019-01-22 11:13:14 -08:00
uint rk { 0u } ;
for ( uint k { 0u } ; k < n ; k + + )
2016-02-18 22:55:03 -08:00
{
2018-05-29 22:15:49 -07:00
if ( rk > k )
2018-12-31 19:54:34 -08:00
std : : swap ( inout [ rk ] , inout [ k ] ) ;
2018-05-29 22:15:49 -07:00
2019-01-22 11:13:14 -08:00
uint m { n } ;
2018-05-29 22:15:49 -07:00
while ( rk & ( m > > = 1 ) )
rk & = ~ m ;
rk | = m ;
2016-02-18 22:55:03 -08:00
}
2012-09-11 01:59:42 -07:00
}
// Performs the summation.
2019-09-11 04:55:54 -07:00
static void FftSummation ( const uint n , const double s , complex_d * cplx )
2016-02-18 22:55:03 -08:00
{
double pi ;
2019-09-11 04:55:54 -07:00
uint m , m2 ;
uint i , k , mk ;
2016-02-18 22:55:03 -08:00
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))
2019-09-11 04:55:54 -07:00
double sm = std : : sin ( 0.5 * pi / m ) ;
auto v = complex_d { - 2.0 * sm * sm , - std : : sin ( pi / m ) } ;
2018-12-31 19:54:34 -08:00
auto w = complex_d { 1.0 , 0.0 } ;
2016-02-18 22:55:03 -08:00
for ( i = 0 ; i < m ; i + + )
{
for ( k = i ; k < n ; k + = m2 )
{
mk = k + m ;
2018-12-31 19:54:34 -08:00
auto t = w * cplx [ mk ] ;
cplx [ mk ] = cplx [ k ] - t ;
cplx [ k ] = cplx [ k ] + t ;
2016-02-18 22:55:03 -08:00
}
2018-12-31 19:54:34 -08:00
w + = v * w ;
2016-02-18 22:55:03 -08:00
}
}
2012-09-11 01:59:42 -07:00
}
// Performs a forward FFT.
2019-03-24 19:00:58 -07:00
void FftForward ( const uint n , complex_d * inout )
2016-02-18 22:55:03 -08:00
{
2018-05-29 22:15:49 -07:00
FftArrange ( n , inout ) ;
FftSummation ( n , 1.0 , inout ) ;
2012-09-11 01:59:42 -07:00
}
// Performs an inverse FFT.
2019-03-24 19:00:58 -07:00
void FftInverse ( const uint n , complex_d * inout )
2016-02-18 22:55:03 -08:00
{
2018-05-29 22:15:49 -07:00
FftArrange ( n , inout ) ;
FftSummation ( n , - 1.0 , inout ) ;
2018-12-31 19:54:34 -08:00
double f { 1.0 / n } ;
for ( uint i { 0 } ; i < n ; i + + )
inout [ i ] * = f ;
2012-03-25 19:57:40 -07:00
}
2017-07-25 16:17:46 -07:00
/* Calculate the complex helical sequence (or discrete-time analytical signal)
* of the given input using the Hilbert transform . Given the natural logarithm
* of a signal ' s magnitude response , the imaginary components can be used as
* the angles for minimum - phase reconstruction .
2012-09-11 01:59:42 -07:00
*/
2018-12-31 19:54:34 -08:00
static void Hilbert ( const uint n , complex_d * inout )
2016-02-18 22:55:03 -08:00
{
uint i ;
2018-05-29 22:15:49 -07:00
// Handle in-place operation.
for ( i = 0 ; i < n ; i + + )
2018-12-31 19:54:34 -08:00
inout [ i ] . imag ( 0.0 ) ;
2018-05-29 22:15:49 -07:00
FftInverse ( n , inout ) ;
2017-07-25 16:17:46 -07:00
for ( i = 1 ; i < ( n + 1 ) / 2 ; i + + )
2018-12-31 19:54:34 -08:00
inout [ i ] * = 2.0 ;
2017-07-25 16:17:46 -07:00
/* Increment i if n is even. */
i + = ( n & 1 ) ^ 1 ;
2016-02-18 22:55:03 -08:00
for ( ; i < n ; i + + )
2018-12-31 19:54:34 -08:00
inout [ i ] = complex_d { 0.0 , 0.0 } ;
2018-05-29 22:15:49 -07:00
FftForward ( n , inout ) ;
2012-09-11 01:59:42 -07:00
}
/* 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 .
*/
2019-03-24 19:00:58 -07:00
void MagnitudeResponse ( const uint n , const complex_d * in , double * out )
2016-02-18 22:55:03 -08:00
{
const uint m = 1 + ( n / 2 ) ;
uint i ;
for ( i = 0 ; i < m ; i + + )
2018-12-31 19:54:34 -08:00
out [ i ] = std : : max ( std : : abs ( in [ i ] ) , EPSILON ) ;
2012-09-11 01:59:42 -07:00
}
2012-03-25 19:57:40 -07:00
2012-09-11 01:59:42 -07:00
/* 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 .
*/
2017-10-22 15:36:42 -07:00
static void LimitMagnitudeResponse ( const uint n , const uint m , const double limit , const double * in , double * out )
2016-02-18 22:55:03 -08:00
{
double halfLim ;
uint i , lower , upper ;
double ave ;
halfLim = limit / 2.0 ;
// Convert the response to dB.
for ( i = 0 ; i < m ; i + + )
2018-12-31 19:54:34 -08:00
out [ i ] = 20.0 * std : : log10 ( in [ i ] ) ;
2016-02-18 22:55:03 -08:00
// Use six octaves to calculate the average magnitude of the signal.
2019-01-08 19:42:44 +01:00
lower = ( static_cast < uint > ( std : : ceil ( n / std : : pow ( 2.0 , 8.0 ) ) ) ) - 1 ;
upper = ( static_cast < uint > ( std : : floor ( n / std : : pow ( 2.0 , 2.0 ) ) ) ) - 1 ;
2016-02-18 22:55:03 -08:00
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 + + )
2018-12-31 19:54:34 -08:00
out [ i ] = std : : pow ( 10.0 , out [ i ] / 20.0 ) ;
2012-09-11 01:59:42 -07:00
}
/* 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 .
*/
2020-08-14 18:22:20 -07:00
static void MinimumPhase ( const uint n , double * mags , complex_d * out )
2016-02-18 22:55:03 -08:00
{
2020-08-14 18:22:20 -07:00
const uint m { ( n / 2 ) + 1 } ;
2016-02-18 22:55:03 -08:00
2018-12-31 19:54:34 -08:00
uint i ;
2016-02-18 22:55:03 -08:00
for ( i = 0 ; i < m ; i + + )
2020-08-14 18:22:20 -07:00
out [ i ] = std : : log ( mags [ i ] ) ;
2016-02-18 22:55:03 -08:00
for ( ; i < n ; i + + )
{
mags [ i ] = mags [ n - i ] ;
2017-08-18 04:32:55 -07:00
out [ i ] = out [ n - i ] ;
2016-02-18 22:55:03 -08:00
}
2018-05-29 22:15:49 -07:00
Hilbert ( n , out ) ;
2016-02-18 22:55:03 -08:00
// Remove any DC offset the filter has.
2017-07-25 16:17:46 -07:00
mags [ 0 ] = EPSILON ;
for ( i = 0 ; i < n ; i + + )
2016-02-18 22:55:03 -08:00
{
2018-12-31 19:54:34 -08:00
auto a = std : : exp ( complex_d { 0.0 , out [ i ] . imag ( ) } ) ;
2020-08-14 18:22:20 -07:00
out [ i ] = a * mags [ i ] ;
2016-02-18 22:55:03 -08:00
}
2012-03-25 19:57:40 -07:00
}
2016-02-18 22:55:03 -08:00
2019-03-24 19:00:58 -07:00
/***************************
* * * File storage output * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * */
// Write an ASCII string to a file.
static int WriteAscii ( const char * out , FILE * fp , const char * filename )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
size_t len ;
2016-02-18 22:55:03 -08:00
2019-03-24 19:00:58 -07:00
len = strlen ( out ) ;
if ( fwrite ( out , 1 , len , fp ) ! = len )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
fclose ( fp ) ;
fprintf ( stderr , " \n Error: Bad write to file '%s'. \n " , filename ) ;
2016-02-18 22:55:03 -08:00
return 0 ;
}
return 1 ;
2012-03-25 19:57:40 -07:00
}
2019-03-24 19:00:58 -07:00
// Write a binary value of the given byte order and byte size to a file,
// loading it from a 32-bit unsigned integer.
2019-03-24 22:06:01 -07:00
static int WriteBin4 ( const uint bytes , const uint32_t in , FILE * fp , const char * filename )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
uint8_t out [ 4 ] ;
2016-02-18 22:55:03 -08:00
uint i ;
2019-03-24 22:06:01 -07:00
for ( i = 0 ; i < bytes ; i + + )
out [ i ] = ( in > > ( i * 8 ) ) & 0x000000FF ;
2019-03-24 19:00:58 -07:00
if ( fwrite ( out , 1 , bytes , fp ) ! = bytes )
{
fprintf ( stderr , " \n Error: Bad write to file '%s'. \n " , filename ) ;
return 0 ;
}
2016-02-18 22:55:03 -08:00
return 1 ;
2012-03-25 19:57:40 -07:00
}
2019-03-24 19:00:58 -07:00
// Store the OpenAL Soft HRTF data set.
static int StoreMhr ( const HrirDataT * hData , const char * filename )
2016-02-18 22:55:03 -08:00
{
2020-02-10 22:25:07 -08:00
const uint channels { ( hData - > mChannelType = = CT_STEREO ) ? 2u : 1u } ;
const uint n { hData - > mIrPoints } ;
uint dither_seed { 22222 } ;
2019-03-24 19:00:58 -07:00
uint fi , ei , ai , i ;
2020-02-10 22:25:07 -08:00
FILE * fp ;
2012-09-11 01:59:42 -07:00
2019-03-24 19:00:58 -07:00
if ( ( fp = fopen ( filename , " wb " ) ) = = nullptr )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
fprintf ( stderr , " \n Error: Could not open MHR file '%s'. \n " , filename ) ;
2017-10-22 15:36:42 -07:00
return 0 ;
2016-02-18 22:55:03 -08:00
}
2019-03-24 19:00:58 -07:00
if ( ! WriteAscii ( MHR_FORMAT , fp , filename ) )
2016-02-18 22:55:03 -08:00
return 0 ;
2019-03-24 22:06:01 -07:00
if ( ! WriteBin4 ( 4 , hData - > mIrRate , fp , filename ) )
2016-02-18 22:55:03 -08:00
return 0 ;
2019-03-24 22:06:01 -07:00
if ( ! WriteBin4 ( 1 , static_cast < uint32_t > ( hData - > mChannelType ) , fp , filename ) )
2019-03-24 19:00:58 -07:00
return 0 ;
2019-03-24 22:06:01 -07:00
if ( ! WriteBin4 ( 1 , hData - > mIrPoints , fp , filename ) )
2019-03-24 19:00:58 -07:00
return 0 ;
2019-03-24 22:06:01 -07:00
if ( ! WriteBin4 ( 1 , hData - > mFdCount , fp , filename ) )
2019-03-24 19:00:58 -07:00
return 0 ;
2020-02-11 01:01:10 -08:00
for ( fi = hData - > mFdCount - 1 ; fi < hData - > mFdCount ; fi - - )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
auto fdist = static_cast < uint32_t > ( std : : round ( 1000.0 * hData - > mFds [ fi ] . mDistance ) ) ;
2019-03-24 22:06:01 -07:00
if ( ! WriteBin4 ( 2 , fdist , fp , filename ) )
2016-02-18 22:55:03 -08:00
return 0 ;
2019-03-24 22:06:01 -07:00
if ( ! WriteBin4 ( 1 , hData - > mFds [ fi ] . mEvCount , fp , filename ) )
2016-02-18 22:55:03 -08:00
return 0 ;
2019-03-24 19:00:58 -07:00
for ( ei = 0 ; ei < hData - > mFds [ fi ] . mEvCount ; ei + + )
{
2019-03-24 22:06:01 -07:00
if ( ! WriteBin4 ( 1 , hData - > mFds [ fi ] . mEvs [ ei ] . mAzCount , fp , filename ) )
2019-03-24 19:00:58 -07:00
return 0 ;
2016-02-18 22:55:03 -08:00
}
}
2019-03-24 19:00:58 -07:00
2020-02-11 01:01:10 -08:00
for ( fi = hData - > mFdCount - 1 ; fi < hData - > mFdCount ; fi - - )
2016-02-18 22:55:03 -08:00
{
2020-02-11 01:01:10 -08:00
constexpr double scale { 8388607.0 } ;
constexpr uint bps { 3u } ;
2019-03-24 19:00:58 -07:00
for ( ei = 0 ; ei < hData - > mFds [ fi ] . mEvCount ; ei + + )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
for ( ai = 0 ; ai < hData - > mFds [ fi ] . mEvs [ ei ] . mAzCount ; ai + + )
{
HrirAzT * azd = & hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] ;
double out [ 2 * MAX_TRUNCSIZE ] ;
TpdfDither ( out , azd - > mIrs [ 0 ] , scale , n , channels , & dither_seed ) ;
if ( hData - > mChannelType = = CT_STEREO )
TpdfDither ( out + 1 , azd - > mIrs [ 1 ] , scale , n , channels , & dither_seed ) ;
for ( i = 0 ; i < ( channels * n ) ; i + + )
{
2020-02-10 22:25:07 -08:00
const auto v = static_cast < int > ( Clamp ( out [ i ] , - scale - 1.0 , scale ) ) ;
2019-03-24 22:06:01 -07:00
if ( ! WriteBin4 ( bps , static_cast < uint32_t > ( v ) , fp , filename ) )
2019-03-24 19:00:58 -07:00
return 0 ;
}
}
2016-02-18 22:55:03 -08:00
}
}
2020-02-11 01:01:10 -08:00
for ( fi = hData - > mFdCount - 1 ; fi < hData - > mFdCount ; fi - - )
2019-03-24 19:00:58 -07:00
{
2020-02-11 01:01:10 -08:00
/* Delay storage has 2 bits of extra precision. */
constexpr double DelayPrecScale { 4.0 } ;
2019-03-24 19:00:58 -07:00
for ( ei = 0 ; ei < hData - > mFds [ fi ] . mEvCount ; ei + + )
{
for ( ai = 0 ; ai < hData - > mFds [ fi ] . mEvs [ ei ] . mAzCount ; ai + + )
{
const HrirAzT & azd = hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] ;
2012-03-25 19:57:40 -07:00
2020-02-11 01:01:10 -08:00
auto v = static_cast < uint > ( std : : round ( azd . mDelays [ 0 ] * DelayPrecScale ) ) ;
if ( ! WriteBin4 ( 1 , v , fp , filename ) ) return 0 ;
2019-03-24 19:00:58 -07:00
if ( hData - > mChannelType = = CT_STEREO )
{
2020-02-11 01:01:10 -08:00
v = static_cast < uint > ( std : : round ( azd . mDelays [ 1 ] * DelayPrecScale ) ) ;
if ( ! WriteBin4 ( 1 , v , fp , filename ) ) return 0 ;
2019-03-24 19:00:58 -07:00
}
}
}
2016-02-18 22:55:03 -08:00
}
2019-03-24 19:00:58 -07:00
fclose ( fp ) ;
2016-02-18 22:55:03 -08:00
return 1 ;
2012-09-11 01:59:42 -07:00
}
2019-03-24 19:00:58 -07:00
/***********************
* * * HRTF processing * * *
* * * * * * * * * * * * * * * * * * * * * * */
/* 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 )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
double maxMags [ MAX_FD_COUNT ] ;
uint fi , ei , ai , ti , i ;
2016-02-18 22:55:03 -08:00
2019-03-24 19:00:58 -07:00
double maxMag { 0.0 } ;
for ( fi = 0 ; fi < hData - > mFdCount ; fi + + )
2017-10-22 15:36:42 -07:00
{
2019-03-24 19:00:58 -07:00
maxMags [ fi ] = 0.0 ;
2016-02-18 22:55:03 -08:00
2019-03-24 19:00:58 -07:00
for ( ei = hData - > mFds [ fi ] . mEvStart ; ei < hData - > mFds [ fi ] . mEvCount ; ei + + )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
for ( ai = 0 ; ai < hData - > mFds [ fi ] . mEvs [ ei ] . mAzCount ; ai + + )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
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 ] ) ;
}
2016-02-18 22:55:03 -08:00
}
}
2019-03-24 19:00:58 -07:00
maxMag = std : : max ( maxMags [ fi ] , maxMag ) ;
2016-02-18 22:55:03 -08:00
}
2019-03-24 19:00:58 -07:00
for ( fi = 0 ; fi < hData - > mFdCount ; fi + + )
2016-02-18 22:55:03 -08:00
{
2019-03-27 11:52:35 -07:00
const double magFactor { maxMag / maxMags [ fi ] } ;
2019-03-24 19:00:58 -07:00
for ( ei = hData - > mFds [ fi ] . mEvStart ; ei < hData - > mFds [ fi ] . mEvCount ; ei + + )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
for ( ai = 0 ; ai < hData - > mFds [ fi ] . mEvs [ ei ] . mAzCount ; ai + + )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
HrirAzT * azd = & hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] ;
for ( ti = 0 ; ti < channels ; ti + + )
{
for ( i = 0 ; i < m ; i + + )
2019-03-27 11:52:35 -07:00
azd - > mIrs [ ti ] [ i ] * = magFactor ;
2019-03-24 19:00:58 -07:00
}
2016-02-18 22:55:03 -08:00
}
}
}
2012-03-25 19:57:40 -07:00
}
2019-03-24 19:00:58 -07:00
/* Calculate the contribution of each HRIR to the diffuse-field average based
* on its coverage volume . All volumes are centered at the spherical HRIR
* coordinates and measured by extruded solid angle .
*/
static void CalculateDfWeights ( const HrirDataT * hData , double * weights )
2019-01-22 11:13:14 -08:00
{
2019-03-24 19:00:58 -07:00
double sum , innerRa , outerRa , evs , ev , upperEv , lowerEv ;
double solidAngle , solidVolume ;
uint fi , ei ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
sum = 0.0 ;
// The head radius acts as the limit for the inner radius.
innerRa = hData - > mRadius ;
for ( fi = 0 ; fi < hData - > mFdCount ; fi + + )
2019-01-22 11:13:14 -08:00
{
2019-03-24 19:00:58 -07:00
// 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 + + )
2019-01-22 11:13:14 -08:00
{
2019-03-24 19:00:58 -07:00
// For each elevation, calculate the upper and lower limits of
// the patch band.
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 surface area of the patch band.
solidAngle = 2.0 * M_PI * ( std : : sin ( upperEv ) - std : : sin ( lowerEv ) ) ;
// 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 ;
2019-01-22 11:13:14 -08:00
}
2019-03-24 19:00:58 -07:00
innerRa = outerRa ;
}
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
for ( fi = 0 ; fi < hData - > mFdCount ; fi + + )
2019-01-22 11:13:14 -08:00
{
2019-03-24 19:00:58 -07:00
// Normalize the weights given the total surface coverage for all
// fields.
for ( ei = hData - > mFds [ fi ] . mEvStart ; ei < hData - > mFds [ fi ] . mEvCount ; ei + + )
weights [ ( fi * MAX_EV_COUNT ) + ei ] / = sum ;
2019-01-22 11:13:14 -08:00
}
}
2019-03-24 19:00:58 -07:00
/* Calculate the diffuse-field average from the given magnitude responses of
* the HRIR set . Weighting can be applied to compensate for the varying
* 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 )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
std : : vector < double > weights ( hData - > mFdCount * MAX_EV_COUNT ) ;
uint count , ti , fi , ei , i , ai ;
2016-02-18 22:55:03 -08:00
2019-03-24 19:00:58 -07:00
if ( weighted )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
// Use coverage weighting to calculate the average.
CalculateDfWeights ( hData , weights . data ( ) ) ;
2016-02-18 22:55:03 -08:00
}
2019-03-24 19:00:58 -07:00
else
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
double weight ;
// If coverage weighting is not used, the weights still need to be
// averaged by the number of existing HRIRs.
count = hData - > mIrCount ;
for ( fi = 0 ; fi < hData - > mFdCount ; fi + + )
{
for ( ei = 0 ; ei < hData - > mFds [ fi ] . mEvStart ; ei + + )
count - = hData - > mFds [ fi ] . mEvs [ ei ] . mAzCount ;
}
weight = 1.0 / count ;
for ( fi = 0 ; fi < hData - > mFdCount ; fi + + )
{
for ( ei = hData - > mFds [ fi ] . mEvStart ; ei < hData - > mFds [ fi ] . mEvCount ; ei + + )
weights [ ( fi * MAX_EV_COUNT ) + ei ] = weight ;
}
}
for ( ti = 0 ; ti < channels ; ti + + )
{
for ( i = 0 ; i < m ; i + + )
dfa [ ( ti * m ) + i ] = 0.0 ;
for ( fi = 0 ; fi < hData - > mFdCount ; fi + + )
{
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 ] ;
// Get the weight for this HRIR's contribution.
double weight = weights [ ( fi * MAX_EV_COUNT ) + ei ] ;
// Add this HRIR's weighted power average to the total.
for ( i = 0 ; i < m ; i + + )
dfa [ ( ti * m ) + i ] + = weight * azd - > mIrs [ ti ] [ i ] * azd - > mIrs [ ti ] [ i ] ;
}
}
}
// Finish the average calculation and keep it from being too small.
for ( i = 0 ; i < m ; i + + )
dfa [ ( ti * m ) + i ] = std : : max ( sqrt ( dfa [ ( ti * m ) + i ] ) , EPSILON ) ;
// Apply a limit to the magnitude range of the diffuse-field average
// if desired.
if ( limit > 0.0 )
LimitMagnitudeResponse ( hData - > mFftSize , m , limit , & dfa [ ti * m ] , & dfa [ ti * m ] ) ;
2016-02-18 22:55:03 -08:00
}
2012-03-25 19:57:40 -07:00
}
2019-03-24 19:00:58 -07:00
// Perform diffuse-field equalization on the magnitude responses of the HRIR
// set using the given average response.
static void DiffuseFieldEqualize ( const uint channels , const uint m , const double * dfa , const HrirDataT * hData )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
uint ti , fi , ei , ai , i ;
2012-09-11 01:59:42 -07:00
2019-03-24 19:00:58 -07:00
for ( fi = 0 ; fi < hData - > mFdCount ; fi + + )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
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 ] ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
for ( ti = 0 ; ti < channels ; ti + + )
{
for ( i = 0 ; i < m ; i + + )
azd - > mIrs [ ti ] [ i ] / = dfa [ ( ti * m ) + i ] ;
}
}
}
2019-01-22 11:13:14 -08:00
}
2012-09-11 01:59:42 -07:00
}
2020-06-17 13:03:26 -07:00
// Resamples the HRIRs for use at the given sampling rate.
static void ResampleHrirs ( const uint rate , HrirDataT * hData )
{
struct Resampler {
const double scale ;
const size_t m ;
/* Resampling from a lower rate to a higher rate. This likely only
* works properly when 1 < = scale < = 2.
*/
void upsample ( double * resampled , const double * ir ) const
{
std : : fill_n ( resampled , m , 0.0 ) ;
resampled [ 0 ] = ir [ 0 ] ;
for ( size_t in { 1 } ; in < m ; + + in )
{
const auto offset = static_cast < double > ( in ) / scale ;
const auto out = static_cast < size_t > ( offset ) ;
const double a { offset - static_cast < double > ( out ) } ;
if ( out = = m - 1 )
resampled [ out ] + = ir [ in ] * ( 1.0 - a ) ;
else
{
resampled [ out ] + = ir [ in ] * ( 1.0 - a ) ;
resampled [ out + 1 ] + = ir [ in ] * a ;
}
}
}
/* Resampling from a higher rate to a lower rate. This likely only
* works properly when 0.5 < = scale < = 1.0 .
*/
void downsample ( double * resampled , const double * ir ) const
{
resampled [ 0 ] = ir [ 0 ] ;
for ( size_t out { 1 } ; out < m ; + + out )
{
const auto offset = static_cast < double > ( out ) * scale ;
const auto in = static_cast < size_t > ( offset ) ;
const double a { offset - static_cast < double > ( in ) } ;
if ( in = = m - 1 )
resampled [ out ] = ir [ in ] * ( 1.0 - a ) ;
else
resampled [ out ] = ir [ in ] * ( 1.0 - a ) + ir [ in + 1 ] * a ;
}
}
} ;
while ( rate > hData - > mIrRate * 2 )
ResampleHrirs ( hData - > mIrRate * 2 , hData ) ;
while ( rate < ( hData - > mIrRate + 1 ) / 2 )
ResampleHrirs ( ( hData - > mIrRate + 1 ) / 2 , hData ) ;
const auto scale = static_cast < double > ( rate ) / hData - > mIrRate ;
const size_t m { hData - > mFftSize / 2u + 1u } ;
auto resampled = std : : vector < double > ( m ) ;
const Resampler resampler { scale , m } ;
auto do_resample = std : : bind (
std : : mem_fn ( ( scale > 1.0 ) ? & Resampler : : upsample : & Resampler : : downsample ) , & resampler ,
_1 , _2 ) ;
const uint channels { ( hData - > mChannelType = = CT_STEREO ) ? 2u : 1u } ;
for ( uint fi { 0 } ; fi < hData - > mFdCount ; + + fi )
{
for ( uint ei { hData - > mFds [ fi ] . mEvStart } ; ei < hData - > mFds [ fi ] . mEvCount ; + + ei )
{
for ( uint ai { 0 } ; ai < hData - > mFds [ fi ] . mEvs [ ei ] . mAzCount ; + + ai )
{
HrirAzT * azd = & hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] ;
for ( uint ti { 0 } ; ti < channels ; + + ti )
{
do_resample ( resampled . data ( ) , azd - > mIrs [ ti ] ) ;
/* This should probably be rescaled according to the scale,
* however it ' ll all be normalized in the end so a constant
* scalar is fine to leave .
*/
std : : transform ( resampled . cbegin ( ) , resampled . cend ( ) , azd - > mIrs [ ti ] ,
[ ] ( const double d ) { return std : : max ( d , EPSILON ) ; } ) ;
}
}
}
}
hData - > mIrRate = rate ;
}
2019-03-24 19:00:58 -07:00
/* Given field and elevation indices and an azimuth, calculate the indices of
* the two HRIRs that bound the coordinate along with a factor for
* calculating the continuous HRIR using interpolation .
*/
static void CalcAzIndices ( const HrirFdT & field , const uint ei , const double az , uint * a0 , uint * a1 , double * af )
2016-02-19 22:23:37 -08:00
{
2019-03-24 19:00:58 -07:00
double f { ( 2.0 * M_PI + az ) * field . mEvs [ ei ] . mAzCount / ( 2.0 * M_PI ) } ;
uint i { static_cast < uint > ( f ) % field . mEvs [ ei ] . mAzCount } ;
2016-02-19 22:23:37 -08:00
2019-03-24 19:00:58 -07:00
f - = std : : floor ( f ) ;
* a0 = i ;
* a1 = ( i + 1 ) % field . mEvs [ ei ] . mAzCount ;
* af = f ;
}
2017-10-22 15:36:42 -07:00
2019-03-24 19:00:58 -07:00
/* Synthesize any missing onset timings at the bottom elevations of each field.
* This just mirrors some top elevations for the bottom , and blends the
* remaining elevations ( not an accurate model ) .
*/
static void SynthesizeOnsets ( HrirDataT * hData )
{
const uint channels { ( hData - > mChannelType = = CT_STEREO ) ? 2u : 1u } ;
auto proc_field = [ channels ] ( HrirFdT & field ) - > void
2016-02-19 22:23:37 -08:00
{
2019-03-24 19:00:58 -07:00
/* Get the starting elevation from the measurements, and use it as the
* upper elevation limit for what needs to be calculated .
*/
const uint upperElevReal { field . mEvStart } ;
if ( upperElevReal < = 0 ) return ;
2017-10-22 15:36:42 -07:00
2019-03-24 19:00:58 -07:00
/* Get the lowest half of the missing elevations' delays by mirroring
* the top elevation delays . The responses are on a spherical grid
* centered between the ears , so these should align .
*/
uint ei { } ;
if ( channels > 1 )
2016-02-19 22:23:37 -08:00
{
2019-03-24 19:00:58 -07:00
/* Take the polar opposite position of the desired measurement and
* swap the ears .
*/
field . mEvs [ 0 ] . mAzs [ 0 ] . mDelays [ 0 ] = field . mEvs [ field . mEvCount - 1 ] . mAzs [ 0 ] . mDelays [ 1 ] ;
field . mEvs [ 0 ] . mAzs [ 0 ] . mDelays [ 1 ] = field . mEvs [ field . mEvCount - 1 ] . mAzs [ 0 ] . mDelays [ 0 ] ;
for ( ei = 1u ; ei < ( upperElevReal + 1 ) / 2 ; + + ei )
2017-10-22 15:36:42 -07:00
{
2019-03-24 19:00:58 -07:00
const uint topElev { field . mEvCount - ei - 1 } ;
2017-10-22 15:36:42 -07:00
2019-03-24 19:00:58 -07:00
for ( uint ai { 0u } ; ai < field . mEvs [ ei ] . mAzCount ; ai + + )
2017-10-22 15:36:42 -07:00
{
2019-03-24 19:00:58 -07:00
uint a0 , a1 ;
double af ;
/* Rotate this current azimuth by a half-circle, and lookup
* the mirrored elevation to find the indices for the polar
* opposite position ( may need blending ) .
*/
const double az { field . mEvs [ ei ] . mAzs [ ai ] . mAzimuth + M_PI } ;
CalcAzIndices ( field , topElev , az , & a0 , & a1 , & af ) ;
/* Blend the delays, and again, swap the ears. */
field . mEvs [ ei ] . mAzs [ ai ] . mDelays [ 0 ] = Lerp (
field . mEvs [ topElev ] . mAzs [ a0 ] . mDelays [ 1 ] ,
field . mEvs [ topElev ] . mAzs [ a1 ] . mDelays [ 1 ] , af ) ;
field . mEvs [ ei ] . mAzs [ ai ] . mDelays [ 1 ] = Lerp (
field . mEvs [ topElev ] . mAzs [ a0 ] . mDelays [ 0 ] ,
field . mEvs [ topElev ] . mAzs [ a1 ] . mDelays [ 0 ] , af ) ;
2017-10-22 15:36:42 -07:00
}
}
2016-02-19 22:23:37 -08:00
}
2019-03-24 19:00:58 -07:00
else
2017-10-22 15:36:42 -07:00
{
2019-03-24 19:00:58 -07:00
field . mEvs [ 0 ] . mAzs [ 0 ] . mDelays [ 0 ] = field . mEvs [ field . mEvCount - 1 ] . mAzs [ 0 ] . mDelays [ 0 ] ;
for ( ei = 1u ; ei < ( upperElevReal + 1 ) / 2 ; + + ei )
2017-10-22 15:36:42 -07:00
{
2019-03-24 19:00:58 -07:00
const uint topElev { field . mEvCount - ei - 1 } ;
2017-10-22 15:36:42 -07:00
2019-03-24 19:00:58 -07:00
for ( uint ai { 0u } ; ai < field . mEvs [ ei ] . mAzCount ; ai + + )
2017-10-22 15:36:42 -07:00
{
2019-03-24 19:00:58 -07:00
uint a0 , a1 ;
double af ;
2017-10-22 15:36:42 -07:00
2019-03-24 19:00:58 -07:00
/* For mono data sets, mirror the azimuth front<->back
* since the other ear is a mirror of what we have ( e . g .
* the left ear ' s back - left is simulated with the right
* ear ' s front - right , which uses the left ear ' s front - left
* measurement ) .
*/
double az { field . mEvs [ ei ] . mAzs [ ai ] . mAzimuth } ;
if ( az < = M_PI ) az = M_PI - az ;
else az = ( M_PI * 2.0 ) - az + M_PI ;
CalcAzIndices ( field , topElev , az , & a0 , & a1 , & af ) ;
field . mEvs [ ei ] . mAzs [ ai ] . mDelays [ 0 ] = Lerp (
field . mEvs [ topElev ] . mAzs [ a0 ] . mDelays [ 0 ] ,
field . mEvs [ topElev ] . mAzs [ a1 ] . mDelays [ 0 ] , af ) ;
2017-10-22 15:36:42 -07:00
}
}
}
2019-03-24 19:00:58 -07:00
/* Record the lowest elevation filled in with the mirrored top. */
const uint lowerElevFake { ei - 1u } ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
/* Fill in the remaining delays using bilinear interpolation. This
* helps smooth the transition back to the real delays .
*/
for ( ; ei < upperElevReal ; + + ei )
{
const double ef { ( field . mEvs [ upperElevReal ] . mElevation - field . mEvs [ ei ] . mElevation ) /
( field . mEvs [ upperElevReal ] . mElevation - field . mEvs [ lowerElevFake ] . mElevation ) } ;
2016-02-18 22:55:03 -08:00
2019-03-24 19:00:58 -07:00
for ( uint ai { 0u } ; ai < field . mEvs [ ei ] . mAzCount ; ai + + )
{
uint a0 , a1 , a2 , a3 ;
double af0 , af1 ;
2013-12-18 12:43:59 -08:00
2019-03-24 19:00:58 -07:00
double az { field . mEvs [ ei ] . mAzs [ ai ] . mAzimuth } ;
CalcAzIndices ( field , upperElevReal , az , & a0 , & a1 , & af0 ) ;
CalcAzIndices ( field , lowerElevFake , az , & a2 , & a3 , & af1 ) ;
double blend [ 4 ] {
( 1.0 - ef ) * ( 1.0 - af0 ) ,
( 1.0 - ef ) * ( af0 ) ,
( ef ) * ( 1.0 - af1 ) ,
( ef ) * ( af1 )
} ;
2016-02-18 22:55:03 -08:00
2019-03-24 19:00:58 -07:00
for ( uint ti { 0u } ; ti < channels ; ti + + )
{
field . mEvs [ ei ] . mAzs [ ai ] . mDelays [ ti ] =
field . mEvs [ upperElevReal ] . mAzs [ a0 ] . mDelays [ ti ] * blend [ 0 ] +
field . mEvs [ upperElevReal ] . mAzs [ a1 ] . mDelays [ ti ] * blend [ 1 ] +
field . mEvs [ lowerElevFake ] . mAzs [ a2 ] . mDelays [ ti ] * blend [ 2 ] +
field . mEvs [ lowerElevFake ] . mAzs [ a3 ] . mDelays [ ti ] * blend [ 3 ] ;
}
}
}
} ;
std : : for_each ( hData - > mFds . begin ( ) , hData - > mFds . begin ( ) + hData - > mFdCount , proc_field ) ;
2012-03-25 19:57:40 -07:00
}
2019-03-24 19:00:58 -07:00
/* Attempt to synthesize any missing HRIRs at the bottom elevations of each
2020-06-21 19:28:37 -07:00
* field . Right now this just blends the lowest elevation HRIRs together and
* applies a low - pass filter to simulate body occlusion . It is a simple , if
* inaccurate model .
2019-01-22 11:13:14 -08:00
*/
2019-03-24 19:00:58 -07:00
static void SynthesizeHrirs ( HrirDataT * hData )
2019-01-22 11:13:14 -08:00
{
2019-03-24 19:00:58 -07:00
const uint channels { ( hData - > mChannelType = = CT_STEREO ) ? 2u : 1u } ;
2020-06-21 19:28:37 -07:00
auto htemp = std : : vector < complex_d > ( hData - > mFftSize ) ;
2020-06-21 00:29:57 -07:00
const uint m { hData - > mFftSize / 2u + 1u } ;
2020-06-21 19:28:37 -07:00
auto filter = std : : vector < double > ( m ) ;
const double beta { 3.5e-6 * hData - > mIrRate } ;
2019-01-22 11:13:14 -08:00
2020-06-21 19:28:37 -07:00
auto proc_field = [ channels , m , beta , & htemp , & filter ] ( HrirFdT & field ) - > void
2019-01-22 11:13:14 -08:00
{
2019-03-24 19:00:58 -07:00
const uint oi { field . mEvStart } ;
if ( oi < = 0 ) return ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
for ( uint ti { 0u } ; ti < channels ; ti + + )
2019-01-22 11:13:14 -08:00
{
2020-06-19 15:28:22 -07:00
uint a0 , a1 ;
double af ;
/* Use the lowest immediate-left response for the left ear and
* lowest immediate - right response for the right ear . Given no comb
* effects as a result of the left response reaching the right ear
* and vice - versa , this produces a decent phantom - center response
2020-06-21 00:29:57 -07:00
* underneath the head .
2019-03-24 19:00:58 -07:00
*/
2020-06-19 15:28:22 -07:00
CalcAzIndices ( field , oi , ( ( ti = = 0 ) ? - M_PI : M_PI ) / 2.0 , & a0 , & a1 , & af ) ;
2020-06-21 00:29:57 -07:00
for ( uint i { 0u } ; i < m ; i + + )
2019-03-24 19:00:58 -07:00
{
2020-06-19 15:28:22 -07:00
field . mEvs [ 0 ] . mAzs [ 0 ] . mIrs [ ti ] [ i ] = Lerp ( field . mEvs [ oi ] . mAzs [ a0 ] . mIrs [ ti ] [ i ] ,
field . mEvs [ oi ] . mAzs [ a1 ] . mIrs [ ti ] [ i ] , af ) ;
2019-03-24 19:00:58 -07:00
}
2020-06-19 15:28:22 -07:00
}
2019-01-22 11:13:14 -08:00
2020-06-19 15:28:22 -07:00
for ( uint ei { 1u } ; ei < field . mEvStart ; ei + + )
{
const double of { static_cast < double > ( ei ) / field . mEvStart } ;
2020-06-21 19:28:37 -07:00
const double b { ( 1.0 - of ) * beta } ;
double lp [ 4 ] { } ;
/* Calculate a low-pass filter to simulate body occlusion. */
lp [ 0 ] = Lerp ( 1.0 , lp [ 0 ] , b ) ;
lp [ 1 ] = Lerp ( lp [ 0 ] , lp [ 1 ] , b ) ;
lp [ 2 ] = Lerp ( lp [ 1 ] , lp [ 2 ] , b ) ;
lp [ 3 ] = Lerp ( lp [ 2 ] , lp [ 3 ] , b ) ;
htemp [ 0 ] = lp [ 3 ] ;
for ( size_t i { 1u } ; i < htemp . size ( ) ; i + + )
{
lp [ 0 ] = Lerp ( 0.0 , lp [ 0 ] , b ) ;
lp [ 1 ] = Lerp ( lp [ 0 ] , lp [ 1 ] , b ) ;
lp [ 2 ] = Lerp ( lp [ 1 ] , lp [ 2 ] , b ) ;
lp [ 3 ] = Lerp ( lp [ 2 ] , lp [ 3 ] , b ) ;
htemp [ i ] = lp [ 3 ] ;
}
/* Get the filter's frequency-domain response and extract the
* frequency magnitudes ( phase will be reconstructed later ) ) .
*/
FftForward ( static_cast < uint > ( htemp . size ( ) ) , htemp . data ( ) ) ;
std : : transform ( htemp . cbegin ( ) , htemp . cbegin ( ) + m , filter . begin ( ) ,
[ ] ( const complex_d & c ) - > double { return std : : abs ( c ) ; } ) ;
2020-06-19 15:28:22 -07:00
for ( uint ai { 0u } ; ai < field . mEvs [ ei ] . mAzCount ; ai + + )
2019-01-22 11:13:14 -08:00
{
2020-06-19 15:28:22 -07:00
uint a0 , a1 ;
double af ;
2019-01-22 11:13:14 -08:00
2020-06-19 15:28:22 -07:00
CalcAzIndices ( field , oi , field . mEvs [ ei ] . mAzs [ ai ] . mAzimuth , & a0 , & a1 , & af ) ;
for ( uint ti { 0u } ; ti < channels ; ti + + )
{
2020-06-21 00:29:57 -07:00
for ( uint i { 0u } ; i < m ; i + + )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
/* Blend the two defined HRIRs closest to this azimuth,
* then blend that with the synthesized - 90 elevation .
*/
const double s1 { Lerp ( field . mEvs [ oi ] . mAzs [ a0 ] . mIrs [ ti ] [ i ] ,
field . mEvs [ oi ] . mAzs [ a1 ] . mIrs [ ti ] [ i ] , af ) } ;
2020-06-21 19:28:37 -07:00
const double s { Lerp ( field . mEvs [ 0 ] . mAzs [ 0 ] . mIrs [ ti ] [ i ] , s1 , of ) } ;
field . mEvs [ ei ] . mAzs [ ai ] . mIrs [ ti ] [ i ] = s * filter [ i ] ;
2020-06-21 00:29:57 -07:00
}
}
}
}
2020-06-21 19:28:37 -07:00
const double b { beta } ;
double lp [ 4 ] { } ;
lp [ 0 ] = Lerp ( 1.0 , lp [ 0 ] , b ) ;
lp [ 1 ] = Lerp ( lp [ 0 ] , lp [ 1 ] , b ) ;
lp [ 2 ] = Lerp ( lp [ 1 ] , lp [ 2 ] , b ) ;
lp [ 3 ] = Lerp ( lp [ 2 ] , lp [ 3 ] , b ) ;
htemp [ 0 ] = lp [ 3 ] ;
for ( size_t i { 1u } ; i < htemp . size ( ) ; i + + )
{
lp [ 0 ] = Lerp ( 0.0 , lp [ 0 ] , b ) ;
lp [ 1 ] = Lerp ( lp [ 0 ] , lp [ 1 ] , b ) ;
lp [ 2 ] = Lerp ( lp [ 1 ] , lp [ 2 ] , b ) ;
lp [ 3 ] = Lerp ( lp [ 2 ] , lp [ 3 ] , b ) ;
htemp [ i ] = lp [ 3 ] ;
}
FftForward ( static_cast < uint > ( htemp . size ( ) ) , htemp . data ( ) ) ;
std : : transform ( htemp . cbegin ( ) , htemp . cbegin ( ) + m , filter . begin ( ) ,
[ ] ( const complex_d & c ) - > double { return std : : abs ( c ) ; } ) ;
for ( uint ti { 0u } ; ti < channels ; ti + + )
{
for ( uint i { 0u } ; i < m ; i + + )
field . mEvs [ 0 ] . mAzs [ 0 ] . mIrs [ ti ] [ i ] * = filter [ i ] ;
}
2020-06-21 00:29:57 -07:00
} ;
std : : for_each ( hData - > mFds . begin ( ) , hData - > mFds . begin ( ) + hData - > mFdCount , proc_field ) ;
}
2020-06-21 19:28:37 -07:00
// The following routines assume a full set of HRIRs for all elevations.
2020-06-21 00:29:57 -07:00
/* Perform minimum-phase reconstruction using the magnitude responses of the
* HRIR set . Work is delegated to this struct , which runs asynchronously on one
* or more threads ( sharing the same reconstructor object ) .
*/
struct HrirReconstructor {
std : : vector < double * > mIrs ;
std : : atomic < size_t > mCurrent ;
std : : atomic < size_t > mDone ;
uint mFftSize ;
uint mIrPoints ;
void Worker ( )
{
auto h = std : : vector < complex_d > ( mFftSize ) ;
2020-08-14 18:22:20 -07:00
auto mags = std : : vector < double > ( mFftSize ) ;
size_t m { ( mFftSize / 2 ) + 1 } ;
2020-06-21 00:29:57 -07:00
while ( 1 )
{
/* Load the current index to process. */
size_t idx { mCurrent . load ( ) } ;
do {
/* If the index is at the end, we're done. */
if ( idx > = mIrs . size ( ) )
return ;
/* Otherwise, increment the current index atomically so other
* threads know to go to the next one . If this call fails , the
* current index was just changed by another thread and the new
* value is loaded into idx , which we ' ll recheck .
*/
} while ( ! mCurrent . compare_exchange_weak ( idx , idx + 1 , std : : memory_order_relaxed ) ) ;
/* Now do the reconstruction, and apply the inverse FFT to get the
* time - domain response .
*/
2020-08-14 18:22:20 -07:00
for ( size_t i { 0 } ; i < m ; + + i )
mags [ i ] = std : : max ( mIrs [ idx ] [ i ] , EPSILON ) ;
MinimumPhase ( mFftSize , mags . data ( ) , h . data ( ) ) ;
2020-06-21 00:29:57 -07:00
FftInverse ( mFftSize , h . data ( ) ) ;
for ( uint i { 0u } ; i < mIrPoints ; + + i )
mIrs [ idx ] [ i ] = h [ i ] . real ( ) ;
/* Increment the number of IRs done. */
mDone . fetch_add ( 1 ) ;
}
}
} ;
static void ReconstructHrirs ( const HrirDataT * hData , const uint numThreads )
{
const uint channels { ( hData - > mChannelType = = CT_STEREO ) ? 2u : 1u } ;
/* Set up the reconstructor with the needed size info and pointers to the
* IRs to process .
*/
HrirReconstructor reconstructor ;
reconstructor . mCurrent . store ( 0 , std : : memory_order_relaxed ) ;
reconstructor . mDone . store ( 0 , std : : memory_order_relaxed ) ;
reconstructor . mFftSize = hData - > mFftSize ;
reconstructor . mIrPoints = hData - > mIrPoints ;
for ( uint fi { 0u } ; fi < hData - > mFdCount ; fi + + )
{
const HrirFdT & field = hData - > mFds [ fi ] ;
for ( uint ei { 0 } ; ei < field . mEvCount ; ei + + )
{
const HrirEvT & elev = field . mEvs [ ei ] ;
for ( uint ai { 0u } ; ai < elev . mAzCount ; ai + + )
{
const HrirAzT & azd = elev . mAzs [ ai ] ;
for ( uint ti { 0u } ; ti < channels ; ti + + )
reconstructor . mIrs . push_back ( azd . mIrs [ ti ] ) ;
}
}
}
/* Launch threads to work on reconstruction. */
std : : vector < std : : thread > thrds ;
thrds . reserve ( numThreads ) ;
for ( size_t i { 0 } ; i < numThreads ; + + i )
thrds . emplace_back ( std : : mem_fn ( & HrirReconstructor : : Worker ) , & reconstructor ) ;
/* Keep track of the number of IRs done, periodically reporting it. */
size_t count ;
do {
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds { 50 } ) ;
count = reconstructor . mDone . load ( ) ;
size_t pcdone { count * 100 / reconstructor . mIrs . size ( ) } ;
printf ( " \r %3zu%% done (%zu of %zu) " , pcdone , count , reconstructor . mIrs . size ( ) ) ;
fflush ( stdout ) ;
} while ( count < reconstructor . mIrs . size ( ) ) ;
fputc ( ' \n ' , stdout ) ;
for ( auto & thrd : thrds )
{
if ( thrd . joinable ( ) )
thrd . join ( ) ;
}
}
2019-03-24 19:00:58 -07:00
// Normalize the HRIR set and slightly attenuate the result.
static void NormalizeHrirs ( HrirDataT * hData )
2017-10-22 15:36:42 -07:00
{
2019-03-24 19:00:58 -07:00
const uint channels { ( hData - > mChannelType = = CT_STEREO ) ? 2u : 1u } ;
const uint irSize { hData - > mIrPoints } ;
2017-10-22 15:36:42 -07:00
2019-03-24 19:00:58 -07:00
/* Find the maximum amplitude and RMS out of all the IRs. */
struct LevelPair { double amp , rms ; } ;
2019-09-23 18:37:36 -07:00
auto proc0_field = [ channels , irSize ] ( const LevelPair levels0 , const HrirFdT & field ) - > LevelPair
2016-02-18 22:55:03 -08:00
{
2019-09-23 18:37:36 -07:00
auto proc_elev = [ channels , irSize ] ( const LevelPair levels1 , const HrirEvT & elev ) - > LevelPair
2019-01-22 11:13:14 -08:00
{
2019-09-23 18:37:36 -07:00
auto proc_azi = [ channels , irSize ] ( const LevelPair levels2 , const HrirAzT & azi ) - > LevelPair
2019-01-22 11:13:14 -08:00
{
2019-09-23 18:37:36 -07:00
auto proc_channel = [ irSize ] ( const LevelPair levels3 , const double * ir ) - > LevelPair
2019-01-22 11:13:14 -08:00
{
2019-03-24 19:00:58 -07:00
/* Calculate the peak amplitude and RMS of this IR. */
auto current = std : : accumulate ( ir , ir + irSize , LevelPair { 0.0 , 0.0 } ,
2019-09-23 18:37:36 -07:00
[ ] ( const LevelPair cur , const double impulse ) - > LevelPair
2019-03-24 19:00:58 -07:00
{
2019-09-23 18:37:36 -07:00
return { std : : max ( std : : abs ( impulse ) , cur . amp ) ,
cur . rms + impulse * impulse } ;
2019-03-24 19:00:58 -07:00
} ) ;
current . rms = std : : sqrt ( current . rms / irSize ) ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
/* Accumulate levels by taking the maximum amplitude and RMS. */
2019-09-23 18:37:36 -07:00
return LevelPair { std : : max ( current . amp , levels3 . amp ) ,
std : : max ( current . rms , levels3 . rms ) } ;
2019-03-24 19:00:58 -07:00
} ;
2019-09-23 18:37:36 -07:00
return std : : accumulate ( azi . mIrs , azi . mIrs + channels , levels2 , proc_channel ) ;
2019-03-24 19:00:58 -07:00
} ;
2019-09-23 18:37:36 -07:00
return std : : accumulate ( elev . mAzs , elev . mAzs + elev . mAzCount , levels1 , proc_azi ) ;
2019-03-24 19:00:58 -07:00
} ;
2019-09-23 18:37:36 -07:00
return std : : accumulate ( field . mEvs , field . mEvs + field . mEvCount , levels0 , proc_elev ) ;
2019-03-24 19:00:58 -07:00
} ;
const auto maxlev = std : : accumulate ( hData - > mFds . begin ( ) , hData - > mFds . begin ( ) + hData - > mFdCount ,
LevelPair { 0.0 , 0.0 } , proc0_field ) ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
/* Normalize using the maximum RMS of the HRIRs. The RMS measure for the
* non - filtered signal is of an impulse with equal length ( to the filter ) :
*
* rms_impulse = sqrt ( sum ( [ 1 ^ 2 , 0 ^ 2 , 0 ^ 2 , . . . ] ) / n )
* = sqrt ( 1 / n )
*
* This helps keep a more consistent volume between the non - filtered signal
* and various data sets .
*/
double factor { std : : sqrt ( 1.0 / irSize ) / maxlev . rms } ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
/* Also ensure the samples themselves won't clip. */
factor = std : : min ( factor , 0.99 / maxlev . amp ) ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
/* Now scale all IRs by the given factor. */
auto proc1_field = [ channels , irSize , factor ] ( HrirFdT & field ) - > void
{
auto proc_elev = [ channels , irSize , factor ] ( HrirEvT & elev ) - > void
{
auto proc_azi = [ channels , irSize , factor ] ( HrirAzT & azi ) - > void
2019-01-22 11:13:14 -08:00
{
2019-03-24 19:00:58 -07:00
auto proc_channel = [ irSize , factor ] ( double * ir ) - > void
{
std : : transform ( ir , ir + irSize , ir ,
std : : bind ( std : : multiplies < double > { } , _1 , factor ) ) ;
2019-01-22 11:13:14 -08:00
} ;
2019-03-24 19:00:58 -07:00
std : : for_each ( azi . mIrs , azi . mIrs + channels , proc_channel ) ;
} ;
std : : for_each ( elev . mAzs , elev . mAzs + elev . mAzCount , proc_azi ) ;
} ;
std : : for_each ( field . mEvs , field . mEvs + field . mEvCount , proc_elev ) ;
} ;
std : : for_each ( hData - > mFds . begin ( ) , hData - > mFds . begin ( ) + hData - > mFdCount , proc1_field ) ;
}
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
// 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 ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
azp = std : : asin ( std : : cos ( ev ) * std : : sin ( az ) ) ;
dlp = std : : sqrt ( ( dist * dist ) + ( rad * rad ) + ( 2.0 * dist * rad * sin ( azp ) ) ) ;
l = std : : sqrt ( ( dist * dist ) - ( rad * rad ) ) ;
al = ( 0.5 * M_PI ) + azp ;
if ( dlp > l )
dlp = l + ( rad * ( al - std : : acos ( rad / dist ) ) ) ;
return dlp / 343.3 ;
}
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
// Calculate the effective head-related time delays for each minimum-phase
// 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 customRatio { radius / hData - > mRadius } ;
uint ti , fi , ei , ai ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
if ( model = = HM_SPHERE )
{
for ( fi = 0 ; fi < hData - > mFdCount ; fi + + )
{
for ( ei = 0 ; ei < hData - > mFds [ fi ] . mEvCount ; ei + + )
{
HrirEvT * evd = & hData - > mFds [ fi ] . mEvs [ ei ] ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
for ( ai = 0 ; ai < evd - > mAzCount ; ai + + )
2019-01-22 11:13:14 -08:00
{
2019-03-24 19:00:58 -07:00
HrirAzT * azd = & evd - > mAzs [ ai ] ;
2019-01-22 11:13:14 -08:00
2019-03-24 19:00:58 -07:00
for ( ti = 0 ; ti < channels ; ti + + )
azd - > mDelays [ ti ] = CalcLTD ( evd - > mElevation , azd - > mAzimuth , radius , hData - > mFds [ fi ] . mDistance ) ;
}
2019-01-22 11:13:14 -08:00
}
2012-09-11 01:59:42 -07:00
}
2019-03-24 19:00:58 -07:00
}
else if ( customRatio ! = 1.0 )
{
for ( fi = 0 ; fi < hData - > mFdCount ; fi + + )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
for ( ei = 0 ; ei < hData - > mFds [ fi ] . mEvCount ; ei + + )
2017-10-22 15:36:42 -07:00
{
2019-03-24 19:00:58 -07:00
HrirEvT * evd = & hData - > mFds [ fi ] . mEvs [ ei ] ;
2017-10-22 15:36:42 -07:00
2019-03-24 19:00:58 -07:00
for ( ai = 0 ; ai < evd - > mAzCount ; ai + + )
2017-10-22 15:36:42 -07:00
{
2019-03-24 19:00:58 -07:00
HrirAzT * azd = & evd - > mAzs [ ai ] ;
for ( ti = 0 ; ti < channels ; ti + + )
azd - > mDelays [ ti ] * = customRatio ;
2017-10-22 15:36:42 -07:00
}
}
}
2016-02-18 22:55:03 -08:00
}
2019-03-24 19:00:58 -07:00
2020-02-10 22:25:07 -08:00
double maxHrtd { 0.0 } ;
2017-10-22 15:36:42 -07:00
for ( fi = 0 ; fi < hData - > mFdCount ; fi + + )
{
2019-03-24 19:00:58 -07:00
double minHrtd { std : : numeric_limits < double > : : infinity ( ) } ;
2017-10-22 15:36:42 -07:00
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 ] ;
2019-03-24 19:00:58 -07:00
for ( ti = 0 ; ti < channels ; ti + + )
minHrtd = std : : min ( azd - > mDelays [ ti ] , minHrtd ) ;
2017-10-22 15:36:42 -07:00
}
}
2019-03-24 19:00:58 -07:00
for ( ei = 0 ; ei < hData - > mFds [ fi ] . mEvCount ; ei + + )
2017-10-22 15:36:42 -07:00
{
2019-10-22 15:23:17 -07:00
for ( ai = 0 ; ai < hData - > mFds [ fi ] . mEvs [ ei ] . mAzCount ; ai + + )
2017-10-22 15:36:42 -07:00
{
2019-10-22 15:23:17 -07:00
HrirAzT * azd = & hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] ;
for ( ti = 0 ; ti < channels ; ti + + )
2020-02-10 22:25:07 -08:00
{
azd - > mDelays [ ti ] = ( azd - > mDelays [ ti ] - minHrtd ) * hData - > mIrRate ;
maxHrtd = std : : max ( maxHrtd , azd - > mDelays [ ti ] ) ;
}
}
}
}
if ( maxHrtd > MAX_HRTD )
{
fprintf ( stdout , " Scaling for max delay of %f samples to %f \n ... \n " , maxHrtd , MAX_HRTD ) ;
const double scale { MAX_HRTD / maxHrtd } ;
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 + + )
{
HrirAzT * azd = & hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] ;
for ( ti = 0 ; ti < channels ; ti + + )
azd - > mDelays [ ti ] * = scale ;
}
2017-10-22 15:36:42 -07:00
}
}
}
2019-03-24 19:00:58 -07:00
}
// Allocate and configure dynamic HRIR structures.
2019-03-25 20:16:02 -07:00
int PrepareHrirData ( const uint fdCount , const double ( & distances ) [ MAX_FD_COUNT ] ,
const uint ( & evCounts ) [ MAX_FD_COUNT ] , const uint azCounts [ MAX_FD_COUNT * MAX_EV_COUNT ] ,
HrirDataT * hData )
2019-03-24 19:00:58 -07:00
{
uint evTotal = 0 , azTotal = 0 , fi , ei , ai ;
for ( fi = 0 ; fi < fdCount ; fi + + )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
evTotal + = evCounts [ fi ] ;
for ( ei = 0 ; ei < evCounts [ fi ] ; ei + + )
azTotal + = azCounts [ ( fi * MAX_EV_COUNT ) + ei ] ;
}
if ( ! fdCount | | ! evTotal | | ! azTotal )
return 0 ;
hData - > mEvsBase . resize ( evTotal ) ;
hData - > mAzsBase . resize ( azTotal ) ;
hData - > mFds . resize ( fdCount ) ;
hData - > mIrCount = azTotal ;
hData - > mFdCount = fdCount ;
evTotal = 0 ;
azTotal = 0 ;
for ( fi = 0 ; fi < fdCount ; fi + + )
{
hData - > mFds [ fi ] . mDistance = distances [ fi ] ;
hData - > mFds [ fi ] . mEvCount = evCounts [ fi ] ;
hData - > mFds [ fi ] . mEvStart = 0 ;
hData - > mFds [ fi ] . mEvs = & hData - > mEvsBase [ evTotal ] ;
evTotal + = evCounts [ fi ] ;
for ( ei = 0 ; ei < evCounts [ fi ] ; ei + + )
2016-02-18 22:55:03 -08:00
{
2019-03-24 19:00:58 -07:00
uint azCount = azCounts [ ( fi * MAX_EV_COUNT ) + ei ] ;
2017-10-22 15:36:42 -07:00
2019-03-24 19:00:58 -07:00
hData - > mFds [ fi ] . mIrCount + = azCount ;
hData - > mFds [ fi ] . mEvs [ ei ] . mElevation = - M_PI / 2.0 + M_PI * ei / ( evCounts [ fi ] - 1 ) ;
hData - > mFds [ fi ] . mEvs [ ei ] . mIrCount + = azCount ;
hData - > mFds [ fi ] . mEvs [ ei ] . mAzCount = azCount ;
hData - > mFds [ fi ] . mEvs [ ei ] . mAzs = & hData - > mAzsBase [ azTotal ] ;
for ( ai = 0 ; ai < azCount ; ai + + )
{
hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] . mAzimuth = 2.0 * M_PI * ai / azCount ;
hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] . mIndex = azTotal + ai ;
hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] . mDelays [ 0 ] = 0.0 ;
hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] . mDelays [ 1 ] = 0.0 ;
hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] . mIrs [ 0 ] = nullptr ;
hData - > mFds [ fi ] . mEvs [ ei ] . mAzs [ ai ] . mIrs [ 1 ] = nullptr ;
2016-02-18 22:55:03 -08:00
}
2019-03-24 19:00:58 -07:00
azTotal + = azCount ;
2016-02-18 22:55:03 -08:00
}
}
2019-03-24 19:00:58 -07:00
return 1 ;
2012-09-11 01:59:42 -07:00
}
2019-03-24 19:00:58 -07:00
2012-09-11 01:59:42 -07:00
/* 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 .
*/
2019-09-23 18:37:36 -07:00
static int ProcessDefinition ( const char * inName , const uint outRate , const ChannelModeT chanMode ,
2020-06-19 16:43:09 -07:00
const bool farfield , const uint numThreads , const uint fftSize , const int equalize ,
const int surface , const double limit , const uint truncSize , const HeadModelT model ,
const double radius , const char * outName )
2016-02-18 22:55:03 -08:00
{
HrirDataT hData ;
2020-06-19 16:43:09 -07:00
fprintf ( stdout , " Using %u thread%s. \n " , numThreads , ( numThreads = = 1 ) ? " " : " s " ) ;
2019-03-25 00:21:45 -07:00
if ( ! inName )
{
inName = " stdin " ;
2019-09-23 18:37:36 -07:00
fprintf ( stdout , " Reading HRIR definition from %s... \n " , inName ) ;
if ( ! LoadDefInput ( std : : cin , nullptr , 0 , inName , fftSize , truncSize , chanMode , & hData ) )
return 0 ;
2019-03-25 00:21:45 -07:00
}
else
2016-02-18 22:55:03 -08:00
{
2019-09-23 18:37:36 -07:00
std : : unique_ptr < al : : ifstream > input { new al : : ifstream { inName } } ;
if ( ! input - > is_open ( ) )
2016-02-18 22:55:03 -08:00
{
2019-03-25 00:21:45 -07:00
fprintf ( stderr , " Error: Could not open input file '%s' \n " , inName ) ;
2016-02-18 22:55:03 -08:00
return 0 ;
}
2019-03-25 13:01:44 -07:00
2019-09-23 18:37:36 -07:00
char startbytes [ 4 ] { } ;
input - > read ( startbytes , sizeof ( startbytes ) ) ;
std : : streamsize startbytecount { input - > gcount ( ) } ;
if ( startbytecount ! = sizeof ( startbytes ) | | ! input - > good ( ) )
2019-03-25 13:01:44 -07:00
{
fprintf ( stderr , " Error: Could not read input file '%s' \n " , inName ) ;
return 0 ;
}
2019-09-23 18:37:36 -07:00
if ( startbytes [ 0 ] = = ' \x89 ' & & startbytes [ 1 ] = = ' H ' & & startbytes [ 2 ] = = ' D '
& & startbytes [ 3 ] = = ' F ' )
2019-03-25 13:01:44 -07:00
{
2019-09-23 18:37:36 -07:00
input = nullptr ;
2019-03-25 20:16:02 -07:00
fprintf ( stdout , " Reading HRTF data from %s... \n " , inName ) ;
2020-06-19 16:43:09 -07:00
if ( ! LoadSofaFile ( inName , numThreads , fftSize , truncSize , chanMode , & hData ) )
2019-03-25 20:16:02 -07:00
return 0 ;
2019-03-25 13:01:44 -07:00
}
2019-09-23 18:37:36 -07:00
else
{
fprintf ( stdout , " Reading HRIR definition from %s... \n " , inName ) ;
if ( ! LoadDefInput ( * input , startbytes , startbytecount , inName , fftSize , truncSize , chanMode , & hData ) )
return 0 ;
}
2016-02-18 22:55:03 -08:00
}
2019-03-25 00:21:45 -07:00
2016-02-18 22:55:03 -08:00
if ( equalize )
{
2019-09-23 18:37:36 -07:00
uint c { ( hData . mChannelType = = CT_STEREO ) ? 2u : 1u } ;
uint m { hData . mFftSize / 2u + 1u } ;
auto dfa = std : : vector < double > ( c * m ) ;
2017-10-22 15:36:42 -07:00
2019-01-22 11:13:14 -08:00
if ( hData . mFdCount > 1 )
{
fprintf ( stdout , " Balancing field magnitudes... \n " ) ;
BalanceFieldMagnitudes ( & hData , c , m ) ;
}
2016-02-18 22:55:03 -08:00
fprintf ( stdout , " Calculating diffuse-field average... \n " ) ;
2018-12-31 19:54:34 -08:00
CalculateDiffuseFieldAverage ( & hData , c , m , surface , limit , dfa . data ( ) ) ;
2016-02-18 22:55:03 -08:00
fprintf ( stdout , " Performing diffuse-field equalization... \n " ) ;
2018-12-31 19:54:34 -08:00
DiffuseFieldEqualize ( c , m , dfa . data ( ) , & hData ) ;
2016-02-18 22:55:03 -08:00
}
2020-06-17 15:21:08 -07:00
if ( hData . mFds . size ( ) > 1 )
{
fprintf ( stdout , " Sorting %zu fields... \n " , hData . mFds . size ( ) ) ;
std : : sort ( hData . mFds . begin ( ) , hData . mFds . end ( ) ,
[ ] ( const HrirFdT & lhs , const HrirFdT & rhs ) noexcept
{ return lhs . mDistance < rhs . mDistance ; } ) ;
if ( farfield )
{
fprintf ( stdout , " Clearing %zu near field%s... \n " , hData . mFds . size ( ) - 1 ,
( hData . mFds . size ( ) - 1 ! = 1 ) ? " s " : " " ) ;
hData . mFds . erase ( hData . mFds . cbegin ( ) , hData . mFds . cend ( ) - 1 ) ;
hData . mFdCount = 1 ;
}
}
2020-06-17 13:03:26 -07:00
if ( outRate ! = 0 & & outRate ! = hData . mIrRate )
{
fprintf ( stdout , " Resampling HRIRs... \n " ) ;
ResampleHrirs ( outRate , & hData ) ;
}
2016-02-18 22:55:03 -08:00
fprintf ( stdout , " Synthesizing missing elevations... \n " ) ;
if ( model = = HM_DATASET )
SynthesizeOnsets ( & hData ) ;
SynthesizeHrirs ( & hData ) ;
2020-06-21 00:29:57 -07:00
fprintf ( stdout , " Performing minimum phase reconstruction... \n " ) ;
ReconstructHrirs ( & hData , numThreads ) ;
fprintf ( stdout , " Truncating minimum-phase HRIRs... \n " ) ;
hData . mIrPoints = truncSize ;
2016-02-18 22:55:03 -08:00
fprintf ( stdout , " Normalizing final HRIRs... \n " ) ;
NormalizeHrirs ( & hData ) ;
fprintf ( stdout , " Calculating impulse delays... \n " ) ;
CalculateHrtds ( model , ( radius > DEFAULT_CUSTOM_RADIUS ) ? radius : hData . mRadius , & hData ) ;
2020-08-13 16:02:13 -07:00
const auto rateStr = std : : to_string ( hData . mIrRate ) ;
const auto expName = StrSubst ( { outName , strlen ( outName ) } , { " %r " , 2 } ,
{ rateStr . data ( ) , rateStr . size ( ) } ) ;
fprintf ( stdout , " Creating MHR data set %s... \n " , expName . c_str ( ) ) ;
return StoreMhr ( & hData , expName . c_str ( ) ) ;
2012-09-11 01:59:42 -07:00
}
2016-02-18 06:11:54 -08:00
static void PrintHelp ( const char * argv0 , FILE * ofile )
{
2017-10-22 15:36:42 -07:00
fprintf ( ofile , " Usage: %s [<option>...] \n \n " , argv0 ) ;
2016-02-18 06:11:54 -08:00
fprintf ( ofile , " Options: \n " ) ;
2017-08-20 04:30:53 -07:00
fprintf ( ofile , " -r <rate> Change the data set sample rate to the specified value and \n " ) ;
2016-02-18 06:11:54 -08:00
fprintf ( ofile , " resample the HRIRs accordingly. \n " ) ;
2019-03-24 22:43:43 -07:00
fprintf ( ofile , " -m Change the data set to mono, mirroring the left ear for the \n " ) ;
fprintf ( ofile , " right ear. \n " ) ;
2020-06-17 15:21:08 -07:00
fprintf ( ofile , " -a Change the data set to single field, using the farthest field. \n " ) ;
2020-06-19 16:43:09 -07:00
fprintf ( ofile , " -j <threads> Number of threads used to process HRIRs (default: 2). \n " ) ;
2017-08-20 04:30:53 -07:00
fprintf ( ofile , " -f <points> Override the FFT window size (default: %u). \n " , DEFAULT_FFTSIZE ) ;
fprintf ( ofile , " -e {on|off} Toggle diffuse-field equalization (default: %s). \n " , ( DEFAULT_EQUALIZE ? " on " : " off " ) ) ;
fprintf ( ofile , " -s {on|off} Toggle surface-weighted diffuse-field average (default: %s). \n " , ( DEFAULT_SURFACE ? " on " : " off " ) ) ;
fprintf ( ofile , " -l {<dB>|none} Specify a limit to the magnitude range of the diffuse-field \n " ) ;
2016-02-18 06:11:54 -08:00
fprintf ( ofile , " average (default: %.2f). \n " , DEFAULT_LIMIT ) ;
2017-08-20 04:30:53 -07:00
fprintf ( ofile , " -w <points> Specify the size of the truncation window that's applied \n " ) ;
2016-02-18 06:11:54 -08:00
fprintf ( ofile , " after minimum-phase reconstruction (default: %u). \n " , DEFAULT_TRUNCSIZE ) ;
2017-08-20 04:30:53 -07:00
fprintf ( ofile , " -d {dataset| Specify the model used for calculating the head-delay timing \n " ) ;
2016-02-18 06:11:54 -08:00
fprintf ( ofile , " sphere} values (default: %s). \n " , ( ( DEFAULT_HEAD_MODEL = = HM_DATASET ) ? " dataset " : " sphere " ) ) ;
2019-01-22 11:13:14 -08:00
fprintf ( ofile , " -c <radius> Use a customized head radius measured to-ear in meters. \n " ) ;
2017-08-20 04:30:53 -07:00
fprintf ( ofile , " -i <filename> Specify an HRIR definition file to use (defaults to stdin). \n " ) ;
2017-10-22 15:36:42 -07:00
fprintf ( ofile , " -o <filename> Specify an output file. Use of '%%r' will be substituted with \n " ) ;
fprintf ( ofile , " the data set sample rate. \n " ) ;
2012-03-25 19:57:40 -07:00
}
2012-09-11 01:59:42 -07:00
2016-02-18 06:11:54 -08:00
// Standard command line dispatch.
2017-08-20 04:30:53 -07:00
int main ( int argc , char * argv [ ] )
2016-02-18 06:11:54 -08:00
{
2019-01-07 12:37:13 +01:00
const char * inName = nullptr , * outName = nullptr ;
2016-02-18 06:11:54 -08:00
uint outRate , fftSize ;
int equalize , surface ;
2019-01-07 12:37:13 +01:00
char * end = nullptr ;
2019-03-24 22:43:43 -07:00
ChannelModeT chanMode ;
2016-02-18 06:11:54 -08:00
HeadModelT model ;
2020-06-19 16:43:09 -07:00
uint numThreads ;
2016-02-18 06:11:54 -08:00
uint truncSize ;
double radius ;
2020-06-17 15:21:08 -07:00
bool farfield ;
2016-02-18 06:11:54 -08:00
double limit ;
2017-08-20 04:30:53 -07:00
int opt ;
2016-02-18 06:11:54 -08:00
2017-08-20 16:54:08 -07:00
if ( argc < 2 )
2016-02-18 06:11:54 -08:00
{
fprintf ( stdout , " HRTF Processing and Composition Utility \n \n " ) ;
PrintHelp ( argv [ 0 ] , stdout ) ;
2017-08-20 04:30:53 -07:00
exit ( EXIT_SUCCESS ) ;
2016-02-18 06:11:54 -08:00
}
2012-09-11 01:59:42 -07:00
2017-08-20 16:54:08 -07:00
outName = " ./oalsoft_hrtf_%r.mhr " ;
2016-02-18 06:11:54 -08:00
outRate = 0 ;
2019-03-24 22:43:43 -07:00
chanMode = CM_AllowStereo ;
2019-03-24 19:00:58 -07:00
fftSize = DEFAULT_FFTSIZE ;
2016-02-18 06:11:54 -08:00
equalize = DEFAULT_EQUALIZE ;
surface = DEFAULT_SURFACE ;
limit = DEFAULT_LIMIT ;
2020-06-19 16:43:09 -07:00
numThreads = 2 ;
2016-02-18 06:11:54 -08:00
truncSize = DEFAULT_TRUNCSIZE ;
model = DEFAULT_HEAD_MODEL ;
radius = DEFAULT_CUSTOM_RADIUS ;
2020-06-17 15:21:08 -07:00
farfield = false ;
2016-02-18 06:11:54 -08:00
2020-06-19 16:43:09 -07:00
while ( ( opt = getopt ( argc , argv , " r:maj:f:e:s:l:w:d:c:e:i:o:h " ) ) ! = - 1 )
2016-02-18 06:11:54 -08:00
{
2017-08-20 04:30:53 -07:00
switch ( opt )
2016-02-18 06:11:54 -08:00
{
2017-08-20 04:30:53 -07:00
case ' r ' :
2019-09-11 04:55:54 -07:00
outRate = static_cast < uint > ( strtoul ( optarg , & end , 10 ) ) ;
2016-02-18 06:11:54 -08:00
if ( end [ 0 ] ! = ' \0 ' | | outRate < MIN_RATE | | outRate > MAX_RATE )
{
2019-01-22 11:13:14 -08:00
fprintf ( stderr , " \n Error: Got unexpected value \" %s \" for option -%c, expected between %u to %u. \n " , optarg , opt , MIN_RATE , MAX_RATE ) ;
2017-08-20 04:30:53 -07:00
exit ( EXIT_FAILURE ) ;
2016-02-18 06:11:54 -08:00
}
2017-08-20 04:30:53 -07:00
break ;
2019-03-24 22:43:43 -07:00
case ' m ' :
chanMode = CM_ForceMono ;
break ;
2020-06-17 15:21:08 -07:00
case ' a ' :
farfield = true ;
break ;
2020-06-19 16:43:09 -07:00
case ' j ' :
numThreads = static_cast < uint > ( strtoul ( optarg , & end , 10 ) ) ;
if ( end [ 0 ] ! = ' \0 ' | | numThreads > 64 )
{
fprintf ( stderr , " \n Error: Got unexpected value \" %s \" for option -%c, expected between %u to %u. \n " , optarg , opt , 0 , 64 ) ;
exit ( EXIT_FAILURE ) ;
}
if ( numThreads = = 0 )
numThreads = std : : thread : : hardware_concurrency ( ) ;
break ;
2017-08-20 04:30:53 -07:00
case ' f ' :
2019-09-11 04:55:54 -07:00
fftSize = static_cast < uint > ( strtoul ( optarg , & end , 10 ) ) ;
2016-02-18 06:11:54 -08:00
if ( end [ 0 ] ! = ' \0 ' | | ( fftSize & ( fftSize - 1 ) ) | | fftSize < MIN_FFTSIZE | | fftSize > MAX_FFTSIZE )
{
2019-01-22 11:13:14 -08:00
fprintf ( stderr , " \n Error: Got unexpected value \" %s \" for option -%c, expected a power-of-two between %u to %u. \n " , optarg , opt , MIN_FFTSIZE , MAX_FFTSIZE ) ;
2017-08-20 04:30:53 -07:00
exit ( EXIT_FAILURE ) ;
2016-02-18 06:11:54 -08:00
}
2017-08-20 04:30:53 -07:00
break ;
case ' e ' :
if ( strcmp ( optarg , " on " ) = = 0 )
2016-02-18 06:11:54 -08:00
equalize = 1 ;
2017-08-20 04:30:53 -07:00
else if ( strcmp ( optarg , " off " ) = = 0 )
2016-02-18 06:11:54 -08:00
equalize = 0 ;
else
{
2019-01-22 11:13:14 -08:00
fprintf ( stderr , " \n Error: Got unexpected value \" %s \" for option -%c, expected on or off. \n " , optarg , opt ) ;
2017-08-20 04:30:53 -07:00
exit ( EXIT_FAILURE ) ;
2016-02-18 06:11:54 -08:00
}
2017-08-20 04:30:53 -07:00
break ;
case ' s ' :
if ( strcmp ( optarg , " on " ) = = 0 )
2016-02-18 06:11:54 -08:00
surface = 1 ;
2017-08-20 04:30:53 -07:00
else if ( strcmp ( optarg , " off " ) = = 0 )
2016-02-18 06:11:54 -08:00
surface = 0 ;
else
{
2019-01-22 11:13:14 -08:00
fprintf ( stderr , " \n Error: Got unexpected value \" %s \" for option -%c, expected on or off. \n " , optarg , opt ) ;
2017-08-20 04:30:53 -07:00
exit ( EXIT_FAILURE ) ;
2016-02-18 06:11:54 -08:00
}
2017-08-20 04:30:53 -07:00
break ;
case ' l ' :
if ( strcmp ( optarg , " none " ) = = 0 )
2016-02-18 06:11:54 -08:00
limit = 0.0 ;
else
{
2017-08-20 04:30:53 -07:00
limit = strtod ( optarg , & end ) ;
2016-02-18 06:11:54 -08:00
if ( end [ 0 ] ! = ' \0 ' | | limit < MIN_LIMIT | | limit > MAX_LIMIT )
{
2019-01-22 11:13:14 -08:00
fprintf ( stderr , " \n Error: Got unexpected value \" %s \" for option -%c, expected between %.0f to %.0f. \n " , optarg , opt , MIN_LIMIT , MAX_LIMIT ) ;
2017-08-20 04:30:53 -07:00
exit ( EXIT_FAILURE ) ;
2016-02-18 06:11:54 -08:00
}
}
2017-08-20 04:30:53 -07:00
break ;
case ' w ' :
2019-09-11 04:55:54 -07:00
truncSize = static_cast < uint > ( strtoul ( optarg , & end , 10 ) ) ;
2019-12-11 01:20:00 -08:00
if ( end [ 0 ] ! = ' \0 ' | | truncSize < MIN_TRUNCSIZE | | truncSize > MAX_TRUNCSIZE )
2016-02-18 06:11:54 -08:00
{
2019-12-11 01:20:00 -08:00
fprintf ( stderr , " \n Error: Got unexpected value \" %s \" for option -%c, expected between %u to %u. \n " , optarg , opt , MIN_TRUNCSIZE , MAX_TRUNCSIZE ) ;
2017-08-20 04:30:53 -07:00
exit ( EXIT_FAILURE ) ;
2016-02-18 06:11:54 -08:00
}
2017-08-20 04:30:53 -07:00
break ;
case ' d ' :
if ( strcmp ( optarg , " dataset " ) = = 0 )
2016-02-18 06:11:54 -08:00
model = HM_DATASET ;
2017-08-20 04:30:53 -07:00
else if ( strcmp ( optarg , " sphere " ) = = 0 )
2016-02-18 06:11:54 -08:00
model = HM_SPHERE ;
else
{
2019-01-22 11:13:14 -08:00
fprintf ( stderr , " \n Error: Got unexpected value \" %s \" for option -%c, expected dataset or sphere. \n " , optarg , opt ) ;
2017-08-20 04:30:53 -07:00
exit ( EXIT_FAILURE ) ;
2016-02-18 06:11:54 -08:00
}
2017-08-20 04:30:53 -07:00
break ;
case ' c ' :
radius = strtod ( optarg , & end ) ;
2016-02-18 06:11:54 -08:00
if ( end [ 0 ] ! = ' \0 ' | | radius < MIN_CUSTOM_RADIUS | | radius > MAX_CUSTOM_RADIUS )
{
2019-01-22 11:13:14 -08:00
fprintf ( stderr , " \n Error: Got unexpected value \" %s \" for option -%c, expected between %.2f to %.2f. \n " , optarg , opt , MIN_CUSTOM_RADIUS , MAX_CUSTOM_RADIUS ) ;
2017-08-20 04:30:53 -07:00
exit ( EXIT_FAILURE ) ;
2016-02-18 06:11:54 -08:00
}
2017-08-20 04:30:53 -07:00
break ;
case ' i ' :
inName = optarg ;
break ;
case ' o ' :
outName = optarg ;
break ;
case ' h ' :
PrintHelp ( argv [ 0 ] , stdout ) ;
exit ( EXIT_SUCCESS ) ;
default : /* '?' */
PrintHelp ( argv [ 0 ] , stderr ) ;
exit ( EXIT_FAILURE ) ;
2016-02-18 06:11:54 -08:00
}
}
2017-08-20 04:30:53 -07:00
2020-06-19 16:43:09 -07:00
int ret = ProcessDefinition ( inName , outRate , chanMode , farfield , numThreads , fftSize , equalize ,
surface , limit , truncSize , model , radius , outName ) ;
2019-03-24 22:43:43 -07:00
if ( ! ret ) return - 1 ;
2016-02-18 06:11:54 -08:00
fprintf ( stdout , " Operation completed. \n " ) ;
2017-08-20 04:30:53 -07:00
return EXIT_SUCCESS ;
2016-02-18 06:11:54 -08:00
}