diff options
Diffstat (limited to 'core/ambdec.cpp')
-rw-r--r-- | core/ambdec.cpp | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/core/ambdec.cpp b/core/ambdec.cpp new file mode 100644 index 00000000..8ca182c4 --- /dev/null +++ b/core/ambdec.cpp @@ -0,0 +1,306 @@ + +#include "config.h" + +#include "ambdec.h" + +#include <algorithm> +#include <cctype> +#include <cstdarg> +#include <cstddef> +#include <cstdio> +#include <iterator> +#include <sstream> +#include <string> + +#include "albit.h" +#include "alfstream.h" +#include "alspan.h" +#include "opthelpers.h" + + +namespace { + +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; + return !(endpos < buffer.length() && buffer[endpos] != '#'); +} + + +enum class ReaderScope { + Global, + Speakers, + LFMatrix, + HFMatrix, +}; + +#ifdef __USE_MINGW_ANSI_STDIO +[[gnu::format(gnu_printf,2,3)]] +#else +[[gnu::format(printf,2,3)]] +#endif +al::optional<std::string> make_error(size_t linenum, const char *fmt, ...) +{ + al::optional<std::string> ret; + auto &str = ret.emplace(); + + str.resize(256); + int printed{std::snprintf(const_cast<char*>(str.data()), str.length(), "Line %zu: ", linenum)}; + if(printed < 0) printed = 0; + auto plen = std::min(static_cast<size_t>(printed), str.length()); + + std::va_list args, args2; + va_start(args, fmt); + va_copy(args2, args); + const int msglen{std::vsnprintf(&str[plen], str.size()-plen, fmt, args)}; + if(msglen >= 0 && static_cast<size_t>(msglen) >= str.size()-plen) + { + str.resize(static_cast<size_t>(msglen) + plen + 1u); + std::vsnprintf(&str[plen], str.size()-plen, fmt, args2); + } + va_end(args2); + va_end(args); + + return ret; +} + +} // namespace + +AmbDecConf::~AmbDecConf() = default; + + +al::optional<std::string> AmbDecConf::load(const char *fname) noexcept +{ + al::ifstream f{fname}; + if(!f.is_open()) + return std::string("Failed to open file \"")+fname+"\""; + + ReaderScope scope{ReaderScope::Global}; + size_t speaker_pos{0}; + size_t lfmatrix_pos{0}; + size_t hfmatrix_pos{0}; + size_t linenum{0}; + + std::string buffer; + while(f.good() && std::getline(f, buffer)) + { + ++linenum; + + std::istringstream istr{buffer}; + std::string command{read_word(istr)}; + if(command.empty() || command[0] == '#') + continue; + + if(command == "/}") + { + if(scope == ReaderScope::Global) + return make_error(linenum, "Unexpected /} in global scope"); + scope = ReaderScope::Global; + continue; + } + + if(scope == ReaderScope::Speakers) + { + if(command == "add_spkr") + { + if(speaker_pos == NumSpeakers) + return make_error(linenum, "Too many speakers specified"); + + AmbDecConf::SpeakerConf &spkr = Speakers[speaker_pos++]; + istr >> spkr.Name; + istr >> spkr.Distance; + istr >> spkr.Azimuth; + istr >> spkr.Elevation; + istr >> spkr.Connection; + } + else + return make_error(linenum, "Unexpected speakers command: %s", command.c_str()); + } + else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix) + { + auto &gains = (scope == ReaderScope::LFMatrix) ? LFOrderGain : HFOrderGain; + auto *matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix; + auto &pos = (scope == ReaderScope::LFMatrix) ? lfmatrix_pos : hfmatrix_pos; + + if(command == "order_gain") + { + size_t toread{(ChanMask > Ambi3OrderMask) ? 5u : 4u}; + std::size_t curgain{0u}; + float value{}; + while(toread) + { + --toread; + istr >> value; + if(curgain < al::size(gains)) + gains[curgain++] = value; + } + } + else if(command == "add_row") + { + if(pos == NumSpeakers) + return make_error(linenum, "Too many matrix rows specified"); + + unsigned int mask{ChanMask}; + + AmbDecConf::CoeffArray &mtxrow = matrix[pos++]; + mtxrow.fill(0.0f); + + float value{}; + while(mask) + { + auto idx = static_cast<unsigned>(al::countr_zero(mask)); + mask &= ~(1u << idx); + + istr >> value; + if(idx < mtxrow.size()) + mtxrow[idx] = value; + } + } + else + return make_error(linenum, "Unexpected matrix command: %s", command.c_str()); + } + // Global scope commands + else if(command == "/description") + { + while(istr.good() && std::isspace(istr.peek())) + istr.ignore(); + std::getline(istr, Description); + while(!Description.empty() && std::isspace(Description.back())) + Description.pop_back(); + } + else if(command == "/version") + { + if(Version) + return make_error(linenum, "Duplicate version definition"); + istr >> Version; + if(Version != 3) + return make_error(linenum, "Unsupported version: %d", Version); + } + else if(command == "/dec/chan_mask") + { + if(ChanMask) + return make_error(linenum, "Duplicate chan_mask definition"); + istr >> std::hex >> ChanMask >> std::dec; + + if(!ChanMask || ChanMask > Ambi4OrderMask) + return make_error(linenum, "Invalid chan_mask: 0x%x", ChanMask); + if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa) + return make_error(linenum, "FuMa not compatible with over third-order"); + } + else if(command == "/dec/freq_bands") + { + if(FreqBands) + return make_error(linenum, "Duplicate freq_bands"); + istr >> FreqBands; + if(FreqBands != 1 && FreqBands != 2) + return make_error(linenum, "Invalid freq_bands: %u", FreqBands); + } + else if(command == "/dec/speakers") + { + if(NumSpeakers) + return make_error(linenum, "Duplicate speakers"); + istr >> NumSpeakers; + if(!NumSpeakers) + return make_error(linenum, "Invalid speakers: %zu", NumSpeakers); + Speakers = std::make_unique<SpeakerConf[]>(NumSpeakers); + } + else if(command == "/dec/coeff_scale") + { + if(CoeffScale != AmbDecScale::Unset) + return make_error(linenum, "Duplicate 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 + return make_error(linenum, "Unexpected coeff_scale: %s", scale.c_str()); + + if(ChanMask > Ambi3OrderMask && CoeffScale == AmbDecScale::FuMa) + return make_error(linenum, "FuMa not compatible with over third-order"); + } + else if(command == "/opt/xover_freq") + { + istr >> XOverFreq; + } + else if(command == "/opt/xover_ratio") + { + istr >> XOverRatio; + } + 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/{") + { + if(!NumSpeakers) + return make_error(linenum, "Speakers defined without a count"); + scope = ReaderScope::Speakers; + } + else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{") + { + if(!NumSpeakers) + return make_error(linenum, "Matrix defined without a speaker count"); + if(!ChanMask) + return make_error(linenum, "Matrix defined without a channel mask"); + + if(!Matrix) + { + Matrix = std::make_unique<CoeffArray[]>(NumSpeakers * FreqBands); + LFMatrix = Matrix.get(); + HFMatrix = LFMatrix + NumSpeakers*(FreqBands-1); + } + + if(FreqBands == 1) + { + if(command != "/matrix/{") + return make_error(linenum, "Unexpected \"%s\" for a single-band decoder", + command.c_str()); + scope = ReaderScope::HFMatrix; + } + else + { + if(command == "/lfmatrix/{") + scope = ReaderScope::LFMatrix; + else if(command == "/hfmatrix/{") + scope = ReaderScope::HFMatrix; + else + return make_error(linenum, "Unexpected \"%s\" for a dual-band decoder", + command.c_str()); + } + } + else if(command == "/end") + { + const auto endpos = static_cast<std::size_t>(istr.tellg()); + if(!is_at_end(buffer, endpos)) + return make_error(linenum, "Extra junk on end: %s", buffer.substr(endpos).c_str()); + + if(speaker_pos < NumSpeakers || hfmatrix_pos < NumSpeakers + || (FreqBands == 2 && lfmatrix_pos < NumSpeakers)) + return make_error(linenum, "Incomplete decoder definition"); + if(CoeffScale == AmbDecScale::Unset) + return make_error(linenum, "No coefficient scaling defined"); + + return al::nullopt; + } + else + return make_error(linenum, "Unexpected command: %s", command.c_str()); + + istr.clear(); + const auto endpos = static_cast<std::size_t>(istr.tellg()); + if(!is_at_end(buffer, endpos)) + return make_error(linenum, "Extra junk on line: %s", buffer.substr(endpos).c_str()); + buffer.clear(); + } + return make_error(linenum, "Unexpected end of file"); +} |