#include "config.h" #include "ambdec.h" #include #include #include #include #include #include #include "compat.h" namespace { int readline(std::istream &f, std::string &output) { while(f.good() && f.peek() == '\n') f.ignore(); return std::getline(f, output) && !output.empty(); } bool read_clipped_line(std::istream &f, std::string &buffer) { while(readline(f, buffer)) { std::size_t pos{0}; while(pos < buffer.length() && std::isspace(buffer[pos])) pos++; buffer.erase(0, pos); std::size_t cmtpos{buffer.find_first_of('#')}; if(cmtpos < buffer.length()) buffer.resize(cmtpos); while(!buffer.empty() && std::isspace(buffer.back())) buffer.pop_back(); if(!buffer.empty()) return true; } return false; } std::string read_word(std::istream &f) { std::string ret; f >> ret; return ret; } bool is_at_end(const std::string &buffer, std::size_t endpos) { while(endpos < buffer.length() && std::isspace(buffer[endpos])) ++endpos; if(endpos < buffer.length()) return false; return true; } bool load_ambdec_speakers(AmbDecConf *conf, std::istream &f, std::string &buffer) { ALsizei cur = 0; while(cur < conf->NumSpeakers) { std::istringstream istr{buffer}; std::string cmd{read_word(istr)}; if(cmd.empty()) { if(!read_clipped_line(f, buffer)) { ERR("Unexpected end of file\n"); return false; } continue; } if(cmd == "add_spkr") { istr >> conf->Speakers[cur].Name; if(istr.fail()) WARN("Name not specified for speaker %u\n", cur+1); istr >> conf->Speakers[cur].Distance; if(istr.fail()) WARN("Distance not specified for speaker %u\n", cur+1); istr >> conf->Speakers[cur].Azimuth; if(istr.fail()) WARN("Azimuth not specified for speaker %u\n", cur+1); istr >> conf->Speakers[cur].Elevation; if(istr.fail()) WARN("Elevation not specified for speaker %u\n", cur+1); istr >> conf->Speakers[cur].Connection; if(istr.fail()) TRACE("Connection not specified for speaker %u\n", cur+1); cur++; } else { ERR("Unexpected speakers command: %s\n", cmd.c_str()); return false; } istr.clear(); std::istream::pos_type endpos{istr.tellg()}; if(!is_at_end(buffer, endpos)) { ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); return false; } buffer.clear(); } return true; } bool load_ambdec_matrix(ALfloat *gains, ALfloat (*matrix)[MAX_AMBI_COEFFS], ALsizei maxrow, std::istream &f, std::string &buffer) { bool gotgains = false; ALsizei cur = 0; while(cur < maxrow) { std::istringstream istr{buffer}; std::string cmd{read_word(istr)}; if(cmd.empty()) { if(!read_clipped_line(f, buffer)) { ERR("Unexpected end of file\n"); return false; } continue; } if(cmd == "order_gain") { ALuint curgain = 0; float value; while(istr.good()) { istr >> value; if(istr.fail()) break; if(!istr.eof() && !std::isspace(istr.peek())) { ERR("Extra junk on gain %u: %s\n", curgain+1, buffer.c_str()+istr.tellg()); return false; } if(curgain < MAX_AMBI_ORDER+1) gains[curgain++] = value; } while(curgain < MAX_AMBI_ORDER+1) gains[curgain++] = 0.0f; gotgains = true; } else if(cmd == "add_row") { ALuint curidx = 0; float value; while(istr.good()) { istr >> value; if(istr.fail()) break; if(!istr.eof() && !std::isspace(istr.peek())) { ERR("Extra junk on matrix element %ux%u: %s\n", cur, curidx, buffer.c_str()+istr.tellg()); return false; } if(curidx < MAX_AMBI_COEFFS) matrix[cur][curidx++] = value; } while(curidx < MAX_AMBI_COEFFS) matrix[cur][curidx++] = 0.0f; cur++; } else { ERR("Unexpected matrix command: %s\n", cmd.c_str()); return false; } istr.clear(); std::istream::pos_type endpos{istr.tellg()}; if(!is_at_end(buffer, endpos)) { ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); return false; } buffer.clear(); } if(!gotgains) { ERR("Matrix order_gain not specified\n"); return false; } return true; } } // namespace int AmbDecConf::load(const char *fname) noexcept { al::ifstream f{fname}; if(!f.is_open()) { ERR("Failed to open: %s\n", fname); return 0; } std::string buffer; while(read_clipped_line(f, buffer)) { std::istringstream istr{buffer}; std::string command{read_word(istr)}; if(command.empty()) { ERR("Malformed line: %s\n", buffer.c_str()); return 0; } if(command == "/description") istr >> Description; else if(command == "/version") { istr >> Version; if(!istr.eof() && !std::isspace(istr.peek())) { ERR("Extra junk after version: %s\n", buffer.c_str()+istr.tellg()); return 0; } if(Version != 3) { ERR("Unsupported version: %u\n", Version); return 0; } } else if(command == "/dec/chan_mask") { istr >> std::hex >> ChanMask >> std::dec; if(!istr.eof() && !std::isspace(istr.peek())) { ERR("Extra junk after mask: %s\n", buffer.c_str()+istr.tellg()); return 0; } } else if(command == "/dec/freq_bands") { istr >> FreqBands; if(!istr.eof() && !std::isspace(istr.peek())) { ERR("Extra junk after freq_bands: %s\n", buffer.c_str()+istr.tellg()); return 0; } if(FreqBands != 1 && FreqBands != 2) { ERR("Invalid freq_bands value: %u\n", FreqBands); return 0; } } else if(command == "/dec/speakers") { istr >> NumSpeakers; if(!istr.eof() && !std::isspace(istr.peek())) { ERR("Extra junk after speakers: %s\n", buffer.c_str()+istr.tellg()); return 0; } if(NumSpeakers > MAX_OUTPUT_CHANNELS) { ERR("Unsupported speaker count: %u\n", NumSpeakers); return 0; } } else if(command == "/dec/coeff_scale") { std::string scale = read_word(istr); if(scale == "n3d") CoeffScale = AmbDecScale::N3D; else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D; else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa; else { ERR("Unsupported coeff scale: %s\n", scale.c_str()); return 0; } } else if(command == "/opt/xover_freq") { istr >> XOverFreq; if(!istr.eof() && !std::isspace(istr.peek())) { ERR("Extra junk after xover_freq: %s\n", buffer.c_str()+istr.tellg()); return 0; } } else if(command == "/opt/xover_ratio") { istr >> XOverRatio; if(!istr.eof() && !std::isspace(istr.peek())) { ERR("Extra junk after xover_ratio: %s\n", buffer.c_str()+istr.tellg()); return 0; } } else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp" || command == "/opt/delay_comp" || command == "/opt/level_comp") { /* Unused */ read_word(istr); } else if(command == "/speakers/{") { std::istream::pos_type endpos{istr.tellg()}; if(!is_at_end(buffer, endpos)) { ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); return 0; } buffer.clear(); if(!load_ambdec_speakers(this, f, buffer)) return 0; if(!read_clipped_line(f, buffer)) { ERR("Unexpected end of file\n"); return 0; } std::istringstream istr2{buffer}; std::string endmark{read_word(istr2)}; if(endmark != "/}") { ERR("Expected /} after speaker definitions, got %s\n", endmark.c_str()); return 0; } istr.swap(istr2); } else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{") { std::istream::pos_type endpos{istr.tellg()}; if(!is_at_end(buffer, endpos)) { ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); return 0; } buffer.clear(); if(FreqBands == 1) { if(command != "/matrix/{") { ERR("Unexpected \"%s\" type for a single-band decoder\n", command.c_str()); return 0; } if(!load_ambdec_matrix(HFOrderGain, HFMatrix, NumSpeakers, f, buffer)) return 0; } else { if(command == "/lfmatrix/{") { if(!load_ambdec_matrix(LFOrderGain, LFMatrix, NumSpeakers, f, buffer)) return 0; } else if(command == "/hfmatrix/{") { if(!load_ambdec_matrix(HFOrderGain, HFMatrix, NumSpeakers, f, buffer)) return 0; } else { ERR("Unexpected \"%s\" type for a dual-band decoder\n", command.c_str()); return 0; } } if(!read_clipped_line(f, buffer)) { ERR("Unexpected end of file\n"); return 0; } std::istringstream istr2{buffer}; std::string endmark{read_word(istr2)}; if(endmark != "/}") { ERR("Expected /} after matrix definitions, got %s\n", endmark.c_str()); return 0; } istr.swap(istr2); } else if(command == "/end") { std::istream::pos_type endpos{istr.tellg()}; if(!is_at_end(buffer, endpos)) { ERR("Unexpected junk on end: %s\n", buffer.c_str()+endpos); return 0; } return 1; } else { ERR("Unexpected command: %s\n", command.c_str()); return 0; } istr.clear(); std::istream::pos_type endpos{istr.tellg()}; if(!is_at_end(buffer, endpos)) { ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos); return 0; } buffer.clear(); } ERR("Unexpected end of file\n"); return 0; }