]> git.tdb.fi Git - ext/openal.git/blob - utils/makemhr/loadsofa.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / utils / makemhr / loadsofa.cpp
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
24 #include "loadsofa.h"
25
26 #include <algorithm>
27 #include <atomic>
28 #include <chrono>
29 #include <cmath>
30 #include <cstdio>
31 #include <functional>
32 #include <future>
33 #include <iterator>
34 #include <memory>
35 #include <numeric>
36 #include <string>
37 #include <thread>
38 #include <vector>
39
40 #include "aloptional.h"
41 #include "alspan.h"
42 #include "makemhr.h"
43 #include "polyphase_resampler.h"
44 #include "sofa-support.h"
45
46 #include "mysofa.h"
47
48
49 using uint = unsigned int;
50
51 /* Attempts to produce a compatible layout.  Most data sets tend to be
52  * uniform and have the same major axis as used by OpenAL Soft's HRTF model.
53  * This will remove outliers and produce a maximally dense layout when
54  * possible.  Those sets that contain purely random measurements or use
55  * different major axes will fail.
56  */
57 static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData)
58 {
59     fprintf(stdout, "Detecting compatible layout...\n");
60
61     auto fds = GetCompatibleLayout(m, xyzs);
62     if(fds.size() > MAX_FD_COUNT)
63     {
64         fprintf(stdout, "Incompatible layout (inumerable radii).\n");
65         return false;
66     }
67
68     double distances[MAX_FD_COUNT]{};
69     uint evCounts[MAX_FD_COUNT]{};
70     auto azCounts = std::vector<std::array<uint,MAX_EV_COUNT>>(MAX_FD_COUNT);
71     for(auto &azs : azCounts) azs.fill(0u);
72
73     uint fi{0u}, ir_total{0u};
74     for(const auto &field : fds)
75     {
76         distances[fi] = field.mDistance;
77         evCounts[fi] = field.mEvCount;
78
79         for(uint ei{0u};ei < field.mEvStart;ei++)
80             azCounts[fi][ei] = field.mAzCounts[field.mEvCount-ei-1];
81         for(uint ei{field.mEvStart};ei < field.mEvCount;ei++)
82         {
83             azCounts[fi][ei] = field.mAzCounts[ei];
84             ir_total += field.mAzCounts[ei];
85         }
86
87         ++fi;
88     }
89     fprintf(stdout, "Using %u of %u IRs.\n", ir_total, m);
90     const auto azs = al::as_span(azCounts).first<MAX_FD_COUNT>();
91     return PrepareHrirData({distances, fi}, evCounts, azs, hData);
92 }
93
94
95 float GetSampleRate(MYSOFA_HRTF *sofaHrtf)
96 {
97     const char *srate_dim{nullptr};
98     const char *srate_units{nullptr};
99     MYSOFA_ARRAY *srate_array{&sofaHrtf->DataSamplingRate};
100     MYSOFA_ATTRIBUTE *srate_attrs{srate_array->attributes};
101     while(srate_attrs)
102     {
103         if(std::string{"DIMENSION_LIST"} == srate_attrs->name)
104         {
105             if(srate_dim)
106             {
107                 fprintf(stderr, "Duplicate SampleRate.DIMENSION_LIST\n");
108                 return 0.0f;
109             }
110             srate_dim = srate_attrs->value;
111         }
112         else if(std::string{"Units"} == srate_attrs->name)
113         {
114             if(srate_units)
115             {
116                 fprintf(stderr, "Duplicate SampleRate.Units\n");
117                 return 0.0f;
118             }
119             srate_units = srate_attrs->value;
120         }
121         else
122             fprintf(stderr, "Unexpected sample rate attribute: %s = %s\n", srate_attrs->name,
123                 srate_attrs->value);
124         srate_attrs = srate_attrs->next;
125     }
126     if(!srate_dim)
127     {
128         fprintf(stderr, "Missing sample rate dimensions\n");
129         return 0.0f;
130     }
131     if(srate_dim != std::string{"I"})
132     {
133         fprintf(stderr, "Unsupported sample rate dimensions: %s\n", srate_dim);
134         return 0.0f;
135     }
136     if(!srate_units)
137     {
138         fprintf(stderr, "Missing sample rate unit type\n");
139         return 0.0f;
140     }
141     if(srate_units != std::string{"hertz"})
142     {
143         fprintf(stderr, "Unsupported sample rate unit type: %s\n", srate_units);
144         return 0.0f;
145     }
146     /* I dimensions guarantees 1 element, so just extract it. */
147     if(srate_array->values[0] < MIN_RATE || srate_array->values[0] > MAX_RATE)
148     {
149         fprintf(stderr, "Sample rate out of range: %f (expected %u to %u)", srate_array->values[0],
150             MIN_RATE, MAX_RATE);
151         return 0.0f;
152     }
153     return srate_array->values[0];
154 }
155
156 enum class DelayType : uint8_t {
157     None,
158     I_R, /* [1][Channels] */
159     M_R, /* [HRIRs][Channels] */
160     Invalid,
161 };
162 DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf)
163 {
164     const char *delay_dim{nullptr};
165     MYSOFA_ARRAY *delay_array{&sofaHrtf->DataDelay};
166     MYSOFA_ATTRIBUTE *delay_attrs{delay_array->attributes};
167     while(delay_attrs)
168     {
169         if(std::string{"DIMENSION_LIST"} == delay_attrs->name)
170         {
171             if(delay_dim)
172             {
173                 fprintf(stderr, "Duplicate Delay.DIMENSION_LIST\n");
174                 return DelayType::Invalid;
175             }
176             delay_dim = delay_attrs->value;
177         }
178         else
179             fprintf(stderr, "Unexpected delay attribute: %s = %s\n", delay_attrs->name,
180                 delay_attrs->value ? delay_attrs->value : "<null>");
181         delay_attrs = delay_attrs->next;
182     }
183     if(!delay_dim)
184     {
185         fprintf(stderr, "Missing delay dimensions\n");
186         return DelayType::None;
187     }
188     if(delay_dim == std::string{"I,R"})
189         return DelayType::I_R;
190     else if(delay_dim == std::string{"M,R"})
191         return DelayType::M_R;
192
193     fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim);
194     return DelayType::Invalid;
195 }
196
197 bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
198 {
199     const char *ir_dim{nullptr};
200     MYSOFA_ARRAY *ir_array{&sofaHrtf->DataIR};
201     MYSOFA_ATTRIBUTE *ir_attrs{ir_array->attributes};
202     while(ir_attrs)
203     {
204         if(std::string{"DIMENSION_LIST"} == ir_attrs->name)
205         {
206             if(ir_dim)
207             {
208                 fprintf(stderr, "Duplicate IR.DIMENSION_LIST\n");
209                 return false;
210             }
211             ir_dim = ir_attrs->value;
212         }
213         else
214             fprintf(stderr, "Unexpected IR attribute: %s = %s\n", ir_attrs->name,
215                 ir_attrs->value ? ir_attrs->value : "<null>");
216         ir_attrs = ir_attrs->next;
217     }
218     if(!ir_dim)
219     {
220         fprintf(stderr, "Missing IR dimensions\n");
221         return false;
222     }
223     if(ir_dim != std::string{"M,R,N"})
224     {
225         fprintf(stderr, "Unsupported IR dimensions: %s\n", ir_dim);
226         return false;
227     }
228     return true;
229 }
230
231
232 /* Calculate the onset time of a HRIR. */
233 static constexpr int OnsetRateMultiple{10};
234 static double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n,
235     al::span<double> upsampled, const double *hrir)
236 {
237     rs.process(n, hrir, static_cast<uint>(upsampled.size()), upsampled.data());
238
239     auto abs_lt = [](const double &lhs, const double &rhs) -> bool
240     { return std::abs(lhs) < std::abs(rhs); };
241     auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt);
242     return static_cast<double>(std::distance(upsampled.cbegin(), iter)) /
243         (double{OnsetRateMultiple}*rate);
244 }
245
246 /* Calculate the magnitude response of a HRIR. */
247 static void CalcHrirMagnitude(const uint points, const uint n, al::span<complex_d> h, double *hrir)
248 {
249     auto iter = std::copy_n(hrir, points, h.begin());
250     std::fill(iter, h.end(), complex_d{0.0, 0.0});
251
252     FftForward(n, h.data());
253     MagnitudeResponse(n, h.data(), hrir);
254 }
255
256 static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType delayType,
257     const uint outRate)
258 {
259     std::atomic<uint> loaded_count{0u};
260
261     auto load_proc = [sofaHrtf,hData,delayType,outRate,&loaded_count]() -> bool
262     {
263         const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
264         hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize, 0.0);
265         double *hrirs = hData->mHrirsBase.data();
266
267         std::unique_ptr<double[]> restmp;
268         al::optional<PPhaseResampler> resampler;
269         if(outRate && outRate != hData->mIrRate)
270         {
271             resampler.emplace().init(hData->mIrRate, outRate);
272             restmp = std::make_unique<double[]>(sofaHrtf->N);
273         }
274
275         for(uint si{0u};si < sofaHrtf->M;++si)
276         {
277             loaded_count.fetch_add(1u);
278
279             float aer[3]{
280                 sofaHrtf->SourcePosition.values[3*si],
281                 sofaHrtf->SourcePosition.values[3*si + 1],
282                 sofaHrtf->SourcePosition.values[3*si + 2]
283             };
284             mysofa_c2s(aer);
285
286             if(std::abs(aer[1]) >= 89.999f)
287                 aer[0] = 0.0f;
288             else
289                 aer[0] = std::fmod(360.0f - aer[0], 360.0f);
290
291             auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(),
292                 [&aer](const HrirFdT &fld) -> bool
293                 { return (std::abs(aer[2] - fld.mDistance) < 0.001); });
294             if(field == hData->mFds.cend())
295                 continue;
296
297             const double evscale{180.0 / static_cast<double>(field->mEvs.size()-1)};
298             double ef{(90.0 + aer[1]) / evscale};
299             auto ei = static_cast<uint>(std::round(ef));
300             ef = (ef - ei) * evscale;
301             if(std::abs(ef) >= 0.1) continue;
302
303             const double azscale{360.0 / static_cast<double>(field->mEvs[ei].mAzs.size())};
304             double af{aer[0] / azscale};
305             auto ai = static_cast<uint>(std::round(af));
306             af = (af-ai) * azscale;
307             ai %= static_cast<uint>(field->mEvs[ei].mAzs.size());
308             if(std::abs(af) >= 0.1) continue;
309
310             HrirAzT *azd = &field->mEvs[ei].mAzs[ai];
311             if(azd->mIrs[0] != nullptr)
312             {
313                 fprintf(stderr, "\nMultiple measurements near [ a=%f, e=%f, r=%f ].\n",
314                     aer[0], aer[1], aer[2]);
315                 return false;
316             }
317
318             for(uint ti{0u};ti < channels;++ti)
319             {
320                 azd->mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd->mIndex)];
321                 if(!resampler)
322                     std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N],
323                         sofaHrtf->N, azd->mIrs[ti]);
324                 else
325                 {
326                     std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N],
327                         sofaHrtf->N, restmp.get());
328                     resampler->process(sofaHrtf->N, restmp.get(), hData->mIrSize, azd->mIrs[ti]);
329                 }
330             }
331
332             /* Include any per-channel or per-HRIR delays. */
333             if(delayType == DelayType::I_R)
334             {
335                 const float *delayValues{sofaHrtf->DataDelay.values};
336                 for(uint ti{0u};ti < channels;++ti)
337                     azd->mDelays[ti] = delayValues[ti] / static_cast<float>(hData->mIrRate);
338             }
339             else if(delayType == DelayType::M_R)
340             {
341                 const float *delayValues{sofaHrtf->DataDelay.values};
342                 for(uint ti{0u};ti < channels;++ti)
343                     azd->mDelays[ti] = delayValues[si*sofaHrtf->R + ti] /
344                         static_cast<float>(hData->mIrRate);
345             }
346         }
347
348         if(outRate && outRate != hData->mIrRate)
349         {
350             const double scale{static_cast<double>(outRate) / hData->mIrRate};
351             hData->mIrRate = outRate;
352             hData->mIrPoints = std::min(static_cast<uint>(std::ceil(hData->mIrPoints*scale)),
353                 hData->mIrSize);
354         }
355         return true;
356     };
357
358     std::future_status load_status{};
359     auto load_future = std::async(std::launch::async, load_proc);
360     do {
361         load_status = load_future.wait_for(std::chrono::milliseconds{50});
362         printf("\rLoading HRIRs... %u of %u", loaded_count.load(), sofaHrtf->M);
363         fflush(stdout);
364     } while(load_status != std::future_status::ready);
365     fputc('\n', stdout);
366     return load_future.get();
367 }
368
369
370 /* Calculates the frequency magnitudes of the HRIR set. Work is delegated to
371  * this struct, which runs asynchronously on one or more threads (sharing the
372  * same calculator object).
373  */
374 struct MagCalculator {
375     const uint mFftSize{};
376     const uint mIrPoints{};
377     std::vector<double*> mIrs{};
378     std::atomic<size_t> mCurrent{};
379     std::atomic<size_t> mDone{};
380
381     void Worker()
382     {
383         auto htemp = std::vector<complex_d>(mFftSize);
384
385         while(1)
386         {
387             /* Load the current index to process. */
388             size_t idx{mCurrent.load()};
389             do {
390                 /* If the index is at the end, we're done. */
391                 if(idx >= mIrs.size())
392                     return;
393                 /* Otherwise, increment the current index atomically so other
394                  * threads know to go to the next one. If this call fails, the
395                  * current index was just changed by another thread and the new
396                  * value is loaded into idx, which we'll recheck.
397                  */
398             } while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed));
399
400             CalcHrirMagnitude(mIrPoints, mFftSize, htemp, mIrs[idx]);
401
402             /* Increment the number of IRs done. */
403             mDone.fetch_add(1);
404         }
405     }
406 };
407
408 bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize,
409     const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData)
410 {
411     int err;
412     MySofaHrtfPtr sofaHrtf{mysofa_load(filename, &err)};
413     if(!sofaHrtf)
414     {
415         fprintf(stdout, "Error: Could not load %s: %s\n", filename, SofaErrorStr(err));
416         return false;
417     }
418
419     /* NOTE: Some valid SOFA files are failing this check. */
420     err = mysofa_check(sofaHrtf.get());
421     if(err != MYSOFA_OK)
422         fprintf(stderr, "Warning: Supposedly malformed source file '%s' (%s).\n", filename,
423             SofaErrorStr(err));
424
425     mysofa_tocartesian(sofaHrtf.get());
426
427     /* Make sure emitter and receiver counts are sane. */
428     if(sofaHrtf->E != 1)
429     {
430         fprintf(stderr, "%u emitters not supported\n", sofaHrtf->E);
431         return false;
432     }
433     if(sofaHrtf->R > 2 || sofaHrtf->R < 1)
434     {
435         fprintf(stderr, "%u receivers not supported\n", sofaHrtf->R);
436         return false;
437     }
438     /* Assume R=2 is a stereo measurement, and R=1 is mono left-ear-only. */
439     if(sofaHrtf->R == 2 && chanMode == CM_AllowStereo)
440         hData->mChannelType = CT_STEREO;
441     else
442         hData->mChannelType = CT_MONO;
443
444     /* Check and set the FFT and IR size. */
445     if(sofaHrtf->N > fftSize)
446     {
447         fprintf(stderr, "Sample points exceeds the FFT size.\n");
448         return false;
449     }
450     if(sofaHrtf->N < truncSize)
451     {
452         fprintf(stderr, "Sample points is below the truncation size.\n");
453         return false;
454     }
455     hData->mIrPoints = sofaHrtf->N;
456     hData->mFftSize = fftSize;
457     hData->mIrSize = std::max(1u + (fftSize/2u), sofaHrtf->N);
458
459     /* Assume a default head radius of 9cm. */
460     hData->mRadius = 0.09;
461
462     hData->mIrRate = static_cast<uint>(GetSampleRate(sofaHrtf.get()) + 0.5f);
463     if(!hData->mIrRate)
464         return false;
465
466     DelayType delayType = PrepareDelay(sofaHrtf.get());
467     if(delayType == DelayType::Invalid)
468         return false;
469
470     if(!CheckIrData(sofaHrtf.get()))
471         return false;
472     if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData))
473         return false;
474     if(!LoadResponses(sofaHrtf.get(), hData, delayType, outRate))
475         return false;
476     sofaHrtf = nullptr;
477
478     for(uint fi{0u};fi < hData->mFds.size();fi++)
479     {
480         uint ei{0u};
481         for(;ei < hData->mFds[fi].mEvs.size();ei++)
482         {
483             uint ai{0u};
484             for(;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
485             {
486                 HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
487                 if(azd.mIrs[0] != nullptr) break;
488             }
489             if(ai < hData->mFds[fi].mEvs[ei].mAzs.size())
490                 break;
491         }
492         if(ei >= hData->mFds[fi].mEvs.size())
493         {
494             fprintf(stderr, "Missing source references [ %d, *, * ].\n", fi);
495             return false;
496         }
497         hData->mFds[fi].mEvStart = ei;
498         for(;ei < hData->mFds[fi].mEvs.size();ei++)
499         {
500             for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
501             {
502                 HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
503                 if(azd.mIrs[0] == nullptr)
504                 {
505                     fprintf(stderr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai);
506                     return false;
507                 }
508             }
509         }
510     }
511
512
513     size_t hrir_total{0};
514     const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
515     double *hrirs = hData->mHrirsBase.data();
516     for(uint fi{0u};fi < hData->mFds.size();fi++)
517     {
518         for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++)
519         {
520             for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
521             {
522                 HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
523                 for(uint ti{0u};ti < channels;ti++)
524                     azd.mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd.mIndex)];
525             }
526         }
527
528         for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();ei++)
529             hrir_total += hData->mFds[fi].mEvs[ei].mAzs.size() * channels;
530     }
531
532     std::atomic<size_t> hrir_done{0};
533     auto onset_proc = [hData,channels,&hrir_done]() -> bool
534     {
535         /* Temporary buffer used to calculate the IR's onset. */
536         auto upsampled = std::vector<double>(OnsetRateMultiple * hData->mIrPoints);
537         /* This resampler is used to help detect the response onset. */
538         PPhaseResampler rs;
539         rs.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate);
540
541         for(auto &field : hData->mFds)
542         {
543             for(auto &elev : field.mEvs.subspan(field.mEvStart))
544             {
545                 for(auto &azd : elev.mAzs)
546                 {
547                     for(uint ti{0};ti < channels;ti++)
548                     {
549                         hrir_done.fetch_add(1u, std::memory_order_acq_rel);
550                         azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, hData->mIrPoints,
551                             upsampled, azd.mIrs[ti]);
552                     }
553                 }
554             }
555         }
556         return true;
557     };
558
559     std::future_status load_status{};
560     auto load_future = std::async(std::launch::async, onset_proc);
561     do {
562         load_status = load_future.wait_for(std::chrono::milliseconds{50});
563         printf("\rCalculating HRIR onsets... %zu of %zu", hrir_done.load(), hrir_total);
564         fflush(stdout);
565     } while(load_status != std::future_status::ready);
566     fputc('\n', stdout);
567     if(!load_future.get())
568         return false;
569
570     MagCalculator calculator{hData->mFftSize, hData->mIrPoints};
571     for(auto &field : hData->mFds)
572     {
573         for(auto &elev : field.mEvs.subspan(field.mEvStart))
574         {
575             for(auto &azd : elev.mAzs)
576             {
577                 for(uint ti{0};ti < channels;ti++)
578                     calculator.mIrs.push_back(azd.mIrs[ti]);
579             }
580         }
581     }
582
583     std::vector<std::thread> thrds;
584     thrds.reserve(numThreads);
585     for(size_t i{0};i < numThreads;++i)
586         thrds.emplace_back(std::mem_fn(&MagCalculator::Worker), &calculator);
587     size_t count;
588     do {
589         std::this_thread::sleep_for(std::chrono::milliseconds{50});
590         count = calculator.mDone.load();
591
592         printf("\rCalculating HRIR magnitudes... %zu of %zu", count, calculator.mIrs.size());
593         fflush(stdout);
594     } while(count != calculator.mIrs.size());
595     fputc('\n', stdout);
596
597     for(auto &thrd : thrds)
598     {
599         if(thrd.joinable())
600             thrd.join();
601     }
602     return true;
603 }