]> git.tdb.fi Git - ext/openal.git/blob - utils/sofa-support.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / utils / sofa-support.cpp
1 /*
2  * SOFA utility methods for inspecting SOFA file metrics and determining HRTF
3  * utility compatible layouts.
4  *
5  * Copyright (C) 2018-2019  Christopher Fitzgerald
6  * Copyright (C) 2019  Christopher Robinson
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Or visit:  http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
23  */
24
25 #include "sofa-support.h"
26
27 #include <stdio.h>
28
29 #include <algorithm>
30 #include <array>
31 #include <cmath>
32 #include <utility>
33 #include <vector>
34
35 #include "mysofa.h"
36
37
38 namespace {
39
40 using uint = unsigned int;
41 using double3 = std::array<double,3>;
42
43
44 /* Produces a sorted array of unique elements from a particular axis of the
45  * triplets array.  The filters are used to focus on particular coordinates
46  * of other axes as necessary.  The epsilons are used to constrain the
47  * equality of unique elements.
48  */
49 std::vector<double> GetUniquelySortedElems(const std::vector<double3> &aers, const uint axis,
50     const double *const (&filters)[3], const double (&epsilons)[3])
51 {
52     std::vector<double> elems;
53     for(const double3 &aer : aers)
54     {
55         const double elem{aer[axis]};
56
57         uint j;
58         for(j = 0;j < 3;j++)
59         {
60             if(filters[j] && std::abs(aer[j] - *filters[j]) > epsilons[j])
61                 break;
62         }
63         if(j < 3)
64             continue;
65
66         auto iter = elems.begin();
67         for(;iter != elems.end();++iter)
68         {
69             const double delta{elem - *iter};
70             if(delta > epsilons[axis]) continue;
71             if(delta >= -epsilons[axis]) break;
72
73             iter = elems.emplace(iter, elem);
74             break;
75         }
76         if(iter == elems.end())
77             elems.emplace_back(elem);
78     }
79     return elems;
80 }
81
82 /* Given a list of azimuths, this will produce the smallest step size that can
83  * uniformly cover the list. Ideally this will be over half, but in degenerate
84  * cases this can fall to a minimum of 5 (the lower limit).
85  */
86 double GetUniformAzimStep(const double epsilon, const std::vector<double> &elems)
87 {
88     if(elems.size() < 5) return 0.0;
89
90     /* Get the maximum count possible, given the first two elements. It would
91      * be impossible to have more than this since the first element must be
92      * included.
93      */
94     uint count{static_cast<uint>(std::ceil(360.0 / (elems[1]-elems[0])))};
95     count = std::min(count, 255u);
96
97     for(;count >= 5;--count)
98     {
99         /* Given the stepping value for this number of elements, check each
100          * multiple to ensure there's a matching element.
101          */
102         const double step{360.0 / count};
103         bool good{true};
104         size_t idx{1u};
105         for(uint mult{1u};mult < count && good;++mult)
106         {
107             const double target{step*mult + elems[0]};
108             while(idx < elems.size() && target-elems[idx] > epsilon)
109                 ++idx;
110             good &= (idx < elems.size()) && !(std::abs(target-elems[idx++]) > epsilon);
111         }
112         if(good)
113             return step;
114     }
115     return 0.0;
116 }
117
118 /* Given a list of elevations, this will produce the smallest step size that
119  * can uniformly cover the list. Ideally this will be over half, but in
120  * degenerate cases this can fall to a minimum of 5 (the lower limit).
121  */
122 double GetUniformElevStep(const double epsilon, std::vector<double> &elems)
123 {
124     if(elems.size() < 5) return 0.0;
125
126     /* Reverse the elevations so it increments starting with -90 (flipped from
127      * +90). This makes it easier to work out a proper stepping value.
128      */
129     std::reverse(elems.begin(), elems.end());
130     for(auto &v : elems) v *= -1.0;
131
132     uint count{static_cast<uint>(std::ceil(180.0 / (elems[1]-elems[0])))};
133     count = std::min(count, 255u);
134
135     double ret{0.0};
136     for(;count >= 5;--count)
137     {
138         const double step{180.0 / count};
139         bool good{true};
140         size_t idx{1u};
141         /* Elevations don't need to match all multiples if there's not enough
142          * elements to check. Missing elevations can be synthesized.
143          */
144         for(uint mult{1u};mult <= count && idx < elems.size() && good;++mult)
145         {
146             const double target{step*mult + elems[0]};
147             while(idx < elems.size() && target-elems[idx] > epsilon)
148                 ++idx;
149             good &= !(idx < elems.size()) || !(std::abs(target-elems[idx++]) > epsilon);
150         }
151         if(good)
152         {
153             ret = step;
154             break;
155         }
156     }
157     /* Re-reverse the elevations to restore the correct order. */
158     for(auto &v : elems) v *= -1.0;
159     std::reverse(elems.begin(), elems.end());
160
161     return ret;
162 }
163
164 } // namespace
165
166
167 const char *SofaErrorStr(int err)
168 {
169     switch(err)
170     {
171     case MYSOFA_OK: return "OK";
172     case MYSOFA_INVALID_FORMAT: return "Invalid format";
173     case MYSOFA_UNSUPPORTED_FORMAT: return "Unsupported format";
174     case MYSOFA_INTERNAL_ERROR: return "Internal error";
175     case MYSOFA_NO_MEMORY: return "Out of memory";
176     case MYSOFA_READ_ERROR: return "Read error";
177     }
178     return "Unknown";
179 }
180
181 std::vector<SofaField> GetCompatibleLayout(const size_t m, const float *xyzs)
182 {
183     auto aers = std::vector<double3>(m, double3{});
184     for(size_t i{0u};i < m;++i)
185     {
186         float vals[3]{xyzs[i*3], xyzs[i*3 + 1], xyzs[i*3 + 2]};
187         mysofa_c2s(&vals[0]);
188         aers[i] = {vals[0], vals[1], vals[2]};
189     }
190
191     auto radii = GetUniquelySortedElems(aers, 2, {}, {0.1, 0.1, 0.001});
192     std::vector<SofaField> fds;
193     fds.reserve(radii.size());
194
195     for(const double dist : radii)
196     {
197         auto elevs = GetUniquelySortedElems(aers, 1, {nullptr, nullptr, &dist}, {0.1, 0.1, 0.001});
198
199         /* Remove elevations that don't have a valid set of azimuths. */
200         auto invalid_elev = [&dist,&aers](const double ev) -> bool
201         {
202             auto azims = GetUniquelySortedElems(aers, 0, {nullptr, &ev, &dist}, {0.1, 0.1, 0.001});
203
204             if(std::abs(ev) > 89.999)
205                 return azims.size() != 1;
206             if(azims.empty() || !(std::abs(azims[0]) < 0.1))
207                 return true;
208             return GetUniformAzimStep(0.1, azims) <= 0.0;
209         };
210         elevs.erase(std::remove_if(elevs.begin(), elevs.end(), invalid_elev), elevs.end());
211
212         double step{GetUniformElevStep(0.1, elevs)};
213         if(step <= 0.0)
214         {
215             if(elevs.empty())
216                 fprintf(stdout, "No usable elevations on field distance %f.\n", dist);
217             else
218             {
219                 fprintf(stdout, "Non-uniform elevations on field distance %.3f.\nGot: %+.2f", dist,
220                     elevs[0]);
221                 for(size_t ei{1u};ei < elevs.size();++ei)
222                     fprintf(stdout, ", %+.2f", elevs[ei]);
223                 fputc('\n', stdout);
224             }
225             continue;
226         }
227
228         uint evStart{0u};
229         for(uint ei{0u};ei < elevs.size();ei++)
230         {
231             if(!(elevs[ei] < 0.0))
232             {
233                 fprintf(stdout, "Too many missing elevations on field distance %f.\n", dist);
234                 return fds;
235             }
236
237             double eif{(90.0+elevs[ei]) / step};
238             const double ev_start{std::round(eif)};
239
240             if(std::abs(eif - ev_start) < (0.1/step))
241             {
242                 evStart = static_cast<uint>(ev_start);
243                 break;
244             }
245         }
246
247         const auto evCount = static_cast<uint>(std::round(180.0 / step)) + 1;
248         if(evCount < 5)
249         {
250             fprintf(stdout, "Too few uniform elevations on field distance %f.\n", dist);
251             continue;
252         }
253
254         SofaField field{};
255         field.mDistance = dist;
256         field.mEvCount = evCount;
257         field.mEvStart = evStart;
258         field.mAzCounts.resize(evCount, 0u);
259         auto &azCounts = field.mAzCounts;
260
261         for(uint ei{evStart};ei < evCount;ei++)
262         {
263             double ev{-90.0 + ei*180.0/(evCount - 1)};
264             auto azims = GetUniquelySortedElems(aers, 0, {nullptr, &ev, &dist}, {0.1, 0.1, 0.001});
265
266             if(ei == 0 || ei == (evCount-1))
267             {
268                 if(azims.size() != 1)
269                 {
270                     fprintf(stdout, "Non-singular poles on field distance %f.\n", dist);
271                     return fds;
272                 }
273                 azCounts[ei] = 1;
274             }
275             else
276             {
277                 step = GetUniformAzimStep(0.1, azims);
278                 if(step <= 0.0)
279                 {
280                     fprintf(stdout, "Non-uniform azimuths on elevation %f, field distance %f.\n",
281                         ev, dist);
282                     return fds;
283                 }
284                 azCounts[ei] = static_cast<uint>(std::round(360.0f / step));
285             }
286         }
287
288         fds.emplace_back(std::move(field));
289     }
290
291     return fds;
292 }