Enable proper full C++ exception handling on MSVC
[openal-soft.git] / core / ambdec.cpp
blobba3a4b8b37e15976ba7706e5b041edebf1fddc18
2 #include "config.h"
4 #include "ambdec.h"
6 #include <algorithm>
7 #include <cctype>
8 #include <cstdarg>
9 #include <cstddef>
10 #include <cstdio>
11 #include <filesystem>
12 #include <fstream>
13 #include <iterator>
14 #include <sstream>
15 #include <string>
17 #include "albit.h"
18 #include "alspan.h"
19 #include "opthelpers.h"
22 namespace {
24 std::string read_word(std::istream &f)
26 std::string ret;
27 f >> ret;
28 return ret;
31 bool is_at_end(const std::string &buffer, std::size_t endpos)
33 while(endpos < buffer.length() && std::isspace(buffer[endpos]))
34 ++endpos;
35 return !(endpos < buffer.length() && buffer[endpos] != '#');
39 enum class ReaderScope {
40 Global,
41 Speakers,
42 LFMatrix,
43 HFMatrix,
46 #ifdef __MINGW32__
47 [[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]]
48 #else
49 [[gnu::format(printf,2,3)]]
50 #endif
51 std::optional<std::string> make_error(size_t linenum, const char *fmt, ...)
53 std::optional<std::string> ret;
54 auto &str = ret.emplace();
56 str.resize(256);
57 int printed{std::snprintf(str.data(), str.length(), "Line %zu: ", linenum)};
58 if(printed < 0) printed = 0;
59 auto plen = std::min(static_cast<size_t>(printed), str.length());
61 /* NOLINTBEGIN(*-array-to-pointer-decay) */
62 std::va_list args, args2;
63 va_start(args, fmt);
64 va_copy(args2, args);
65 const int msglen{std::vsnprintf(&str[plen], str.size()-plen, fmt, args)};
66 if(msglen >= 0 && static_cast<size_t>(msglen) >= str.size()-plen)
68 str.resize(static_cast<size_t>(msglen) + plen + 1u);
69 std::vsnprintf(&str[plen], str.size()-plen, fmt, args2);
71 va_end(args2);
72 va_end(args);
73 /* NOLINTEND(*-array-to-pointer-decay) */
75 return ret;
78 } // namespace
80 AmbDecConf::~AmbDecConf() = default;
83 std::optional<std::string> AmbDecConf::load(const char *fname) noexcept
85 std::ifstream f{std::filesystem::u8path(fname)};
86 if(!f.is_open())
87 return std::string("Failed to open file \"")+fname+"\"";
89 ReaderScope scope{ReaderScope::Global};
90 size_t speaker_pos{0};
91 size_t lfmatrix_pos{0};
92 size_t hfmatrix_pos{0};
93 size_t linenum{0};
95 std::string buffer;
96 while(f.good() && std::getline(f, buffer))
98 ++linenum;
100 std::istringstream istr{buffer};
101 std::string command{read_word(istr)};
102 if(command.empty() || command[0] == '#')
103 continue;
105 if(command == "/}")
107 if(scope == ReaderScope::Global)
108 return make_error(linenum, "Unexpected /} in global scope");
109 scope = ReaderScope::Global;
110 continue;
113 if(scope == ReaderScope::Speakers)
115 if(command == "add_spkr")
117 if(speaker_pos == Speakers.size())
118 return make_error(linenum, "Too many speakers specified");
120 AmbDecConf::SpeakerConf &spkr = Speakers[speaker_pos++];
121 istr >> spkr.Name;
122 istr >> spkr.Distance;
123 istr >> spkr.Azimuth;
124 istr >> spkr.Elevation;
125 istr >> spkr.Connection;
127 else
128 return make_error(linenum, "Unexpected speakers command: %s", command.c_str());
130 else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix)
132 auto &gains = (scope == ReaderScope::LFMatrix) ? LFOrderGain : HFOrderGain;
133 auto matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix;
134 auto &pos = (scope == ReaderScope::LFMatrix) ? lfmatrix_pos : hfmatrix_pos;
136 if(command == "order_gain")
138 size_t toread{(ChanMask > Ambi3OrderMask) ? 5u : 4u};
139 std::size_t curgain{0u};
140 float value{};
141 while(toread)
143 --toread;
144 istr >> value;
145 if(curgain < std::size(gains))
146 gains[curgain++] = value;
149 else if(command == "add_row")
151 if(pos == Speakers.size())
152 return make_error(linenum, "Too many matrix rows specified");
154 unsigned int mask{ChanMask};
156 AmbDecConf::CoeffArray &mtxrow = matrix[pos++];
157 mtxrow.fill(0.0f);
159 float value{};
160 while(mask)
162 auto idx = static_cast<unsigned>(al::countr_zero(mask));
163 mask &= ~(1u << idx);
165 istr >> value;
166 if(idx < mtxrow.size())
167 mtxrow[idx] = value;
170 else
171 return make_error(linenum, "Unexpected matrix command: %s", command.c_str());
173 // Global scope commands
174 else if(command == "/description")
176 while(istr.good() && std::isspace(istr.peek()))
177 istr.ignore();
178 std::getline(istr, Description);
179 while(!Description.empty() && std::isspace(Description.back()))
180 Description.pop_back();
182 else if(command == "/version")
184 if(Version)
185 return make_error(linenum, "Duplicate version definition");
186 istr >> Version;
187 if(Version != 3)
188 return make_error(linenum, "Unsupported version: %d", Version);
190 else if(command == "/dec/chan_mask")
192 if(ChanMask)
193 return make_error(linenum, "Duplicate chan_mask definition");
194 istr >> std::hex >> ChanMask >> std::dec;
196 if(!ChanMask || ChanMask > Ambi4OrderMask)
197 return make_error(linenum, "Invalid chan_mask: 0x%x", ChanMask);
198 if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa)
199 return make_error(linenum, "FuMa not compatible with over third-order");
201 else if(command == "/dec/freq_bands")
203 if(FreqBands)
204 return make_error(linenum, "Duplicate freq_bands");
205 istr >> FreqBands;
206 if(FreqBands != 1 && FreqBands != 2)
207 return make_error(linenum, "Invalid freq_bands: %u", FreqBands);
209 else if(command == "/dec/speakers")
211 if(!Speakers.empty())
212 return make_error(linenum, "Duplicate speakers");
213 size_t numspeakers{};
214 istr >> numspeakers;
215 if(!numspeakers)
216 return make_error(linenum, "Invalid speakers: %zu", numspeakers);
217 Speakers.resize(numspeakers);
219 else if(command == "/dec/coeff_scale")
221 if(CoeffScale != AmbDecScale::Unset)
222 return make_error(linenum, "Duplicate coeff_scale");
224 std::string scale{read_word(istr)};
225 if(scale == "n3d") CoeffScale = AmbDecScale::N3D;
226 else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D;
227 else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa;
228 else
229 return make_error(linenum, "Unexpected coeff_scale: %s", scale.c_str());
231 if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa)
232 return make_error(linenum, "FuMa not compatible with over third-order");
234 else if(command == "/opt/xover_freq")
236 istr >> XOverFreq;
238 else if(command == "/opt/xover_ratio")
240 istr >> XOverRatio;
242 else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp"
243 || command == "/opt/delay_comp" || command == "/opt/level_comp")
245 /* Unused */
246 read_word(istr);
248 else if(command == "/speakers/{")
250 if(Speakers.empty())
251 return make_error(linenum, "Speakers defined without a count");
252 scope = ReaderScope::Speakers;
254 else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{")
256 if(Speakers.empty())
257 return make_error(linenum, "Matrix defined without a speaker count");
258 if(!ChanMask)
259 return make_error(linenum, "Matrix defined without a channel mask");
261 if(Matrix.empty())
263 Matrix.resize(Speakers.size() * FreqBands);
264 LFMatrix = al::span{Matrix}.first(Speakers.size());
265 HFMatrix = al::span{Matrix}.subspan(Speakers.size()*(FreqBands-1));
268 if(FreqBands == 1)
270 if(command != "/matrix/{")
271 return make_error(linenum, "Unexpected \"%s\" for a single-band decoder",
272 command.c_str());
273 scope = ReaderScope::HFMatrix;
275 else
277 if(command == "/lfmatrix/{")
278 scope = ReaderScope::LFMatrix;
279 else if(command == "/hfmatrix/{")
280 scope = ReaderScope::HFMatrix;
281 else
282 return make_error(linenum, "Unexpected \"%s\" for a dual-band decoder",
283 command.c_str());
286 else if(command == "/end")
288 const auto endpos = static_cast<std::size_t>(istr.tellg());
289 if(!is_at_end(buffer, endpos))
290 return make_error(linenum, "Extra junk on end: %s", buffer.substr(endpos).c_str());
292 if(speaker_pos < Speakers.size() || hfmatrix_pos < Speakers.size()
293 || (FreqBands == 2 && lfmatrix_pos < Speakers.size()))
294 return make_error(linenum, "Incomplete decoder definition");
295 if(CoeffScale == AmbDecScale::Unset)
296 return make_error(linenum, "No coefficient scaling defined");
298 return std::nullopt;
300 else
301 return make_error(linenum, "Unexpected command: %s", command.c_str());
303 istr.clear();
304 const auto endpos = static_cast<std::size_t>(istr.tellg());
305 if(!is_at_end(buffer, endpos))
306 return make_error(linenum, "Extra junk on line: %s", buffer.substr(endpos).c_str());
307 buffer.clear();
309 return make_error(linenum, "Unexpected end of file");