2 * HRTF utility for producing and demonstrating the process of creating an
3 * OpenAL Soft compatible HRIR data set.
5 * Copyright (C) 2011-2019 Christopher Fitzgerald
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
23 * --------------------------------------------------------------------------
25 * A big thanks goes out to all those whose work done in the field of
26 * binaural sound synthesis using measured HRTFs makes this utility and the
27 * OpenAL Soft implementation possible.
29 * The algorithm for diffuse-field equalization was adapted from the work
30 * done by Rio Emmanuel and Larcher Veronique of IRCAM and Bill Gardner of
31 * MIT Media Laboratory. It operates as follows:
33 * 1. Take the FFT of each HRIR and only keep the magnitude responses.
34 * 2. Calculate the diffuse-field power-average of all HRIRs weighted by
35 * their contribution to the total surface area covered by their
36 * measurement. This has since been modified to use coverage volume for
37 * multi-field HRIR data sets.
38 * 3. Take the diffuse-field average and limit its magnitude range.
39 * 4. Equalize the responses by using the inverse of the diffuse-field
41 * 5. Reconstruct the minimum-phase responses.
42 * 5. Zero the DC component.
43 * 6. IFFT the result and truncate to the desired-length minimum-phase FIR.
45 * The spherical head algorithm for calculating propagation delay was adapted
48 * Modeling Interaural Time Difference Assuming a Spherical Head
50 * Music 150, Musical Acoustics, Stanford University
53 * The formulae for calculating the Kaiser window metrics are from the
56 * Discrete-Time Signal Processing
57 * Alan V. Oppenheim and Ronald W. Schafer
58 * Prentice-Hall Signal Processing Series
88 #include "../getopt.h"
91 #include "alcomplex.h"
92 #include "alfstream.h"
98 #include "win_main_utf8.h"
103 using namespace std::placeholders
;
108 #define M_PI (3.14159265358979323846)
112 HrirDataT::~HrirDataT() = default;
114 // Head model used for calculating the impulse delays.
117 HM_DATASET
, // Measure the onset from the dataset.
118 HM_SPHERE
// Calculate the onset using a spherical head model.
122 // The epsilon used to maintain signal stability.
123 #define EPSILON (1e-9)
125 // The limits to the FFT window size override on the command line.
126 #define MIN_FFTSIZE (65536)
127 #define MAX_FFTSIZE (131072)
129 // The limits to the equalization range limit on the command line.
130 #define MIN_LIMIT (2.0)
131 #define MAX_LIMIT (120.0)
133 // The limits to the truncation window size on the command line.
134 #define MIN_TRUNCSIZE (16)
135 #define MAX_TRUNCSIZE (128)
137 // The limits to the custom head radius on the command line.
138 #define MIN_CUSTOM_RADIUS (0.05)
139 #define MAX_CUSTOM_RADIUS (0.15)
141 // The defaults for the command line options.
142 #define DEFAULT_FFTSIZE (65536)
143 #define DEFAULT_EQUALIZE (1)
144 #define DEFAULT_SURFACE (1)
145 #define DEFAULT_LIMIT (24.0)
146 #define DEFAULT_TRUNCSIZE (32)
147 #define DEFAULT_HEAD_MODEL (HM_DATASET)
148 #define DEFAULT_CUSTOM_RADIUS (0.0)
150 // The maximum propagation delay value supported by OpenAL Soft.
151 #define MAX_HRTD (63.0)
153 // The OpenAL Soft HRTF format marker. It stands for minimum-phase head
154 // response protocol 03.
155 #define MHR_FORMAT ("MinPHR03")
157 /* Channel index enums. Mono uses LeftChannel only. */
158 enum ChannelIndex
: uint
{
164 /* Performs a string substitution. Any case-insensitive occurrences of the
165 * pattern string are replaced with the replacement string. The result is
166 * truncated if necessary.
168 static std::string
StrSubst(al::span
<const char> in
, const al::span
<const char> pat
,
169 const al::span
<const char> rep
)
172 ret
.reserve(in
.size() + pat
.size());
174 while(in
.size() >= pat
.size())
176 if(al::strncasecmp(in
.data(), pat
.data(), pat
.size()) == 0)
178 in
= in
.subspan(pat
.size());
179 ret
.append(rep
.data(), rep
.size());
184 while(endpos
< in
.size() && in
[endpos
] != pat
.front())
186 ret
.append(in
.data(), endpos
);
187 in
= in
.subspan(endpos
);
190 ret
.append(in
.data(), in
.size());
196 /*********************
197 *** Math routines ***
198 *********************/
200 // Simple clamp routine.
201 static double Clamp(const double val
, const double lower
, const double upper
)
203 return std::min(std::max(val
, lower
), upper
);
206 static inline uint
dither_rng(uint
*seed
)
208 *seed
= *seed
* 96314165 + 907633515;
212 // Performs a triangular probability density function dither. The input samples
213 // should be normalized (-1 to +1).
214 static void TpdfDither(double *RESTRICT out
, const double *RESTRICT in
, const double scale
,
215 const uint count
, const uint step
, uint
*seed
)
217 static constexpr double PRNG_SCALE
= 1.0 / std::numeric_limits
<uint
>::max();
219 for(uint i
{0};i
< count
;i
++)
221 uint prn0
{dither_rng(seed
)};
222 uint prn1
{dither_rng(seed
)};
223 *out
= std::round(*(in
++)*scale
+ (prn0
*PRNG_SCALE
- prn1
*PRNG_SCALE
));
229 /* Calculate the complex helical sequence (or discrete-time analytical signal)
230 * of the given input using the Hilbert transform. Given the natural logarithm
231 * of a signal's magnitude response, the imaginary components can be used as
232 * the angles for minimum-phase reconstruction.
234 inline static void Hilbert(const uint n
, complex_d
*inout
)
235 { complex_hilbert({inout
, n
}); }
237 /* Calculate the magnitude response of the given input. This is used in
238 * place of phase decomposition, since the phase residuals are discarded for
239 * minimum phase reconstruction. The mirrored half of the response is also
242 void MagnitudeResponse(const uint n
, const complex_d
*in
, double *out
)
244 const uint m
= 1 + (n
/ 2);
247 out
[i
] = std::max(std::abs(in
[i
]), EPSILON
);
250 /* Apply a range limit (in dB) to the given magnitude response. This is used
251 * to adjust the effects of the diffuse-field average on the equalization
254 static void LimitMagnitudeResponse(const uint n
, const uint m
, const double limit
, const double *in
, double *out
)
257 uint i
, lower
, upper
;
260 halfLim
= limit
/ 2.0;
261 // Convert the response to dB.
263 out
[i
] = 20.0 * std::log10(in
[i
]);
264 // Use six octaves to calculate the average magnitude of the signal.
265 lower
= (static_cast<uint
>(std::ceil(n
/ std::pow(2.0, 8.0)))) - 1;
266 upper
= (static_cast<uint
>(std::floor(n
/ std::pow(2.0, 2.0)))) - 1;
268 for(i
= lower
;i
<= upper
;i
++)
270 ave
/= upper
- lower
+ 1;
271 // Keep the response within range of the average magnitude.
273 out
[i
] = Clamp(out
[i
], ave
- halfLim
, ave
+ halfLim
);
274 // Convert the response back to linear magnitude.
276 out
[i
] = std::pow(10.0, out
[i
] / 20.0);
279 /* Reconstructs the minimum-phase component for the given magnitude response
280 * of a signal. This is equivalent to phase recomposition, sans the missing
281 * residuals (which were discarded). The mirrored half of the response is
284 static void MinimumPhase(const uint n
, double *mags
, complex_d
*out
)
286 const uint m
{(n
/2) + 1};
290 out
[i
] = std::log(mags
[i
]);
293 mags
[i
] = mags
[n
- i
];
297 // Remove any DC offset the filter has.
301 auto a
= std::exp(complex_d
{0.0, out
[i
].imag()});
302 out
[i
] = a
* mags
[i
];
307 /***************************
308 *** File storage output ***
309 ***************************/
311 // Write an ASCII string to a file.
312 static int WriteAscii(const char *out
, FILE *fp
, const char *filename
)
317 if(fwrite(out
, 1, len
, fp
) != len
)
320 fprintf(stderr
, "\nError: Bad write to file '%s'.\n", filename
);
326 // Write a binary value of the given byte order and byte size to a file,
327 // loading it from a 32-bit unsigned integer.
328 static int WriteBin4(const uint bytes
, const uint32_t in
, FILE *fp
, const char *filename
)
333 for(i
= 0;i
< bytes
;i
++)
334 out
[i
] = (in
>>(i
*8)) & 0x000000FF;
336 if(fwrite(out
, 1, bytes
, fp
) != bytes
)
338 fprintf(stderr
, "\nError: Bad write to file '%s'.\n", filename
);
344 // Store the OpenAL Soft HRTF data set.
345 static int StoreMhr(const HrirDataT
*hData
, const char *filename
)
347 const uint channels
{(hData
->mChannelType
== CT_STEREO
) ? 2u : 1u};
348 const uint n
{hData
->mIrPoints
};
349 uint dither_seed
{22222};
353 if((fp
=fopen(filename
, "wb")) == nullptr)
355 fprintf(stderr
, "\nError: Could not open MHR file '%s'.\n", filename
);
358 if(!WriteAscii(MHR_FORMAT
, fp
, filename
))
360 if(!WriteBin4(4, hData
->mIrRate
, fp
, filename
))
362 if(!WriteBin4(1, static_cast<uint32_t>(hData
->mChannelType
), fp
, filename
))
364 if(!WriteBin4(1, hData
->mIrPoints
, fp
, filename
))
366 if(!WriteBin4(1, static_cast<uint
>(hData
->mFds
.size()), fp
, filename
))
368 for(fi
= static_cast<uint
>(hData
->mFds
.size()-1);fi
< hData
->mFds
.size();fi
--)
370 auto fdist
= static_cast<uint32_t>(std::round(1000.0 * hData
->mFds
[fi
].mDistance
));
371 if(!WriteBin4(2, fdist
, fp
, filename
))
373 if(!WriteBin4(1, hData
->mFds
[fi
].mEvCount
, fp
, filename
))
375 for(ei
= 0;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
377 if(!WriteBin4(1, hData
->mFds
[fi
].mEvs
[ei
].mAzCount
, fp
, filename
))
382 for(fi
= static_cast<uint
>(hData
->mFds
.size()-1);fi
< hData
->mFds
.size();fi
--)
384 constexpr double scale
{8388607.0};
385 constexpr uint bps
{3u};
387 for(ei
= 0;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
389 for(ai
= 0;ai
< hData
->mFds
[fi
].mEvs
[ei
].mAzCount
;ai
++)
391 HrirAzT
*azd
= &hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
];
392 double out
[2 * MAX_TRUNCSIZE
];
394 TpdfDither(out
, azd
->mIrs
[0], scale
, n
, channels
, &dither_seed
);
395 if(hData
->mChannelType
== CT_STEREO
)
396 TpdfDither(out
+1, azd
->mIrs
[1], scale
, n
, channels
, &dither_seed
);
397 for(i
= 0;i
< (channels
* n
);i
++)
399 const auto v
= static_cast<int>(Clamp(out
[i
], -scale
-1.0, scale
));
400 if(!WriteBin4(bps
, static_cast<uint32_t>(v
), fp
, filename
))
406 for(fi
= static_cast<uint
>(hData
->mFds
.size()-1);fi
< hData
->mFds
.size();fi
--)
408 /* Delay storage has 2 bits of extra precision. */
409 constexpr double DelayPrecScale
{4.0};
410 for(ei
= 0;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
412 for(ai
= 0;ai
< hData
->mFds
[fi
].mEvs
[ei
].mAzCount
;ai
++)
414 const HrirAzT
&azd
= hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
];
416 auto v
= static_cast<uint
>(std::round(azd
.mDelays
[0]*DelayPrecScale
));
417 if(!WriteBin4(1, v
, fp
, filename
)) return 0;
418 if(hData
->mChannelType
== CT_STEREO
)
420 v
= static_cast<uint
>(std::round(azd
.mDelays
[1]*DelayPrecScale
));
421 if(!WriteBin4(1, v
, fp
, filename
)) return 0;
431 /***********************
432 *** HRTF processing ***
433 ***********************/
435 /* Balances the maximum HRIR magnitudes of multi-field data sets by
436 * independently normalizing each field in relation to the overall maximum.
437 * This is done to ignore distance attenuation.
439 static void BalanceFieldMagnitudes(const HrirDataT
*hData
, const uint channels
, const uint m
)
441 double maxMags
[MAX_FD_COUNT
];
442 uint fi
, ei
, ai
, ti
, i
;
445 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
449 for(ei
= hData
->mFds
[fi
].mEvStart
;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
451 for(ai
= 0;ai
< hData
->mFds
[fi
].mEvs
[ei
].mAzCount
;ai
++)
453 HrirAzT
*azd
= &hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
];
454 for(ti
= 0;ti
< channels
;ti
++)
457 maxMags
[fi
] = std::max(azd
->mIrs
[ti
][i
], maxMags
[fi
]);
462 maxMag
= std::max(maxMags
[fi
], maxMag
);
465 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
467 const double magFactor
{maxMag
/ maxMags
[fi
]};
469 for(ei
= hData
->mFds
[fi
].mEvStart
;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
471 for(ai
= 0;ai
< hData
->mFds
[fi
].mEvs
[ei
].mAzCount
;ai
++)
473 HrirAzT
*azd
= &hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
];
474 for(ti
= 0;ti
< channels
;ti
++)
477 azd
->mIrs
[ti
][i
] *= magFactor
;
484 /* Calculate the contribution of each HRIR to the diffuse-field average based
485 * on its coverage volume. All volumes are centered at the spherical HRIR
486 * coordinates and measured by extruded solid angle.
488 static void CalculateDfWeights(const HrirDataT
*hData
, double *weights
)
490 double sum
, innerRa
, outerRa
, evs
, ev
, upperEv
, lowerEv
;
491 double solidAngle
, solidVolume
;
495 // The head radius acts as the limit for the inner radius.
496 innerRa
= hData
->mRadius
;
497 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
499 // Each volume ends half way between progressive field measurements.
500 if((fi
+ 1) < hData
->mFds
.size())
501 outerRa
= 0.5f
* (hData
->mFds
[fi
].mDistance
+ hData
->mFds
[fi
+ 1].mDistance
);
502 // The final volume has its limit extended to some practical value.
503 // This is done to emphasize the far-field responses in the average.
507 evs
= M_PI
/ 2.0 / (hData
->mFds
[fi
].mEvCount
- 1);
508 for(ei
= hData
->mFds
[fi
].mEvStart
;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
510 // For each elevation, calculate the upper and lower limits of
512 ev
= hData
->mFds
[fi
].mEvs
[ei
].mElevation
;
513 lowerEv
= std::max(-M_PI
/ 2.0, ev
- evs
);
514 upperEv
= std::min(M_PI
/ 2.0, ev
+ evs
);
515 // Calculate the surface area of the patch band.
516 solidAngle
= 2.0 * M_PI
* (std::sin(upperEv
) - std::sin(lowerEv
));
517 // Then the volume of the extruded patch band.
518 solidVolume
= solidAngle
* (std::pow(outerRa
, 3.0) - std::pow(innerRa
, 3.0)) / 3.0;
519 // Each weight is the volume of one extruded patch.
520 weights
[(fi
* MAX_EV_COUNT
) + ei
] = solidVolume
/ hData
->mFds
[fi
].mEvs
[ei
].mAzCount
;
521 // Sum the total coverage volume of the HRIRs for all fields.
528 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
530 // Normalize the weights given the total surface coverage for all
532 for(ei
= hData
->mFds
[fi
].mEvStart
;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
533 weights
[(fi
* MAX_EV_COUNT
) + ei
] /= sum
;
537 /* Calculate the diffuse-field average from the given magnitude responses of
538 * the HRIR set. Weighting can be applied to compensate for the varying
539 * coverage of each HRIR. The final average can then be limited by the
540 * specified magnitude range (in positive dB; 0.0 to skip).
542 static void CalculateDiffuseFieldAverage(const HrirDataT
*hData
, const uint channels
, const uint m
, const int weighted
, const double limit
, double *dfa
)
544 std::vector
<double> weights(hData
->mFds
.size() * MAX_EV_COUNT
);
545 uint count
, ti
, fi
, ei
, i
, ai
;
549 // Use coverage weighting to calculate the average.
550 CalculateDfWeights(hData
, weights
.data());
556 // If coverage weighting is not used, the weights still need to be
557 // averaged by the number of existing HRIRs.
558 count
= hData
->mIrCount
;
559 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
561 for(ei
= 0;ei
< hData
->mFds
[fi
].mEvStart
;ei
++)
562 count
-= hData
->mFds
[fi
].mEvs
[ei
].mAzCount
;
564 weight
= 1.0 / count
;
566 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
568 for(ei
= hData
->mFds
[fi
].mEvStart
;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
569 weights
[(fi
* MAX_EV_COUNT
) + ei
] = weight
;
572 for(ti
= 0;ti
< channels
;ti
++)
575 dfa
[(ti
* m
) + i
] = 0.0;
576 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
578 for(ei
= hData
->mFds
[fi
].mEvStart
;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
580 for(ai
= 0;ai
< hData
->mFds
[fi
].mEvs
[ei
].mAzCount
;ai
++)
582 HrirAzT
*azd
= &hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
];
583 // Get the weight for this HRIR's contribution.
584 double weight
= weights
[(fi
* MAX_EV_COUNT
) + ei
];
586 // Add this HRIR's weighted power average to the total.
588 dfa
[(ti
* m
) + i
] += weight
* azd
->mIrs
[ti
][i
] * azd
->mIrs
[ti
][i
];
592 // Finish the average calculation and keep it from being too small.
594 dfa
[(ti
* m
) + i
] = std::max(sqrt(dfa
[(ti
* m
) + i
]), EPSILON
);
595 // Apply a limit to the magnitude range of the diffuse-field average
598 LimitMagnitudeResponse(hData
->mFftSize
, m
, limit
, &dfa
[ti
* m
], &dfa
[ti
* m
]);
602 // Perform diffuse-field equalization on the magnitude responses of the HRIR
603 // set using the given average response.
604 static void DiffuseFieldEqualize(const uint channels
, const uint m
, const double *dfa
, const HrirDataT
*hData
)
606 uint ti
, fi
, ei
, ai
, i
;
608 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
610 for(ei
= hData
->mFds
[fi
].mEvStart
;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
612 for(ai
= 0;ai
< hData
->mFds
[fi
].mEvs
[ei
].mAzCount
;ai
++)
614 HrirAzT
*azd
= &hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
];
616 for(ti
= 0;ti
< channels
;ti
++)
619 azd
->mIrs
[ti
][i
] /= dfa
[(ti
* m
) + i
];
626 /* Given field and elevation indices and an azimuth, calculate the indices of
627 * the two HRIRs that bound the coordinate along with a factor for
628 * calculating the continuous HRIR using interpolation.
630 static void CalcAzIndices(const HrirFdT
&field
, const uint ei
, const double az
, uint
*a0
, uint
*a1
, double *af
)
632 double f
{(2.0*M_PI
+ az
) * field
.mEvs
[ei
].mAzCount
/ (2.0*M_PI
)};
633 uint i
{static_cast<uint
>(f
) % field
.mEvs
[ei
].mAzCount
};
637 *a1
= (i
+ 1) % field
.mEvs
[ei
].mAzCount
;
641 /* Synthesize any missing onset timings at the bottom elevations of each field.
642 * This just mirrors some top elevations for the bottom, and blends the
643 * remaining elevations (not an accurate model).
645 static void SynthesizeOnsets(HrirDataT
*hData
)
647 const uint channels
{(hData
->mChannelType
== CT_STEREO
) ? 2u : 1u};
649 auto proc_field
= [channels
](HrirFdT
&field
) -> void
651 /* Get the starting elevation from the measurements, and use it as the
652 * upper elevation limit for what needs to be calculated.
654 const uint upperElevReal
{field
.mEvStart
};
655 if(upperElevReal
<= 0) return;
657 /* Get the lowest half of the missing elevations' delays by mirroring
658 * the top elevation delays. The responses are on a spherical grid
659 * centered between the ears, so these should align.
664 /* Take the polar opposite position of the desired measurement and
667 field
.mEvs
[0].mAzs
[0].mDelays
[0] = field
.mEvs
[field
.mEvCount
-1].mAzs
[0].mDelays
[1];
668 field
.mEvs
[0].mAzs
[0].mDelays
[1] = field
.mEvs
[field
.mEvCount
-1].mAzs
[0].mDelays
[0];
669 for(ei
= 1u;ei
< (upperElevReal
+1)/2;++ei
)
671 const uint topElev
{field
.mEvCount
-ei
-1};
673 for(uint ai
{0u};ai
< field
.mEvs
[ei
].mAzCount
;ai
++)
678 /* Rotate this current azimuth by a half-circle, and lookup
679 * the mirrored elevation to find the indices for the polar
680 * opposite position (may need blending).
682 const double az
{field
.mEvs
[ei
].mAzs
[ai
].mAzimuth
+ M_PI
};
683 CalcAzIndices(field
, topElev
, az
, &a0
, &a1
, &af
);
685 /* Blend the delays, and again, swap the ears. */
686 field
.mEvs
[ei
].mAzs
[ai
].mDelays
[0] = Lerp(
687 field
.mEvs
[topElev
].mAzs
[a0
].mDelays
[1],
688 field
.mEvs
[topElev
].mAzs
[a1
].mDelays
[1], af
);
689 field
.mEvs
[ei
].mAzs
[ai
].mDelays
[1] = Lerp(
690 field
.mEvs
[topElev
].mAzs
[a0
].mDelays
[0],
691 field
.mEvs
[topElev
].mAzs
[a1
].mDelays
[0], af
);
697 field
.mEvs
[0].mAzs
[0].mDelays
[0] = field
.mEvs
[field
.mEvCount
-1].mAzs
[0].mDelays
[0];
698 for(ei
= 1u;ei
< (upperElevReal
+1)/2;++ei
)
700 const uint topElev
{field
.mEvCount
-ei
-1};
702 for(uint ai
{0u};ai
< field
.mEvs
[ei
].mAzCount
;ai
++)
707 /* For mono data sets, mirror the azimuth front<->back
708 * since the other ear is a mirror of what we have (e.g.
709 * the left ear's back-left is simulated with the right
710 * ear's front-right, which uses the left ear's front-left
713 double az
{field
.mEvs
[ei
].mAzs
[ai
].mAzimuth
};
714 if(az
<= M_PI
) az
= M_PI
- az
;
715 else az
= (M_PI
*2.0)-az
+ M_PI
;
716 CalcAzIndices(field
, topElev
, az
, &a0
, &a1
, &af
);
718 field
.mEvs
[ei
].mAzs
[ai
].mDelays
[0] = Lerp(
719 field
.mEvs
[topElev
].mAzs
[a0
].mDelays
[0],
720 field
.mEvs
[topElev
].mAzs
[a1
].mDelays
[0], af
);
724 /* Record the lowest elevation filled in with the mirrored top. */
725 const uint lowerElevFake
{ei
-1u};
727 /* Fill in the remaining delays using bilinear interpolation. This
728 * helps smooth the transition back to the real delays.
730 for(;ei
< upperElevReal
;++ei
)
732 const double ef
{(field
.mEvs
[upperElevReal
].mElevation
- field
.mEvs
[ei
].mElevation
) /
733 (field
.mEvs
[upperElevReal
].mElevation
- field
.mEvs
[lowerElevFake
].mElevation
)};
735 for(uint ai
{0u};ai
< field
.mEvs
[ei
].mAzCount
;ai
++)
740 double az
{field
.mEvs
[ei
].mAzs
[ai
].mAzimuth
};
741 CalcAzIndices(field
, upperElevReal
, az
, &a0
, &a1
, &af0
);
742 CalcAzIndices(field
, lowerElevFake
, az
, &a2
, &a3
, &af1
);
744 (1.0-ef
) * (1.0-af0
),
750 for(uint ti
{0u};ti
< channels
;ti
++)
752 field
.mEvs
[ei
].mAzs
[ai
].mDelays
[ti
] =
753 field
.mEvs
[upperElevReal
].mAzs
[a0
].mDelays
[ti
]*blend
[0] +
754 field
.mEvs
[upperElevReal
].mAzs
[a1
].mDelays
[ti
]*blend
[1] +
755 field
.mEvs
[lowerElevFake
].mAzs
[a2
].mDelays
[ti
]*blend
[2] +
756 field
.mEvs
[lowerElevFake
].mAzs
[a3
].mDelays
[ti
]*blend
[3];
761 std::for_each(hData
->mFds
.begin(), hData
->mFds
.end(), proc_field
);
764 /* Attempt to synthesize any missing HRIRs at the bottom elevations of each
765 * field. Right now this just blends the lowest elevation HRIRs together and
766 * applies a low-pass filter to simulate body occlusion. It is a simple, if
769 static void SynthesizeHrirs(HrirDataT
*hData
)
771 const uint channels
{(hData
->mChannelType
== CT_STEREO
) ? 2u : 1u};
772 auto htemp
= std::vector
<complex_d
>(hData
->mFftSize
);
773 const uint m
{hData
->mFftSize
/2u + 1u};
774 auto filter
= std::vector
<double>(m
);
775 const double beta
{3.5e-6 * hData
->mIrRate
};
777 auto proc_field
= [channels
,m
,beta
,&htemp
,&filter
](HrirFdT
&field
) -> void
779 const uint oi
{field
.mEvStart
};
782 for(uint ti
{0u};ti
< channels
;ti
++)
787 /* Use the lowest immediate-left response for the left ear and
788 * lowest immediate-right response for the right ear. Given no comb
789 * effects as a result of the left response reaching the right ear
790 * and vice-versa, this produces a decent phantom-center response
791 * underneath the head.
793 CalcAzIndices(field
, oi
, ((ti
==0) ? -M_PI
: M_PI
) / 2.0, &a0
, &a1
, &af
);
794 for(uint i
{0u};i
< m
;i
++)
796 field
.mEvs
[0].mAzs
[0].mIrs
[ti
][i
] = Lerp(field
.mEvs
[oi
].mAzs
[a0
].mIrs
[ti
][i
],
797 field
.mEvs
[oi
].mAzs
[a1
].mIrs
[ti
][i
], af
);
801 for(uint ei
{1u};ei
< field
.mEvStart
;ei
++)
803 const double of
{static_cast<double>(ei
) / field
.mEvStart
};
804 const double b
{(1.0 - of
) * beta
};
807 /* Calculate a low-pass filter to simulate body occlusion. */
808 lp
[0] = Lerp(1.0, lp
[0], b
);
809 lp
[1] = Lerp(lp
[0], lp
[1], b
);
810 lp
[2] = Lerp(lp
[1], lp
[2], b
);
811 lp
[3] = Lerp(lp
[2], lp
[3], b
);
813 for(size_t i
{1u};i
< htemp
.size();i
++)
815 lp
[0] = Lerp(0.0, lp
[0], b
);
816 lp
[1] = Lerp(lp
[0], lp
[1], b
);
817 lp
[2] = Lerp(lp
[1], lp
[2], b
);
818 lp
[3] = Lerp(lp
[2], lp
[3], b
);
821 /* Get the filter's frequency-domain response and extract the
822 * frequency magnitudes (phase will be reconstructed later)).
824 FftForward(static_cast<uint
>(htemp
.size()), htemp
.data());
825 std::transform(htemp
.cbegin(), htemp
.cbegin()+m
, filter
.begin(),
826 [](const complex_d
&c
) -> double { return std::abs(c
); });
828 for(uint ai
{0u};ai
< field
.mEvs
[ei
].mAzCount
;ai
++)
833 CalcAzIndices(field
, oi
, field
.mEvs
[ei
].mAzs
[ai
].mAzimuth
, &a0
, &a1
, &af
);
834 for(uint ti
{0u};ti
< channels
;ti
++)
836 for(uint i
{0u};i
< m
;i
++)
838 /* Blend the two defined HRIRs closest to this azimuth,
839 * then blend that with the synthesized -90 elevation.
841 const double s1
{Lerp(field
.mEvs
[oi
].mAzs
[a0
].mIrs
[ti
][i
],
842 field
.mEvs
[oi
].mAzs
[a1
].mIrs
[ti
][i
], af
)};
843 const double s
{Lerp(field
.mEvs
[0].mAzs
[0].mIrs
[ti
][i
], s1
, of
)};
844 field
.mEvs
[ei
].mAzs
[ai
].mIrs
[ti
][i
] = s
* filter
[i
];
849 const double b
{beta
};
851 lp
[0] = Lerp(1.0, lp
[0], b
);
852 lp
[1] = Lerp(lp
[0], lp
[1], b
);
853 lp
[2] = Lerp(lp
[1], lp
[2], b
);
854 lp
[3] = Lerp(lp
[2], lp
[3], b
);
856 for(size_t i
{1u};i
< htemp
.size();i
++)
858 lp
[0] = Lerp(0.0, lp
[0], b
);
859 lp
[1] = Lerp(lp
[0], lp
[1], b
);
860 lp
[2] = Lerp(lp
[1], lp
[2], b
);
861 lp
[3] = Lerp(lp
[2], lp
[3], b
);
864 FftForward(static_cast<uint
>(htemp
.size()), htemp
.data());
865 std::transform(htemp
.cbegin(), htemp
.cbegin()+m
, filter
.begin(),
866 [](const complex_d
&c
) -> double { return std::abs(c
); });
868 for(uint ti
{0u};ti
< channels
;ti
++)
870 for(uint i
{0u};i
< m
;i
++)
871 field
.mEvs
[0].mAzs
[0].mIrs
[ti
][i
] *= filter
[i
];
874 std::for_each(hData
->mFds
.begin(), hData
->mFds
.end(), proc_field
);
877 // The following routines assume a full set of HRIRs for all elevations.
879 /* Perform minimum-phase reconstruction using the magnitude responses of the
880 * HRIR set. Work is delegated to this struct, which runs asynchronously on one
881 * or more threads (sharing the same reconstructor object).
883 struct HrirReconstructor
{
884 std::vector
<double*> mIrs
;
885 std::atomic
<size_t> mCurrent
;
886 std::atomic
<size_t> mDone
;
892 auto h
= std::vector
<complex_d
>(mFftSize
);
893 auto mags
= std::vector
<double>(mFftSize
);
894 size_t m
{(mFftSize
/2) + 1};
898 /* Load the current index to process. */
899 size_t idx
{mCurrent
.load()};
901 /* If the index is at the end, we're done. */
902 if(idx
>= mIrs
.size())
904 /* Otherwise, increment the current index atomically so other
905 * threads know to go to the next one. If this call fails, the
906 * current index was just changed by another thread and the new
907 * value is loaded into idx, which we'll recheck.
909 } while(!mCurrent
.compare_exchange_weak(idx
, idx
+1, std::memory_order_relaxed
));
911 /* Now do the reconstruction, and apply the inverse FFT to get the
912 * time-domain response.
914 for(size_t i
{0};i
< m
;++i
)
915 mags
[i
] = std::max(mIrs
[idx
][i
], EPSILON
);
916 MinimumPhase(mFftSize
, mags
.data(), h
.data());
917 FftInverse(mFftSize
, h
.data());
918 for(uint i
{0u};i
< mIrPoints
;++i
)
919 mIrs
[idx
][i
] = h
[i
].real();
921 /* Increment the number of IRs done. */
927 static void ReconstructHrirs(const HrirDataT
*hData
, const uint numThreads
)
929 const uint channels
{(hData
->mChannelType
== CT_STEREO
) ? 2u : 1u};
931 /* Set up the reconstructor with the needed size info and pointers to the
934 HrirReconstructor reconstructor
;
935 reconstructor
.mCurrent
.store(0, std::memory_order_relaxed
);
936 reconstructor
.mDone
.store(0, std::memory_order_relaxed
);
937 reconstructor
.mFftSize
= hData
->mFftSize
;
938 reconstructor
.mIrPoints
= hData
->mIrPoints
;
939 for(uint fi
{0u};fi
< hData
->mFds
.size();fi
++)
941 const HrirFdT
&field
= hData
->mFds
[fi
];
942 for(uint ei
{0};ei
< field
.mEvCount
;ei
++)
944 const HrirEvT
&elev
= field
.mEvs
[ei
];
945 for(uint ai
{0u};ai
< elev
.mAzCount
;ai
++)
947 const HrirAzT
&azd
= elev
.mAzs
[ai
];
948 for(uint ti
{0u};ti
< channels
;ti
++)
949 reconstructor
.mIrs
.push_back(azd
.mIrs
[ti
]);
954 /* Launch threads to work on reconstruction. */
955 std::vector
<std::thread
> thrds
;
956 thrds
.reserve(numThreads
);
957 for(size_t i
{0};i
< numThreads
;++i
)
958 thrds
.emplace_back(std::mem_fn(&HrirReconstructor::Worker
), &reconstructor
);
960 /* Keep track of the number of IRs done, periodically reporting it. */
963 std::this_thread::sleep_for(std::chrono::milliseconds
{50});
965 count
= reconstructor
.mDone
.load();
966 size_t pcdone
{count
* 100 / reconstructor
.mIrs
.size()};
968 printf("\r%3zu%% done (%zu of %zu)", pcdone
, count
, reconstructor
.mIrs
.size());
970 } while(count
< reconstructor
.mIrs
.size());
973 for(auto &thrd
: thrds
)
980 // Normalize the HRIR set and slightly attenuate the result.
981 static void NormalizeHrirs(HrirDataT
*hData
)
983 const uint channels
{(hData
->mChannelType
== CT_STEREO
) ? 2u : 1u};
984 const uint irSize
{hData
->mIrPoints
};
986 /* Find the maximum amplitude and RMS out of all the IRs. */
987 struct LevelPair
{ double amp
, rms
; };
988 auto mesasure_channel
= [irSize
](const LevelPair levels
, const double *ir
)
990 /* Calculate the peak amplitude and RMS of this IR. */
991 auto current
= std::accumulate(ir
, ir
+irSize
, LevelPair
{0.0, 0.0},
992 [](const LevelPair cur
, const double impulse
)
994 return LevelPair
{std::max(std::abs(impulse
), cur
.amp
), cur
.rms
+ impulse
*impulse
};
996 current
.rms
= std::sqrt(current
.rms
/ irSize
);
998 /* Accumulate levels by taking the maximum amplitude and RMS. */
999 return LevelPair
{std::max(current
.amp
, levels
.amp
), std::max(current
.rms
, levels
.rms
)};
1001 auto measure_azi
= [channels
,mesasure_channel
](const LevelPair levels
, const HrirAzT
&azi
)
1002 { return std::accumulate(azi
.mIrs
, azi
.mIrs
+channels
, levels
, mesasure_channel
); };
1003 auto measure_elev
= [measure_azi
](const LevelPair levels
, const HrirEvT
&elev
)
1004 { return std::accumulate(elev
.mAzs
, elev
.mAzs
+elev
.mAzCount
, levels
, measure_azi
); };
1005 auto measure_field
= [measure_elev
](const LevelPair levels
, const HrirFdT
&field
)
1006 { return std::accumulate(field
.mEvs
, field
.mEvs
+field
.mEvCount
, levels
, measure_elev
); };
1008 const auto maxlev
= std::accumulate(hData
->mFds
.begin(), hData
->mFds
.end(),
1009 LevelPair
{0.0, 0.0}, measure_field
);
1011 /* Normalize using the maximum RMS of the HRIRs. The RMS measure for the
1012 * non-filtered signal is of an impulse with equal length (to the filter):
1014 * rms_impulse = sqrt(sum([ 1^2, 0^2, 0^2, ... ]) / n)
1017 * This helps keep a more consistent volume between the non-filtered signal
1018 * and various data sets.
1020 double factor
{std::sqrt(1.0 / irSize
) / maxlev
.rms
};
1022 /* Also ensure the samples themselves won't clip. */
1023 factor
= std::min(factor
, 0.99/maxlev
.amp
);
1025 /* Now scale all IRs by the given factor. */
1026 auto proc_channel
= [irSize
,factor
](double *ir
)
1027 { std::transform(ir
, ir
+irSize
, ir
, [factor
](auto s
){ return s
* factor
; }); };
1028 auto proc_azi
= [channels
,proc_channel
](HrirAzT
&azi
)
1029 { std::for_each(azi
.mIrs
, azi
.mIrs
+channels
, proc_channel
); };
1030 auto proc_elev
= [proc_azi
](HrirEvT
&elev
)
1031 { std::for_each(elev
.mAzs
, elev
.mAzs
+elev
.mAzCount
, proc_azi
); };
1032 auto proc1_field
= [proc_elev
](HrirFdT
&field
)
1033 { std::for_each(field
.mEvs
, field
.mEvs
+field
.mEvCount
, proc_elev
); };
1035 std::for_each(hData
->mFds
.begin(), hData
->mFds
.end(), proc1_field
);
1038 // Calculate the left-ear time delay using a spherical head model.
1039 static double CalcLTD(const double ev
, const double az
, const double rad
, const double dist
)
1041 double azp
, dlp
, l
, al
;
1043 azp
= std::asin(std::cos(ev
) * std::sin(az
));
1044 dlp
= std::sqrt((dist
*dist
) + (rad
*rad
) + (2.0*dist
*rad
*sin(azp
)));
1045 l
= std::sqrt((dist
*dist
) - (rad
*rad
));
1046 al
= (0.5 * M_PI
) + azp
;
1048 dlp
= l
+ (rad
* (al
- std::acos(rad
/ dist
)));
1052 // Calculate the effective head-related time delays for each minimum-phase
1053 // HRIR. This is done per-field since distance delay is ignored.
1054 static void CalculateHrtds(const HeadModelT model
, const double radius
, HrirDataT
*hData
)
1056 uint channels
= (hData
->mChannelType
== CT_STEREO
) ? 2 : 1;
1057 double customRatio
{radius
/ hData
->mRadius
};
1058 uint ti
, fi
, ei
, ai
;
1060 if(model
== HM_SPHERE
)
1062 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
1064 for(ei
= 0;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
1066 HrirEvT
*evd
= &hData
->mFds
[fi
].mEvs
[ei
];
1068 for(ai
= 0;ai
< evd
->mAzCount
;ai
++)
1070 HrirAzT
*azd
= &evd
->mAzs
[ai
];
1072 for(ti
= 0;ti
< channels
;ti
++)
1073 azd
->mDelays
[ti
] = CalcLTD(evd
->mElevation
, azd
->mAzimuth
, radius
, hData
->mFds
[fi
].mDistance
);
1078 else if(customRatio
!= 1.0)
1080 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
1082 for(ei
= 0;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
1084 HrirEvT
*evd
= &hData
->mFds
[fi
].mEvs
[ei
];
1086 for(ai
= 0;ai
< evd
->mAzCount
;ai
++)
1088 HrirAzT
*azd
= &evd
->mAzs
[ai
];
1089 for(ti
= 0;ti
< channels
;ti
++)
1090 azd
->mDelays
[ti
] *= customRatio
;
1096 double maxHrtd
{0.0};
1097 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
1099 double minHrtd
{std::numeric_limits
<double>::infinity()};
1100 for(ei
= 0;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
1102 for(ai
= 0;ai
< hData
->mFds
[fi
].mEvs
[ei
].mAzCount
;ai
++)
1104 HrirAzT
*azd
= &hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
];
1106 for(ti
= 0;ti
< channels
;ti
++)
1107 minHrtd
= std::min(azd
->mDelays
[ti
], minHrtd
);
1111 for(ei
= 0;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
1113 for(ai
= 0;ai
< hData
->mFds
[fi
].mEvs
[ei
].mAzCount
;ai
++)
1115 HrirAzT
*azd
= &hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
];
1117 for(ti
= 0;ti
< channels
;ti
++)
1119 azd
->mDelays
[ti
] = (azd
->mDelays
[ti
]-minHrtd
) * hData
->mIrRate
;
1120 maxHrtd
= std::max(maxHrtd
, azd
->mDelays
[ti
]);
1125 if(maxHrtd
> MAX_HRTD
)
1127 fprintf(stdout
, " Scaling for max delay of %f samples to %f\n...\n", maxHrtd
, MAX_HRTD
);
1128 const double scale
{MAX_HRTD
/ maxHrtd
};
1129 for(fi
= 0;fi
< hData
->mFds
.size();fi
++)
1131 for(ei
= 0;ei
< hData
->mFds
[fi
].mEvCount
;ei
++)
1133 for(ai
= 0;ai
< hData
->mFds
[fi
].mEvs
[ei
].mAzCount
;ai
++)
1135 HrirAzT
*azd
= &hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
];
1136 for(ti
= 0;ti
< channels
;ti
++)
1137 azd
->mDelays
[ti
] *= scale
;
1144 // Allocate and configure dynamic HRIR structures.
1145 int PrepareHrirData(const uint fdCount
, const double (&distances
)[MAX_FD_COUNT
],
1146 const uint (&evCounts
)[MAX_FD_COUNT
], const uint azCounts
[MAX_FD_COUNT
* MAX_EV_COUNT
],
1149 uint evTotal
= 0, azTotal
= 0, fi
, ei
, ai
;
1151 for(fi
= 0;fi
< fdCount
;fi
++)
1153 evTotal
+= evCounts
[fi
];
1154 for(ei
= 0;ei
< evCounts
[fi
];ei
++)
1155 azTotal
+= azCounts
[(fi
* MAX_EV_COUNT
) + ei
];
1157 if(!fdCount
|| !evTotal
|| !azTotal
)
1160 hData
->mEvsBase
.resize(evTotal
);
1161 hData
->mAzsBase
.resize(azTotal
);
1162 hData
->mFds
.resize(fdCount
);
1163 hData
->mIrCount
= azTotal
;
1166 for(fi
= 0;fi
< fdCount
;fi
++)
1168 hData
->mFds
[fi
].mDistance
= distances
[fi
];
1169 hData
->mFds
[fi
].mEvCount
= evCounts
[fi
];
1170 hData
->mFds
[fi
].mEvStart
= 0;
1171 hData
->mFds
[fi
].mEvs
= &hData
->mEvsBase
[evTotal
];
1172 evTotal
+= evCounts
[fi
];
1173 for(ei
= 0;ei
< evCounts
[fi
];ei
++)
1175 uint azCount
= azCounts
[(fi
* MAX_EV_COUNT
) + ei
];
1177 hData
->mFds
[fi
].mEvs
[ei
].mElevation
= -M_PI
/ 2.0 + M_PI
* ei
/ (evCounts
[fi
] - 1);
1178 hData
->mFds
[fi
].mEvs
[ei
].mAzCount
= azCount
;
1179 hData
->mFds
[fi
].mEvs
[ei
].mAzs
= &hData
->mAzsBase
[azTotal
];
1180 for(ai
= 0;ai
< azCount
;ai
++)
1182 hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
].mAzimuth
= 2.0 * M_PI
* ai
/ azCount
;
1183 hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
].mIndex
= azTotal
+ ai
;
1184 hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
].mDelays
[0] = 0.0;
1185 hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
].mDelays
[1] = 0.0;
1186 hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
].mIrs
[0] = nullptr;
1187 hData
->mFds
[fi
].mEvs
[ei
].mAzs
[ai
].mIrs
[1] = nullptr;
1196 /* Parse the data set definition and process the source data, storing the
1197 * resulting data set as desired. If the input name is NULL it will read
1198 * from standard input.
1200 static int ProcessDefinition(const char *inName
, const uint outRate
, const ChannelModeT chanMode
,
1201 const bool farfield
, const uint numThreads
, const uint fftSize
, const int equalize
,
1202 const int surface
, const double limit
, const uint truncSize
, const HeadModelT model
,
1203 const double radius
, const char *outName
)
1207 fprintf(stdout
, "Using %u thread%s.\n", numThreads
, (numThreads
==1)?"":"s");
1211 fprintf(stdout
, "Reading HRIR definition from %s...\n", inName
);
1212 if(!LoadDefInput(std::cin
, nullptr, 0, inName
, fftSize
, truncSize
, outRate
, chanMode
, &hData
))
1217 std::unique_ptr
<al::ifstream
> input
{new al::ifstream
{inName
}};
1218 if(!input
->is_open())
1220 fprintf(stderr
, "Error: Could not open input file '%s'\n", inName
);
1224 char startbytes
[4]{};
1225 input
->read(startbytes
, sizeof(startbytes
));
1226 std::streamsize startbytecount
{input
->gcount()};
1227 if(startbytecount
!= sizeof(startbytes
) || !input
->good())
1229 fprintf(stderr
, "Error: Could not read input file '%s'\n", inName
);
1233 if(startbytes
[0] == '\x89' && startbytes
[1] == 'H' && startbytes
[2] == 'D'
1234 && startbytes
[3] == 'F')
1237 fprintf(stdout
, "Reading HRTF data from %s...\n", inName
);
1238 if(!LoadSofaFile(inName
, numThreads
, fftSize
, truncSize
, outRate
, chanMode
, &hData
))
1243 fprintf(stdout
, "Reading HRIR definition from %s...\n", inName
);
1244 if(!LoadDefInput(*input
, startbytes
, startbytecount
, inName
, fftSize
, truncSize
, outRate
, chanMode
, &hData
))
1251 uint c
{(hData
.mChannelType
== CT_STEREO
) ? 2u : 1u};
1252 uint m
{hData
.mFftSize
/2u + 1u};
1253 auto dfa
= std::vector
<double>(c
* m
);
1255 if(hData
.mFds
.size() > 1)
1257 fprintf(stdout
, "Balancing field magnitudes...\n");
1258 BalanceFieldMagnitudes(&hData
, c
, m
);
1260 fprintf(stdout
, "Calculating diffuse-field average...\n");
1261 CalculateDiffuseFieldAverage(&hData
, c
, m
, surface
, limit
, dfa
.data());
1262 fprintf(stdout
, "Performing diffuse-field equalization...\n");
1263 DiffuseFieldEqualize(c
, m
, dfa
.data(), &hData
);
1265 if(hData
.mFds
.size() > 1)
1267 fprintf(stdout
, "Sorting %zu fields...\n", hData
.mFds
.size());
1268 std::sort(hData
.mFds
.begin(), hData
.mFds
.end(),
1269 [](const HrirFdT
&lhs
, const HrirFdT
&rhs
) noexcept
1270 { return lhs
.mDistance
< rhs
.mDistance
; });
1273 fprintf(stdout
, "Clearing %zu near field%s...\n", hData
.mFds
.size()-1,
1274 (hData
.mFds
.size()-1 != 1) ? "s" : "");
1275 hData
.mFds
.erase(hData
.mFds
.cbegin(), hData
.mFds
.cend()-1);
1278 fprintf(stdout
, "Synthesizing missing elevations...\n");
1279 if(model
== HM_DATASET
)
1280 SynthesizeOnsets(&hData
);
1281 SynthesizeHrirs(&hData
);
1282 fprintf(stdout
, "Performing minimum phase reconstruction...\n");
1283 ReconstructHrirs(&hData
, numThreads
);
1284 fprintf(stdout
, "Truncating minimum-phase HRIRs...\n");
1285 hData
.mIrPoints
= truncSize
;
1286 fprintf(stdout
, "Normalizing final HRIRs...\n");
1287 NormalizeHrirs(&hData
);
1288 fprintf(stdout
, "Calculating impulse delays...\n");
1289 CalculateHrtds(model
, (radius
> DEFAULT_CUSTOM_RADIUS
) ? radius
: hData
.mRadius
, &hData
);
1291 const auto rateStr
= std::to_string(hData
.mIrRate
);
1292 const auto expName
= StrSubst({outName
, strlen(outName
)}, {"%r", 2},
1293 {rateStr
.data(), rateStr
.size()});
1294 fprintf(stdout
, "Creating MHR data set %s...\n", expName
.c_str());
1295 return StoreMhr(&hData
, expName
.c_str());
1298 static void PrintHelp(const char *argv0
, FILE *ofile
)
1300 fprintf(ofile
, "Usage: %s [<option>...]\n\n", argv0
);
1301 fprintf(ofile
, "Options:\n");
1302 fprintf(ofile
, " -r <rate> Change the data set sample rate to the specified value and\n");
1303 fprintf(ofile
, " resample the HRIRs accordingly.\n");
1304 fprintf(ofile
, " -m Change the data set to mono, mirroring the left ear for the\n");
1305 fprintf(ofile
, " right ear.\n");
1306 fprintf(ofile
, " -a Change the data set to single field, using the farthest field.\n");
1307 fprintf(ofile
, " -j <threads> Number of threads used to process HRIRs (default: 2).\n");
1308 fprintf(ofile
, " -f <points> Override the FFT window size (default: %u).\n", DEFAULT_FFTSIZE
);
1309 fprintf(ofile
, " -e {on|off} Toggle diffuse-field equalization (default: %s).\n", (DEFAULT_EQUALIZE
? "on" : "off"));
1310 fprintf(ofile
, " -s {on|off} Toggle surface-weighted diffuse-field average (default: %s).\n", (DEFAULT_SURFACE
? "on" : "off"));
1311 fprintf(ofile
, " -l {<dB>|none} Specify a limit to the magnitude range of the diffuse-field\n");
1312 fprintf(ofile
, " average (default: %.2f).\n", DEFAULT_LIMIT
);
1313 fprintf(ofile
, " -w <points> Specify the size of the truncation window that's applied\n");
1314 fprintf(ofile
, " after minimum-phase reconstruction (default: %u).\n", DEFAULT_TRUNCSIZE
);
1315 fprintf(ofile
, " -d {dataset| Specify the model used for calculating the head-delay timing\n");
1316 fprintf(ofile
, " sphere} values (default: %s).\n", ((DEFAULT_HEAD_MODEL
== HM_DATASET
) ? "dataset" : "sphere"));
1317 fprintf(ofile
, " -c <radius> Use a customized head radius measured to-ear in meters.\n");
1318 fprintf(ofile
, " -i <filename> Specify an HRIR definition file to use (defaults to stdin).\n");
1319 fprintf(ofile
, " -o <filename> Specify an output file. Use of '%%r' will be substituted with\n");
1320 fprintf(ofile
, " the data set sample rate.\n");
1323 // Standard command line dispatch.
1324 int main(int argc
, char *argv
[])
1326 const char *inName
= nullptr, *outName
= nullptr;
1327 uint outRate
, fftSize
;
1328 int equalize
, surface
;
1329 char *end
= nullptr;
1330 ChannelModeT chanMode
;
1341 fprintf(stdout
, "HRTF Processing and Composition Utility\n\n");
1342 PrintHelp(argv
[0], stdout
);
1346 outName
= "./oalsoft_hrtf_%r.mhr";
1348 chanMode
= CM_AllowStereo
;
1349 fftSize
= DEFAULT_FFTSIZE
;
1350 equalize
= DEFAULT_EQUALIZE
;
1351 surface
= DEFAULT_SURFACE
;
1352 limit
= DEFAULT_LIMIT
;
1354 truncSize
= DEFAULT_TRUNCSIZE
;
1355 model
= DEFAULT_HEAD_MODEL
;
1356 radius
= DEFAULT_CUSTOM_RADIUS
;
1359 while((opt
=getopt(argc
, argv
, "r:maj:f:e:s:l:w:d:c:e:i:o:h")) != -1)
1364 outRate
= static_cast<uint
>(strtoul(optarg
, &end
, 10));
1365 if(end
[0] != '\0' || outRate
< MIN_RATE
|| outRate
> MAX_RATE
)
1367 fprintf(stderr
, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg
, opt
, MIN_RATE
, MAX_RATE
);
1373 chanMode
= CM_ForceMono
;
1381 numThreads
= static_cast<uint
>(strtoul(optarg
, &end
, 10));
1382 if(end
[0] != '\0' || numThreads
> 64)
1384 fprintf(stderr
, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg
, opt
, 0, 64);
1388 numThreads
= std::thread::hardware_concurrency();
1392 fftSize
= static_cast<uint
>(strtoul(optarg
, &end
, 10));
1393 if(end
[0] != '\0' || (fftSize
&(fftSize
-1)) || fftSize
< MIN_FFTSIZE
|| fftSize
> MAX_FFTSIZE
)
1395 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
);
1401 if(strcmp(optarg
, "on") == 0)
1403 else if(strcmp(optarg
, "off") == 0)
1407 fprintf(stderr
, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg
, opt
);
1413 if(strcmp(optarg
, "on") == 0)
1415 else if(strcmp(optarg
, "off") == 0)
1419 fprintf(stderr
, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg
, opt
);
1425 if(strcmp(optarg
, "none") == 0)
1429 limit
= strtod(optarg
, &end
);
1430 if(end
[0] != '\0' || limit
< MIN_LIMIT
|| limit
> MAX_LIMIT
)
1432 fprintf(stderr
, "\nError: Got unexpected value \"%s\" for option -%c, expected between %.0f to %.0f.\n", optarg
, opt
, MIN_LIMIT
, MAX_LIMIT
);
1439 truncSize
= static_cast<uint
>(strtoul(optarg
, &end
, 10));
1440 if(end
[0] != '\0' || truncSize
< MIN_TRUNCSIZE
|| truncSize
> MAX_TRUNCSIZE
)
1442 fprintf(stderr
, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg
, opt
, MIN_TRUNCSIZE
, MAX_TRUNCSIZE
);
1448 if(strcmp(optarg
, "dataset") == 0)
1450 else if(strcmp(optarg
, "sphere") == 0)
1454 fprintf(stderr
, "\nError: Got unexpected value \"%s\" for option -%c, expected dataset or sphere.\n", optarg
, opt
);
1460 radius
= strtod(optarg
, &end
);
1461 if(end
[0] != '\0' || radius
< MIN_CUSTOM_RADIUS
|| radius
> MAX_CUSTOM_RADIUS
)
1463 fprintf(stderr
, "\nError: Got unexpected value \"%s\" for option -%c, expected between %.2f to %.2f.\n", optarg
, opt
, MIN_CUSTOM_RADIUS
, MAX_CUSTOM_RADIUS
);
1477 PrintHelp(argv
[0], stdout
);
1481 PrintHelp(argv
[0], stderr
);
1486 int ret
= ProcessDefinition(inName
, outRate
, chanMode
, farfield
, numThreads
, fftSize
, equalize
,
1487 surface
, limit
, truncSize
, model
, radius
, outName
);
1489 fprintf(stdout
, "Operation completed.\n");
1491 return EXIT_SUCCESS
;