Remove a redundant struct member
[openal-soft.git] / utils / makemhr / makemhr.cpp
blobdac6bd3c51bf19217a8be3339a4e068e6e84e1c6
1 /*
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
40 * average.
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
46 * from the paper:
48 * Modeling Interaural Time Difference Assuming a Spherical Head
49 * Joel David Miller
50 * Music 150, Musical Acoustics, Stanford University
51 * December 2, 2001
53 * The formulae for calculating the Kaiser window metrics are from the
54 * the textbook:
56 * Discrete-Time Signal Processing
57 * Alan V. Oppenheim and Ronald W. Schafer
58 * Prentice-Hall Signal Processing Series
59 * 1999
62 #define _UNICODE
63 #include "config.h"
65 #include "makemhr.h"
67 #include <algorithm>
68 #include <atomic>
69 #include <chrono>
70 #include <cmath>
71 #include <complex>
72 #include <cstdint>
73 #include <cstdio>
74 #include <cstdlib>
75 #include <cstring>
76 #include <functional>
77 #include <iostream>
78 #include <limits>
79 #include <memory>
80 #include <numeric>
81 #include <thread>
82 #include <utility>
83 #include <vector>
85 #ifdef HAVE_GETOPT
86 #include <unistd.h>
87 #else
88 #include "../getopt.h"
89 #endif
91 #include "alcomplex.h"
92 #include "alfstream.h"
93 #include "alspan.h"
94 #include "alstring.h"
95 #include "loaddef.h"
96 #include "loadsofa.h"
98 #include "win_main_utf8.h"
101 namespace {
103 using namespace std::placeholders;
105 } // namespace
107 #ifndef M_PI
108 #define M_PI (3.14159265358979323846)
109 #endif
112 HrirDataT::~HrirDataT() = default;
114 // Head model used for calculating the impulse delays.
115 enum HeadModelT {
116 HM_NONE,
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 {
159 LeftChannel = 0u,
160 RightChannel = 1u
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)
171 std::string ret;
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());
181 else
183 size_t endpos{1};
184 while(endpos < in.size() && in[endpos] != pat.front())
185 ++endpos;
186 ret.append(in.data(), endpos);
187 in = in.subspan(endpos);
190 ret.append(in.data(), in.size());
192 return ret;
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;
209 return *seed;
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));
224 out += step;
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
240 * discarded.
242 void MagnitudeResponse(const uint n, const complex_d *in, double *out)
244 const uint m = 1 + (n / 2);
245 uint i;
246 for(i = 0;i < m;i++)
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
252 * process.
254 static void LimitMagnitudeResponse(const uint n, const uint m, const double limit, const double *in, double *out)
256 double halfLim;
257 uint i, lower, upper;
258 double ave;
260 halfLim = limit / 2.0;
261 // Convert the response to dB.
262 for(i = 0;i < m;i++)
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;
267 ave = 0.0;
268 for(i = lower;i <= upper;i++)
269 ave += out[i];
270 ave /= upper - lower + 1;
271 // Keep the response within range of the average magnitude.
272 for(i = 0;i < m;i++)
273 out[i] = Clamp(out[i], ave - halfLim, ave + halfLim);
274 // Convert the response back to linear magnitude.
275 for(i = 0;i < m;i++)
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
282 * reconstructed.
284 static void MinimumPhase(const uint n, double *mags, complex_d *out)
286 const uint m{(n/2) + 1};
288 uint i;
289 for(i = 0;i < m;i++)
290 out[i] = std::log(mags[i]);
291 for(;i < n;i++)
293 mags[i] = mags[n - i];
294 out[i] = out[n - i];
296 Hilbert(n, out);
297 // Remove any DC offset the filter has.
298 mags[0] = EPSILON;
299 for(i = 0;i < n;i++)
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)
314 size_t len;
316 len = strlen(out);
317 if(fwrite(out, 1, len, fp) != len)
319 fclose(fp);
320 fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename);
321 return 0;
323 return 1;
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)
330 uint8_t out[4];
331 uint i;
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);
339 return 0;
341 return 1;
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};
350 uint fi, ei, ai, i;
351 FILE *fp;
353 if((fp=fopen(filename, "wb")) == nullptr)
355 fprintf(stderr, "\nError: Could not open MHR file '%s'.\n", filename);
356 return 0;
358 if(!WriteAscii(MHR_FORMAT, fp, filename))
359 return 0;
360 if(!WriteBin4(4, hData->mIrRate, fp, filename))
361 return 0;
362 if(!WriteBin4(1, static_cast<uint32_t>(hData->mChannelType), fp, filename))
363 return 0;
364 if(!WriteBin4(1, hData->mIrPoints, fp, filename))
365 return 0;
366 if(!WriteBin4(1, static_cast<uint>(hData->mFds.size()), fp, filename))
367 return 0;
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))
372 return 0;
373 if(!WriteBin4(1, hData->mFds[fi].mEvCount, fp, filename))
374 return 0;
375 for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
377 if(!WriteBin4(1, hData->mFds[fi].mEvs[ei].mAzCount, fp, filename))
378 return 0;
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))
401 return 0;
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;
426 fclose(fp);
427 return 1;
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;
444 double maxMag{0.0};
445 for(fi = 0;fi < hData->mFds.size();fi++)
447 maxMags[fi] = 0.0;
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++)
456 for(i = 0;i < m;i++)
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++)
476 for(i = 0;i < m;i++)
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;
492 uint fi, ei;
494 sum = 0.0;
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.
504 else
505 outerRa = 10.0f;
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
511 // the patch band.
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.
522 sum += solidAngle;
525 innerRa = outerRa;
528 for(fi = 0;fi < hData->mFds.size();fi++)
530 // Normalize the weights given the total surface coverage for all
531 // fields.
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;
547 if(weighted)
549 // Use coverage weighting to calculate the average.
550 CalculateDfWeights(hData, weights.data());
552 else
554 double weight;
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++)
574 for(i = 0;i < m;i++)
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.
587 for(i = 0;i < m;i++)
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.
593 for(i = 0;i < m;i++)
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
596 // if desired.
597 if(limit > 0.0)
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++)
618 for(i = 0;i < m;i++)
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};
635 f -= std::floor(f);
636 *a0 = i;
637 *a1 = (i + 1) % field.mEvs[ei].mAzCount;
638 *af = f;
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.
661 uint ei{};
662 if(channels > 1)
664 /* Take the polar opposite position of the desired measurement and
665 * swap the ears.
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++)
675 uint a0, a1;
676 double af;
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);
695 else
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++)
704 uint a0, a1;
705 double af;
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
711 * measurement).
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++)
737 uint a0, a1, a2, a3;
738 double af0, af1;
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);
743 double blend[4]{
744 (1.0-ef) * (1.0-af0),
745 (1.0-ef) * ( af0),
746 ( ef) * (1.0-af1),
747 ( ef) * ( af1)
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
767 * inaccurate model.
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};
780 if(oi <= 0) return;
782 for(uint ti{0u};ti < channels;ti++)
784 uint a0, a1;
785 double af;
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};
805 double lp[4]{};
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);
812 htemp[0] = lp[3];
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);
819 htemp[i] = lp[3];
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++)
830 uint a0, a1;
831 double af;
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};
850 double lp[4]{};
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);
855 htemp[0] = lp[3];
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);
862 htemp[i] = lp[3];
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;
887 uint mFftSize;
888 uint mIrPoints;
890 void Worker()
892 auto h = std::vector<complex_d>(mFftSize);
893 auto mags = std::vector<double>(mFftSize);
894 size_t m{(mFftSize/2) + 1};
896 while(1)
898 /* Load the current index to process. */
899 size_t idx{mCurrent.load()};
900 do {
901 /* If the index is at the end, we're done. */
902 if(idx >= mIrs.size())
903 return;
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. */
922 mDone.fetch_add(1);
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
932 * IRs to process.
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. */
961 size_t count;
962 do {
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());
969 fflush(stdout);
970 } while(count < reconstructor.mIrs.size());
971 fputc('\n', stdout);
973 for(auto &thrd : thrds)
975 if(thrd.joinable())
976 thrd.join();
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)
1015 * = sqrt(1 / 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;
1047 if(dlp > l)
1048 dlp = l + (rad * (al - std::acos(rad / dist)));
1049 return dlp / 343.3;
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],
1147 HrirDataT *hData)
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)
1158 return 0;
1160 hData->mEvsBase.resize(evTotal);
1161 hData->mAzsBase.resize(azTotal);
1162 hData->mFds.resize(fdCount);
1163 hData->mIrCount = azTotal;
1164 evTotal = 0;
1165 azTotal = 0;
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;
1189 azTotal += azCount;
1192 return 1;
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)
1205 HrirDataT hData;
1207 fprintf(stdout, "Using %u thread%s.\n", numThreads, (numThreads==1)?"":"s");
1208 if(!inName)
1210 inName = "stdin";
1211 fprintf(stdout, "Reading HRIR definition from %s...\n", inName);
1212 if(!LoadDefInput(std::cin, nullptr, 0, inName, fftSize, truncSize, outRate, chanMode, &hData))
1213 return 0;
1215 else
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);
1221 return 0;
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);
1230 return 0;
1233 if(startbytes[0] == '\x89' && startbytes[1] == 'H' && startbytes[2] == 'D'
1234 && startbytes[3] == 'F')
1236 input = nullptr;
1237 fprintf(stdout, "Reading HRTF data from %s...\n", inName);
1238 if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, outRate, chanMode, &hData))
1239 return 0;
1241 else
1243 fprintf(stdout, "Reading HRIR definition from %s...\n", inName);
1244 if(!LoadDefInput(*input, startbytes, startbytecount, inName, fftSize, truncSize, outRate, chanMode, &hData))
1245 return 0;
1249 if(equalize)
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; });
1271 if(farfield)
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;
1331 HeadModelT model;
1332 uint numThreads;
1333 uint truncSize;
1334 double radius;
1335 bool farfield;
1336 double limit;
1337 int opt;
1339 if(argc < 2)
1341 fprintf(stdout, "HRTF Processing and Composition Utility\n\n");
1342 PrintHelp(argv[0], stdout);
1343 exit(EXIT_SUCCESS);
1346 outName = "./oalsoft_hrtf_%r.mhr";
1347 outRate = 0;
1348 chanMode = CM_AllowStereo;
1349 fftSize = DEFAULT_FFTSIZE;
1350 equalize = DEFAULT_EQUALIZE;
1351 surface = DEFAULT_SURFACE;
1352 limit = DEFAULT_LIMIT;
1353 numThreads = 2;
1354 truncSize = DEFAULT_TRUNCSIZE;
1355 model = DEFAULT_HEAD_MODEL;
1356 radius = DEFAULT_CUSTOM_RADIUS;
1357 farfield = false;
1359 while((opt=getopt(argc, argv, "r:maj:f:e:s:l:w:d:c:e:i:o:h")) != -1)
1361 switch(opt)
1363 case 'r':
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);
1368 exit(EXIT_FAILURE);
1370 break;
1372 case 'm':
1373 chanMode = CM_ForceMono;
1374 break;
1376 case 'a':
1377 farfield = true;
1378 break;
1380 case 'j':
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);
1385 exit(EXIT_FAILURE);
1387 if(numThreads == 0)
1388 numThreads = std::thread::hardware_concurrency();
1389 break;
1391 case 'f':
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);
1396 exit(EXIT_FAILURE);
1398 break;
1400 case 'e':
1401 if(strcmp(optarg, "on") == 0)
1402 equalize = 1;
1403 else if(strcmp(optarg, "off") == 0)
1404 equalize = 0;
1405 else
1407 fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt);
1408 exit(EXIT_FAILURE);
1410 break;
1412 case 's':
1413 if(strcmp(optarg, "on") == 0)
1414 surface = 1;
1415 else if(strcmp(optarg, "off") == 0)
1416 surface = 0;
1417 else
1419 fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt);
1420 exit(EXIT_FAILURE);
1422 break;
1424 case 'l':
1425 if(strcmp(optarg, "none") == 0)
1426 limit = 0.0;
1427 else
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);
1433 exit(EXIT_FAILURE);
1436 break;
1438 case 'w':
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);
1443 exit(EXIT_FAILURE);
1445 break;
1447 case 'd':
1448 if(strcmp(optarg, "dataset") == 0)
1449 model = HM_DATASET;
1450 else if(strcmp(optarg, "sphere") == 0)
1451 model = HM_SPHERE;
1452 else
1454 fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected dataset or sphere.\n", optarg, opt);
1455 exit(EXIT_FAILURE);
1457 break;
1459 case 'c':
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);
1464 exit(EXIT_FAILURE);
1466 break;
1468 case 'i':
1469 inName = optarg;
1470 break;
1472 case 'o':
1473 outName = optarg;
1474 break;
1476 case 'h':
1477 PrintHelp(argv[0], stdout);
1478 exit(EXIT_SUCCESS);
1480 default: /* '?' */
1481 PrintHelp(argv[0], stderr);
1482 exit(EXIT_FAILURE);
1486 int ret = ProcessDefinition(inName, outRate, chanMode, farfield, numThreads, fftSize, equalize,
1487 surface, limit, truncSize, model, radius, outName);
1488 if(!ret) return -1;
1489 fprintf(stdout, "Operation completed.\n");
1491 return EXIT_SUCCESS;