aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2020-12-25 06:30:47 -0800
committerChris Robinson <[email protected]>2020-12-25 06:30:47 -0800
commit20820fd01beb265722d8521ad725c3c479800273 (patch)
treea015e542f7a39b1c9945f995e4b5ea43f05ae1d3 /core
parentfe9ec157fd0ea647452c4894209c4016fffac682 (diff)
Move the ambdec loader to core
Diffstat (limited to 'core')
-rw-r--r--core/ambdec.cpp434
-rw-r--r--core/ambdec.h48
2 files changed, 482 insertions, 0 deletions
diff --git a/core/ambdec.cpp b/core/ambdec.cpp
new file mode 100644
index 00000000..a50d6d95
--- /dev/null
+++ b/core/ambdec.cpp
@@ -0,0 +1,434 @@
+
+#include "config.h"
+
+#include "ambdec.h"
+
+#include <algorithm>
+#include <cctype>
+#include <cstddef>
+#include <iterator>
+#include <sstream>
+#include <string>
+
+#include "alfstream.h"
+#include "core/logging.h"
+
+
+namespace {
+
+template<typename T, std::size_t N>
+constexpr inline std::size_t size(const T(&)[N]) noexcept
+{ return N; }
+
+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;
+ return !(endpos < buffer.length());
+}
+
+
+bool load_ambdec_speakers(al::vector<AmbDecConf::SpeakerConf> &spkrs, const std::size_t num_speakers, std::istream &f, std::string &buffer)
+{
+ while(spkrs.size() < num_speakers)
+ {
+ 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")
+ {
+ spkrs.emplace_back();
+ AmbDecConf::SpeakerConf &spkr = spkrs.back();
+ const size_t spkr_num{spkrs.size()};
+
+ istr >> spkr.Name;
+ if(istr.fail()) WARN("Name not specified for speaker %zu\n", spkr_num);
+ istr >> spkr.Distance;
+ if(istr.fail()) WARN("Distance not specified for speaker %zu\n", spkr_num);
+ istr >> spkr.Azimuth;
+ if(istr.fail()) WARN("Azimuth not specified for speaker %zu\n", spkr_num);
+ istr >> spkr.Elevation;
+ if(istr.fail()) WARN("Elevation not specified for speaker %zu\n", spkr_num);
+ istr >> spkr.Connection;
+ if(istr.fail()) TRACE("Connection not specified for speaker %zu\n", spkr_num);
+ }
+ else
+ {
+ ERR("Unexpected speakers command: %s\n", cmd.c_str());
+ return false;
+ }
+
+ istr.clear();
+ const auto endpos = static_cast<std::size_t>(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(float (&gains)[MaxAmbiOrder+1], al::vector<AmbDecConf::CoeffArray> &matrix, const std::size_t maxrow, std::istream &f, std::string &buffer)
+{
+ bool gotgains{false};
+ std::size_t cur{0u};
+ 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")
+ {
+ std::size_t curgain{0u};
+ float value;
+ while(istr.good())
+ {
+ istr >> value;
+ if(istr.fail()) break;
+ if(!istr.eof() && !std::isspace(istr.peek()))
+ {
+ ERR("Extra junk on gain %zu: %s\n", curgain+1,
+ buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
+ return false;
+ }
+ if(curgain < size(gains))
+ gains[curgain++] = value;
+ }
+ std::fill(std::begin(gains)+curgain, std::end(gains), 0.0f);
+ gotgains = true;
+ }
+ else if(cmd == "add_row")
+ {
+ matrix.emplace_back();
+ AmbDecConf::CoeffArray &mtxrow = matrix.back();
+ std::size_t curidx{0u};
+ float value{};
+ while(istr.good())
+ {
+ istr >> value;
+ if(istr.fail()) break;
+ if(!istr.eof() && !std::isspace(istr.peek()))
+ {
+ ERR("Extra junk on matrix element %zux%zu: %s\n", curidx,
+ matrix.size(), buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
+ matrix.pop_back();
+ return false;
+ }
+ if(curidx < mtxrow.size())
+ mtxrow[curidx++] = value;
+ }
+ std::fill(mtxrow.begin()+curidx, mtxrow.end(), 0.0f);
+ cur++;
+ }
+ else
+ {
+ ERR("Unexpected matrix command: %s\n", cmd.c_str());
+ return false;
+ }
+
+ istr.clear();
+ const auto endpos = static_cast<std::size_t>(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::size_t num_speakers{0u};
+ 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()+static_cast<std::size_t>(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()+static_cast<std::size_t>(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()+static_cast<std::size_t>(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 >> num_speakers;
+ if(!istr.eof() && !std::isspace(istr.peek()))
+ {
+ ERR("Extra junk after speakers: %s\n",
+ buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
+ return 0;
+ }
+ Speakers.reserve(num_speakers);
+ LFMatrix.reserve(num_speakers);
+ HFMatrix.reserve(num_speakers);
+ }
+ 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()+static_cast<std::size_t>(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()+static_cast<std::size_t>(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/{")
+ {
+ const auto endpos = static_cast<std::size_t>(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(Speakers, num_speakers, 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/{")
+ {
+ const auto endpos = static_cast<std::size_t>(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, num_speakers, f, buffer))
+ return 0;
+ }
+ else
+ {
+ if(command == "/lfmatrix/{")
+ {
+ if(!load_ambdec_matrix(LFOrderGain, LFMatrix, num_speakers, f, buffer))
+ return 0;
+ }
+ else if(command == "/hfmatrix/{")
+ {
+ if(!load_ambdec_matrix(HFOrderGain, HFMatrix, num_speakers, 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")
+ {
+ const auto endpos = static_cast<std::size_t>(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();
+ const auto endpos = static_cast<std::size_t>(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;
+}
diff --git a/core/ambdec.h b/core/ambdec.h
new file mode 100644
index 00000000..9f0db6b5
--- /dev/null
+++ b/core/ambdec.h
@@ -0,0 +1,48 @@
+#ifndef CORE_AMBDEC_H
+#define CORE_AMBDEC_H
+
+#include <array>
+#include <string>
+
+#include "core/ambidefs.h"
+#include "vector.h"
+
+/* Helpers to read .ambdec configuration files. */
+
+enum class AmbDecScale {
+ N3D,
+ SN3D,
+ FuMa,
+};
+struct AmbDecConf {
+ std::string Description;
+ int Version{0}; /* Must be 3 */
+
+ unsigned int ChanMask{0u};
+ unsigned int FreqBands{0u}; /* Must be 1 or 2 */
+ AmbDecScale CoeffScale{};
+
+ float XOverFreq{0.0f};
+ float XOverRatio{0.0f};
+
+ struct SpeakerConf {
+ std::string Name;
+ float Distance{0.0f};
+ float Azimuth{0.0f};
+ float Elevation{0.0f};
+ std::string Connection;
+ };
+ al::vector<SpeakerConf> Speakers;
+
+ using CoeffArray = std::array<float,MaxAmbiChannels>;
+ /* Unused when FreqBands == 1 */
+ float LFOrderGain[MaxAmbiOrder+1]{};
+ al::vector<CoeffArray> LFMatrix;
+
+ float HFOrderGain[MaxAmbiOrder+1]{};
+ al::vector<CoeffArray> HFMatrix;
+
+ int load(const char *fname) noexcept;
+};
+
+#endif /* CORE_AMBDEC_H */