From 20820fd01beb265722d8521ad725c3c479800273 Mon Sep 17 00:00:00 2001
From: Chris Robinson <chris.kcat@gmail.com>
Date: Fri, 25 Dec 2020 06:30:47 -0800
Subject: Move the ambdec loader to core

---
 CMakeLists.txt     |   4 +-
 alc/ambdec.cpp     | 434 -----------------------------------------------------
 alc/ambdec.h       |  48 ------
 alc/bformatdec.cpp |   2 +-
 alc/panning.cpp    |   2 +-
 core/ambdec.cpp    | 434 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 core/ambdec.h      |  48 ++++++
 7 files changed, 486 insertions(+), 486 deletions(-)
 delete mode 100644 alc/ambdec.cpp
 delete mode 100644 alc/ambdec.h
 create mode 100644 core/ambdec.cpp
 create mode 100644 core/ambdec.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2cbe7850..05d04201 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -584,6 +584,8 @@ set(COMMON_OBJS
 
 # Core library routines
 set(CORE_OBJS
+    core/ambdec.cpp
+    core/ambdec.h
     core/ambidefs.cpp
     core/ambidefs.h
     core/bs2b.cpp
@@ -659,8 +661,6 @@ set(ALC_OBJS
     alc/alconfig.cpp
     alc/alconfig.h
     alc/alcontext.h
-    alc/ambdec.cpp
-    alc/ambdec.h
     alc/async_event.h
     alc/bformatdec.cpp
     alc/bformatdec.h
diff --git a/alc/ambdec.cpp b/alc/ambdec.cpp
deleted file mode 100644
index a50d6d95..00000000
--- a/alc/ambdec.cpp
+++ /dev/null
@@ -1,434 +0,0 @@
-
-#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/alc/ambdec.h b/alc/ambdec.h
deleted file mode 100644
index c96ded5b..00000000
--- a/alc/ambdec.h
+++ /dev/null
@@ -1,48 +0,0 @@
-#ifndef AMBDEC_H
-#define 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 /* AMBDEC_H */
diff --git a/alc/bformatdec.cpp b/alc/bformatdec.cpp
index c47a042f..b1bd8981 100644
--- a/alc/bformatdec.cpp
+++ b/alc/bformatdec.cpp
@@ -12,7 +12,7 @@
 
 #include "almalloc.h"
 #include "alu.h"
-#include "ambdec.h"
+#include "core/ambdec.h"
 #include "core/filters/splitter.h"
 #include "front_stablizer.h"
 #include "math_defs.h"
diff --git a/alc/panning.cpp b/alc/panning.cpp
index cb77199f..456f6bef 100644
--- a/alc/panning.cpp
+++ b/alc/panning.cpp
@@ -47,8 +47,8 @@
 #include "alspan.h"
 #include "alstring.h"
 #include "alu.h"
-#include "ambdec.h"
 #include "bformatdec.h"
+#include "core/ambdec.h"
 #include "core/ambidefs.h"
 #include "core/bs2b.h"
 #include "core/devformat.h"
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 */
-- 
cgit v1.2.3