aboutsummaryrefslogtreecommitdiffstats
path: root/core/ambdec.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/ambdec.cpp')
-rw-r--r--core/ambdec.cpp306
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");
+}