🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

661 lines
20 KiB

  1. /*
  2. * HRTF utility for producing and demonstrating the process of creating an
  3. * OpenAL Soft compatible HRIR data set.
  4. *
  5. * Copyright (C) 2018-2019 Christopher Fitzgerald
  6. *
  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.
  11. *
  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.
  16. *
  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.
  20. *
  21. * Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  22. */
  23. #include <memory>
  24. #include <numeric>
  25. #include <algorithm>
  26. #include "mysofa.h"
  27. #include "loadsofa.h"
  28. static const char *SofaErrorStr(int err)
  29. {
  30. switch(err)
  31. {
  32. case MYSOFA_OK: return "OK";
  33. case MYSOFA_INVALID_FORMAT: return "Invalid format";
  34. case MYSOFA_UNSUPPORTED_FORMAT: return "Unsupported format";
  35. case MYSOFA_INTERNAL_ERROR: return "Internal error";
  36. case MYSOFA_NO_MEMORY: return "Out of memory";
  37. case MYSOFA_READ_ERROR: return "Read error";
  38. }
  39. return "Unknown";
  40. }
  41. /* Produces a sorted array of unique elements from a particular axis of the
  42. * triplets array. The filters are used to focus on particular coordinates
  43. * of other axes as necessary. The epsilons are used to constrain the
  44. * equality of unique elements.
  45. */
  46. static uint GetUniquelySortedElems(const uint m, const float *triplets, const int axis,
  47. const double *const (&filters)[3], const double (&epsilons)[3], float *elems)
  48. {
  49. uint count{0u};
  50. for(uint i{0u};i < 3*m;i += 3)
  51. {
  52. const float elem{triplets[i + axis]};
  53. uint j;
  54. for(j = 0;j < 3;j++)
  55. {
  56. if(filters[j] && std::fabs(triplets[i + j] - *filters[j]) > epsilons[j])
  57. break;
  58. }
  59. if(j < 3)
  60. continue;
  61. for(j = 0;j < count;j++)
  62. {
  63. const float delta{elem - elems[j]};
  64. if(delta > epsilons[axis])
  65. continue;
  66. if(delta >= -epsilons[axis])
  67. break;
  68. for(uint k{count};k > j;k--)
  69. elems[k] = elems[k - 1];
  70. elems[j] = elem;
  71. count++;
  72. break;
  73. }
  74. if(j >= count)
  75. elems[count++] = elem;
  76. }
  77. return count;
  78. }
  79. /* Given a list of elements, this will produce the smallest step size that
  80. * can uniformly cover a fair portion of the list. Ideally this will be over
  81. * half, but in degenerate cases this can fall to a minimum of 5 (the lower
  82. * limit on elevations necessary to build a layout).
  83. */
  84. static float GetUniformStepSize(const double epsilon, const uint m, const float *elems)
  85. {
  86. auto steps = std::vector<float>(m, 0.0f);
  87. auto counts = std::vector<uint>(m, 0u);
  88. float step{0.0f};
  89. uint count{0u};
  90. for(uint stride{1u};stride < m/2;stride++)
  91. {
  92. for(uint i{0u};i < m-stride;i++)
  93. {
  94. const float step{elems[i + stride] - elems[i]};
  95. uint j;
  96. for(j = 0;j < count;j++)
  97. {
  98. if(std::fabs(step - steps[j]) < epsilon)
  99. {
  100. counts[j]++;
  101. break;
  102. }
  103. }
  104. if(j >= count)
  105. {
  106. steps[j] = step;
  107. counts[j] = 1;
  108. count++;
  109. }
  110. }
  111. for(uint i{1u};i < count;i++)
  112. {
  113. if(counts[i] > counts[0])
  114. {
  115. steps[0] = steps[i];
  116. counts[0] = counts[i];
  117. }
  118. }
  119. count = 1;
  120. if(counts[0] > m/2)
  121. {
  122. step = steps[0];
  123. return step;
  124. }
  125. }
  126. if(counts[0] > 5)
  127. step = steps[0];
  128. return step;
  129. }
  130. /* Attempts to produce a compatible layout. Most data sets tend to be
  131. * uniform and have the same major axis as used by OpenAL Soft's HRTF model.
  132. * This will remove outliers and produce a maximally dense layout when
  133. * possible. Those sets that contain purely random measurements or use
  134. * different major axes will fail.
  135. */
  136. static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData)
  137. {
  138. std::vector<float> aers(3*m, 0.0f);
  139. std::vector<float> elems(m, 0.0f);
  140. for(uint i{0u};i < 3*m;i += 3)
  141. {
  142. aers[i] = xyzs[i];
  143. aers[i + 1] = xyzs[i + 1];
  144. aers[i + 2] = xyzs[i + 2];
  145. mysofa_c2s(&aers[i]);
  146. }
  147. const uint fdCount{GetUniquelySortedElems(m, aers.data(), 2,
  148. (const double*[3]){ nullptr, nullptr, nullptr }, (const double[3]){ 0.1, 0.1, 0.001 },
  149. elems.data())};
  150. if(fdCount > MAX_FD_COUNT)
  151. {
  152. fprintf(stdout, "Incompatible layout (inumerable radii).\n");
  153. return false;
  154. }
  155. double distances[MAX_FD_COUNT]{};
  156. uint evCounts[MAX_FD_COUNT]{};
  157. uint evStarts[MAX_FD_COUNT]{};
  158. auto azCounts = std::vector<uint>(MAX_FD_COUNT * MAX_EV_COUNT);
  159. for(uint fi{0u};fi < fdCount;fi++)
  160. {
  161. distances[fi] = elems[fi];
  162. if(fi > 0 && distances[fi] <= distances[fi-1])
  163. {
  164. fprintf(stderr, "Distances must increase.\n");
  165. return 0;
  166. }
  167. }
  168. if(distances[0] < hData->mRadius)
  169. {
  170. fprintf(stderr, "Distance cannot start below head radius.\n");
  171. return 0;
  172. }
  173. for(uint fi{0u};fi < fdCount;fi++)
  174. {
  175. const double dist{distances[fi]};
  176. uint evCount{GetUniquelySortedElems(m, aers.data(), 1,
  177. (const double*[3]){ nullptr, nullptr, &dist }, (const double[3]){ 0.1, 0.1, 0.001 },
  178. elems.data())};
  179. if(evCount > MAX_EV_COUNT)
  180. {
  181. fprintf(stderr, "Incompatible layout (innumerable elevations).\n");
  182. return false;
  183. }
  184. float step{GetUniformStepSize(0.1, evCount, elems.data())};
  185. if(step <= 0.0f)
  186. {
  187. fprintf(stderr, "Incompatible layout (non-uniform elevations).\n");
  188. return false;
  189. }
  190. uint evStart{0u};
  191. for(uint ei{0u};ei < evCount;ei++)
  192. {
  193. float ev{90.0f + elems[ei]};
  194. float eif{std::round(ev / step)};
  195. if(std::fabs(eif - (uint)eif) < (0.1f / step))
  196. {
  197. evStart = static_cast<uint>(eif);
  198. break;
  199. }
  200. }
  201. evCount = static_cast<uint>(std::round(180.0f / step)) + 1;
  202. if(evCount < 5)
  203. {
  204. fprintf(stderr, "Incompatible layout (too few uniform elevations).\n");
  205. return false;
  206. }
  207. evCounts[fi] = evCount;
  208. evStarts[fi] = evStart;
  209. for(uint ei{evStart};ei < evCount;ei++)
  210. {
  211. const double ev{-90.0 + ei*180.0/(evCount - 1)};
  212. const uint azCount{GetUniquelySortedElems(m, aers.data(), 0,
  213. (const double*[3]){ nullptr, &ev, &dist }, (const double[3]){ 0.1, 0.1, 0.001 },
  214. elems.data())};
  215. if(azCount > MAX_AZ_COUNT)
  216. {
  217. fprintf(stderr, "Incompatible layout (innumerable azimuths).\n");
  218. return false;
  219. }
  220. if(ei > 0 && ei < (evCount - 1))
  221. {
  222. step = GetUniformStepSize(0.1, azCount, elems.data());
  223. if(step <= 0.0f)
  224. {
  225. fprintf(stderr, "Incompatible layout (non-uniform azimuths).\n");
  226. return false;
  227. }
  228. azCounts[fi*MAX_EV_COUNT + ei] = static_cast<uint>(std::round(360.0f / step));
  229. }
  230. else if(azCount != 1)
  231. {
  232. fprintf(stderr, "Incompatible layout (non-singular poles).\n");
  233. return false;
  234. }
  235. else
  236. {
  237. azCounts[fi*MAX_EV_COUNT + ei] = 1;
  238. }
  239. }
  240. for(uint ei{0u};ei < evStart;ei++)
  241. azCounts[fi*MAX_EV_COUNT + ei] = azCounts[fi*MAX_EV_COUNT + evCount - ei - 1];
  242. }
  243. return PrepareHrirData(fdCount, distances, evCounts, azCounts.data(), hData) != 0;
  244. }
  245. bool PrepareSampleRate(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
  246. {
  247. const char *srate_dim{nullptr};
  248. const char *srate_units{nullptr};
  249. MYSOFA_ARRAY *srate_array{&sofaHrtf->DataSamplingRate};
  250. MYSOFA_ATTRIBUTE *srate_attrs{srate_array->attributes};
  251. while(srate_attrs)
  252. {
  253. if(std::string{"DIMENSION_LIST"} == srate_attrs->name)
  254. {
  255. if(srate_dim)
  256. {
  257. fprintf(stderr, "Duplicate SampleRate.DIMENSION_LIST\n");
  258. return false;
  259. }
  260. srate_dim = srate_attrs->value;
  261. }
  262. else if(std::string{"Units"} == srate_attrs->name)
  263. {
  264. if(srate_units)
  265. {
  266. fprintf(stderr, "Duplicate SampleRate.Units\n");
  267. return false;
  268. }
  269. srate_units = srate_attrs->value;
  270. }
  271. else
  272. fprintf(stderr, "Unexpected sample rate attribute: %s = %s\n", srate_attrs->name,
  273. srate_attrs->value);
  274. srate_attrs = srate_attrs->next;
  275. }
  276. if(!srate_dim)
  277. {
  278. fprintf(stderr, "Missing sample rate dimensions\n");
  279. return false;
  280. }
  281. if(srate_dim != std::string{"I"})
  282. {
  283. fprintf(stderr, "Unsupported sample rate dimensions: %s\n", srate_dim);
  284. return false;
  285. }
  286. if(!srate_units)
  287. {
  288. fprintf(stderr, "Missing sample rate unit type\n");
  289. return false;
  290. }
  291. if(srate_units != std::string{"hertz"})
  292. {
  293. fprintf(stderr, "Unsupported sample rate unit type: %s\n", srate_units);
  294. return false;
  295. }
  296. /* I dimensions guarantees 1 element, so just extract it. */
  297. hData->mIrRate = static_cast<uint>(srate_array->values[0] + 0.5f);
  298. if(hData->mIrRate < MIN_RATE || hData->mIrRate > MAX_RATE)
  299. {
  300. fprintf(stderr, "Sample rate out of range: %u (expected %u to %u)", hData->mIrRate,
  301. MIN_RATE, MAX_RATE);
  302. return false;
  303. }
  304. return true;
  305. }
  306. bool PrepareDelay(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
  307. {
  308. const char *delay_dim{nullptr};
  309. MYSOFA_ARRAY *delay_array{&sofaHrtf->DataDelay};
  310. MYSOFA_ATTRIBUTE *delay_attrs{delay_array->attributes};
  311. while(delay_attrs)
  312. {
  313. if(std::string{"DIMENSION_LIST"} == delay_attrs->name)
  314. {
  315. if(delay_dim)
  316. {
  317. fprintf(stderr, "Duplicate Delay.DIMENSION_LIST\n");
  318. return false;
  319. }
  320. delay_dim = delay_attrs->value;
  321. }
  322. else
  323. fprintf(stderr, "Unexpected delay attribute: %s = %s\n", delay_attrs->name,
  324. delay_attrs->value);
  325. delay_attrs = delay_attrs->next;
  326. }
  327. if(!delay_dim)
  328. {
  329. fprintf(stderr, "Missing delay dimensions\n");
  330. /*return false;*/
  331. }
  332. else if(delay_dim != std::string{"I,R"})
  333. {
  334. fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim);
  335. return false;
  336. }
  337. else if(hData->mChannelType == CT_STEREO)
  338. {
  339. /* I,R is 1xChannelCount. Makemhr currently removes any delay constant,
  340. * so we can ignore this as long as it's equal.
  341. */
  342. if(delay_array->values[0] != delay_array->values[1])
  343. {
  344. fprintf(stderr, "Mismatched delays not supported: %f, %f\n", delay_array->values[0],
  345. delay_array->values[1]);
  346. return false;
  347. }
  348. }
  349. return true;
  350. }
  351. bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
  352. {
  353. const char *ir_dim{nullptr};
  354. MYSOFA_ARRAY *ir_array{&sofaHrtf->DataIR};
  355. MYSOFA_ATTRIBUTE *ir_attrs{ir_array->attributes};
  356. while(ir_attrs)
  357. {
  358. if(std::string{"DIMENSION_LIST"} == ir_attrs->name)
  359. {
  360. if(ir_dim)
  361. {
  362. fprintf(stderr, "Duplicate IR.DIMENSION_LIST\n");
  363. return false;
  364. }
  365. ir_dim = ir_attrs->value;
  366. }
  367. else
  368. fprintf(stderr, "Unexpected IR attribute: %s = %s\n", ir_attrs->name,
  369. ir_attrs->value);
  370. ir_attrs = ir_attrs->next;
  371. }
  372. if(!ir_dim)
  373. {
  374. fprintf(stderr, "Missing IR dimensions\n");
  375. return false;
  376. }
  377. if(ir_dim != std::string{"M,R,N"})
  378. {
  379. fprintf(stderr, "Unsupported IR dimensions: %s\n", ir_dim);
  380. return false;
  381. }
  382. return true;
  383. }
  384. /* Calculate the onset time of a HRIR. */
  385. static double CalcHrirOnset(const uint rate, const uint n, std::vector<double> &upsampled,
  386. const double *hrir)
  387. {
  388. {
  389. ResamplerT rs;
  390. ResamplerSetup(&rs, rate, 10 * rate);
  391. ResamplerRun(&rs, n, hrir, 10 * n, upsampled.data());
  392. }
  393. double mag{std::accumulate(upsampled.cbegin(), upsampled.cend(), double{0.0},
  394. [](const double mag, const double sample) -> double
  395. { return std::max(mag, std::abs(sample)); })};
  396. mag *= 0.15;
  397. auto iter = std::find_if(upsampled.cbegin(), upsampled.cend(),
  398. [mag](const double sample) -> bool { return (std::abs(sample) >= mag); });
  399. return static_cast<double>(std::distance(upsampled.cbegin(), iter)) / (10.0*rate);
  400. }
  401. /* Calculate the magnitude response of a HRIR. */
  402. static void CalcHrirMagnitude(const uint points, const uint n, std::vector<complex_d> &h,
  403. const double *hrir, double *mag)
  404. {
  405. auto iter = std::copy_n(hrir, points, h.begin());
  406. std::fill(iter, h.end(), complex_d{0.0, 0.0});
  407. FftForward(n, h.data());
  408. MagnitudeResponse(n, h.data(), mag);
  409. }
  410. static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
  411. {
  412. const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
  413. hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize);
  414. double *hrirs = hData->mHrirsBase.data();
  415. /* Temporary buffers used to calculate the IR's onset and frequency
  416. * magnitudes.
  417. */
  418. auto upsampled = std::vector<double>(10 * hData->mIrPoints);
  419. auto htemp = std::vector<complex_d>(hData->mFftSize);
  420. auto hrir = std::vector<double>(hData->mFftSize);
  421. for(uint si{0u};si < sofaHrtf->M;si++)
  422. {
  423. printf("\rLoading HRIRs... %d of %d", si+1, sofaHrtf->M);
  424. fflush(stdout);
  425. float aer[3]{
  426. sofaHrtf->SourcePosition.values[3*si],
  427. sofaHrtf->SourcePosition.values[3*si + 1],
  428. sofaHrtf->SourcePosition.values[3*si + 2]
  429. };
  430. mysofa_c2s(aer);
  431. if(std::abs(aer[1]) >= 89.999f)
  432. aer[0] = 0.0f;
  433. else
  434. aer[0] = std::fmod(360.0f - aer[0], 360.0f);
  435. auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(),
  436. [&aer](const HrirFdT &fld) -> bool
  437. {
  438. double delta = aer[2] - fld.mDistance;
  439. return (std::abs(delta) < 0.001);
  440. });
  441. if(field == hData->mFds.cend())
  442. continue;
  443. double ef{(90.0+aer[1]) * (field->mEvCount-1) / 180.0};
  444. auto ei = static_cast<int>(std::round(ef));
  445. ef = (ef-ei) * 180.0f / (field->mEvCount-1);
  446. if(std::abs(ef) >= 0.1) continue;
  447. double af{aer[0] * field->mEvs[ei].mAzCount / 360.0f};
  448. auto ai = static_cast<int>(std::round(af));
  449. af = (af-ai) * 360.0f / field->mEvs[ei].mAzCount;
  450. ai %= field->mEvs[ei].mAzCount;
  451. if(std::abs(af) >= 0.1) continue;
  452. HrirAzT *azd = &field->mEvs[ei].mAzs[ai];
  453. if(azd->mIrs[0] != nullptr)
  454. {
  455. fprintf(stderr, "Multiple measurements near [ a=%f, e=%f, r=%f ].\n",
  456. aer[0], aer[1], aer[2]);
  457. return false;
  458. }
  459. for(uint ti{0u};ti < channels;++ti)
  460. {
  461. std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N],
  462. hData->mIrPoints, hrir.begin());
  463. azd->mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd->mIndex)];
  464. azd->mDelays[ti] = CalcHrirOnset(hData->mIrRate, hData->mIrPoints, upsampled,
  465. hrir.data());
  466. CalcHrirMagnitude(hData->mIrPoints, hData->mFftSize, htemp, hrir.data(),
  467. azd->mIrs[ti]);
  468. }
  469. // TODO: Since some SOFA files contain minimum phase HRIRs,
  470. // it would be beneficial to check for per-measurement delays
  471. // (when available) to reconstruct the HRTDs.
  472. }
  473. printf("\n");
  474. return true;
  475. }
  476. struct MySofaHrtfDeleter {
  477. void operator()(MYSOFA_HRTF *ptr) { mysofa_free(ptr); }
  478. };
  479. using MySofaHrtfPtr = std::unique_ptr<MYSOFA_HRTF,MySofaHrtfDeleter>;
  480. bool LoadSofaFile(const char *filename, const uint fftSize, const uint truncSize,
  481. const ChannelModeT chanMode, HrirDataT *hData)
  482. {
  483. int err;
  484. MySofaHrtfPtr sofaHrtf{mysofa_load(filename, &err)};
  485. if(!sofaHrtf)
  486. {
  487. fprintf(stdout, "Error: Could not load %s: %s\n", filename, SofaErrorStr(err));
  488. return false;
  489. }
  490. err = mysofa_check(sofaHrtf.get());
  491. if(err != MYSOFA_OK)
  492. /* NOTE: Some valid SOFA files are failing this check.
  493. {
  494. fprintf(stdout, "Error: Malformed source file '%s' (%s).\n", filename, SofaErrorStr(err));
  495. return false;
  496. }
  497. */
  498. fprintf(stderr, "Warning: Supposedly malformed source file '%s' (%s).\n", filename,
  499. SofaErrorStr(err));
  500. mysofa_tocartesian(sofaHrtf.get());
  501. /* Make sure emitter and receiver counts are sane. */
  502. if(sofaHrtf->E != 1)
  503. {
  504. fprintf(stderr, "%u emitters not supported\n", sofaHrtf->E);
  505. return false;
  506. }
  507. if(sofaHrtf->R > 2 || sofaHrtf->R < 1)
  508. {
  509. fprintf(stderr, "%u receivers not supported\n", sofaHrtf->R);
  510. return false;
  511. }
  512. /* Assume R=2 is a stereo measurement, and R=1 is mono left-ear-only. */
  513. if(sofaHrtf->R == 2 && chanMode == CM_AllowStereo)
  514. hData->mChannelType = CT_STEREO;
  515. else
  516. hData->mChannelType = CT_MONO;
  517. /* Check and set the FFT and IR size. */
  518. if(sofaHrtf->N > fftSize)
  519. {
  520. fprintf(stderr, "Sample points exceeds the FFT size.\n");
  521. return false;
  522. }
  523. if(sofaHrtf->N < truncSize)
  524. {
  525. fprintf(stderr, "Sample points is below the truncation size.\n");
  526. return false;
  527. }
  528. hData->mIrPoints = sofaHrtf->N;
  529. hData->mFftSize = fftSize;
  530. hData->mIrSize = std::max(1u + (fftSize/2u), sofaHrtf->N);
  531. /* Assume a default head radius of 9cm. */
  532. hData->mRadius = 0.09;
  533. if(!PrepareSampleRate(sofaHrtf.get(), hData) || !PrepareDelay(sofaHrtf.get(), hData) ||
  534. !CheckIrData(sofaHrtf.get()))
  535. return false;
  536. if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData))
  537. return false;
  538. if(!LoadResponses(sofaHrtf.get(), hData))
  539. return false;
  540. sofaHrtf = nullptr;
  541. for(uint fi{0u};fi < hData->mFdCount;fi++)
  542. {
  543. uint ei{0u};
  544. for(;ei < hData->mFds[fi].mEvCount;ei++)
  545. {
  546. uint ai{0u};
  547. for(;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
  548. {
  549. HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
  550. if(azd.mIrs[0] != nullptr) break;
  551. }
  552. if(ai < hData->mFds[fi].mEvs[ei].mAzCount)
  553. break;
  554. }
  555. if(ei >= hData->mFds[fi].mEvCount)
  556. {
  557. fprintf(stderr, "Missing source references [ %d, *, * ].\n", fi);
  558. return false;
  559. }
  560. hData->mFds[fi].mEvStart = ei;
  561. for(;ei < hData->mFds[fi].mEvCount;ei++)
  562. {
  563. for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
  564. {
  565. HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
  566. if(azd.mIrs[0] == nullptr)
  567. {
  568. fprintf(stderr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai);
  569. return false;
  570. }
  571. }
  572. }
  573. }
  574. const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
  575. double *hrirs = hData->mHrirsBase.data();
  576. for(uint fi{0u};fi < hData->mFdCount;fi++)
  577. {
  578. for(uint ei{0u};ei < hData->mFds[fi].mEvCount;ei++)
  579. {
  580. for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
  581. {
  582. HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
  583. for(uint ti{0u};ti < channels;ti++)
  584. azd.mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd.mIndex)];
  585. }
  586. }
  587. }
  588. return true;
  589. }