1
2 #include "config.h"
3
4 #include "ambdec.h"
5
6 #include <algorithm>
7 #include <cctype>
8 #include <cstddef>
9 #include <iterator>
10 #include <sstream>
11 #include <string>
12
13 #include "alfstream.h"
14 #include "core/logging.h"
15
16
17 namespace {
18
19 template<typename T, std::size_t N>
size(const T (&)[N])20 constexpr inline std::size_t size(const T(&)[N]) noexcept
21 { return N; }
22
readline(std::istream & f,std::string & output)23 int readline(std::istream &f, std::string &output)
24 {
25 while(f.good() && f.peek() == '\n')
26 f.ignore();
27
28 return std::getline(f, output) && !output.empty();
29 }
30
read_clipped_line(std::istream & f,std::string & buffer)31 bool read_clipped_line(std::istream &f, std::string &buffer)
32 {
33 while(readline(f, buffer))
34 {
35 std::size_t pos{0};
36 while(pos < buffer.length() && std::isspace(buffer[pos]))
37 pos++;
38 buffer.erase(0, pos);
39
40 std::size_t cmtpos{buffer.find_first_of('#')};
41 if(cmtpos < buffer.length())
42 buffer.resize(cmtpos);
43 while(!buffer.empty() && std::isspace(buffer.back()))
44 buffer.pop_back();
45
46 if(!buffer.empty())
47 return true;
48 }
49 return false;
50 }
51
52
read_word(std::istream & f)53 std::string read_word(std::istream &f)
54 {
55 std::string ret;
56 f >> ret;
57 return ret;
58 }
59
is_at_end(const std::string & buffer,std::size_t endpos)60 bool is_at_end(const std::string &buffer, std::size_t endpos)
61 {
62 while(endpos < buffer.length() && std::isspace(buffer[endpos]))
63 ++endpos;
64 return !(endpos < buffer.length());
65 }
66
67
load_ambdec_speakers(AmbDecConf::SpeakerConf * spkrs,const std::size_t num_speakers,std::istream & f,std::string & buffer)68 al::optional<std::string> load_ambdec_speakers(AmbDecConf::SpeakerConf *spkrs,
69 const std::size_t num_speakers, std::istream &f, std::string &buffer)
70 {
71 size_t cur_speaker{0};
72 while(cur_speaker < num_speakers)
73 {
74 std::istringstream istr{buffer};
75
76 std::string cmd{read_word(istr)};
77 if(cmd.empty())
78 {
79 if(!read_clipped_line(f, buffer))
80 return al::make_optional<std::string>("Unexpected end of file");
81 continue;
82 }
83
84 if(cmd == "add_spkr")
85 {
86 AmbDecConf::SpeakerConf &spkr = spkrs[cur_speaker++];
87 const size_t spkr_num{cur_speaker};
88
89 istr >> spkr.Name;
90 if(istr.fail()) WARN("Name not specified for speaker %zu\n", spkr_num);
91 istr >> spkr.Distance;
92 if(istr.fail()) WARN("Distance not specified for speaker %zu\n", spkr_num);
93 istr >> spkr.Azimuth;
94 if(istr.fail()) WARN("Azimuth not specified for speaker %zu\n", spkr_num);
95 istr >> spkr.Elevation;
96 if(istr.fail()) WARN("Elevation not specified for speaker %zu\n", spkr_num);
97 istr >> spkr.Connection;
98 if(istr.fail()) TRACE("Connection not specified for speaker %zu\n", spkr_num);
99 }
100 else
101 return al::make_optional("Unexpected speakers command: "+cmd);
102
103 istr.clear();
104 const auto endpos = static_cast<std::size_t>(istr.tellg());
105 if(!is_at_end(buffer, endpos))
106 return al::make_optional("Extra junk on line: " + buffer.substr(endpos));
107 buffer.clear();
108 }
109
110 return al::nullopt;
111 }
112
load_ambdec_matrix(float (& gains)[MaxAmbiOrder+1],AmbDecConf::CoeffArray * matrix,const std::size_t maxrow,std::istream & f,std::string & buffer)113 al::optional<std::string> load_ambdec_matrix(float (&gains)[MaxAmbiOrder+1],
114 AmbDecConf::CoeffArray *matrix, const std::size_t maxrow, std::istream &f, std::string &buffer)
115 {
116 bool gotgains{false};
117 std::size_t cur{0u};
118 while(cur < maxrow)
119 {
120 std::istringstream istr{buffer};
121
122 std::string cmd{read_word(istr)};
123 if(cmd.empty())
124 {
125 if(!read_clipped_line(f, buffer))
126 return al::make_optional<std::string>("Unexpected end of file");
127 continue;
128 }
129
130 if(cmd == "order_gain")
131 {
132 std::size_t curgain{0u};
133 float value;
134 while(istr.good())
135 {
136 istr >> value;
137 if(istr.fail()) break;
138 if(!istr.eof() && !std::isspace(istr.peek()))
139 return al::make_optional("Extra junk on gain "+std::to_string(curgain+1)+": "+
140 buffer.substr(static_cast<std::size_t>(istr.tellg())));
141 if(curgain < size(gains))
142 gains[curgain++] = value;
143 }
144 std::fill(std::begin(gains)+curgain, std::end(gains), 0.0f);
145 gotgains = true;
146 }
147 else if(cmd == "add_row")
148 {
149 AmbDecConf::CoeffArray &mtxrow = matrix[cur++];
150 std::size_t curidx{0u};
151 float value{};
152 while(istr.good())
153 {
154 istr >> value;
155 if(istr.fail()) break;
156 if(!istr.eof() && !std::isspace(istr.peek()))
157 return al::make_optional("Extra junk on matrix element "+
158 std::to_string(curidx)+"x"+std::to_string(cur-1)+": "+
159 buffer.substr(static_cast<std::size_t>(istr.tellg())));
160 if(curidx < mtxrow.size())
161 mtxrow[curidx++] = value;
162 }
163 std::fill(mtxrow.begin()+curidx, mtxrow.end(), 0.0f);
164 }
165 else
166 return al::make_optional("Unexpected matrix command: "+cmd);
167
168 istr.clear();
169 const auto endpos = static_cast<std::size_t>(istr.tellg());
170 if(!is_at_end(buffer, endpos))
171 return al::make_optional("Extra junk on line: " + buffer.substr(endpos));
172 buffer.clear();
173 }
174
175 if(!gotgains)
176 return al::make_optional<std::string>("Matrix order_gain not specified");
177 return al::nullopt;
178 }
179
180 } // namespace
181
load(const char * fname)182 al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
183 {
184 al::ifstream f{fname};
185 if(!f.is_open())
186 return al::make_optional<std::string>("Failed to open file");
187
188 bool speakers_loaded{false};
189 bool matrix_loaded{false};
190 bool lfmatrix_loaded{false};
191 std::string buffer;
192 while(read_clipped_line(f, buffer))
193 {
194 std::istringstream istr{buffer};
195
196 std::string command{read_word(istr)};
197 if(command.empty())
198 return al::make_optional("Malformed line: "+buffer);
199
200 if(command == "/description")
201 istr >> Description;
202 else if(command == "/version")
203 {
204 istr >> Version;
205 if(!istr.eof() && !std::isspace(istr.peek()))
206 return al::make_optional("Extra junk after version: " +
207 buffer.substr(static_cast<std::size_t>(istr.tellg())));
208 if(Version != 3)
209 return al::make_optional("Unsupported version: "+std::to_string(Version));
210 }
211 else if(command == "/dec/chan_mask")
212 {
213 if(ChanMask)
214 return al::make_optional<std::string>("Duplicate chan_mask definition");
215
216 istr >> std::hex >> ChanMask >> std::dec;
217 if(!istr.eof() && !std::isspace(istr.peek()))
218 return al::make_optional("Extra junk after mask: " +
219 buffer.substr(static_cast<std::size_t>(istr.tellg())));
220
221 if(!ChanMask)
222 return al::make_optional("Invalid chan_mask: "+std::to_string(ChanMask));
223 }
224 else if(command == "/dec/freq_bands")
225 {
226 if(FreqBands)
227 return al::make_optional<std::string>("Duplicate freq_bands");
228
229 istr >> FreqBands;
230 if(!istr.eof() && !std::isspace(istr.peek()))
231 return al::make_optional("Extra junk after freq_bands: " +
232 buffer.substr(static_cast<std::size_t>(istr.tellg())));
233
234 if(FreqBands != 1 && FreqBands != 2)
235 return al::make_optional("Invalid freq_bands: "+std::to_string(FreqBands));
236 }
237 else if(command == "/dec/speakers")
238 {
239 if(NumSpeakers)
240 return al::make_optional<std::string>("Duplicate speakers");
241
242 istr >> NumSpeakers;
243 if(!istr.eof() && !std::isspace(istr.peek()))
244 return al::make_optional("Extra junk after speakers: " +
245 buffer.substr(static_cast<std::size_t>(istr.tellg())));
246
247 if(!NumSpeakers)
248 return al::make_optional("Invalid speakers: "+std::to_string(NumSpeakers));
249 Speakers = std::make_unique<SpeakerConf[]>(NumSpeakers);
250 }
251 else if(command == "/dec/coeff_scale")
252 {
253 std::string scale = read_word(istr);
254 if(scale == "n3d") CoeffScale = AmbDecScale::N3D;
255 else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D;
256 else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa;
257 else
258 return al::make_optional("Unexpected coeff_scale: "+scale);
259 }
260 else if(command == "/opt/xover_freq")
261 {
262 istr >> XOverFreq;
263 if(!istr.eof() && !std::isspace(istr.peek()))
264 return al::make_optional("Extra junk after xover_freq: " +
265 buffer.substr(static_cast<std::size_t>(istr.tellg())));
266 }
267 else if(command == "/opt/xover_ratio")
268 {
269 istr >> XOverRatio;
270 if(!istr.eof() && !std::isspace(istr.peek()))
271 return al::make_optional("Extra junk after xover_ratio: " +
272 buffer.substr(static_cast<std::size_t>(istr.tellg())));
273 }
274 else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp" ||
275 command == "/opt/delay_comp" || command == "/opt/level_comp")
276 {
277 /* Unused */
278 read_word(istr);
279 }
280 else if(command == "/speakers/{")
281 {
282 if(!NumSpeakers)
283 return al::make_optional<std::string>("Speakers defined without a count");
284
285 const auto endpos = static_cast<std::size_t>(istr.tellg());
286 if(!is_at_end(buffer, endpos))
287 return al::make_optional("Extra junk on line: " + buffer.substr(endpos));
288 buffer.clear();
289
290 if(auto err = load_ambdec_speakers(Speakers.get(), NumSpeakers, f, buffer))
291 return err;
292 speakers_loaded = true;
293
294 if(!read_clipped_line(f, buffer))
295 return al::make_optional<std::string>("Unexpected end of file");
296 std::istringstream istr2{buffer};
297 std::string endmark{read_word(istr2)};
298 if(endmark != "/}")
299 return al::make_optional("Expected /} after speaker definitions, got "+endmark);
300 istr.swap(istr2);
301 }
302 else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{")
303 {
304 if(!NumSpeakers)
305 return al::make_optional<std::string>("Matrix defined without a count");
306 const auto endpos = static_cast<std::size_t>(istr.tellg());
307 if(!is_at_end(buffer, endpos))
308 return al::make_optional("Extra junk on line: " + buffer.substr(endpos));
309 buffer.clear();
310
311 if(!Matrix)
312 {
313 Matrix = std::make_unique<CoeffArray[]>(NumSpeakers * FreqBands);
314 LFMatrix = Matrix.get();
315 HFMatrix = LFMatrix + NumSpeakers*(FreqBands-1);
316 }
317
318 if(FreqBands == 1)
319 {
320 if(command != "/matrix/{")
321 return al::make_optional(
322 "Unexpected \""+command+"\" type for a single-band decoder");
323 if(auto err = load_ambdec_matrix(HFOrderGain, HFMatrix, NumSpeakers, f, buffer))
324 return err;
325 matrix_loaded = true;
326 }
327 else
328 {
329 if(command == "/lfmatrix/{")
330 {
331 if(auto err=load_ambdec_matrix(LFOrderGain, LFMatrix, NumSpeakers, f, buffer))
332 return err;
333 lfmatrix_loaded = true;
334 }
335 else if(command == "/hfmatrix/{")
336 {
337 if(auto err=load_ambdec_matrix(HFOrderGain, HFMatrix, NumSpeakers, f, buffer))
338 return err;
339 matrix_loaded = true;
340 }
341 else
342 return al::make_optional(
343 "Unexpected \""+command+"\" type for a dual-band decoder");
344 }
345
346 if(!read_clipped_line(f, buffer))
347 return al::make_optional<std::string>("Unexpected end of file");
348 std::istringstream istr2{buffer};
349 std::string endmark{read_word(istr2)};
350 if(endmark != "/}")
351 return al::make_optional("Expected /} after matrix definitions, got "+endmark);
352 istr.swap(istr2);
353 }
354 else if(command == "/end")
355 {
356 const auto endpos = static_cast<std::size_t>(istr.tellg());
357 if(!is_at_end(buffer, endpos))
358 return al::make_optional("Extra junk on end: " + buffer.substr(endpos));
359
360 if(!speakers_loaded || !matrix_loaded || (FreqBands == 2 && !lfmatrix_loaded))
361 return al::make_optional<std::string>("No decoder defined");
362
363 return al::nullopt;
364 }
365 else
366 return al::make_optional("Unexpected command: " + command);
367
368 istr.clear();
369 const auto endpos = static_cast<std::size_t>(istr.tellg());
370 if(!is_at_end(buffer, endpos))
371 return al::make_optional("Extra junk on line: " + buffer.substr(endpos));
372 buffer.clear();
373 }
374 return al::make_optional<std::string>("Unexpected end of file");
375 }
376