Scale B-Format panning coefficients only when needed
[openal-soft.git] / core / ambdec.cpp
blob8ca182c496bead832268b9ca548a70cc98faef08
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 <iterator>
12 #include <sstream>
13 #include <string>
15 #include "albit.h"
16 #include "alfstream.h"
17 #include "alspan.h"
18 #include "opthelpers.h"
21 namespace {
23 std::string read_word(std::istream &f)
25 std::string ret;
26 f >> ret;
27 return ret;
30 bool is_at_end(const std::string &buffer, std::size_t endpos)
32 while(endpos < buffer.length() && std::isspace(buffer[endpos]))
33 ++endpos;
34 return !(endpos < buffer.length() && buffer[endpos] != '#');
38 enum class ReaderScope {
39 Global,
40 Speakers,
41 LFMatrix,
42 HFMatrix,
45 #ifdef __USE_MINGW_ANSI_STDIO
46 [[gnu::format(gnu_printf,2,3)]]
47 #else
48 [[gnu::format(printf,2,3)]]
49 #endif
50 al::optional<std::string> make_error(size_t linenum, const char *fmt, ...)
52 al::optional<std::string> ret;
53 auto &str = ret.emplace();
55 str.resize(256);
56 int printed{std::snprintf(const_cast<char*>(str.data()), str.length(), "Line %zu: ", linenum)};
57 if(printed < 0) printed = 0;
58 auto plen = std::min(static_cast<size_t>(printed), str.length());
60 std::va_list args, args2;
61 va_start(args, fmt);
62 va_copy(args2, args);
63 const int msglen{std::vsnprintf(&str[plen], str.size()-plen, fmt, args)};
64 if(msglen >= 0 && static_cast<size_t>(msglen) >= str.size()-plen)
66 str.resize(static_cast<size_t>(msglen) + plen + 1u);
67 std::vsnprintf(&str[plen], str.size()-plen, fmt, args2);
69 va_end(args2);
70 va_end(args);
72 return ret;
75 } // namespace
77 AmbDecConf::~AmbDecConf() = default;
80 al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
82 al::ifstream f{fname};
83 if(!f.is_open())
84 return std::string("Failed to open file \"")+fname+"\"";
86 ReaderScope scope{ReaderScope::Global};
87 size_t speaker_pos{0};
88 size_t lfmatrix_pos{0};
89 size_t hfmatrix_pos{0};
90 size_t linenum{0};
92 std::string buffer;
93 while(f.good() && std::getline(f, buffer))
95 ++linenum;
97 std::istringstream istr{buffer};
98 std::string command{read_word(istr)};
99 if(command.empty() || command[0] == '#')
100 continue;
102 if(command == "/}")
104 if(scope == ReaderScope::Global)
105 return make_error(linenum, "Unexpected /} in global scope");
106 scope = ReaderScope::Global;
107 continue;
110 if(scope == ReaderScope::Speakers)
112 if(command == "add_spkr")
114 if(speaker_pos == NumSpeakers)
115 return make_error(linenum, "Too many speakers specified");
117 AmbDecConf::SpeakerConf &spkr = Speakers[speaker_pos++];
118 istr >> spkr.Name;
119 istr >> spkr.Distance;
120 istr >> spkr.Azimuth;
121 istr >> spkr.Elevation;
122 istr >> spkr.Connection;
124 else
125 return make_error(linenum, "Unexpected speakers command: %s", command.c_str());
127 else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix)
129 auto &gains = (scope == ReaderScope::LFMatrix) ? LFOrderGain : HFOrderGain;
130 auto *matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix;
131 auto &pos = (scope == ReaderScope::LFMatrix) ? lfmatrix_pos : hfmatrix_pos;
133 if(command == "order_gain")
135 size_t toread{(ChanMask > Ambi3OrderMask) ? 5u : 4u};
136 std::size_t curgain{0u};
137 float value{};
138 while(toread)
140 --toread;
141 istr >> value;
142 if(curgain < al::size(gains))
143 gains[curgain++] = value;
146 else if(command == "add_row")
148 if(pos == NumSpeakers)
149 return make_error(linenum, "Too many matrix rows specified");
151 unsigned int mask{ChanMask};
153 AmbDecConf::CoeffArray &mtxrow = matrix[pos++];
154 mtxrow.fill(0.0f);
156 float value{};
157 while(mask)
159 auto idx = static_cast<unsigned>(al::countr_zero(mask));
160 mask &= ~(1u << idx);
162 istr >> value;
163 if(idx < mtxrow.size())
164 mtxrow[idx] = value;
167 else
168 return make_error(linenum, "Unexpected matrix command: %s", command.c_str());
170 // Global scope commands
171 else if(command == "/description")
173 while(istr.good() && std::isspace(istr.peek()))
174 istr.ignore();
175 std::getline(istr, Description);
176 while(!Description.empty() && std::isspace(Description.back()))
177 Description.pop_back();
179 else if(command == "/version")
181 if(Version)
182 return make_error(linenum, "Duplicate version definition");
183 istr >> Version;
184 if(Version != 3)
185 return make_error(linenum, "Unsupported version: %d", Version);
187 else if(command == "/dec/chan_mask")
189 if(ChanMask)
190 return make_error(linenum, "Duplicate chan_mask definition");
191 istr >> std::hex >> ChanMask >> std::dec;
193 if(!ChanMask || ChanMask > Ambi4OrderMask)
194 return make_error(linenum, "Invalid chan_mask: 0x%x", ChanMask);
195 if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa)
196 return make_error(linenum, "FuMa not compatible with over third-order");
198 else if(command == "/dec/freq_bands")
200 if(FreqBands)
201 return make_error(linenum, "Duplicate freq_bands");
202 istr >> FreqBands;
203 if(FreqBands != 1 && FreqBands != 2)
204 return make_error(linenum, "Invalid freq_bands: %u", FreqBands);
206 else if(command == "/dec/speakers")
208 if(NumSpeakers)
209 return make_error(linenum, "Duplicate speakers");
210 istr >> NumSpeakers;
211 if(!NumSpeakers)
212 return make_error(linenum, "Invalid speakers: %zu", NumSpeakers);
213 Speakers = std::make_unique<SpeakerConf[]>(NumSpeakers);
215 else if(command == "/dec/coeff_scale")
217 if(CoeffScale != AmbDecScale::Unset)
218 return make_error(linenum, "Duplicate coeff_scale");
220 std::string scale{read_word(istr)};
221 if(scale == "n3d") CoeffScale = AmbDecScale::N3D;
222 else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D;
223 else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa;
224 else
225 return make_error(linenum, "Unexpected coeff_scale: %s", scale.c_str());
227 if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa)
228 return make_error(linenum, "FuMa not compatible with over third-order");
230 else if(command == "/opt/xover_freq")
232 istr >> XOverFreq;
234 else if(command == "/opt/xover_ratio")
236 istr >> XOverRatio;
238 else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp"
239 || command == "/opt/delay_comp" || command == "/opt/level_comp")
241 /* Unused */
242 read_word(istr);
244 else if(command == "/speakers/{")
246 if(!NumSpeakers)
247 return make_error(linenum, "Speakers defined without a count");
248 scope = ReaderScope::Speakers;
250 else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{")
252 if(!NumSpeakers)
253 return make_error(linenum, "Matrix defined without a speaker count");
254 if(!ChanMask)
255 return make_error(linenum, "Matrix defined without a channel mask");
257 if(!Matrix)
259 Matrix = std::make_unique<CoeffArray[]>(NumSpeakers * FreqBands);
260 LFMatrix = Matrix.get();
261 HFMatrix = LFMatrix + NumSpeakers*(FreqBands-1);
264 if(FreqBands == 1)
266 if(command != "/matrix/{")
267 return make_error(linenum, "Unexpected \"%s\" for a single-band decoder",
268 command.c_str());
269 scope = ReaderScope::HFMatrix;
271 else
273 if(command == "/lfmatrix/{")
274 scope = ReaderScope::LFMatrix;
275 else if(command == "/hfmatrix/{")
276 scope = ReaderScope::HFMatrix;
277 else
278 return make_error(linenum, "Unexpected \"%s\" for a dual-band decoder",
279 command.c_str());
282 else if(command == "/end")
284 const auto endpos = static_cast<std::size_t>(istr.tellg());
285 if(!is_at_end(buffer, endpos))
286 return make_error(linenum, "Extra junk on end: %s", buffer.substr(endpos).c_str());
288 if(speaker_pos < NumSpeakers || hfmatrix_pos < NumSpeakers
289 || (FreqBands == 2 && lfmatrix_pos < NumSpeakers))
290 return make_error(linenum, "Incomplete decoder definition");
291 if(CoeffScale == AmbDecScale::Unset)
292 return make_error(linenum, "No coefficient scaling defined");
294 return al::nullopt;
296 else
297 return make_error(linenum, "Unexpected command: %s", command.c_str());
299 istr.clear();
300 const auto endpos = static_cast<std::size_t>(istr.tellg());
301 if(!is_at_end(buffer, endpos))
302 return make_error(linenum, "Extra junk on line: %s", buffer.substr(endpos).c_str());
303 buffer.clear();
305 return make_error(linenum, "Unexpected end of file");