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