mirror of https://github.com/axmolengine/axmol.git
307 lines
10 KiB
C++
307 lines
10 KiB
C++
|
|
#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");
|
|
}
|