mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-15 08:34:40 +00:00
Initial commit
added libraries: opus flac libsndfile updated: libvorbis libogg openal - Everything works as expected for now. Bare in mind libsndfile needed the check for whether or not it could find the xiph libraries removed in order for this to work.
This commit is contained in:
parent
05a083ca6f
commit
a745fc3757
1954 changed files with 431332 additions and 21037 deletions
|
|
@ -5,51 +5,21 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "albit.h"
|
||||
#include "alfstream.h"
|
||||
#include "core/logging.h"
|
||||
#include "alspan.h"
|
||||
#include "opthelpers.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;
|
||||
|
|
@ -61,120 +31,45 @@ 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());
|
||||
return !(endpos < buffer.length() && buffer[endpos] != '#');
|
||||
}
|
||||
|
||||
|
||||
al::optional<std::string> load_ambdec_speakers(AmbDecConf::SpeakerConf *spkrs,
|
||||
const std::size_t num_speakers, std::istream &f, std::string &buffer)
|
||||
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, ...)
|
||||
{
|
||||
size_t cur_speaker{0};
|
||||
while(cur_speaker < num_speakers)
|
||||
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)
|
||||
{
|
||||
std::istringstream istr{buffer};
|
||||
|
||||
std::string cmd{read_word(istr)};
|
||||
if(cmd.empty())
|
||||
{
|
||||
if(!read_clipped_line(f, buffer))
|
||||
return al::make_optional<std::string>("Unexpected end of file");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(cmd == "add_spkr")
|
||||
{
|
||||
AmbDecConf::SpeakerConf &spkr = spkrs[cur_speaker++];
|
||||
const size_t spkr_num{cur_speaker};
|
||||
|
||||
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
|
||||
return al::make_optional("Unexpected speakers command: "+cmd);
|
||||
|
||||
istr.clear();
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
return al::make_optional("Extra junk on line: " + buffer.substr(endpos));
|
||||
buffer.clear();
|
||||
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 al::nullopt;
|
||||
}
|
||||
|
||||
al::optional<std::string> load_ambdec_matrix(float (&gains)[MaxAmbiOrder+1],
|
||||
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))
|
||||
return al::make_optional<std::string>("Unexpected end of file");
|
||||
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()))
|
||||
return al::make_optional("Extra junk on gain "+std::to_string(curgain+1)+": "+
|
||||
buffer.substr(static_cast<std::size_t>(istr.tellg())));
|
||||
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")
|
||||
{
|
||||
AmbDecConf::CoeffArray &mtxrow = matrix[cur++];
|
||||
std::size_t curidx{0u};
|
||||
float value{};
|
||||
while(istr.good())
|
||||
{
|
||||
istr >> value;
|
||||
if(istr.fail()) break;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
return al::make_optional("Extra junk on matrix element "+
|
||||
std::to_string(curidx)+"x"+std::to_string(cur-1)+": "+
|
||||
buffer.substr(static_cast<std::size_t>(istr.tellg())));
|
||||
if(curidx < mtxrow.size())
|
||||
mtxrow[curidx++] = value;
|
||||
}
|
||||
std::fill(mtxrow.begin()+curidx, mtxrow.end(), 0.0f);
|
||||
}
|
||||
else
|
||||
return al::make_optional("Unexpected matrix command: "+cmd);
|
||||
|
||||
istr.clear();
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
return al::make_optional("Extra junk on line: " + buffer.substr(endpos));
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
if(!gotgains)
|
||||
return al::make_optional<std::string>("Matrix order_gain not specified");
|
||||
return al::nullopt;
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -186,96 +81,162 @@ al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
|||
{
|
||||
al::ifstream f{fname};
|
||||
if(!f.is_open())
|
||||
return al::make_optional<std::string>("Failed to open file");
|
||||
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};
|
||||
|
||||
bool speakers_loaded{false};
|
||||
bool matrix_loaded{false};
|
||||
bool lfmatrix_loaded{false};
|
||||
std::string buffer;
|
||||
while(read_clipped_line(f, buffer))
|
||||
while(f.good() && std::getline(f, buffer))
|
||||
{
|
||||
++linenum;
|
||||
|
||||
std::istringstream istr{buffer};
|
||||
|
||||
std::string command{read_word(istr)};
|
||||
if(command.empty())
|
||||
return al::make_optional("Malformed line: "+buffer);
|
||||
if(command.empty() || command[0] == '#')
|
||||
continue;
|
||||
|
||||
if(command == "/description")
|
||||
readline(istr, Description);
|
||||
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(!istr.eof() && !std::isspace(istr.peek()))
|
||||
return al::make_optional("Extra junk after version: " +
|
||||
buffer.substr(static_cast<std::size_t>(istr.tellg())));
|
||||
if(Version != 3)
|
||||
return al::make_optional("Unsupported version: "+std::to_string(Version));
|
||||
return make_error(linenum, "Unsupported version: %d", Version);
|
||||
}
|
||||
else if(command == "/dec/chan_mask")
|
||||
{
|
||||
if(ChanMask)
|
||||
return al::make_optional<std::string>("Duplicate chan_mask definition");
|
||||
|
||||
return make_error(linenum, "Duplicate chan_mask definition");
|
||||
istr >> std::hex >> ChanMask >> std::dec;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
return al::make_optional("Extra junk after mask: " +
|
||||
buffer.substr(static_cast<std::size_t>(istr.tellg())));
|
||||
|
||||
if(!ChanMask)
|
||||
return al::make_optional("Invalid chan_mask: "+std::to_string(ChanMask));
|
||||
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 al::make_optional<std::string>("Duplicate freq_bands");
|
||||
|
||||
return make_error(linenum, "Duplicate freq_bands");
|
||||
istr >> FreqBands;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
return al::make_optional("Extra junk after freq_bands: " +
|
||||
buffer.substr(static_cast<std::size_t>(istr.tellg())));
|
||||
|
||||
if(FreqBands != 1 && FreqBands != 2)
|
||||
return al::make_optional("Invalid freq_bands: "+std::to_string(FreqBands));
|
||||
return make_error(linenum, "Invalid freq_bands: %u", FreqBands);
|
||||
}
|
||||
else if(command == "/dec/speakers")
|
||||
{
|
||||
if(NumSpeakers)
|
||||
return al::make_optional<std::string>("Duplicate speakers");
|
||||
|
||||
return make_error(linenum, "Duplicate speakers");
|
||||
istr >> NumSpeakers;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
return al::make_optional("Extra junk after speakers: " +
|
||||
buffer.substr(static_cast<std::size_t>(istr.tellg())));
|
||||
|
||||
if(!NumSpeakers)
|
||||
return al::make_optional("Invalid speakers: "+std::to_string(NumSpeakers));
|
||||
return make_error(linenum, "Invalid speakers: %zu", NumSpeakers);
|
||||
Speakers = std::make_unique<SpeakerConf[]>(NumSpeakers);
|
||||
}
|
||||
else if(command == "/dec/coeff_scale")
|
||||
{
|
||||
std::string scale = read_word(istr);
|
||||
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 al::make_optional("Unexpected coeff_scale: "+scale);
|
||||
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;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
return al::make_optional("Extra junk after xover_freq: " +
|
||||
buffer.substr(static_cast<std::size_t>(istr.tellg())));
|
||||
}
|
||||
else if(command == "/opt/xover_ratio")
|
||||
{
|
||||
istr >> XOverRatio;
|
||||
if(!istr.eof() && !std::isspace(istr.peek()))
|
||||
return al::make_optional("Extra junk after xover_ratio: " +
|
||||
buffer.substr(static_cast<std::size_t>(istr.tellg())));
|
||||
}
|
||||
else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp" ||
|
||||
command == "/opt/delay_comp" || command == "/opt/level_comp")
|
||||
else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp"
|
||||
|| command == "/opt/delay_comp" || command == "/opt/level_comp")
|
||||
{
|
||||
/* Unused */
|
||||
read_word(istr);
|
||||
|
|
@ -283,33 +244,15 @@ al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
|||
else if(command == "/speakers/{")
|
||||
{
|
||||
if(!NumSpeakers)
|
||||
return al::make_optional<std::string>("Speakers defined without a count");
|
||||
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
return al::make_optional("Extra junk on line: " + buffer.substr(endpos));
|
||||
buffer.clear();
|
||||
|
||||
if(auto err = load_ambdec_speakers(Speakers.get(), NumSpeakers, f, buffer))
|
||||
return err;
|
||||
speakers_loaded = true;
|
||||
|
||||
if(!read_clipped_line(f, buffer))
|
||||
return al::make_optional<std::string>("Unexpected end of file");
|
||||
std::istringstream istr2{buffer};
|
||||
std::string endmark{read_word(istr2)};
|
||||
if(endmark != "/}")
|
||||
return al::make_optional("Expected /} after speaker definitions, got "+endmark);
|
||||
istr.swap(istr2);
|
||||
return make_error(linenum, "Speakers defined without a count");
|
||||
scope = ReaderScope::Speakers;
|
||||
}
|
||||
else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{")
|
||||
{
|
||||
if(!NumSpeakers)
|
||||
return al::make_optional<std::string>("Matrix defined without a count");
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
return al::make_optional("Extra junk on line: " + buffer.substr(endpos));
|
||||
buffer.clear();
|
||||
return make_error(linenum, "Matrix defined without a speaker count");
|
||||
if(!ChanMask)
|
||||
return make_error(linenum, "Matrix defined without a channel mask");
|
||||
|
||||
if(!Matrix)
|
||||
{
|
||||
|
|
@ -321,58 +264,43 @@ al::optional<std::string> AmbDecConf::load(const char *fname) noexcept
|
|||
if(FreqBands == 1)
|
||||
{
|
||||
if(command != "/matrix/{")
|
||||
return al::make_optional(
|
||||
"Unexpected \""+command+"\" type for a single-band decoder");
|
||||
if(auto err = load_ambdec_matrix(HFOrderGain, HFMatrix, NumSpeakers, f, buffer))
|
||||
return err;
|
||||
matrix_loaded = true;
|
||||
return make_error(linenum, "Unexpected \"%s\" for a single-band decoder",
|
||||
command.c_str());
|
||||
scope = ReaderScope::HFMatrix;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(command == "/lfmatrix/{")
|
||||
{
|
||||
if(auto err=load_ambdec_matrix(LFOrderGain, LFMatrix, NumSpeakers, f, buffer))
|
||||
return err;
|
||||
lfmatrix_loaded = true;
|
||||
}
|
||||
scope = ReaderScope::LFMatrix;
|
||||
else if(command == "/hfmatrix/{")
|
||||
{
|
||||
if(auto err=load_ambdec_matrix(HFOrderGain, HFMatrix, NumSpeakers, f, buffer))
|
||||
return err;
|
||||
matrix_loaded = true;
|
||||
}
|
||||
scope = ReaderScope::HFMatrix;
|
||||
else
|
||||
return al::make_optional(
|
||||
"Unexpected \""+command+"\" type for a dual-band decoder");
|
||||
return make_error(linenum, "Unexpected \"%s\" for a dual-band decoder",
|
||||
command.c_str());
|
||||
}
|
||||
|
||||
if(!read_clipped_line(f, buffer))
|
||||
return al::make_optional<std::string>("Unexpected end of file");
|
||||
std::istringstream istr2{buffer};
|
||||
std::string endmark{read_word(istr2)};
|
||||
if(endmark != "/}")
|
||||
return al::make_optional("Expected /} after matrix definitions, got "+endmark);
|
||||
istr.swap(istr2);
|
||||
}
|
||||
else if(command == "/end")
|
||||
{
|
||||
const auto endpos = static_cast<std::size_t>(istr.tellg());
|
||||
if(!is_at_end(buffer, endpos))
|
||||
return al::make_optional("Extra junk on end: " + buffer.substr(endpos));
|
||||
return make_error(linenum, "Extra junk on end: %s", buffer.substr(endpos).c_str());
|
||||
|
||||
if(!speakers_loaded || !matrix_loaded || (FreqBands == 2 && !lfmatrix_loaded))
|
||||
return al::make_optional<std::string>("No decoder defined");
|
||||
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 al::make_optional("Unexpected command: " + command);
|
||||
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 al::make_optional("Extra junk on line: " + buffer.substr(endpos));
|
||||
return make_error(linenum, "Extra junk on line: %s", buffer.substr(endpos).c_str());
|
||||
buffer.clear();
|
||||
}
|
||||
return al::make_optional<std::string>("Unexpected end of file");
|
||||
return make_error(linenum, "Unexpected end of file");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
/* Helpers to read .ambdec configuration files. */
|
||||
|
||||
enum class AmbDecScale {
|
||||
Unset,
|
||||
N3D,
|
||||
SN3D,
|
||||
FuMa,
|
||||
|
|
@ -21,7 +22,7 @@ struct AmbDecConf {
|
|||
|
||||
unsigned int ChanMask{0u};
|
||||
unsigned int FreqBands{0u}; /* Must be 1 or 2 */
|
||||
AmbDecScale CoeffScale{};
|
||||
AmbDecScale CoeffScale{AmbDecScale::Unset};
|
||||
|
||||
float XOverFreq{0.0f};
|
||||
float XOverRatio{0.0f};
|
||||
|
|
|
|||
|
|
@ -3,42 +3,306 @@
|
|||
|
||||
#include "ambidefs.h"
|
||||
|
||||
#include <cassert>
|
||||
#include "alnumbers.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale{{
|
||||
1.00000000e+00f, 1.00000000e+00f
|
||||
}};
|
||||
constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale2O{{
|
||||
7.45355990e-01f, 1.00000000e+00f, 1.00000000e+00f
|
||||
}};
|
||||
constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale3O{{
|
||||
5.89792205e-01f, 8.79693856e-01f, 1.00000000e+00f, 1.00000000e+00f
|
||||
using AmbiChannelFloatArray = std::array<float,MaxAmbiChannels>;
|
||||
|
||||
constexpr auto inv_sqrt2f = static_cast<float>(1.0/al::numbers::sqrt2);
|
||||
constexpr auto inv_sqrt3f = static_cast<float>(1.0/al::numbers::sqrt3);
|
||||
|
||||
|
||||
/* These HF gains are derived from the same 32-point speaker array. The scale
|
||||
* factor between orders represents the same scale factors for any (regular)
|
||||
* speaker array decoder. e.g. Given a first-order source and second-order
|
||||
* output, applying an HF scale of HFScales[1][0] / HFScales[2][0] to channel 0
|
||||
* will result in that channel being subsequently decoded for second-order as
|
||||
* if it was a first-order decoder for that same speaker array.
|
||||
*/
|
||||
constexpr std::array<std::array<float,MaxAmbiOrder+1>,MaxAmbiOrder+1> HFScales{{
|
||||
{{ 4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }},
|
||||
{{ 4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }},
|
||||
{{ 2.981423970e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }},
|
||||
{{ 2.359168820e+00f, 2.031565936e+00f, 1.444598386e+00f, 7.189495850e-01f }},
|
||||
/* 1.947005434e+00f, 1.764337084e+00f, 1.424707344e+00f, 9.755104127e-01f, 4.784482742e-01f */
|
||||
}};
|
||||
|
||||
inline auto& GetDecoderHFScales(uint order) noexcept
|
||||
/* Same as above, but using a 10-point horizontal-only speaker array. Should
|
||||
* only be used when the device is mixing in 2D B-Format for horizontal-only
|
||||
* output.
|
||||
*/
|
||||
constexpr std::array<std::array<float,MaxAmbiOrder+1>,MaxAmbiOrder+1> HFScales2D{{
|
||||
{{ 2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }},
|
||||
{{ 2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }},
|
||||
{{ 1.825741858e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }},
|
||||
{{ 1.581138830e+00f, 1.460781803e+00f, 1.118033989e+00f, 6.050756345e-01f }},
|
||||
/* 1.414213562e+00f, 1.344997024e+00f, 1.144122806e+00f, 8.312538756e-01f, 4.370160244e-01f */
|
||||
}};
|
||||
|
||||
|
||||
/* This calculates a first-order "upsampler" matrix. It combines a first-order
|
||||
* decoder matrix with a max-order encoder matrix, creating a matrix that
|
||||
* behaves as if the B-Format input signal is first decoded to a speaker array
|
||||
* at first-order, then those speaker feeds are encoded to a higher-order
|
||||
* signal. While not perfect, this should accurately encode a lower-order
|
||||
* signal into a higher-order signal.
|
||||
*/
|
||||
constexpr std::array<std::array<float,4>,8> FirstOrderDecoder{{
|
||||
{{ 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, }},
|
||||
{{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,8> FirstOrderEncoder{{
|
||||
CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs(-inv_sqrt3f, inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs(-inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs(-inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs(-inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f),
|
||||
}};
|
||||
static_assert(FirstOrderDecoder.size() == FirstOrderEncoder.size(), "First-order mismatch");
|
||||
|
||||
/* This calculates a 2D first-order "upsampler" matrix. Same as the first-order
|
||||
* matrix, just using a more optimized speaker array for horizontal-only
|
||||
* content.
|
||||
*/
|
||||
constexpr std::array<std::array<float,4>,4> FirstOrder2DDecoder{{
|
||||
{{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, 2.041241452e-01f, }},
|
||||
{{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, -2.041241452e-01f, }},
|
||||
{{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, 2.041241452e-01f, }},
|
||||
{{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, -2.041241452e-01f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,4> FirstOrder2DEncoder{{
|
||||
CalcAmbiCoeffs( inv_sqrt2f, 0.0f, inv_sqrt2f),
|
||||
CalcAmbiCoeffs( inv_sqrt2f, 0.0f, -inv_sqrt2f),
|
||||
CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, inv_sqrt2f),
|
||||
CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, -inv_sqrt2f),
|
||||
}};
|
||||
static_assert(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-order 2D mismatch");
|
||||
|
||||
|
||||
/* This calculates a second-order "upsampler" matrix. Same as the first-order
|
||||
* matrix, just using a slightly more dense speaker array suitable for second-
|
||||
* order content.
|
||||
*/
|
||||
constexpr std::array<std::array<float,9>,12> SecondOrderDecoder{{
|
||||
{{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }},
|
||||
{{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }},
|
||||
{{ 8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }},
|
||||
{{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }},
|
||||
{{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }},
|
||||
{{ 8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }},
|
||||
{{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }},
|
||||
{{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }},
|
||||
{{ 8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }},
|
||||
{{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }},
|
||||
{{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }},
|
||||
{{ 8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,12> SecondOrderEncoder{{
|
||||
CalcAmbiCoeffs( 0.000000000e+00f, -5.257311121e-01f, 8.506508084e-01f),
|
||||
CalcAmbiCoeffs(-8.506508084e-01f, 0.000000000e+00f, 5.257311121e-01f),
|
||||
CalcAmbiCoeffs(-5.257311121e-01f, 8.506508084e-01f, 0.000000000e+00f),
|
||||
CalcAmbiCoeffs( 0.000000000e+00f, 5.257311121e-01f, 8.506508084e-01f),
|
||||
CalcAmbiCoeffs(-8.506508084e-01f, 0.000000000e+00f, -5.257311121e-01f),
|
||||
CalcAmbiCoeffs( 5.257311121e-01f, -8.506508084e-01f, 0.000000000e+00f),
|
||||
CalcAmbiCoeffs( 0.000000000e+00f, -5.257311121e-01f, -8.506508084e-01f),
|
||||
CalcAmbiCoeffs( 8.506508084e-01f, 0.000000000e+00f, -5.257311121e-01f),
|
||||
CalcAmbiCoeffs( 5.257311121e-01f, 8.506508084e-01f, 0.000000000e+00f),
|
||||
CalcAmbiCoeffs( 0.000000000e+00f, 5.257311121e-01f, -8.506508084e-01f),
|
||||
CalcAmbiCoeffs( 8.506508084e-01f, 0.000000000e+00f, 5.257311121e-01f),
|
||||
CalcAmbiCoeffs(-5.257311121e-01f, -8.506508084e-01f, 0.000000000e+00f),
|
||||
}};
|
||||
static_assert(SecondOrderDecoder.size() == SecondOrderEncoder.size(), "Second-order mismatch");
|
||||
|
||||
/* This calculates a 2D second-order "upsampler" matrix. Same as the second-
|
||||
* order matrix, just using a more optimized speaker array for horizontal-only
|
||||
* content.
|
||||
*/
|
||||
constexpr std::array<std::array<float,9>,6> SecondOrder2DDecoder{{
|
||||
{{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
|
||||
{{ 1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }},
|
||||
{{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
|
||||
{{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
|
||||
{{ 1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }},
|
||||
{{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,6> SecondOrder2DEncoder{{
|
||||
CalcAmbiCoeffs(-0.50000000000f, 0.0f, 0.86602540379f),
|
||||
CalcAmbiCoeffs(-1.00000000000f, 0.0f, 0.00000000000f),
|
||||
CalcAmbiCoeffs(-0.50000000000f, 0.0f, -0.86602540379f),
|
||||
CalcAmbiCoeffs( 0.50000000000f, 0.0f, -0.86602540379f),
|
||||
CalcAmbiCoeffs( 1.00000000000f, 0.0f, 0.00000000000f),
|
||||
CalcAmbiCoeffs( 0.50000000000f, 0.0f, 0.86602540379f),
|
||||
}};
|
||||
static_assert(SecondOrder2DDecoder.size() == SecondOrder2DEncoder.size(),
|
||||
"Second-order 2D mismatch");
|
||||
|
||||
|
||||
/* This calculates a third-order "upsampler" matrix. Same as the first-order
|
||||
* matrix, just using a more dense speaker array suitable for third-order
|
||||
* content.
|
||||
*/
|
||||
constexpr std::array<std::array<float,16>,20> ThirdOrderDecoder{{
|
||||
{{ 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }},
|
||||
{{ 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }},
|
||||
{{ 5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }},
|
||||
{{ 5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }},
|
||||
{{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }},
|
||||
{{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }},
|
||||
{{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }},
|
||||
{{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }},
|
||||
{{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f, }},
|
||||
{{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f, }},
|
||||
{{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f, }},
|
||||
{{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f, }},
|
||||
{{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }},
|
||||
{{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,20> ThirdOrderEncoder{{
|
||||
CalcAmbiCoeffs( 0.35682208976f, 0.93417235897f, 0.00000000000f),
|
||||
CalcAmbiCoeffs(-0.35682208976f, 0.93417235897f, 0.00000000000f),
|
||||
CalcAmbiCoeffs( 0.35682208976f, -0.93417235897f, 0.00000000000f),
|
||||
CalcAmbiCoeffs(-0.35682208976f, -0.93417235897f, 0.00000000000f),
|
||||
CalcAmbiCoeffs( 0.93417235897f, 0.00000000000f, 0.35682208976f),
|
||||
CalcAmbiCoeffs( 0.93417235897f, 0.00000000000f, -0.35682208976f),
|
||||
CalcAmbiCoeffs(-0.93417235897f, 0.00000000000f, 0.35682208976f),
|
||||
CalcAmbiCoeffs(-0.93417235897f, 0.00000000000f, -0.35682208976f),
|
||||
CalcAmbiCoeffs( 0.00000000000f, 0.35682208976f, 0.93417235897f),
|
||||
CalcAmbiCoeffs( 0.00000000000f, 0.35682208976f, -0.93417235897f),
|
||||
CalcAmbiCoeffs( 0.00000000000f, -0.35682208976f, 0.93417235897f),
|
||||
CalcAmbiCoeffs( 0.00000000000f, -0.35682208976f, -0.93417235897f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs( -inv_sqrt3f, inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( -inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f),
|
||||
CalcAmbiCoeffs( -inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f),
|
||||
CalcAmbiCoeffs( -inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f),
|
||||
}};
|
||||
static_assert(ThirdOrderDecoder.size() == ThirdOrderEncoder.size(), "Third-order mismatch");
|
||||
|
||||
/* This calculates a 2D third-order "upsampler" matrix. Same as the third-order
|
||||
* matrix, just using a more optimized speaker array for horizontal-only
|
||||
* content.
|
||||
*/
|
||||
constexpr std::array<std::array<float,16>,8> ThirdOrder2DDecoder{{
|
||||
{{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, 1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }},
|
||||
{{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, 5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }},
|
||||
{{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, -5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }},
|
||||
{{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, -1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }},
|
||||
{{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, -1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }},
|
||||
{{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, -5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }},
|
||||
{{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, 5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }},
|
||||
{{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, 1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,8> ThirdOrder2DEncoder{{
|
||||
CalcAmbiCoeffs(-0.38268343237f, 0.0f, 0.92387953251f),
|
||||
CalcAmbiCoeffs(-0.92387953251f, 0.0f, 0.38268343237f),
|
||||
CalcAmbiCoeffs(-0.92387953251f, 0.0f, -0.38268343237f),
|
||||
CalcAmbiCoeffs(-0.38268343237f, 0.0f, -0.92387953251f),
|
||||
CalcAmbiCoeffs( 0.38268343237f, 0.0f, -0.92387953251f),
|
||||
CalcAmbiCoeffs( 0.92387953251f, 0.0f, -0.38268343237f),
|
||||
CalcAmbiCoeffs( 0.92387953251f, 0.0f, 0.38268343237f),
|
||||
CalcAmbiCoeffs( 0.38268343237f, 0.0f, 0.92387953251f),
|
||||
}};
|
||||
static_assert(ThirdOrder2DDecoder.size() == ThirdOrder2DEncoder.size(), "Third-order 2D mismatch");
|
||||
|
||||
|
||||
/* This calculates a 2D fourth-order "upsampler" matrix. There is no 3D fourth-
|
||||
* order upsampler since fourth-order is the max order we'll be supporting for
|
||||
* the foreseeable future. This is only necessary for mixing horizontal-only
|
||||
* fourth-order content to 3D.
|
||||
*/
|
||||
constexpr std::array<std::array<float,25>,10> FourthOrder2DDecoder{{
|
||||
{{ 1.000000000e-01f, 3.568220898e-02f, 0.0f, 1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }},
|
||||
{{ 1.000000000e-01f, 9.341723590e-02f, 0.0f, 6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }},
|
||||
{{ 1.000000000e-01f, 1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, -9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f, }},
|
||||
{{ 1.000000000e-01f, 9.341723590e-02f, 0.0f, -6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }},
|
||||
{{ 1.000000000e-01f, 3.568220898e-02f, 0.0f, -1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }},
|
||||
{{ 1.000000000e-01f, -3.568220898e-02f, 0.0f, -1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }},
|
||||
{{ 1.000000000e-01f, -9.341723590e-02f, 0.0f, -6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }},
|
||||
{{ 1.000000000e-01f, -1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, 9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f, }},
|
||||
{{ 1.000000000e-01f, -9.341723590e-02f, 0.0f, 6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }},
|
||||
{{ 1.000000000e-01f, -3.568220898e-02f, 0.0f, 1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }},
|
||||
}};
|
||||
constexpr std::array<AmbiChannelFloatArray,10> FourthOrder2DEncoder{{
|
||||
CalcAmbiCoeffs( 3.090169944e-01f, 0.000000000e+00f, 9.510565163e-01f),
|
||||
CalcAmbiCoeffs( 8.090169944e-01f, 0.000000000e+00f, 5.877852523e-01f),
|
||||
CalcAmbiCoeffs( 1.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f),
|
||||
CalcAmbiCoeffs( 8.090169944e-01f, 0.000000000e+00f, -5.877852523e-01f),
|
||||
CalcAmbiCoeffs( 3.090169944e-01f, 0.000000000e+00f, -9.510565163e-01f),
|
||||
CalcAmbiCoeffs(-3.090169944e-01f, 0.000000000e+00f, -9.510565163e-01f),
|
||||
CalcAmbiCoeffs(-8.090169944e-01f, 0.000000000e+00f, -5.877852523e-01f),
|
||||
CalcAmbiCoeffs(-1.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f),
|
||||
CalcAmbiCoeffs(-8.090169944e-01f, 0.000000000e+00f, 5.877852523e-01f),
|
||||
CalcAmbiCoeffs(-3.090169944e-01f, 0.000000000e+00f, 9.510565163e-01f),
|
||||
}};
|
||||
static_assert(FourthOrder2DDecoder.size() == FourthOrder2DEncoder.size(), "Fourth-order 2D mismatch");
|
||||
|
||||
|
||||
template<size_t N, size_t M>
|
||||
auto CalcAmbiUpsampler(const std::array<std::array<float,N>,M> &decoder,
|
||||
const std::array<AmbiChannelFloatArray,M> &encoder)
|
||||
{
|
||||
if(order >= 3) return Ambi3DDecoderHFScale3O;
|
||||
if(order == 2) return Ambi3DDecoderHFScale2O;
|
||||
return Ambi3DDecoderHFScale;
|
||||
std::array<AmbiChannelFloatArray,N> res{};
|
||||
|
||||
for(size_t i{0};i < decoder[0].size();++i)
|
||||
{
|
||||
for(size_t j{0};j < encoder[0].size();++j)
|
||||
{
|
||||
double sum{0.0};
|
||||
for(size_t k{0};k < decoder.size();++k)
|
||||
sum += double{decoder[k][i]} * encoder[k][j];
|
||||
res[i][j] = static_cast<float>(sum);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto AmbiScale::GetHFOrderScales(const uint in_order, const uint out_order) noexcept
|
||||
-> std::array<float,MaxAmbiOrder+1>
|
||||
const std::array<AmbiChannelFloatArray,4> AmbiScale::FirstOrderUp{CalcAmbiUpsampler(FirstOrderDecoder, FirstOrderEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,4> AmbiScale::FirstOrder2DUp{CalcAmbiUpsampler(FirstOrder2DDecoder, FirstOrder2DEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,9> AmbiScale::SecondOrderUp{CalcAmbiUpsampler(SecondOrderDecoder, SecondOrderEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,9> AmbiScale::SecondOrder2DUp{CalcAmbiUpsampler(SecondOrder2DDecoder, SecondOrder2DEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,16> AmbiScale::ThirdOrderUp{CalcAmbiUpsampler(ThirdOrderDecoder, ThirdOrderEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,16> AmbiScale::ThirdOrder2DUp{CalcAmbiUpsampler(ThirdOrder2DDecoder, ThirdOrder2DEncoder)};
|
||||
const std::array<AmbiChannelFloatArray,25> AmbiScale::FourthOrder2DUp{CalcAmbiUpsampler(FourthOrder2DDecoder, FourthOrder2DEncoder)};
|
||||
|
||||
|
||||
std::array<float,MaxAmbiOrder+1> AmbiScale::GetHFOrderScales(const uint src_order,
|
||||
const uint dev_order, const bool horizontalOnly) noexcept
|
||||
{
|
||||
std::array<float,MaxAmbiOrder+1> ret{};
|
||||
std::array<float,MaxAmbiOrder+1> res{};
|
||||
|
||||
assert(out_order >= in_order);
|
||||
if(!horizontalOnly)
|
||||
{
|
||||
for(size_t i{0};i < MaxAmbiOrder+1;++i)
|
||||
res[i] = HFScales[src_order][i] / HFScales[dev_order][i];
|
||||
}
|
||||
else
|
||||
{
|
||||
for(size_t i{0};i < MaxAmbiOrder+1;++i)
|
||||
res[i] = HFScales2D[src_order][i] / HFScales2D[dev_order][i];
|
||||
}
|
||||
|
||||
const auto &target = GetDecoderHFScales(out_order);
|
||||
const auto &input = GetDecoderHFScales(in_order);
|
||||
|
||||
for(size_t i{0};i < in_order+1;++i)
|
||||
ret[i] = input[i] / target[i];
|
||||
|
||||
return ret;
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "alnumbers.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
/* The maximum number of Ambisonics channels. For a given order (o), the size
|
||||
|
|
@ -111,8 +114,16 @@ struct AmbiScale {
|
|||
}
|
||||
|
||||
/* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */
|
||||
static std::array<float,MaxAmbiOrder+1> GetHFOrderScales(const uint in_order,
|
||||
const uint out_order) noexcept;
|
||||
static std::array<float,MaxAmbiOrder+1> GetHFOrderScales(const uint src_order,
|
||||
const uint dev_order, const bool horizontalOnly) noexcept;
|
||||
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,4> FirstOrderUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,4> FirstOrder2DUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,9> SecondOrderUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,9> SecondOrder2DUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,16> ThirdOrderUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,16> ThirdOrder2DUp;
|
||||
static const std::array<std::array<float,MaxAmbiChannels>,25> FourthOrder2DUp;
|
||||
};
|
||||
|
||||
struct AmbiIndex {
|
||||
|
|
@ -184,4 +195,56 @@ struct AmbiIndex {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates ambisonic encoder coefficients using the X, Y, and Z direction
|
||||
* components, which must represent a normalized (unit length) vector.
|
||||
*
|
||||
* NOTE: The components use ambisonic coordinates. As a result:
|
||||
*
|
||||
* Ambisonic Y = OpenAL -X
|
||||
* Ambisonic Z = OpenAL Y
|
||||
* Ambisonic X = OpenAL -Z
|
||||
*
|
||||
* The components are ordered such that OpenAL's X, Y, and Z are the first,
|
||||
* second, and third parameters respectively -- simply negate X and Z.
|
||||
*/
|
||||
constexpr auto CalcAmbiCoeffs(const float y, const float z, const float x)
|
||||
{
|
||||
const float xx{x*x}, yy{y*y}, zz{z*z}, xy{x*y}, yz{y*z}, xz{x*z};
|
||||
|
||||
return std::array<float,MaxAmbiChannels>{{
|
||||
/* Zeroth-order */
|
||||
1.0f, /* ACN 0 = 1 */
|
||||
/* First-order */
|
||||
al::numbers::sqrt3_v<float> * y, /* ACN 1 = sqrt(3) * Y */
|
||||
al::numbers::sqrt3_v<float> * z, /* ACN 2 = sqrt(3) * Z */
|
||||
al::numbers::sqrt3_v<float> * x, /* ACN 3 = sqrt(3) * X */
|
||||
/* Second-order */
|
||||
3.872983346e+00f * xy, /* ACN 4 = sqrt(15) * X * Y */
|
||||
3.872983346e+00f * yz, /* ACN 5 = sqrt(15) * Y * Z */
|
||||
1.118033989e+00f * (3.0f*zz - 1.0f), /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */
|
||||
3.872983346e+00f * xz, /* ACN 7 = sqrt(15) * X * Z */
|
||||
1.936491673e+00f * (xx - yy), /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */
|
||||
/* Third-order */
|
||||
2.091650066e+00f * (y*(3.0f*xx - yy)), /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */
|
||||
1.024695076e+01f * (z*xy), /* ACN 10 = sqrt(105) * Z * X * Y */
|
||||
1.620185175e+00f * (y*(5.0f*zz - 1.0f)), /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */
|
||||
1.322875656e+00f * (z*(5.0f*zz - 3.0f)), /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */
|
||||
1.620185175e+00f * (x*(5.0f*zz - 1.0f)), /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */
|
||||
5.123475383e+00f * (z*(xx - yy)), /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */
|
||||
2.091650066e+00f * (x*(xx - 3.0f*yy)), /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */
|
||||
/* Fourth-order */
|
||||
/* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */
|
||||
/* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */
|
||||
/* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */
|
||||
/* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */
|
||||
/* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */
|
||||
/* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */
|
||||
/* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */
|
||||
/* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */
|
||||
/* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */
|
||||
}};
|
||||
}
|
||||
|
||||
#endif /* CORE_AMBIDEFS_H */
|
||||
|
|
|
|||
|
|
@ -10,16 +10,17 @@ using uint = unsigned int;
|
|||
|
||||
struct AsyncEvent {
|
||||
enum : uint {
|
||||
/* End event thread processing. */
|
||||
KillThread = 0,
|
||||
|
||||
/* User event types. */
|
||||
SourceStateChange = 1<<0,
|
||||
BufferCompleted = 1<<1,
|
||||
Disconnected = 1<<2,
|
||||
SourceStateChange,
|
||||
BufferCompleted,
|
||||
Disconnected,
|
||||
UserEventCount,
|
||||
|
||||
/* Internal events. */
|
||||
ReleaseEffectState = 65536,
|
||||
/* Internal events, always processed. */
|
||||
ReleaseEffectState = 128,
|
||||
|
||||
/* End event thread processing. */
|
||||
KillThread,
|
||||
};
|
||||
|
||||
enum class SrcState {
|
||||
|
|
@ -29,7 +30,7 @@ struct AsyncEvent {
|
|||
Pause
|
||||
};
|
||||
|
||||
uint EnumType{0u};
|
||||
const uint EnumType;
|
||||
union {
|
||||
char dummy;
|
||||
struct {
|
||||
|
|
@ -46,7 +47,6 @@ struct AsyncEvent {
|
|||
EffectState *mEffectState;
|
||||
} u{};
|
||||
|
||||
AsyncEvent() noexcept = default;
|
||||
constexpr AsyncEvent(uint type) noexcept : EnumType{type} { }
|
||||
|
||||
DISABLE_ALLOC()
|
||||
|
|
|
|||
|
|
@ -88,15 +88,14 @@ void BFormatDec::processStablize(const al::span<FloatBufferLine> OutBuffer,
|
|||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
/* Move the existing direct L/R signal out so it doesn't get processed by
|
||||
* the stablizer. Add a delay to it so it stays aligned with the stablizer
|
||||
* delay.
|
||||
* the stablizer.
|
||||
*/
|
||||
float *RESTRICT mid{al::assume_aligned<16>(mStablizer->MidDirect.data())};
|
||||
float *RESTRICT side{al::assume_aligned<16>(mStablizer->Side.data())};
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
{
|
||||
mid[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] + OutBuffer[ridx][i];
|
||||
side[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] - OutBuffer[ridx][i];
|
||||
mid[i] = OutBuffer[lidx][i] + OutBuffer[ridx][i];
|
||||
side[i] = OutBuffer[lidx][i] - OutBuffer[ridx][i];
|
||||
}
|
||||
std::fill_n(OutBuffer[lidx].begin(), SamplesToDo, 0.0f);
|
||||
std::fill_n(OutBuffer[ridx].begin(), SamplesToDo, 0.0f);
|
||||
|
|
@ -104,55 +103,36 @@ void BFormatDec::processStablize(const al::span<FloatBufferLine> OutBuffer,
|
|||
/* Decode the B-Format input to OutBuffer. */
|
||||
process(OutBuffer, InSamples, SamplesToDo);
|
||||
|
||||
/* Apply a delay to all channels, except the front-left and front-right, so
|
||||
* they maintain correct timing.
|
||||
/* Include the decoded side signal with the direct side signal. */
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
side[i] += OutBuffer[lidx][i] - OutBuffer[ridx][i];
|
||||
|
||||
/* Get the decoded mid signal and band-split it. */
|
||||
std::transform(OutBuffer[lidx].cbegin(), OutBuffer[lidx].cbegin()+SamplesToDo,
|
||||
OutBuffer[ridx].cbegin(), mStablizer->Temp.begin(),
|
||||
[](const float l, const float r) noexcept { return l + r; });
|
||||
|
||||
mStablizer->MidFilter.process({mStablizer->Temp.data(), SamplesToDo}, mStablizer->MidHF.data(),
|
||||
mStablizer->MidLF.data());
|
||||
|
||||
/* Apply an all-pass to all channels to match the band-splitter's phase
|
||||
* shift. This is to keep the phase synchronized between the existing
|
||||
* signal and the split mid signal.
|
||||
*/
|
||||
const size_t NumChannels{OutBuffer.size()};
|
||||
for(size_t i{0u};i < NumChannels;i++)
|
||||
{
|
||||
if(i == lidx || i == ridx)
|
||||
continue;
|
||||
|
||||
auto &DelayBuf = mStablizer->DelayBuf[i];
|
||||
auto buffer_end = OutBuffer[i].begin() + SamplesToDo;
|
||||
if LIKELY(SamplesToDo >= FrontStablizer::DelayLength)
|
||||
{
|
||||
auto delay_end = std::rotate(OutBuffer[i].begin(),
|
||||
buffer_end - FrontStablizer::DelayLength, buffer_end);
|
||||
std::swap_ranges(OutBuffer[i].begin(), delay_end, DelayBuf.begin());
|
||||
}
|
||||
/* Skip the left and right channels, which are going to get overwritten,
|
||||
* and substitute the direct mid signal and direct+decoded side signal.
|
||||
*/
|
||||
if(i == lidx)
|
||||
mStablizer->ChannelFilters[i].processAllPass({mid, SamplesToDo});
|
||||
else if(i == ridx)
|
||||
mStablizer->ChannelFilters[i].processAllPass({side, SamplesToDo});
|
||||
else
|
||||
{
|
||||
auto delay_start = std::swap_ranges(OutBuffer[i].begin(), buffer_end,
|
||||
DelayBuf.begin());
|
||||
std::rotate(DelayBuf.begin(), delay_start, DelayBuf.end());
|
||||
}
|
||||
mStablizer->ChannelFilters[i].processAllPass({OutBuffer[i].data(), SamplesToDo});
|
||||
}
|
||||
|
||||
/* Include the side signal for what was just decoded. */
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
side[FrontStablizer::DelayLength+i] += OutBuffer[lidx][i] - OutBuffer[ridx][i];
|
||||
|
||||
/* Combine the delayed mid signal with the decoded mid signal. */
|
||||
float *tmpbuf{mStablizer->TempBuf.data()};
|
||||
auto tmpiter = std::copy(mStablizer->MidDelay.cbegin(), mStablizer->MidDelay.cend(), tmpbuf);
|
||||
for(size_t i{0};i < SamplesToDo;++i,++tmpiter)
|
||||
*tmpiter = OutBuffer[lidx][i] + OutBuffer[ridx][i];
|
||||
/* Save the newest samples for next time. */
|
||||
std::copy_n(tmpbuf+SamplesToDo, mStablizer->MidDelay.size(), mStablizer->MidDelay.begin());
|
||||
|
||||
/* Apply an all-pass on the signal in reverse. The future samples are
|
||||
* included with the all-pass to reduce the error in the output samples
|
||||
* (the smaller the delay, the more error is introduced).
|
||||
*/
|
||||
mStablizer->MidFilter.applyAllpassRev({tmpbuf, SamplesToDo+FrontStablizer::DelayLength});
|
||||
|
||||
/* Now apply the band-splitter, combining its phase shift with the reversed
|
||||
* phase shift, restoring the original phase on the split signal.
|
||||
*/
|
||||
mStablizer->MidFilter.process({tmpbuf, SamplesToDo}, mStablizer->MidHF.data(),
|
||||
mStablizer->MidLF.data());
|
||||
|
||||
/* This pans the separate low- and high-frequency signals between being on
|
||||
* the center channel and the left+right channels. The low-frequency signal
|
||||
* is panned 1/3rd toward center and the high-frequency signal is panned
|
||||
|
|
@ -164,6 +144,9 @@ void BFormatDec::processStablize(const al::span<FloatBufferLine> OutBuffer,
|
|||
const float sin_hf{std::sin(1.0f/4.0f * (al::numbers::pi_v<float>*0.5f))};
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
{
|
||||
/* Add the direct mid signal to the processed mid signal so it can be
|
||||
* properly combined with the direct+decoded side signal.
|
||||
*/
|
||||
const float m{mStablizer->MidLF[i]*cos_lf + mStablizer->MidHF[i]*cos_hf + mid[i]};
|
||||
const float c{mStablizer->MidLF[i]*sin_lf + mStablizer->MidHF[i]*sin_hf};
|
||||
const float s{side[i]};
|
||||
|
|
@ -175,11 +158,6 @@ void BFormatDec::processStablize(const al::span<FloatBufferLine> OutBuffer,
|
|||
OutBuffer[ridx][i] = (m - s) * 0.5f;
|
||||
OutBuffer[cidx][i] += c * 0.5f;
|
||||
}
|
||||
/* Move the delayed mid/side samples to the front for next time. */
|
||||
auto mid_end = mStablizer->MidDirect.cbegin() + SamplesToDo;
|
||||
std::copy(mid_end, mid_end+FrontStablizer::DelayLength, mStablizer->MidDirect.begin());
|
||||
auto side_end = mStablizer->Side.cbegin() + SamplesToDo;
|
||||
std::copy(side_end, side_end+FrontStablizer::DelayLength, mStablizer->Side.begin());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#ifndef CORE_BSINC_DEFS_H
|
||||
#define CORE_BSINC_DEFS_H
|
||||
|
||||
/* The number of distinct scale and phase intervals within the filter table. */
|
||||
/* The number of distinct scale and phase intervals within the bsinc filter
|
||||
* tables.
|
||||
*/
|
||||
constexpr unsigned int BSincScaleBits{4};
|
||||
constexpr unsigned int BSincScaleCount{1 << BSincScaleBits};
|
||||
constexpr unsigned int BSincPhaseBits{5};
|
||||
|
|
|
|||
|
|
@ -291,5 +291,5 @@ constexpr BSincTable GenerateBSincTable(const T &filter)
|
|||
|
||||
} // namespace
|
||||
|
||||
const BSincTable bsinc12{GenerateBSincTable(bsinc12_filter)};
|
||||
const BSincTable bsinc24{GenerateBSincTable(bsinc24_filter)};
|
||||
const BSincTable gBSinc12{GenerateBSincTable(bsinc12_filter)};
|
||||
const BSincTable gBSinc24{GenerateBSincTable(bsinc24_filter)};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ struct BSincTable {
|
|||
const float *Tab;
|
||||
};
|
||||
|
||||
extern const BSincTable bsinc12;
|
||||
extern const BSincTable bsinc24;
|
||||
extern const BSincTable gBSinc12;
|
||||
extern const BSincTable gBSinc24;
|
||||
|
||||
#endif /* CORE_BSINC_TABLES_H */
|
||||
|
|
|
|||
|
|
@ -6,6 +6,43 @@
|
|||
#include <stdint.h>
|
||||
|
||||
|
||||
const char *NameFromFormat(FmtType type) noexcept
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case FmtUByte: return "UInt8";
|
||||
case FmtShort: return "Int16";
|
||||
case FmtFloat: return "Float";
|
||||
case FmtDouble: return "Double";
|
||||
case FmtMulaw: return "muLaw";
|
||||
case FmtAlaw: return "aLaw";
|
||||
case FmtIMA4: return "IMA4 ADPCM";
|
||||
case FmtMSADPCM: return "MS ADPCM";
|
||||
}
|
||||
return "<internal error>";
|
||||
}
|
||||
|
||||
const char *NameFromFormat(FmtChannels channels) noexcept
|
||||
{
|
||||
switch(channels)
|
||||
{
|
||||
case FmtMono: return "Mono";
|
||||
case FmtStereo: return "Stereo";
|
||||
case FmtRear: return "Rear";
|
||||
case FmtQuad: return "Quadraphonic";
|
||||
case FmtX51: return "Surround 5.1";
|
||||
case FmtX61: return "Surround 6.1";
|
||||
case FmtX71: return "Surround 7.1";
|
||||
case FmtBFormat2D: return "B-Format 2D";
|
||||
case FmtBFormat3D: return "B-Format 3D";
|
||||
case FmtUHJ2: return "UHJ2";
|
||||
case FmtUHJ3: return "UHJ3";
|
||||
case FmtUHJ4: return "UHJ4";
|
||||
case FmtSuperStereo: return "Super Stereo";
|
||||
}
|
||||
return "<internal error>";
|
||||
}
|
||||
|
||||
uint BytesFromFmt(FmtType type) noexcept
|
||||
{
|
||||
switch(type)
|
||||
|
|
@ -16,6 +53,8 @@ uint BytesFromFmt(FmtType type) noexcept
|
|||
case FmtDouble: return sizeof(double);
|
||||
case FmtMulaw: return sizeof(uint8_t);
|
||||
case FmtAlaw: return sizeof(uint8_t);
|
||||
case FmtIMA4: break;
|
||||
case FmtMSADPCM: break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "albyte.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "ambidefs.h"
|
||||
|
||||
|
||||
|
|
@ -18,6 +19,8 @@ enum FmtType : unsigned char {
|
|||
FmtDouble,
|
||||
FmtMulaw,
|
||||
FmtAlaw,
|
||||
FmtIMA4,
|
||||
FmtMSADPCM,
|
||||
};
|
||||
enum FmtChannels : unsigned char {
|
||||
FmtMono,
|
||||
|
|
@ -46,6 +49,9 @@ enum class AmbiScaling : unsigned char {
|
|||
UHJ,
|
||||
};
|
||||
|
||||
const char *NameFromFormat(FmtType type) noexcept;
|
||||
const char *NameFromFormat(FmtChannels channels) noexcept;
|
||||
|
||||
uint BytesFromFmt(FmtType type) noexcept;
|
||||
uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept;
|
||||
inline uint FrameSizeFromFmt(FmtChannels chans, FmtType type, uint ambiorder) noexcept
|
||||
|
|
@ -79,10 +85,13 @@ struct BufferStorage {
|
|||
CallbackType mCallback{nullptr};
|
||||
void *mUserData{nullptr};
|
||||
|
||||
al::span<al::byte> mData;
|
||||
|
||||
uint mSampleRate{0u};
|
||||
FmtChannels mChannels{FmtMono};
|
||||
FmtType mType{FmtShort};
|
||||
uint mSampleLen{0u};
|
||||
uint mBlockAlign{0u};
|
||||
|
||||
AmbiLayout mAmbiLayout{AmbiLayout::FuMa};
|
||||
AmbiScaling mAmbiScaling{AmbiScaling::FuMa};
|
||||
|
|
@ -93,6 +102,13 @@ struct BufferStorage {
|
|||
{ return ChannelsFromFmt(mChannels, mAmbiOrder); }
|
||||
inline uint frameSizeFromFmt() const noexcept { return channelsFromFmt() * bytesFromFmt(); }
|
||||
|
||||
inline uint blockSizeFromFmt() const noexcept
|
||||
{
|
||||
if(mType == FmtIMA4) return ((mBlockAlign-1)/2 + 4) * channelsFromFmt();
|
||||
if(mType == FmtMSADPCM) return ((mBlockAlign-2)/2 + 7) * channelsFromFmt();
|
||||
return frameSizeFromFmt();
|
||||
};
|
||||
|
||||
inline bool isBFormat() const noexcept { return IsBFormat(mChannels); }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
#include "async_event.h"
|
||||
|
|
@ -13,8 +14,12 @@
|
|||
#include "voice_change.h"
|
||||
|
||||
|
||||
#ifdef __cpp_lib_atomic_is_always_lock_free
|
||||
static_assert(std::atomic<ContextBase::AsyncEventBitset>::is_always_lock_free, "atomic<bitset> isn't lock-free");
|
||||
#endif
|
||||
|
||||
ContextBase::ContextBase(DeviceBase *device) : mDevice{device}
|
||||
{ }
|
||||
{ assert(mEnabledEvts.is_lock_free()); }
|
||||
|
||||
ContextBase::~ContextBase()
|
||||
{
|
||||
|
|
@ -136,3 +141,24 @@ void ContextBase::allocVoices(size_t addcount)
|
|||
delete oldvoices;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EffectSlot *ContextBase::getEffectSlot()
|
||||
{
|
||||
for(auto& cluster : mEffectSlotClusters)
|
||||
{
|
||||
for(size_t i{0};i < EffectSlotClusterSize;++i)
|
||||
{
|
||||
if(!cluster[i].InUse)
|
||||
return &cluster[i];
|
||||
}
|
||||
}
|
||||
|
||||
if(1 >= std::numeric_limits<int>::max()/EffectSlotClusterSize - mEffectSlotClusters.size())
|
||||
throw std::runtime_error{"Allocating too many effect slots"};
|
||||
const size_t totalcount{(mEffectSlotClusters.size()+1) * EffectSlotClusterSize};
|
||||
TRACE("Increasing allocated effect slots to %zu\n", totalcount);
|
||||
|
||||
mEffectSlotClusters.emplace_back(std::make_unique<EffectSlot[]>(EffectSlotClusterSize));
|
||||
return getEffectSlot();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@
|
|||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "async_event.h"
|
||||
#include "atomic.h"
|
||||
#include "bufferline.h"
|
||||
#include "threads.h"
|
||||
|
|
@ -40,17 +42,6 @@ enum class DistanceModel : unsigned char {
|
|||
};
|
||||
|
||||
|
||||
struct WetBuffer {
|
||||
bool mInUse;
|
||||
al::FlexArray<FloatBufferLine, 16> mBuffer;
|
||||
|
||||
WetBuffer(size_t count) : mBuffer{count} { }
|
||||
|
||||
DEF_FAM_NEWDEL(WetBuffer, mBuffer)
|
||||
};
|
||||
using WetBufferPtr = std::unique_ptr<WetBuffer>;
|
||||
|
||||
|
||||
struct ContextProps {
|
||||
std::array<float,3> Position;
|
||||
std::array<float,3> Velocity;
|
||||
|
|
@ -146,7 +137,8 @@ struct ContextBase {
|
|||
std::thread mEventThread;
|
||||
al::semaphore mEventSem;
|
||||
std::unique_ptr<RingBuffer> mAsyncEvents;
|
||||
std::atomic<uint> mEnabledEvts{0u};
|
||||
using AsyncEventBitset = std::bitset<AsyncEvent::UserEventCount>;
|
||||
std::atomic<AsyncEventBitset> mEnabledEvts{0u};
|
||||
|
||||
/* Asynchronous voice change actions are processed as a linked list of
|
||||
* VoiceChange objects by the mixer, which is atomically appended to.
|
||||
|
|
@ -163,6 +155,13 @@ struct ContextBase {
|
|||
al::vector<VoicePropsCluster> mVoicePropClusters;
|
||||
|
||||
|
||||
static constexpr size_t EffectSlotClusterSize{4};
|
||||
EffectSlot *getEffectSlot();
|
||||
|
||||
using EffectSlotCluster = std::unique_ptr<EffectSlot[]>;
|
||||
al::vector<EffectSlotCluster> mEffectSlotClusters;
|
||||
|
||||
|
||||
ContextBase(DeviceBase *device);
|
||||
ContextBase(const ContextBase&) = delete;
|
||||
ContextBase& operator=(const ContextBase&) = delete;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "converter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
|
|
@ -14,9 +15,6 @@
|
|||
#include "alnumeric.h"
|
||||
#include "fpu_ctrl.h"
|
||||
|
||||
struct CTag;
|
||||
struct CopyTag;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -142,7 +140,7 @@ void Multi2Mono(uint chanmask, const size_t step, const float scale, float *REST
|
|||
std::fill_n(dst, frames, 0.0f);
|
||||
for(size_t c{0};chanmask;++c)
|
||||
{
|
||||
if LIKELY((chanmask&1))
|
||||
if((chanmask&1)) LIKELY
|
||||
{
|
||||
for(size_t i{0u};i < frames;i++)
|
||||
dst[i] += LoadSample<T>(ssrc[i*step + c]);
|
||||
|
|
@ -155,7 +153,7 @@ void Multi2Mono(uint chanmask, const size_t step, const float scale, float *REST
|
|||
|
||||
} // namespace
|
||||
|
||||
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
|
||||
SampleConverterPtr SampleConverter::Create(DevFmtType srcType, DevFmtType dstType, size_t numchans,
|
||||
uint srcRate, uint dstRate, Resampler resampler)
|
||||
{
|
||||
if(numchans < 1 || srcRate < 1 || dstRate < 1)
|
||||
|
|
@ -167,8 +165,13 @@ SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType,
|
|||
converter->mSrcTypeSize = BytesFromDevFmt(srcType);
|
||||
converter->mDstTypeSize = BytesFromDevFmt(dstType);
|
||||
|
||||
converter->mSrcPrepCount = 0;
|
||||
converter->mSrcPrepCount = MaxResamplerPadding;
|
||||
converter->mFracOffset = 0;
|
||||
for(auto &chan : converter->mChan)
|
||||
{
|
||||
const al::span<float> buffer{chan.PrevSamples};
|
||||
std::fill(buffer.begin(), buffer.end(), 0.0f);
|
||||
}
|
||||
|
||||
/* Have to set the mixer FPU mode since that's what the resampler code expects. */
|
||||
FPUCtl mixer_mode{};
|
||||
|
|
@ -176,7 +179,8 @@ SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType,
|
|||
mind(srcRate*double{MixerFracOne}/dstRate + 0.5, MaxPitch*MixerFracOne));
|
||||
converter->mIncrement = maxu(step, 1);
|
||||
if(converter->mIncrement == MixerFracOne)
|
||||
converter->mResample = Resample_<CopyTag,CTag>;
|
||||
converter->mResample = [](const InterpState*, const float *RESTRICT src, uint, const uint,
|
||||
const al::span<float> dst) { std::copy_n(src, dst.size(), dst.begin()); };
|
||||
else
|
||||
converter->mResample = PrepareResampler(resampler, converter->mIncrement,
|
||||
&converter->mState);
|
||||
|
|
@ -186,30 +190,20 @@ SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType,
|
|||
|
||||
uint SampleConverter::availableOut(uint srcframes) const
|
||||
{
|
||||
int prepcount{mSrcPrepCount};
|
||||
if(prepcount < 0)
|
||||
{
|
||||
/* Negative prepcount means we need to skip that many input samples. */
|
||||
if(static_cast<uint>(-prepcount) >= srcframes)
|
||||
return 0;
|
||||
srcframes -= static_cast<uint>(-prepcount);
|
||||
prepcount = 0;
|
||||
}
|
||||
|
||||
if(srcframes < 1)
|
||||
{
|
||||
/* No output samples if there's no input samples. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(prepcount < MaxResamplerPadding
|
||||
&& static_cast<uint>(MaxResamplerPadding - prepcount) >= srcframes)
|
||||
const uint prepcount{mSrcPrepCount};
|
||||
if(prepcount < MaxResamplerPadding && MaxResamplerPadding - prepcount >= srcframes)
|
||||
{
|
||||
/* Not enough input samples to generate an output sample. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto DataSize64 = static_cast<uint64_t>(prepcount);
|
||||
uint64_t DataSize64{prepcount};
|
||||
DataSize64 += srcframes;
|
||||
DataSize64 -= MaxResamplerPadding;
|
||||
DataSize64 <<= MixerFracBits;
|
||||
|
|
@ -232,34 +226,19 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint
|
|||
uint pos{0};
|
||||
while(pos < dstframes && NumSrcSamples > 0)
|
||||
{
|
||||
int prepcount{mSrcPrepCount};
|
||||
if(prepcount < 0)
|
||||
{
|
||||
/* Negative prepcount means we need to skip that many input samples. */
|
||||
if(static_cast<uint>(-prepcount) >= NumSrcSamples)
|
||||
{
|
||||
mSrcPrepCount = static_cast<int>(NumSrcSamples) + prepcount;
|
||||
NumSrcSamples = 0;
|
||||
break;
|
||||
}
|
||||
SamplesIn += SrcFrameSize*static_cast<uint>(-prepcount);
|
||||
NumSrcSamples -= static_cast<uint>(-prepcount);
|
||||
mSrcPrepCount = 0;
|
||||
continue;
|
||||
}
|
||||
const uint toread{minu(NumSrcSamples, BufferLineSize - MaxResamplerPadding)};
|
||||
const uint prepcount{mSrcPrepCount};
|
||||
const uint readable{minu(NumSrcSamples, BufferLineSize - prepcount)};
|
||||
|
||||
if(prepcount < MaxResamplerPadding
|
||||
&& static_cast<uint>(MaxResamplerPadding - prepcount) >= toread)
|
||||
if(prepcount < MaxResamplerPadding && MaxResamplerPadding-prepcount >= readable)
|
||||
{
|
||||
/* Not enough input samples to generate an output sample. Store
|
||||
* what we're given for later.
|
||||
*/
|
||||
for(size_t chan{0u};chan < mChan.size();chan++)
|
||||
LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan,
|
||||
mChan.size(), mSrcType, toread);
|
||||
mChan.size(), mSrcType, readable);
|
||||
|
||||
mSrcPrepCount = prepcount + static_cast<int>(toread);
|
||||
mSrcPrepCount = prepcount + readable;
|
||||
NumSrcSamples = 0;
|
||||
break;
|
||||
}
|
||||
|
|
@ -267,8 +246,8 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint
|
|||
float *RESTRICT SrcData{mSrcSamples};
|
||||
float *RESTRICT DstData{mDstSamples};
|
||||
uint DataPosFrac{mFracOffset};
|
||||
auto DataSize64 = static_cast<uint64_t>(prepcount);
|
||||
DataSize64 += toread;
|
||||
uint64_t DataSize64{prepcount};
|
||||
DataSize64 += readable;
|
||||
DataSize64 -= MaxResamplerPadding;
|
||||
DataSize64 <<= MixerFracBits;
|
||||
DataSize64 -= DataPosFrac;
|
||||
|
|
@ -278,6 +257,12 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint
|
|||
clampu64((DataSize64 + increment-1)/increment, 1, BufferLineSize));
|
||||
DstSize = minu(DstSize, dstframes-pos);
|
||||
|
||||
const uint DataPosEnd{DstSize*increment + DataPosFrac};
|
||||
const uint SrcDataEnd{DataPosEnd>>MixerFracBits};
|
||||
|
||||
assert(prepcount+readable >= SrcDataEnd);
|
||||
const uint nextprep{minu(prepcount + readable - SrcDataEnd, MaxResamplerPadding)};
|
||||
|
||||
for(size_t chan{0u};chan < mChan.size();chan++)
|
||||
{
|
||||
const al::byte *SrcSamples{SamplesIn + mSrcTypeSize*chan};
|
||||
|
|
@ -287,42 +272,32 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint
|
|||
* new samples from the input buffer.
|
||||
*/
|
||||
std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData);
|
||||
LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, toread);
|
||||
LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, readable);
|
||||
|
||||
/* Store as many prep samples for next time as possible, given the
|
||||
* number of output samples being generated.
|
||||
*/
|
||||
uint SrcDataEnd{(DstSize*increment + DataPosFrac)>>MixerFracBits};
|
||||
if(SrcDataEnd >= static_cast<uint>(prepcount)+toread)
|
||||
std::fill(std::begin(mChan[chan].PrevSamples),
|
||||
std::end(mChan[chan].PrevSamples), 0.0f);
|
||||
else
|
||||
{
|
||||
const size_t len{minz(al::size(mChan[chan].PrevSamples),
|
||||
static_cast<uint>(prepcount)+toread-SrcDataEnd)};
|
||||
std::copy_n(SrcData+SrcDataEnd, len, mChan[chan].PrevSamples);
|
||||
std::fill(std::begin(mChan[chan].PrevSamples)+len,
|
||||
std::end(mChan[chan].PrevSamples), 0.0f);
|
||||
}
|
||||
std::copy_n(SrcData+SrcDataEnd, nextprep, mChan[chan].PrevSamples);
|
||||
std::fill(std::begin(mChan[chan].PrevSamples)+nextprep,
|
||||
std::end(mChan[chan].PrevSamples), 0.0f);
|
||||
|
||||
/* Now resample, and store the result in the output buffer. */
|
||||
const float *ResampledData{mResample(&mState, SrcData+(MaxResamplerPadding>>1),
|
||||
DataPosFrac, increment, {DstData, DstSize})};
|
||||
mResample(&mState, SrcData+MaxResamplerEdge, DataPosFrac, increment,
|
||||
{DstData, DstSize});
|
||||
|
||||
StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, DstSize);
|
||||
StoreSamples(DstSamples, DstData, mChan.size(), mDstType, DstSize);
|
||||
}
|
||||
|
||||
/* Update the number of prep samples still available, as well as the
|
||||
* fractional offset.
|
||||
*/
|
||||
DataPosFrac += increment*DstSize;
|
||||
mSrcPrepCount = mini(prepcount + static_cast<int>(toread - (DataPosFrac>>MixerFracBits)),
|
||||
MaxResamplerPadding);
|
||||
mFracOffset = DataPosFrac & MixerFracMask;
|
||||
mSrcPrepCount = nextprep;
|
||||
mFracOffset = DataPosEnd & MixerFracMask;
|
||||
|
||||
/* Update the src and dst pointers in case there's still more to do. */
|
||||
SamplesIn += SrcFrameSize*(DataPosFrac>>MixerFracBits);
|
||||
NumSrcSamples -= minu(NumSrcSamples, (DataPosFrac>>MixerFracBits));
|
||||
const uint srcread{minu(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)};
|
||||
SamplesIn += SrcFrameSize*srcread;
|
||||
NumSrcSamples -= srcread;
|
||||
|
||||
dst = static_cast<al::byte*>(dst) + DstFrameSize*DstSize;
|
||||
pos += DstSize;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef CORE_CONVERTER_H
|
||||
#define CORE_CONVERTER_H
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
|
|
@ -17,7 +18,7 @@ struct SampleConverter {
|
|||
uint mSrcTypeSize{};
|
||||
uint mDstTypeSize{};
|
||||
|
||||
int mSrcPrepCount{};
|
||||
uint mSrcPrepCount{};
|
||||
|
||||
uint mFracOffset{};
|
||||
uint mIncrement{};
|
||||
|
|
@ -37,14 +38,20 @@ struct SampleConverter {
|
|||
uint convert(const void **src, uint *srcframes, void *dst, uint dstframes);
|
||||
uint availableOut(uint srcframes) const;
|
||||
|
||||
using SampleOffset = std::chrono::duration<int64_t, std::ratio<1,MixerFracOne>>;
|
||||
SampleOffset currentInputDelay() const noexcept
|
||||
{
|
||||
const int64_t prep{int64_t{mSrcPrepCount} - MaxResamplerEdge};
|
||||
return SampleOffset{(prep<<MixerFracBits) + mFracOffset};
|
||||
}
|
||||
|
||||
static std::unique_ptr<SampleConverter> Create(DevFmtType srcType, DevFmtType dstType,
|
||||
size_t numchans, uint srcRate, uint dstRate, Resampler resampler);
|
||||
|
||||
DEF_FAM_NEWDEL(SampleConverter, mChan)
|
||||
};
|
||||
using SampleConverterPtr = std::unique_ptr<SampleConverter>;
|
||||
|
||||
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
|
||||
uint srcRate, uint dstRate, Resampler resampler);
|
||||
|
||||
|
||||
struct ChannelConverter {
|
||||
DevFmtType mSrcType{};
|
||||
uint mSrcStep{};
|
||||
|
|
|
|||
|
|
@ -11,11 +11,10 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_INTRIN_H
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
#ifdef HAVE_CPUID_H
|
||||
#if defined(HAVE_CPUID_H)
|
||||
#include <cpuid.h>
|
||||
#elif defined(HAVE_INTRIN_H)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
|
|
@ -33,7 +32,7 @@ using reg_type = unsigned int;
|
|||
inline std::array<reg_type,4> get_cpuid(unsigned int f)
|
||||
{
|
||||
std::array<reg_type,4> ret{};
|
||||
__get_cpuid(f, &ret[0], &ret[1], &ret[2], &ret[3]);
|
||||
__get_cpuid(f, ret.data(), &ret[1], &ret[2], &ret[3]);
|
||||
return ret;
|
||||
}
|
||||
#define CAN_GET_CPUID
|
||||
|
|
@ -138,5 +137,5 @@ al::optional<CPUInfo> GetCPUInfo()
|
|||
#endif
|
||||
#endif
|
||||
|
||||
return al::make_optional(ret);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
13
Engine/lib/openal-soft/core/cubic_defs.h
Normal file
13
Engine/lib/openal-soft/core/cubic_defs.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef CORE_CUBIC_DEFS_H
|
||||
#define CORE_CUBIC_DEFS_H
|
||||
|
||||
/* The number of distinct phase intervals within the cubic filter tables. */
|
||||
constexpr unsigned int CubicPhaseBits{5};
|
||||
constexpr unsigned int CubicPhaseCount{1 << CubicPhaseBits};
|
||||
|
||||
struct CubicCoefficients {
|
||||
float mCoeffs[4];
|
||||
float mDeltas[4];
|
||||
};
|
||||
|
||||
#endif /* CORE_CUBIC_DEFS_H */
|
||||
59
Engine/lib/openal-soft/core/cubic_tables.cpp
Normal file
59
Engine/lib/openal-soft/core/cubic_tables.cpp
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
|
||||
#include "cubic_tables.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "core/mixer/defs.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
struct SplineFilterArray {
|
||||
alignas(16) CubicCoefficients mTable[CubicPhaseCount]{};
|
||||
|
||||
constexpr SplineFilterArray()
|
||||
{
|
||||
/* Fill in the main coefficients. */
|
||||
for(size_t pi{0};pi < CubicPhaseCount;++pi)
|
||||
{
|
||||
const double mu{static_cast<double>(pi) / CubicPhaseCount};
|
||||
const double mu2{mu*mu}, mu3{mu2*mu};
|
||||
mTable[pi].mCoeffs[0] = static_cast<float>(-0.5*mu3 + mu2 + -0.5*mu);
|
||||
mTable[pi].mCoeffs[1] = static_cast<float>( 1.5*mu3 + -2.5*mu2 + 1.0);
|
||||
mTable[pi].mCoeffs[2] = static_cast<float>(-1.5*mu3 + 2.0*mu2 + 0.5*mu);
|
||||
mTable[pi].mCoeffs[3] = static_cast<float>( 0.5*mu3 + -0.5*mu2);
|
||||
}
|
||||
|
||||
/* Fill in the coefficient deltas. */
|
||||
for(size_t pi{0};pi < CubicPhaseCount-1;++pi)
|
||||
{
|
||||
mTable[pi].mDeltas[0] = mTable[pi+1].mCoeffs[0] - mTable[pi].mCoeffs[0];
|
||||
mTable[pi].mDeltas[1] = mTable[pi+1].mCoeffs[1] - mTable[pi].mCoeffs[1];
|
||||
mTable[pi].mDeltas[2] = mTable[pi+1].mCoeffs[2] - mTable[pi].mCoeffs[2];
|
||||
mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3];
|
||||
}
|
||||
|
||||
const size_t pi{CubicPhaseCount - 1};
|
||||
mTable[pi].mDeltas[0] = -mTable[pi].mCoeffs[0];
|
||||
mTable[pi].mDeltas[1] = -mTable[pi].mCoeffs[1];
|
||||
mTable[pi].mDeltas[2] = 1.0f - mTable[pi].mCoeffs[2];
|
||||
mTable[pi].mDeltas[3] = -mTable[pi].mCoeffs[3];
|
||||
}
|
||||
|
||||
constexpr auto getTable() const noexcept { return al::as_span(mTable); }
|
||||
};
|
||||
|
||||
constexpr SplineFilterArray SplineFilter{};
|
||||
|
||||
} // namespace
|
||||
|
||||
const CubicTable gCubicSpline{SplineFilter.getTable()};
|
||||
17
Engine/lib/openal-soft/core/cubic_tables.h
Normal file
17
Engine/lib/openal-soft/core/cubic_tables.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef CORE_CUBIC_TABLES_H
|
||||
#define CORE_CUBIC_TABLES_H
|
||||
|
||||
#include "alspan.h"
|
||||
#include "cubic_defs.h"
|
||||
|
||||
|
||||
struct CubicTable {
|
||||
al::span<const CubicCoefficients,CubicPhaseCount> Tab;
|
||||
};
|
||||
|
||||
/* A Catmull-Rom spline. The spline passes through the center two samples,
|
||||
* ensuring no discontinuity while moving through a series of samples.
|
||||
*/
|
||||
extern const CubicTable gCubicSpline;
|
||||
|
||||
#endif /* CORE_CUBIC_TABLES_H */
|
||||
|
|
@ -28,6 +28,8 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept
|
|||
case DevFmtX51: return 6;
|
||||
case DevFmtX61: return 7;
|
||||
case DevFmtX71: return 8;
|
||||
case DevFmtX714: return 12;
|
||||
case DevFmtX3D71: return 8;
|
||||
case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1);
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -57,6 +59,8 @@ const char *DevFmtChannelsString(DevFmtChannels chans) noexcept
|
|||
case DevFmtX51: return "5.1 Surround";
|
||||
case DevFmtX61: return "6.1 Surround";
|
||||
case DevFmtX71: return "7.1 Surround";
|
||||
case DevFmtX714: return "7.1.4 Surround";
|
||||
case DevFmtX3D71: return "3D7.1 Surround";
|
||||
case DevFmtAmbi3D: return "Ambisonic 3D";
|
||||
}
|
||||
return "(unknown channels)";
|
||||
|
|
|
|||
|
|
@ -25,6 +25,23 @@ enum Channel : unsigned char {
|
|||
TopBackCenter,
|
||||
TopBackRight,
|
||||
|
||||
Aux0,
|
||||
Aux1,
|
||||
Aux2,
|
||||
Aux3,
|
||||
Aux4,
|
||||
Aux5,
|
||||
Aux6,
|
||||
Aux7,
|
||||
Aux8,
|
||||
Aux9,
|
||||
Aux10,
|
||||
Aux11,
|
||||
Aux12,
|
||||
Aux13,
|
||||
Aux14,
|
||||
Aux15,
|
||||
|
||||
MaxChannels
|
||||
};
|
||||
|
||||
|
|
@ -48,6 +65,8 @@ enum DevFmtChannels : unsigned char {
|
|||
DevFmtX51,
|
||||
DevFmtX61,
|
||||
DevFmtX71,
|
||||
DevFmtX714,
|
||||
DevFmtX3D71,
|
||||
DevFmtAmbi3D,
|
||||
|
||||
DevFmtChannelsDefault = DevFmtStereo
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ using uint = unsigned int;
|
|||
|
||||
#define MIN_OUTPUT_RATE 8000
|
||||
#define MAX_OUTPUT_RATE 192000
|
||||
#define DEFAULT_OUTPUT_RATE 44100
|
||||
#define DEFAULT_OUTPUT_RATE 48000
|
||||
|
||||
#define DEFAULT_UPDATE_SIZE 882 /* 20ms */
|
||||
#define DEFAULT_UPDATE_SIZE 960 /* 20ms */
|
||||
#define DEFAULT_NUM_UPDATES 3
|
||||
|
||||
|
||||
|
|
@ -69,17 +69,17 @@ struct InputRemixMap {
|
|||
struct TargetMix { Channel channel; float mix; };
|
||||
|
||||
Channel channel;
|
||||
std::array<TargetMix,2> targets;
|
||||
al::span<const TargetMix> targets;
|
||||
};
|
||||
|
||||
|
||||
/* Maximum delay in samples for speaker distance compensation. */
|
||||
#define MAX_DELAY_LENGTH 1024
|
||||
|
||||
struct DistanceComp {
|
||||
/* Maximum delay in samples for speaker distance compensation. */
|
||||
static constexpr uint MaxDelay{1024};
|
||||
|
||||
struct ChanData {
|
||||
float Gain{1.0f};
|
||||
uint Length{0u}; /* Valid range is [0...MAX_DELAY_LENGTH). */
|
||||
uint Length{0u}; /* Valid range is [0...MaxDelay). */
|
||||
float *Buffer{nullptr};
|
||||
};
|
||||
|
||||
|
|
@ -95,17 +95,49 @@ struct DistanceComp {
|
|||
};
|
||||
|
||||
|
||||
constexpr uint InvalidChannelIndex{~0u};
|
||||
|
||||
struct BFChannelConfig {
|
||||
float Scale;
|
||||
uint Index;
|
||||
};
|
||||
|
||||
|
||||
struct MixParams {
|
||||
/* Coefficient channel mapping for mixing to the buffer. */
|
||||
std::array<BFChannelConfig,MAX_OUTPUT_CHANNELS> AmbiMap{};
|
||||
std::array<BFChannelConfig,MaxAmbiChannels> AmbiMap{};
|
||||
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
|
||||
/**
|
||||
* Helper to set an identity/pass-through panning for ambisonic mixing. The
|
||||
* source is expected to be a 3D ACN/N3D ambisonic buffer, and for each
|
||||
* channel [0...count), the given functor is called with the source channel
|
||||
* index, destination channel index, and the gain for that channel. If the
|
||||
* destination channel is INVALID_CHANNEL_INDEX, the given source channel
|
||||
* is not used for output.
|
||||
*/
|
||||
template<typename F>
|
||||
void setAmbiMixParams(const MixParams &inmix, const float gainbase, F func) const
|
||||
{
|
||||
const size_t numIn{inmix.Buffer.size()};
|
||||
const size_t numOut{Buffer.size()};
|
||||
for(size_t i{0};i < numIn;++i)
|
||||
{
|
||||
auto idx = InvalidChannelIndex;
|
||||
auto gain = 0.0f;
|
||||
|
||||
for(size_t j{0};j < numOut;++j)
|
||||
{
|
||||
if(AmbiMap[j].Index == inmix.AmbiMap[i].Index)
|
||||
{
|
||||
idx = static_cast<uint>(j);
|
||||
gain = AmbiMap[j].Scale * gainbase;
|
||||
break;
|
||||
}
|
||||
}
|
||||
func(i, idx, gain);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct RealMixParams {
|
||||
|
|
@ -115,10 +147,12 @@ struct RealMixParams {
|
|||
al::span<FloatBufferLine> Buffer;
|
||||
};
|
||||
|
||||
using AmbiRotateMatrix = std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels>;
|
||||
|
||||
enum {
|
||||
// Frequency was requested by the app or config file
|
||||
FrequencyRequest,
|
||||
// Channel configuration was requested by the config file
|
||||
// Channel configuration was requested by the app or config file
|
||||
ChannelsRequest,
|
||||
// Sample type was requested by the config file
|
||||
SampleTypeRequest,
|
||||
|
|
@ -152,6 +186,8 @@ struct DeviceBase {
|
|||
DevFmtType FmtType{};
|
||||
uint mAmbiOrder{0};
|
||||
float mXOverFreq{400.0f};
|
||||
/* If the main device mix is horizontal/2D only. */
|
||||
bool m2DMixing{false};
|
||||
/* For DevFmtAmbi* output only, specifies the channel order and
|
||||
* normalization.
|
||||
*/
|
||||
|
|
@ -182,14 +218,16 @@ struct DeviceBase {
|
|||
std::chrono::nanoseconds ClockBase{0};
|
||||
std::chrono::nanoseconds FixedLatency{0};
|
||||
|
||||
AmbiRotateMatrix mAmbiRotateMatrix{};
|
||||
AmbiRotateMatrix mAmbiRotateMatrix2{};
|
||||
|
||||
/* Temp storage used for mixer processing. */
|
||||
static constexpr size_t MixerLineSize{BufferLineSize + MaxResamplerPadding +
|
||||
UhjDecoder::sFilterDelay};
|
||||
static constexpr size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding};
|
||||
static constexpr size_t MixerChannelsMax{16};
|
||||
using MixerBufferLine = std::array<float,MixerLineSize>;
|
||||
alignas(16) std::array<MixerBufferLine,MixerChannelsMax> mSampleData;
|
||||
alignas(16) std::array<float,MixerLineSize+MaxResamplerPadding> mResampleData;
|
||||
|
||||
alignas(16) float ResampledData[BufferLineSize];
|
||||
alignas(16) float FilteredData[BufferLineSize];
|
||||
union {
|
||||
alignas(16) float HrtfSourceData[BufferLineSize + HrtfHistoryLength];
|
||||
|
|
@ -217,7 +255,7 @@ struct DeviceBase {
|
|||
uint mIrSize{0};
|
||||
|
||||
/* Ambisonic-to-UHJ encoder */
|
||||
std::unique_ptr<UhjEncoder> mUhjEncoder;
|
||||
std::unique_ptr<UhjEncoderBase> mUhjEncoder;
|
||||
|
||||
/* Ambisonic decoder for speakers */
|
||||
std::unique_ptr<BFormatDec> AmbiDecoder;
|
||||
|
|
@ -272,7 +310,7 @@ struct DeviceBase {
|
|||
void ProcessBs2b(const size_t SamplesToDo);
|
||||
|
||||
inline void postProcess(const size_t SamplesToDo)
|
||||
{ if LIKELY(PostProcess) (this->*PostProcess)(SamplesToDo); }
|
||||
{ if(PostProcess) LIKELY (this->*PostProcess)(SamplesToDo); }
|
||||
|
||||
void renderSamples(const al::span<float*> outBuffers, const uint numSamples);
|
||||
void renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep);
|
||||
|
|
@ -285,26 +323,23 @@ struct DeviceBase {
|
|||
#endif
|
||||
void handleDisconnect(const char *msg, ...);
|
||||
|
||||
/**
|
||||
* Returns the index for the given channel name (e.g. FrontCenter), or
|
||||
* INVALID_CHANNEL_INDEX if it doesn't exist.
|
||||
*/
|
||||
uint channelIdxByName(Channel chan) const noexcept
|
||||
{ return RealOut.ChannelIndex[chan]; }
|
||||
|
||||
DISABLE_ALLOC()
|
||||
|
||||
private:
|
||||
uint renderSamples(const uint numSamples);
|
||||
};
|
||||
|
||||
|
||||
/* Must be less than 15 characters (16 including terminating null) for
|
||||
* compatibility with pthread_setname_np limitations. */
|
||||
#define MIXER_THREAD_NAME "alsoft-mixer"
|
||||
|
||||
#define RECORD_THREAD_NAME "alsoft-record"
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index for the given channel name (e.g. FrontCenter), or
|
||||
* INVALID_CHANNEL_INDEX if it doesn't exist.
|
||||
*/
|
||||
inline uint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcept
|
||||
{ return real.ChannelIndex[chan]; }
|
||||
#define INVALID_CHANNEL_INDEX ~0u
|
||||
|
||||
#endif /* CORE_DEVICE_H */
|
||||
|
|
|
|||
|
|
@ -61,32 +61,29 @@ enum class VMorpherWaveform {
|
|||
|
||||
union EffectProps {
|
||||
struct {
|
||||
// Shared Reverb Properties
|
||||
float Density;
|
||||
float Diffusion;
|
||||
float Gain;
|
||||
float GainHF;
|
||||
float GainLF;
|
||||
float DecayTime;
|
||||
float DecayHFRatio;
|
||||
float DecayLFRatio;
|
||||
float ReflectionsGain;
|
||||
float ReflectionsDelay;
|
||||
float ReflectionsPan[3];
|
||||
float LateReverbGain;
|
||||
float LateReverbDelay;
|
||||
float AirAbsorptionGainHF;
|
||||
float RoomRolloffFactor;
|
||||
bool DecayHFLimit;
|
||||
|
||||
// Additional EAX Reverb Properties
|
||||
float GainLF;
|
||||
float DecayLFRatio;
|
||||
float ReflectionsPan[3];
|
||||
float LateReverbPan[3];
|
||||
float EchoTime;
|
||||
float EchoDepth;
|
||||
float ModulationTime;
|
||||
float ModulationDepth;
|
||||
float AirAbsorptionGainHF;
|
||||
float HFReference;
|
||||
float LFReference;
|
||||
float RoomRolloffFactor;
|
||||
bool DecayHFLimit;
|
||||
} Reverb;
|
||||
|
||||
struct {
|
||||
|
|
@ -178,17 +175,12 @@ struct EffectTarget {
|
|||
};
|
||||
|
||||
struct EffectState : public al::intrusive_ref<EffectState> {
|
||||
struct Buffer {
|
||||
const BufferStorage *storage;
|
||||
al::span<const al::byte> samples;
|
||||
};
|
||||
|
||||
al::span<FloatBufferLine> mOutTarget;
|
||||
|
||||
|
||||
virtual ~EffectState() = default;
|
||||
|
||||
virtual void deviceUpdate(const DeviceBase *device, const Buffer &buffer) = 0;
|
||||
virtual void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) = 0;
|
||||
virtual void update(const ContextBase *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target) = 0;
|
||||
virtual void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
|
||||
|
|
|
|||
|
|
@ -17,9 +17,3 @@ EffectSlotArray *EffectSlot::CreatePtrArray(size_t count) noexcept
|
|||
void *ptr{al_calloc(alignof(EffectSlotArray), EffectSlotArray::Sizeof(count*2))};
|
||||
return al::construct_at(static_cast<EffectSlotArray*>(ptr), count);
|
||||
}
|
||||
|
||||
EffectSlot::~EffectSlot()
|
||||
{
|
||||
if(mWetBuffer)
|
||||
mWetBuffer->mInUse = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ struct EffectSlotProps {
|
|||
|
||||
|
||||
struct EffectSlot {
|
||||
bool InUse{false};
|
||||
|
||||
std::atomic<EffectSlotProps*> Update{nullptr};
|
||||
|
||||
/* Wet buffer configuration is ACN channel order with N3D scaling.
|
||||
|
|
@ -66,7 +68,7 @@ struct EffectSlot {
|
|||
|
||||
EffectSlotType EffectType{EffectSlotType::None};
|
||||
EffectProps mEffectProps{};
|
||||
EffectState *mEffectState{nullptr};
|
||||
al::intrusive_ptr<EffectState> mEffectState;
|
||||
|
||||
float RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */
|
||||
float DecayTime{0.0f};
|
||||
|
|
@ -76,13 +78,12 @@ struct EffectSlot {
|
|||
float AirAbsorptionGainHF{1.0f};
|
||||
|
||||
/* Mixing buffer used by the Wet mix. */
|
||||
WetBuffer *mWetBuffer{nullptr};
|
||||
al::vector<FloatBufferLine,16> mWetBuffer;
|
||||
|
||||
~EffectSlot();
|
||||
|
||||
static EffectSlotArray *CreatePtrArray(size_t count) noexcept;
|
||||
|
||||
DISABLE_ALLOC()
|
||||
DEF_NEWDEL(EffectSlot)
|
||||
};
|
||||
|
||||
#endif /* CORE_EFFECTSLOT_H */
|
||||
|
|
|
|||
|
|
@ -11,18 +11,17 @@
|
|||
|
||||
namespace al {
|
||||
|
||||
/* Defined here to avoid inlining it. */
|
||||
base_exception::~base_exception() { }
|
||||
base_exception::~base_exception() = default;
|
||||
|
||||
void base_exception::setMessage(const char* msg, std::va_list args)
|
||||
{
|
||||
std::va_list args2;
|
||||
va_copy(args2, args);
|
||||
int msglen{std::vsnprintf(nullptr, 0, msg, args)};
|
||||
if LIKELY(msglen > 0)
|
||||
if(msglen > 0) LIKELY
|
||||
{
|
||||
mMessage.resize(static_cast<size_t>(msglen)+1);
|
||||
std::vsnprintf(&mMessage[0], mMessage.length(), msg, args2);
|
||||
std::vsnprintf(const_cast<char*>(mMessage.data()), mMessage.length(), msg, args2);
|
||||
mMessage.pop_back();
|
||||
}
|
||||
va_end(args2);
|
||||
|
|
|
|||
|
|
@ -14,8 +14,12 @@
|
|||
template<typename Real>
|
||||
void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ)
|
||||
{
|
||||
// Limit gain to -100dB
|
||||
assert(gain > 0.00001f);
|
||||
/* HACK: Limit gain to -100dB. This shouldn't ever happen, all callers
|
||||
* already clamp to minimum of 0.001, or have a limited range of values
|
||||
* that don't go below 0.126. But it seems to with some callers. This needs
|
||||
* to be investigated.
|
||||
*/
|
||||
gain = std::max(gain, Real(0.00001));
|
||||
|
||||
const Real w0{al::numbers::pi_v<Real>*2.0f * f0norm};
|
||||
const Real sin_w0{std::sin(w0)};
|
||||
|
|
|
|||
|
|
@ -160,17 +160,18 @@ void BandSplitterR<Real>::processScale(const al::span<Real> samples, const Real
|
|||
}
|
||||
|
||||
template<typename Real>
|
||||
void BandSplitterR<Real>::applyAllpassRev(const al::span<Real> samples) const
|
||||
void BandSplitterR<Real>::processAllPass(const al::span<Real> samples)
|
||||
{
|
||||
const Real coeff{mCoeff};
|
||||
Real z1{0.0f};
|
||||
Real z1{mApZ1};
|
||||
auto proc_sample = [coeff,&z1](const Real in) noexcept -> Real
|
||||
{
|
||||
const Real out{in*coeff + z1};
|
||||
z1 = in - out*coeff;
|
||||
return out;
|
||||
};
|
||||
std::transform(samples.rbegin(), samples.rend(), samples.rbegin(), proc_sample);
|
||||
std::transform(samples.cbegin(), samples.cend(), samples.begin(), proc_sample);
|
||||
mApZ1 = z1;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -31,12 +31,9 @@ public:
|
|||
|
||||
/**
|
||||
* The all-pass portion of the band splitter. Applies the same phase shift
|
||||
* without splitting the signal, in reverse. It starts from the back of the
|
||||
* span and works toward the front, creating a phase shift of -n degrees
|
||||
* instead of +n. Note that each use of this method is indepedent, it does
|
||||
* not track history between calls.
|
||||
* without splitting or scaling the signal.
|
||||
*/
|
||||
void applyAllpassRev(const al::span<Real> samples) const;
|
||||
void processAllPass(const al::span<Real> samples);
|
||||
};
|
||||
using BandSplitter = BandSplitterR<float>;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,27 +10,22 @@
|
|||
|
||||
|
||||
struct FrontStablizer {
|
||||
static constexpr size_t DelayLength{256u};
|
||||
FrontStablizer(size_t numchans) : ChannelFilters{numchans} { }
|
||||
|
||||
FrontStablizer(size_t numchans) : DelayBuf{numchans} { }
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize + DelayLength> Side{};
|
||||
alignas(16) std::array<float,BufferLineSize + DelayLength> MidDirect{};
|
||||
alignas(16) std::array<float,DelayLength> MidDelay{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize + DelayLength> TempBuf{};
|
||||
alignas(16) std::array<float,BufferLineSize> MidDirect{};
|
||||
alignas(16) std::array<float,BufferLineSize> Side{};
|
||||
alignas(16) std::array<float,BufferLineSize> Temp{};
|
||||
|
||||
BandSplitter MidFilter;
|
||||
alignas(16) FloatBufferLine MidLF{};
|
||||
alignas(16) FloatBufferLine MidHF{};
|
||||
|
||||
using DelayLine = std::array<float,DelayLength>;
|
||||
al::FlexArray<DelayLine,16> DelayBuf;
|
||||
al::FlexArray<BandSplitter,16> ChannelFilters;
|
||||
|
||||
static std::unique_ptr<FrontStablizer> Create(size_t numchans)
|
||||
{ return std::unique_ptr<FrontStablizer>{new(FamCount(numchans)) FrontStablizer{numchans}}; }
|
||||
|
||||
DEF_FAM_NEWDEL(FrontStablizer, DelayBuf)
|
||||
DEF_FAM_NEWDEL(FrontStablizer, ChannelFilters)
|
||||
};
|
||||
|
||||
#endif /* CORE_FRONT_STABLIZER_H */
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ const PathNamePair &GetProcBinary()
|
|||
if(len == 0)
|
||||
{
|
||||
ERR("Failed to get process name: error %lu\n", GetLastError());
|
||||
procbin = al::make_optional<PathNamePair>();
|
||||
procbin.emplace();
|
||||
return *procbin;
|
||||
}
|
||||
|
||||
|
|
@ -59,16 +59,15 @@ const PathNamePair &GetProcBinary()
|
|||
if(fullpath.back() != 0)
|
||||
fullpath.push_back(0);
|
||||
|
||||
std::replace(fullpath.begin(), fullpath.end(), '/', '\\');
|
||||
auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\');
|
||||
sep = std::find(fullpath.rbegin()+1, sep, '/');
|
||||
if(sep != fullpath.rend())
|
||||
{
|
||||
*sep = 0;
|
||||
procbin = al::make_optional<PathNamePair>(wstr_to_utf8(fullpath.data()),
|
||||
wstr_to_utf8(&*sep + 1));
|
||||
procbin.emplace(wstr_to_utf8(fullpath.data()), wstr_to_utf8(al::to_address(sep.base())));
|
||||
}
|
||||
else
|
||||
procbin = al::make_optional<PathNamePair>(std::string{}, wstr_to_utf8(fullpath.data()));
|
||||
procbin.emplace(std::string{}, wstr_to_utf8(fullpath.data()));
|
||||
|
||||
TRACE("Got binary: %s, %s\n", procbin->path.c_str(), procbin->fname.c_str());
|
||||
return *procbin;
|
||||
|
|
@ -285,11 +284,10 @@ const PathNamePair &GetProcBinary()
|
|||
|
||||
auto sep = std::find(pathname.crbegin(), pathname.crend(), '/');
|
||||
if(sep != pathname.crend())
|
||||
procbin = al::make_optional<PathNamePair>(std::string(pathname.cbegin(), sep.base()-1),
|
||||
procbin.emplace(std::string(pathname.cbegin(), sep.base()-1),
|
||||
std::string(sep.base(), pathname.cend()));
|
||||
else
|
||||
procbin = al::make_optional<PathNamePair>(std::string{},
|
||||
std::string(pathname.cbegin(), pathname.cend()));
|
||||
procbin.emplace(std::string{}, std::string(pathname.cbegin(), pathname.cend()));
|
||||
|
||||
TRACE("Got binary: \"%s\", \"%s\"\n", procbin->path.c_str(), procbin->fname.c_str());
|
||||
return *procbin;
|
||||
|
|
@ -408,6 +406,20 @@ al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir)
|
|||
DirectorySearch(path.c_str(), ext, &results);
|
||||
}
|
||||
|
||||
#ifdef ALSOFT_INSTALL_DATADIR
|
||||
// Search the installation data directory
|
||||
{
|
||||
std::string path{ALSOFT_INSTALL_DATADIR};
|
||||
if(!path.empty())
|
||||
{
|
||||
if(path.back() != '/')
|
||||
path += '/';
|
||||
path += subdir;
|
||||
DirectorySearch(path.c_str(), ext, &results);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
|
@ -461,29 +473,6 @@ bool SetRTPriorityRTKit(int prio)
|
|||
/* Don't stupidly exit if the connection dies while doing this. */
|
||||
dbus_connection_set_exit_on_disconnect(conn.get(), false);
|
||||
|
||||
auto limit_rttime = [](DBusConnection *c) -> int
|
||||
{
|
||||
using ulonglong = unsigned long long;
|
||||
long long maxrttime{rtkit_get_rttime_usec_max(c)};
|
||||
if(maxrttime <= 0) return static_cast<int>(std::abs(maxrttime));
|
||||
const ulonglong umaxtime{static_cast<ulonglong>(maxrttime)};
|
||||
|
||||
struct rlimit rlim{};
|
||||
if(getrlimit(RLIMIT_RTTIME, &rlim) != 0)
|
||||
return errno;
|
||||
|
||||
TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime, ulonglong{rlim.rlim_max},
|
||||
ulonglong{rlim.rlim_cur});
|
||||
if(rlim.rlim_max > umaxtime)
|
||||
{
|
||||
rlim.rlim_max = static_cast<rlim_t>(umaxtime);
|
||||
rlim.rlim_cur = std::min(rlim.rlim_cur, rlim.rlim_max);
|
||||
if(setrlimit(RLIMIT_RTTIME, &rlim) != 0)
|
||||
return errno;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
int nicemin{};
|
||||
int err{rtkit_get_min_nice_level(conn.get(), &nicemin)};
|
||||
if(err == -ENOENT)
|
||||
|
|
@ -495,6 +484,29 @@ bool SetRTPriorityRTKit(int prio)
|
|||
int rtmax{rtkit_get_max_realtime_priority(conn.get())};
|
||||
TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin);
|
||||
|
||||
auto limit_rttime = [](DBusConnection *c) -> int
|
||||
{
|
||||
using ulonglong = unsigned long long;
|
||||
long long maxrttime{rtkit_get_rttime_usec_max(c)};
|
||||
if(maxrttime <= 0) return static_cast<int>(std::abs(maxrttime));
|
||||
const ulonglong umaxtime{static_cast<ulonglong>(maxrttime)};
|
||||
|
||||
struct rlimit rlim{};
|
||||
if(getrlimit(RLIMIT_RTTIME, &rlim) != 0)
|
||||
return errno;
|
||||
|
||||
TRACE("RTTime max: %llu (hard: %llu, soft: %llu)\n", umaxtime,
|
||||
static_cast<ulonglong>(rlim.rlim_max), static_cast<ulonglong>(rlim.rlim_cur));
|
||||
if(rlim.rlim_max > umaxtime)
|
||||
{
|
||||
rlim.rlim_max = static_cast<rlim_t>(std::min<ulonglong>(umaxtime,
|
||||
std::numeric_limits<rlim_t>::max()));
|
||||
rlim.rlim_cur = std::min(rlim.rlim_cur, rlim.rlim_max);
|
||||
if(setrlimit(RLIMIT_RTTIME, &rlim) != 0)
|
||||
return errno;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
if(rtmax > 0)
|
||||
{
|
||||
if(AllowRTTimeLimit)
|
||||
|
|
|
|||
|
|
@ -42,12 +42,28 @@ namespace {
|
|||
struct HrtfEntry {
|
||||
std::string mDispName;
|
||||
std::string mFilename;
|
||||
|
||||
/* GCC warns when it tries to inline this. */
|
||||
~HrtfEntry();
|
||||
};
|
||||
HrtfEntry::~HrtfEntry() = default;
|
||||
|
||||
struct LoadedHrtf {
|
||||
std::string mFilename;
|
||||
std::unique_ptr<HrtfStore> mEntry;
|
||||
|
||||
template<typename T, typename U>
|
||||
LoadedHrtf(T&& name, U&& entry)
|
||||
: mFilename{std::forward<T>(name)}, mEntry{std::forward<U>(entry)}
|
||||
{ }
|
||||
LoadedHrtf(LoadedHrtf&&) = default;
|
||||
/* GCC warns when it tries to inline this. */
|
||||
~LoadedHrtf();
|
||||
|
||||
LoadedHrtf& operator=(LoadedHrtf&&) = default;
|
||||
};
|
||||
LoadedHrtf::~LoadedHrtf() = default;
|
||||
|
||||
|
||||
/* Data set limits must be the same as or more flexible than those defined in
|
||||
* the makemhr utility.
|
||||
|
|
@ -164,8 +180,8 @@ struct IdxBlend { uint idx; float blend; };
|
|||
*/
|
||||
IdxBlend CalcEvIndex(uint evcount, float ev)
|
||||
{
|
||||
ev = (al::numbers::pi_v<float>*0.5f + ev) * static_cast<float>(evcount-1) /
|
||||
al::numbers::pi_v<float>;
|
||||
ev = (al::numbers::pi_v<float>*0.5f + ev) * static_cast<float>(evcount-1) *
|
||||
al::numbers::inv_pi_v<float>;
|
||||
uint idx{float2uint(ev)};
|
||||
|
||||
return IdxBlend{minu(idx, evcount-1), ev-static_cast<float>(idx)};
|
||||
|
|
@ -176,8 +192,8 @@ IdxBlend CalcEvIndex(uint evcount, float ev)
|
|||
*/
|
||||
IdxBlend CalcAzIndex(uint azcount, float az)
|
||||
{
|
||||
az = (al::numbers::pi_v<float>*2.0f + az) * static_cast<float>(azcount) /
|
||||
(al::numbers::pi_v<float>*2.0f);
|
||||
az = (al::numbers::pi_v<float>*2.0f + az) * static_cast<float>(azcount) *
|
||||
(al::numbers::inv_pi_v<float>*0.5f);
|
||||
uint idx{float2uint(az)};
|
||||
|
||||
return IdxBlend{idx%azcount, az-static_cast<float>(idx)};
|
||||
|
|
@ -189,36 +205,37 @@ IdxBlend CalcAzIndex(uint azcount, float az)
|
|||
/* Calculates static HRIR coefficients and delays for the given polar elevation
|
||||
* and azimuth in radians. The coefficients are normalized.
|
||||
*/
|
||||
void GetHrtfCoeffs(const HrtfStore *Hrtf, float elevation, float azimuth, float distance,
|
||||
float spread, HrirArray &coeffs, const al::span<uint,2> delays)
|
||||
void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float spread,
|
||||
HrirArray &coeffs, const al::span<uint,2> delays)
|
||||
{
|
||||
const float dirfact{1.0f - (al::numbers::inv_pi_v<float>/2.0f * spread)};
|
||||
|
||||
const auto *field = Hrtf->field;
|
||||
const auto *field_end = field + Hrtf->fdCount-1;
|
||||
size_t ebase{0};
|
||||
while(distance < field->distance && field != field_end)
|
||||
auto match_field = [&ebase,distance](const Field &field) noexcept -> bool
|
||||
{
|
||||
ebase += field->evCount;
|
||||
++field;
|
||||
}
|
||||
if(distance >= field.distance)
|
||||
return true;
|
||||
ebase += field.evCount;
|
||||
return false;
|
||||
};
|
||||
auto field = std::find_if(mFields.begin(), mFields.end()-1, match_field);
|
||||
|
||||
/* Calculate the elevation indices. */
|
||||
const auto elev0 = CalcEvIndex(field->evCount, elevation);
|
||||
const size_t elev1_idx{minu(elev0.idx+1, field->evCount-1)};
|
||||
const size_t ir0offset{Hrtf->elev[ebase + elev0.idx].irOffset};
|
||||
const size_t ir1offset{Hrtf->elev[ebase + elev1_idx].irOffset};
|
||||
const size_t ir0offset{mElev[ebase + elev0.idx].irOffset};
|
||||
const size_t ir1offset{mElev[ebase + elev1_idx].irOffset};
|
||||
|
||||
/* Calculate azimuth indices. */
|
||||
const auto az0 = CalcAzIndex(Hrtf->elev[ebase + elev0.idx].azCount, azimuth);
|
||||
const auto az1 = CalcAzIndex(Hrtf->elev[ebase + elev1_idx].azCount, azimuth);
|
||||
const auto az0 = CalcAzIndex(mElev[ebase + elev0.idx].azCount, azimuth);
|
||||
const auto az1 = CalcAzIndex(mElev[ebase + elev1_idx].azCount, azimuth);
|
||||
|
||||
/* Calculate the HRIR indices to blend. */
|
||||
const size_t idx[4]{
|
||||
ir0offset + az0.idx,
|
||||
ir0offset + ((az0.idx+1) % Hrtf->elev[ebase + elev0.idx].azCount),
|
||||
ir0offset + ((az0.idx+1) % mElev[ebase + elev0.idx].azCount),
|
||||
ir1offset + az1.idx,
|
||||
ir1offset + ((az1.idx+1) % Hrtf->elev[ebase + elev1_idx].azCount)
|
||||
ir1offset + ((az1.idx+1) % mElev[ebase + elev1_idx].azCount)
|
||||
};
|
||||
|
||||
/* Calculate bilinear blending weights, attenuated according to the
|
||||
|
|
@ -232,21 +249,21 @@ void GetHrtfCoeffs(const HrtfStore *Hrtf, float elevation, float azimuth, float
|
|||
};
|
||||
|
||||
/* Calculate the blended HRIR delays. */
|
||||
float d{Hrtf->delays[idx[0]][0]*blend[0] + Hrtf->delays[idx[1]][0]*blend[1] +
|
||||
Hrtf->delays[idx[2]][0]*blend[2] + Hrtf->delays[idx[3]][0]*blend[3]};
|
||||
float d{mDelays[idx[0]][0]*blend[0] + mDelays[idx[1]][0]*blend[1] + mDelays[idx[2]][0]*blend[2]
|
||||
+ mDelays[idx[3]][0]*blend[3]};
|
||||
delays[0] = fastf2u(d * float{1.0f/HrirDelayFracOne});
|
||||
d = Hrtf->delays[idx[0]][1]*blend[0] + Hrtf->delays[idx[1]][1]*blend[1] +
|
||||
Hrtf->delays[idx[2]][1]*blend[2] + Hrtf->delays[idx[3]][1]*blend[3];
|
||||
d = mDelays[idx[0]][1]*blend[0] + mDelays[idx[1]][1]*blend[1] + mDelays[idx[2]][1]*blend[2]
|
||||
+ mDelays[idx[3]][1]*blend[3];
|
||||
delays[1] = fastf2u(d * float{1.0f/HrirDelayFracOne});
|
||||
|
||||
/* Calculate the blended HRIR coefficients. */
|
||||
float *coeffout{al::assume_aligned<16>(&coeffs[0][0])};
|
||||
float *coeffout{al::assume_aligned<16>(coeffs[0].data())};
|
||||
coeffout[0] = PassthruCoeff * (1.0f-dirfact);
|
||||
coeffout[1] = PassthruCoeff * (1.0f-dirfact);
|
||||
std::fill_n(coeffout+2, size_t{HrirLength-1}*2, 0.0f);
|
||||
for(size_t c{0};c < 4;c++)
|
||||
{
|
||||
const float *srccoeffs{al::assume_aligned<16>(Hrtf->coeffs[idx[c]][0].data())};
|
||||
const float *srccoeffs{al::assume_aligned<16>(mCoeffs[idx[c]][0].data())};
|
||||
const float mult{blend[c]};
|
||||
auto blend_coeffs = [mult](const float src, const float coeff) noexcept -> float
|
||||
{ return src*mult + coeff; };
|
||||
|
|
@ -258,7 +275,7 @@ void GetHrtfCoeffs(const HrtfStore *Hrtf, float elevation, float azimuth, float
|
|||
std::unique_ptr<DirectHrtfState> DirectHrtfState::Create(size_t num_chans)
|
||||
{ return std::unique_ptr<DirectHrtfState>{new(FamCount(num_chans)) DirectHrtfState{num_chans}}; }
|
||||
|
||||
void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize,
|
||||
void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin,
|
||||
const al::span<const AngularPoint> AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels],
|
||||
const float XOverFreq, const al::span<const float,MaxAmbiOrder+1> AmbiOrderHFGain)
|
||||
{
|
||||
|
|
@ -268,11 +285,12 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize,
|
|||
uint ldelay, rdelay;
|
||||
};
|
||||
|
||||
const double xover_norm{double{XOverFreq} / Hrtf->sampleRate};
|
||||
const double xover_norm{double{XOverFreq} / Hrtf->mSampleRate};
|
||||
mChannels[0].mSplitter.init(static_cast<float>(xover_norm));
|
||||
for(size_t i{0};i < mChannels.size();++i)
|
||||
{
|
||||
const size_t order{AmbiIndex::OrderFromChannel()[i]};
|
||||
mChannels[i].mSplitter.init(static_cast<float>(xover_norm));
|
||||
mChannels[i].mSplitter = mChannels[0].mSplitter;
|
||||
mChannels[i].mHfScale = AmbiOrderHFGain[order];
|
||||
}
|
||||
|
||||
|
|
@ -280,33 +298,26 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize,
|
|||
al::vector<ImpulseResponse> impres; impres.reserve(AmbiPoints.size());
|
||||
auto calc_res = [Hrtf,&max_delay,&min_delay](const AngularPoint &pt) -> ImpulseResponse
|
||||
{
|
||||
auto &field = Hrtf->field[0];
|
||||
auto &field = Hrtf->mFields[0];
|
||||
const auto elev0 = CalcEvIndex(field.evCount, pt.Elev.value);
|
||||
const size_t elev1_idx{minu(elev0.idx+1, field.evCount-1)};
|
||||
const size_t ir0offset{Hrtf->elev[elev0.idx].irOffset};
|
||||
const size_t ir1offset{Hrtf->elev[elev1_idx].irOffset};
|
||||
const size_t ir0offset{Hrtf->mElev[elev0.idx].irOffset};
|
||||
const size_t ir1offset{Hrtf->mElev[elev1_idx].irOffset};
|
||||
|
||||
const auto az0 = CalcAzIndex(Hrtf->elev[elev0.idx].azCount, pt.Azim.value);
|
||||
const auto az1 = CalcAzIndex(Hrtf->elev[elev1_idx].azCount, pt.Azim.value);
|
||||
const auto az0 = CalcAzIndex(Hrtf->mElev[elev0.idx].azCount, pt.Azim.value);
|
||||
const auto az1 = CalcAzIndex(Hrtf->mElev[elev1_idx].azCount, pt.Azim.value);
|
||||
|
||||
const size_t idx[4]{
|
||||
ir0offset + az0.idx,
|
||||
ir0offset + ((az0.idx+1) % Hrtf->elev[elev0.idx].azCount),
|
||||
ir0offset + ((az0.idx+1) % Hrtf->mElev[elev0.idx].azCount),
|
||||
ir1offset + az1.idx,
|
||||
ir1offset + ((az1.idx+1) % Hrtf->elev[elev1_idx].azCount)
|
||||
ir1offset + ((az1.idx+1) % Hrtf->mElev[elev1_idx].azCount)
|
||||
};
|
||||
|
||||
const std::array<double,4> blend{{
|
||||
(1.0-elev0.blend) * (1.0-az0.blend),
|
||||
(1.0-elev0.blend) * ( az0.blend),
|
||||
( elev0.blend) * (1.0-az1.blend),
|
||||
( elev0.blend) * ( az1.blend)
|
||||
}};
|
||||
|
||||
/* The largest blend factor serves as the closest HRIR. */
|
||||
const size_t irOffset{idx[std::max_element(blend.begin(), blend.end()) - blend.begin()]};
|
||||
ImpulseResponse res{Hrtf->coeffs[irOffset],
|
||||
Hrtf->delays[irOffset][0], Hrtf->delays[irOffset][1]};
|
||||
const size_t irOffset{idx[(elev0.blend >= 0.5f)*2 + (az1.blend >= 0.5f)]};
|
||||
ImpulseResponse res{Hrtf->mCoeffs[irOffset],
|
||||
Hrtf->mDelays[irOffset][0], Hrtf->mDelays[irOffset][1]};
|
||||
|
||||
min_delay = minu(min_delay, minu(res.ldelay, res.rdelay));
|
||||
max_delay = maxu(max_delay, maxu(res.ldelay, res.rdelay));
|
||||
|
|
@ -320,13 +331,12 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize,
|
|||
TRACE("Min delay: %.2f, max delay: %.2f, FIR length: %u\n",
|
||||
min_delay/double{HrirDelayFracOne}, max_delay/double{HrirDelayFracOne}, irSize);
|
||||
|
||||
const bool per_hrir_min{mChannels.size() > AmbiChannelsFromOrder(1)};
|
||||
auto tmpres = al::vector<std::array<double2,HrirLength>>(mChannels.size());
|
||||
max_delay = 0;
|
||||
for(size_t c{0u};c < AmbiPoints.size();++c)
|
||||
{
|
||||
const ConstHrirSpan hrir{impres[c].hrir};
|
||||
const uint base_delay{per_hrir_min ? minu(impres[c].ldelay, impres[c].rdelay) : min_delay};
|
||||
const uint base_delay{perHrirMin ? minu(impres[c].ldelay, impres[c].rdelay) : min_delay};
|
||||
const uint ldelay{hrir_delay_round(impres[c].ldelay - base_delay)};
|
||||
const uint rdelay{hrir_delay_round(impres[c].rdelay - base_delay)};
|
||||
max_delay = maxu(max_delay, maxu(impres[c].ldelay, impres[c].rdelay) - base_delay);
|
||||
|
|
@ -363,7 +373,7 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize,
|
|||
|
||||
namespace {
|
||||
|
||||
std::unique_ptr<HrtfStore> CreateHrtfStore(uint rate, ushort irSize,
|
||||
std::unique_ptr<HrtfStore> CreateHrtfStore(uint rate, uint8_t irSize,
|
||||
const al::span<const HrtfStore::Field> fields,
|
||||
const al::span<const HrtfStore::Elevation> elevs, const HrirArray *coeffs,
|
||||
const ubyte2 *delays, const char *filename)
|
||||
|
|
@ -371,23 +381,20 @@ std::unique_ptr<HrtfStore> CreateHrtfStore(uint rate, ushort irSize,
|
|||
const size_t irCount{size_t{elevs.back().azCount} + elevs.back().irOffset};
|
||||
size_t total{sizeof(HrtfStore)};
|
||||
total = RoundUp(total, alignof(HrtfStore::Field)); /* Align for field infos */
|
||||
total += sizeof(std::declval<HrtfStore&>().field[0])*fields.size();
|
||||
total += sizeof(std::declval<HrtfStore&>().mFields[0])*fields.size();
|
||||
total = RoundUp(total, alignof(HrtfStore::Elevation)); /* Align for elevation infos */
|
||||
total += sizeof(std::declval<HrtfStore&>().elev[0])*elevs.size();
|
||||
total += sizeof(std::declval<HrtfStore&>().mElev[0])*elevs.size();
|
||||
total = RoundUp(total, 16); /* Align for coefficients using SIMD */
|
||||
total += sizeof(std::declval<HrtfStore&>().coeffs[0])*irCount;
|
||||
total += sizeof(std::declval<HrtfStore&>().delays[0])*irCount;
|
||||
total += sizeof(std::declval<HrtfStore&>().mCoeffs[0])*irCount;
|
||||
total += sizeof(std::declval<HrtfStore&>().mDelays[0])*irCount;
|
||||
|
||||
void *ptr{al_calloc(16, total)};
|
||||
std::unique_ptr<HrtfStore> Hrtf{al::construct_at(static_cast<HrtfStore*>(ptr))};
|
||||
if(!Hrtf)
|
||||
ERR("Out of memory allocating storage for %s.\n", filename);
|
||||
else
|
||||
std::unique_ptr<HrtfStore> Hrtf{};
|
||||
if(void *ptr{al_calloc(16, total)})
|
||||
{
|
||||
Hrtf.reset(al::construct_at(static_cast<HrtfStore*>(ptr)));
|
||||
InitRef(Hrtf->mRef, 1u);
|
||||
Hrtf->sampleRate = rate;
|
||||
Hrtf->irSize = irSize;
|
||||
Hrtf->fdCount = static_cast<uint>(fields.size());
|
||||
Hrtf->mSampleRate = rate;
|
||||
Hrtf->mIrSize = irSize;
|
||||
|
||||
/* Set up pointers to storage following the main HRTF struct. */
|
||||
char *base = reinterpret_cast<char*>(Hrtf.get());
|
||||
|
|
@ -408,7 +415,7 @@ std::unique_ptr<HrtfStore> CreateHrtfStore(uint rate, ushort irSize,
|
|||
auto delays_ = reinterpret_cast<ubyte2*>(base + offset);
|
||||
offset += sizeof(delays_[0])*irCount;
|
||||
|
||||
if(unlikely(offset != total))
|
||||
if(offset != total)
|
||||
throw std::runtime_error{"HrtfStore allocation size mismatch"};
|
||||
|
||||
/* Copy input data to storage. */
|
||||
|
|
@ -418,11 +425,13 @@ std::unique_ptr<HrtfStore> CreateHrtfStore(uint rate, ushort irSize,
|
|||
std::uninitialized_copy_n(delays, irCount, delays_);
|
||||
|
||||
/* Finally, assign the storage pointers. */
|
||||
Hrtf->field = field_;
|
||||
Hrtf->elev = elev_;
|
||||
Hrtf->coeffs = coeffs_;
|
||||
Hrtf->delays = delays_;
|
||||
Hrtf->mFields = al::as_span(field_, fields.size());
|
||||
Hrtf->mElev = elev_;
|
||||
Hrtf->mCoeffs = coeffs_;
|
||||
Hrtf->mDelays = delays_;
|
||||
}
|
||||
else
|
||||
ERR("Out of memory allocating storage for %s.\n", filename);
|
||||
|
||||
return Hrtf;
|
||||
}
|
||||
|
|
@ -590,14 +599,14 @@ std::unique_ptr<HrtfStore> LoadHrtf00(std::istream &data, const char *filename)
|
|||
MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data());
|
||||
|
||||
const HrtfStore::Field field[1]{{0.0f, evCount}};
|
||||
return CreateHrtfStore(rate, irSize, field, {elevs.data(), elevs.size()}, coeffs.data(),
|
||||
delays.data(), filename);
|
||||
return CreateHrtfStore(rate, static_cast<uint8_t>(irSize), field, {elevs.data(), elevs.size()},
|
||||
coeffs.data(), delays.data(), filename);
|
||||
}
|
||||
|
||||
std::unique_ptr<HrtfStore> LoadHrtf01(std::istream &data, const char *filename)
|
||||
{
|
||||
uint rate{readle<uint32_t>(data)};
|
||||
ushort irSize{readle<uint8_t>(data)};
|
||||
uint8_t irSize{readle<uint8_t>(data)};
|
||||
ubyte evCount{readle<uint8_t>(data)};
|
||||
if(!data || data.eof())
|
||||
{
|
||||
|
|
@ -682,7 +691,7 @@ std::unique_ptr<HrtfStore> LoadHrtf02(std::istream &data, const char *filename)
|
|||
uint rate{readle<uint32_t>(data)};
|
||||
ubyte sampleType{readle<uint8_t>(data)};
|
||||
ubyte channelType{readle<uint8_t>(data)};
|
||||
ushort irSize{readle<uint8_t>(data)};
|
||||
uint8_t irSize{readle<uint8_t>(data)};
|
||||
ubyte fdCount{readle<uint8_t>(data)};
|
||||
if(!data || data.eof())
|
||||
{
|
||||
|
|
@ -948,7 +957,7 @@ std::unique_ptr<HrtfStore> LoadHrtf03(std::istream &data, const char *filename)
|
|||
|
||||
uint rate{readle<uint32_t>(data)};
|
||||
ubyte channelType{readle<uint8_t>(data)};
|
||||
ushort irSize{readle<uint8_t>(data)};
|
||||
uint8_t irSize{readle<uint8_t>(data)};
|
||||
ubyte fdCount{readle<uint8_t>(data)};
|
||||
if(!data || data.eof())
|
||||
{
|
||||
|
|
@ -1197,7 +1206,9 @@ al::span<const char> GetResource(int /*name*/)
|
|||
|
||||
#else
|
||||
|
||||
#include "hrtf_default.h"
|
||||
constexpr unsigned char hrtf_default[]{
|
||||
#include "default_hrtf.txt"
|
||||
};
|
||||
|
||||
al::span<const char> GetResource(int name)
|
||||
{
|
||||
|
|
@ -1283,7 +1294,7 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate)
|
|||
while(handle != LoadedHrtfs.end() && handle->mFilename == fname)
|
||||
{
|
||||
HrtfStore *hrtf{handle->mEntry.get()};
|
||||
if(hrtf && hrtf->sampleRate == devrate)
|
||||
if(hrtf && hrtf->mSampleRate == devrate)
|
||||
{
|
||||
hrtf->add_ref();
|
||||
return HrtfStorePtr{hrtf};
|
||||
|
|
@ -1352,24 +1363,24 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if(hrtf->sampleRate != devrate)
|
||||
if(hrtf->mSampleRate != devrate)
|
||||
{
|
||||
TRACE("Resampling HRTF %s (%uhz -> %uhz)\n", name.c_str(), hrtf->sampleRate, devrate);
|
||||
TRACE("Resampling HRTF %s (%uhz -> %uhz)\n", name.c_str(), hrtf->mSampleRate, devrate);
|
||||
|
||||
/* Calculate the last elevation's index and get the total IR count. */
|
||||
const size_t lastEv{std::accumulate(hrtf->field, hrtf->field+hrtf->fdCount, size_t{0},
|
||||
const size_t lastEv{std::accumulate(hrtf->mFields.begin(), hrtf->mFields.end(), size_t{0},
|
||||
[](const size_t curval, const HrtfStore::Field &field) noexcept -> size_t
|
||||
{ return curval + field.evCount; }
|
||||
) - 1};
|
||||
const size_t irCount{size_t{hrtf->elev[lastEv].irOffset} + hrtf->elev[lastEv].azCount};
|
||||
const size_t irCount{size_t{hrtf->mElev[lastEv].irOffset} + hrtf->mElev[lastEv].azCount};
|
||||
|
||||
/* Resample all the IRs. */
|
||||
std::array<std::array<double,HrirLength>,2> inout;
|
||||
PPhaseResampler rs;
|
||||
rs.init(hrtf->sampleRate, devrate);
|
||||
rs.init(hrtf->mSampleRate, devrate);
|
||||
for(size_t i{0};i < irCount;++i)
|
||||
{
|
||||
HrirArray &coeffs = const_cast<HrirArray&>(hrtf->coeffs[i]);
|
||||
HrirArray &coeffs = const_cast<HrirArray&>(hrtf->mCoeffs[i]);
|
||||
for(size_t j{0};j < 2;++j)
|
||||
{
|
||||
std::transform(coeffs.cbegin(), coeffs.cend(), inout[0].begin(),
|
||||
|
|
@ -1384,12 +1395,12 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate)
|
|||
/* Scale the delays for the new sample rate. */
|
||||
float max_delay{0.0f};
|
||||
auto new_delays = al::vector<float2>(irCount);
|
||||
const float rate_scale{static_cast<float>(devrate)/static_cast<float>(hrtf->sampleRate)};
|
||||
const float rate_scale{static_cast<float>(devrate)/static_cast<float>(hrtf->mSampleRate)};
|
||||
for(size_t i{0};i < irCount;++i)
|
||||
{
|
||||
for(size_t j{0};j < 2;++j)
|
||||
{
|
||||
const float new_delay{std::round(hrtf->delays[i][j] * rate_scale) /
|
||||
const float new_delay{std::round(hrtf->mDelays[i][j] * rate_scale) /
|
||||
float{HrirDelayFracOne}};
|
||||
max_delay = maxf(max_delay, new_delay);
|
||||
new_delays[i][j] = new_delay;
|
||||
|
|
@ -1409,7 +1420,7 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate)
|
|||
|
||||
for(size_t i{0};i < irCount;++i)
|
||||
{
|
||||
ubyte2 &delays = const_cast<ubyte2&>(hrtf->delays[i]);
|
||||
ubyte2 &delays = const_cast<ubyte2&>(hrtf->mDelays[i]);
|
||||
for(size_t j{0};j < 2;++j)
|
||||
delays[j] = static_cast<ubyte>(float2int(new_delays[i][j]*delay_scale + 0.5f));
|
||||
}
|
||||
|
|
@ -1417,14 +1428,14 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate)
|
|||
/* Scale the IR size for the new sample rate and update the stored
|
||||
* sample rate.
|
||||
*/
|
||||
const float newIrSize{std::round(static_cast<float>(hrtf->irSize) * rate_scale)};
|
||||
hrtf->irSize = static_cast<uint>(minf(HrirLength, newIrSize));
|
||||
hrtf->sampleRate = devrate;
|
||||
const float newIrSize{std::round(static_cast<float>(hrtf->mIrSize) * rate_scale)};
|
||||
hrtf->mIrSize = static_cast<uint8_t>(minf(HrirLength, newIrSize));
|
||||
hrtf->mSampleRate = devrate;
|
||||
}
|
||||
|
||||
TRACE("Loaded HRTF %s for sample rate %uhz, %u-sample filter\n", name.c_str(),
|
||||
hrtf->sampleRate, hrtf->irSize);
|
||||
handle = LoadedHrtfs.emplace(handle, LoadedHrtf{fname, std::move(hrtf)});
|
||||
hrtf->mSampleRate, hrtf->mIrSize);
|
||||
handle = LoadedHrtfs.emplace(handle, fname, std::move(hrtf));
|
||||
|
||||
return HrtfStorePtr{handle->mEntry.get()};
|
||||
}
|
||||
|
|
@ -1436,7 +1447,7 @@ void HrtfStore::add_ref()
|
|||
TRACE("HrtfStore %p increasing refcount to %u\n", decltype(std::declval<void*>()){this}, ref);
|
||||
}
|
||||
|
||||
void HrtfStore::release()
|
||||
void HrtfStore::dec_ref()
|
||||
{
|
||||
auto ref = DecrementRef(mRef);
|
||||
TRACE("HrtfStore %p decreasing refcount to %u\n", decltype(std::declval<void*>()){this}, ref);
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@
|
|||
struct HrtfStore {
|
||||
RefCount mRef;
|
||||
|
||||
uint sampleRate;
|
||||
uint irSize;
|
||||
uint mSampleRate : 24;
|
||||
uint mIrSize : 8;
|
||||
|
||||
struct Field {
|
||||
float distance;
|
||||
|
|
@ -30,19 +30,21 @@ struct HrtfStore {
|
|||
/* NOTE: Fields are stored *backwards*. field[0] is the farthest field, and
|
||||
* field[fdCount-1] is the nearest.
|
||||
*/
|
||||
uint fdCount;
|
||||
const Field *field;
|
||||
al::span<const Field> mFields;
|
||||
|
||||
struct Elevation {
|
||||
ushort azCount;
|
||||
ushort irOffset;
|
||||
};
|
||||
Elevation *elev;
|
||||
const HrirArray *coeffs;
|
||||
const ubyte2 *delays;
|
||||
Elevation *mElev;
|
||||
const HrirArray *mCoeffs;
|
||||
const ubyte2 *mDelays;
|
||||
|
||||
void getCoeffs(float elevation, float azimuth, float distance, float spread, HrirArray &coeffs,
|
||||
const al::span<uint,2> delays);
|
||||
|
||||
void add_ref();
|
||||
void release();
|
||||
void dec_ref();
|
||||
|
||||
DEF_PLACE_NEWDEL()
|
||||
};
|
||||
|
|
@ -71,7 +73,7 @@ struct DirectHrtfState {
|
|||
* high-frequency gains for the decoder. The calculated impulse responses
|
||||
* are ordered and scaled according to the matrix input.
|
||||
*/
|
||||
void build(const HrtfStore *Hrtf, const uint irSize,
|
||||
void build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin,
|
||||
const al::span<const AngularPoint> AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels],
|
||||
const float XOverFreq, const al::span<const float,MaxAmbiOrder+1> AmbiOrderHFGain);
|
||||
|
||||
|
|
@ -84,7 +86,4 @@ struct DirectHrtfState {
|
|||
al::vector<std::string> EnumerateHrtf(al::optional<std::string> pathopt);
|
||||
HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate);
|
||||
|
||||
void GetHrtfCoeffs(const HrtfStore *Hrtf, float elevation, float azimuth, float distance,
|
||||
float spread, HrirArray &coeffs, const al::span<uint,2> delays);
|
||||
|
||||
#endif /* CORE_HRTF_H */
|
||||
|
|
|
|||
|
|
@ -7,30 +7,53 @@
|
|||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "alspan.h"
|
||||
#include "strutils.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#elif defined(__ANDROID__)
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
void al_print(LogLevel level, FILE *logfile, const char *fmt, ...)
|
||||
{
|
||||
/* Kind of ugly since string literals are const char arrays with a size
|
||||
* that includes the null terminator, which we want to exclude from the
|
||||
* span.
|
||||
*/
|
||||
auto prefix = al::as_span("[ALSOFT] (--) ").first<14>();
|
||||
switch(level)
|
||||
{
|
||||
case LogLevel::Disable: break;
|
||||
case LogLevel::Error: prefix = al::as_span("[ALSOFT] (EE) ").first<14>(); break;
|
||||
case LogLevel::Warning: prefix = al::as_span("[ALSOFT] (WW) ").first<14>(); break;
|
||||
case LogLevel::Trace: prefix = al::as_span("[ALSOFT] (II) ").first<14>(); break;
|
||||
}
|
||||
|
||||
al::vector<char> dynmsg;
|
||||
char stcmsg[256];
|
||||
char *str{stcmsg};
|
||||
std::array<char,256> stcmsg{};
|
||||
|
||||
char *str{stcmsg.data()};
|
||||
auto prefend1 = std::copy_n(prefix.begin(), prefix.size(), stcmsg.begin());
|
||||
al::span<char> msg{prefend1, stcmsg.end()};
|
||||
|
||||
std::va_list args, args2;
|
||||
va_start(args, fmt);
|
||||
va_copy(args2, args);
|
||||
const int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)};
|
||||
if(unlikely(msglen >= 0 && static_cast<size_t>(msglen) >= sizeof(stcmsg)))
|
||||
const int msglen{std::vsnprintf(msg.data(), msg.size(), fmt, args)};
|
||||
if(msglen >= 0 && static_cast<size_t>(msglen) >= msg.size()) UNLIKELY
|
||||
{
|
||||
dynmsg.resize(static_cast<size_t>(msglen) + 1u);
|
||||
dynmsg.resize(static_cast<size_t>(msglen)+prefix.size() + 1u);
|
||||
|
||||
str = dynmsg.data();
|
||||
std::vsnprintf(str, dynmsg.size(), fmt, args2);
|
||||
auto prefend2 = std::copy_n(prefix.begin(), prefix.size(), dynmsg.begin());
|
||||
msg = {prefend2, dynmsg.end()};
|
||||
|
||||
std::vsnprintf(msg.data(), msg.size(), fmt, args2);
|
||||
}
|
||||
va_end(args2);
|
||||
va_end(args);
|
||||
|
|
@ -40,47 +63,14 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...)
|
|||
fputs(str, logfile);
|
||||
fflush(logfile);
|
||||
}
|
||||
#if defined(_WIN32) && !defined(NDEBUG)
|
||||
/* OutputDebugStringW has no 'level' property to distinguish between
|
||||
* informational, warning, or error debug messages. So only print them for
|
||||
* non-Release builds.
|
||||
*/
|
||||
#ifndef NDEBUG
|
||||
std::wstring wstr{utf8_to_wstr(str)};
|
||||
OutputDebugStringW(wstr.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
void al_print(LogLevel level, FILE *logfile, const char *fmt, ...)
|
||||
{
|
||||
al::vector<char> dynmsg;
|
||||
char stcmsg[256];
|
||||
char *str{stcmsg};
|
||||
|
||||
std::va_list args, args2;
|
||||
va_start(args, fmt);
|
||||
va_copy(args2, args);
|
||||
const int msglen{std::vsnprintf(str, sizeof(stcmsg), fmt, args)};
|
||||
if(unlikely(msglen >= 0 && static_cast<size_t>(msglen) >= sizeof(stcmsg)))
|
||||
{
|
||||
dynmsg.resize(static_cast<size_t>(msglen) + 1u);
|
||||
str = dynmsg.data();
|
||||
std::vsnprintf(str, dynmsg.size(), fmt, args2);
|
||||
}
|
||||
va_end(args2);
|
||||
va_end(args);
|
||||
|
||||
if(gLogLevel >= level)
|
||||
{
|
||||
std::fputs(str, logfile);
|
||||
std::fflush(logfile);
|
||||
}
|
||||
#ifdef __ANDROID__
|
||||
#elif defined(__ANDROID__)
|
||||
auto android_severity = [](LogLevel l) noexcept
|
||||
{
|
||||
switch(l)
|
||||
|
|
@ -97,5 +87,3 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...)
|
|||
__android_log_print(android_severity(level), "openal", "%s", str);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -16,25 +16,6 @@ extern LogLevel gLogLevel;
|
|||
|
||||
extern FILE *gLogFile;
|
||||
|
||||
|
||||
#if !defined(_WIN32) && !defined(__ANDROID__)
|
||||
#define TRACE(...) do { \
|
||||
if UNLIKELY(gLogLevel >= LogLevel::Trace) \
|
||||
fprintf(gLogFile, "[ALSOFT] (II) " __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define WARN(...) do { \
|
||||
if UNLIKELY(gLogLevel >= LogLevel::Warning) \
|
||||
fprintf(gLogFile, "[ALSOFT] (WW) " __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define ERR(...) do { \
|
||||
if UNLIKELY(gLogLevel >= LogLevel::Error) \
|
||||
fprintf(gLogFile, "[ALSOFT] (EE) " __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#else
|
||||
|
||||
#ifdef __USE_MINGW_ANSI_STDIO
|
||||
[[gnu::format(gnu_printf,3,4)]]
|
||||
#else
|
||||
|
|
@ -42,11 +23,29 @@ extern FILE *gLogFile;
|
|||
#endif
|
||||
void al_print(LogLevel level, FILE *logfile, const char *fmt, ...);
|
||||
|
||||
#define TRACE(...) al_print(LogLevel::Trace, gLogFile, "[ALSOFT] (II) " __VA_ARGS__)
|
||||
#if (!defined(_WIN32) || defined(NDEBUG)) && !defined(__ANDROID__)
|
||||
#define TRACE(...) do { \
|
||||
if(gLogLevel >= LogLevel::Trace) UNLIKELY \
|
||||
al_print(LogLevel::Trace, gLogFile, __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define WARN(...) al_print(LogLevel::Warning, gLogFile, "[ALSOFT] (WW) " __VA_ARGS__)
|
||||
#define WARN(...) do { \
|
||||
if(gLogLevel >= LogLevel::Warning) UNLIKELY \
|
||||
al_print(LogLevel::Warning, gLogFile, __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define ERR(...) al_print(LogLevel::Error, gLogFile, "[ALSOFT] (EE) " __VA_ARGS__)
|
||||
#define ERR(...) do { \
|
||||
if(gLogLevel >= LogLevel::Error) UNLIKELY \
|
||||
al_print(LogLevel::Error, gLogFile, __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#else
|
||||
|
||||
#define TRACE(...) al_print(LogLevel::Trace, gLogFile, __VA_ARGS__)
|
||||
|
||||
#define WARN(...) al_print(LogLevel::Warning, gLogFile, __VA_ARGS__)
|
||||
|
||||
#define ERR(...) al_print(LogLevel::Error, gLogFile, __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#endif /* CORE_LOGGING_H */
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in)
|
|||
goto found_place;
|
||||
} while(lowerIndex--);
|
||||
lowerIndex = mask;
|
||||
} while(1);
|
||||
} while(true);
|
||||
found_place:
|
||||
|
||||
lowerIndex = (lowerIndex + 1) & mask;
|
||||
|
|
@ -87,10 +87,10 @@ void ShiftSlidingHold(SlidingHold *Hold, const uint n)
|
|||
if(exp_last-exp_begin < 0)
|
||||
{
|
||||
std::transform(exp_begin, std::end(Hold->mExpiries), exp_begin,
|
||||
std::bind(std::minus<>{}, _1, n));
|
||||
[n](uint e){ return e - n; });
|
||||
exp_begin = std::begin(Hold->mExpiries);
|
||||
}
|
||||
std::transform(exp_begin, exp_last+1, exp_begin, std::bind(std::minus<>{}, _1, n));
|
||||
std::transform(exp_begin, exp_last+1, exp_begin, [n](uint e){ return e - n; });
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ void LinkChannels(Compressor *Comp, const uint SamplesToDo, const FloatBufferLin
|
|||
* it uses an instantaneous squared peak detector and a squared RMS detector
|
||||
* both with 200ms release times.
|
||||
*/
|
||||
static void CrestDetector(Compressor *Comp, const uint SamplesToDo)
|
||||
void CrestDetector(Compressor *Comp, const uint SamplesToDo)
|
||||
{
|
||||
const float a_crest{Comp->mCrestCoeff};
|
||||
float y2_peak{Comp->mLastPeakSq};
|
||||
|
|
@ -155,7 +155,7 @@ void PeakDetector(Compressor *Comp, const uint SamplesToDo)
|
|||
/* Clamp the minimum amplitude to near-zero and convert to logarithm. */
|
||||
auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
|
||||
std::transform(side_begin, side_begin+SamplesToDo, side_begin,
|
||||
[](const float s) -> float { return std::log(maxf(0.000001f, s)); });
|
||||
[](float s) { return std::log(maxf(0.000001f, s)); });
|
||||
}
|
||||
|
||||
/* An optional hold can be used to extend the peak detector so it can more
|
||||
|
|
@ -295,7 +295,7 @@ void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutB
|
|||
float *delaybuf{al::assume_aligned<16>(Comp->mDelay[c].data())};
|
||||
|
||||
auto inout_end = inout + SamplesToDo;
|
||||
if LIKELY(SamplesToDo >= lookAhead)
|
||||
if(SamplesToDo >= lookAhead) LIKELY
|
||||
{
|
||||
auto delay_end = std::rotate(inout, inout_end - lookAhead, inout_end);
|
||||
std::swap_ranges(inout, delay_end, delaybuf);
|
||||
|
|
@ -404,7 +404,7 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer)
|
|||
{
|
||||
float *buffer{al::assume_aligned<16>(input.data())};
|
||||
std::transform(buffer, buffer+SamplesToDo, buffer,
|
||||
std::bind(std::multiplies<float>{}, _1, preGain));
|
||||
[preGain](float s) { return s * preGain; });
|
||||
};
|
||||
std::for_each(OutBuffer, OutBuffer+numChans, apply_gain);
|
||||
}
|
||||
|
|
@ -430,7 +430,7 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer)
|
|||
float *buffer{al::assume_aligned<16>(input.data())};
|
||||
const float *gains{al::assume_aligned<16>(&sideChain[0])};
|
||||
std::transform(gains, gains+SamplesToDo, buffer, buffer,
|
||||
std::bind(std::multiplies<float>{}, _1, _2));
|
||||
[](float g, float s) { return g * s; });
|
||||
};
|
||||
std::for_each(OutBuffer, OutBuffer+numChans, apply_comp);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,45 +13,14 @@
|
|||
struct CTag;
|
||||
|
||||
|
||||
MixerFunc MixSamples{Mix_<CTag>};
|
||||
MixerOutFunc MixSamplesOut{Mix_<CTag>};
|
||||
MixerOneFunc MixSamplesOne{Mix_<CTag>};
|
||||
|
||||
|
||||
std::array<float,MaxAmbiChannels> CalcAmbiCoeffs(const float y, const float z, const float x,
|
||||
const float spread)
|
||||
{
|
||||
std::array<float,MaxAmbiChannels> coeffs;
|
||||
|
||||
/* Zeroth-order */
|
||||
coeffs[0] = 1.0f; /* ACN 0 = 1 */
|
||||
/* First-order */
|
||||
coeffs[1] = al::numbers::sqrt3_v<float> * y; /* ACN 1 = sqrt(3) * Y */
|
||||
coeffs[2] = al::numbers::sqrt3_v<float> * z; /* ACN 2 = sqrt(3) * Z */
|
||||
coeffs[3] = al::numbers::sqrt3_v<float> * x; /* ACN 3 = sqrt(3) * X */
|
||||
/* Second-order */
|
||||
const float xx{x*x}, yy{y*y}, zz{z*z}, xy{x*y}, yz{y*z}, xz{x*z};
|
||||
coeffs[4] = 3.872983346f * xy; /* ACN 4 = sqrt(15) * X * Y */
|
||||
coeffs[5] = 3.872983346f * yz; /* ACN 5 = sqrt(15) * Y * Z */
|
||||
coeffs[6] = 1.118033989f * (3.0f*zz - 1.0f); /* ACN 6 = sqrt(5)/2 * (3*Z*Z - 1) */
|
||||
coeffs[7] = 3.872983346f * xz; /* ACN 7 = sqrt(15) * X * Z */
|
||||
coeffs[8] = 1.936491673f * (xx - yy); /* ACN 8 = sqrt(15)/2 * (X*X - Y*Y) */
|
||||
/* Third-order */
|
||||
coeffs[9] = 2.091650066f * (y*(3.0f*xx - yy)); /* ACN 9 = sqrt(35/8) * Y * (3*X*X - Y*Y) */
|
||||
coeffs[10] = 10.246950766f * (z*xy); /* ACN 10 = sqrt(105) * Z * X * Y */
|
||||
coeffs[11] = 1.620185175f * (y*(5.0f*zz - 1.0f)); /* ACN 11 = sqrt(21/8) * Y * (5*Z*Z - 1) */
|
||||
coeffs[12] = 1.322875656f * (z*(5.0f*zz - 3.0f)); /* ACN 12 = sqrt(7)/2 * Z * (5*Z*Z - 3) */
|
||||
coeffs[13] = 1.620185175f * (x*(5.0f*zz - 1.0f)); /* ACN 13 = sqrt(21/8) * X * (5*Z*Z - 1) */
|
||||
coeffs[14] = 5.123475383f * (z*(xx - yy)); /* ACN 14 = sqrt(105)/2 * Z * (X*X - Y*Y) */
|
||||
coeffs[15] = 2.091650066f * (x*(xx - 3.0f*yy)); /* ACN 15 = sqrt(35/8) * X * (X*X - 3*Y*Y) */
|
||||
/* Fourth-order */
|
||||
/* ACN 16 = sqrt(35)*3/2 * X * Y * (X*X - Y*Y) */
|
||||
/* ACN 17 = sqrt(35/2)*3/2 * (3*X*X - Y*Y) * Y * Z */
|
||||
/* ACN 18 = sqrt(5)*3/2 * X * Y * (7*Z*Z - 1) */
|
||||
/* ACN 19 = sqrt(5/2)*3/2 * Y * Z * (7*Z*Z - 3) */
|
||||
/* ACN 20 = 3/8 * (35*Z*Z*Z*Z - 30*Z*Z + 3) */
|
||||
/* ACN 21 = sqrt(5/2)*3/2 * X * Z * (7*Z*Z - 3) */
|
||||
/* ACN 22 = sqrt(5)*3/4 * (X*X - Y*Y) * (7*Z*Z - 1) */
|
||||
/* ACN 23 = sqrt(35/2)*3/2 * (X*X - 3*Y*Y) * X * Z */
|
||||
/* ACN 24 = sqrt(35)*3/8 * (X*X*X*X - 6*X*X*Y*Y + Y*Y*Y*Y) */
|
||||
std::array<float,MaxAmbiChannels> coeffs{CalcAmbiCoeffs(y, z, x)};
|
||||
|
||||
if(spread > 0.0f)
|
||||
{
|
||||
|
|
@ -114,7 +83,7 @@ std::array<float,MaxAmbiChannels> CalcAmbiCoeffs(const float y, const float z, c
|
|||
}
|
||||
|
||||
void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain,
|
||||
const al::span<float,MAX_OUTPUT_CHANNELS> gains)
|
||||
const al::span<float,MaxAmbiChannels> gains)
|
||||
{
|
||||
auto ambimap = mix->AmbiMap.cbegin();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,25 @@
|
|||
|
||||
struct MixParams;
|
||||
|
||||
using MixerFunc = void(*)(const al::span<const float> InSamples,
|
||||
/* Mixer functions that handle one input and multiple output channels. */
|
||||
using MixerOutFunc = void(*)(const al::span<const float> InSamples,
|
||||
const al::span<FloatBufferLine> OutBuffer, float *CurrentGains, const float *TargetGains,
|
||||
const size_t Counter, const size_t OutPos);
|
||||
|
||||
extern MixerFunc MixSamples;
|
||||
extern MixerOutFunc MixSamplesOut;
|
||||
inline void MixSamples(const al::span<const float> InSamples,
|
||||
const al::span<FloatBufferLine> OutBuffer, float *CurrentGains, const float *TargetGains,
|
||||
const size_t Counter, const size_t OutPos)
|
||||
{ MixSamplesOut(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); }
|
||||
|
||||
/* Mixer functions that handle one input and one output channel. */
|
||||
using MixerOneFunc = void(*)(const al::span<const float> InSamples, float *OutBuffer,
|
||||
float &CurrentGain, const float TargetGain, const size_t Counter);
|
||||
|
||||
extern MixerOneFunc MixSamplesOne;
|
||||
inline void MixSamples(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter)
|
||||
{ MixSamplesOne(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); }
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -51,6 +65,18 @@ inline std::array<float,MaxAmbiChannels> CalcDirectionCoeffs(const float (&dir)[
|
|||
return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread);
|
||||
}
|
||||
|
||||
/**
|
||||
* CalcDirectionCoeffs
|
||||
*
|
||||
* Calculates ambisonic coefficients based on an OpenAL direction vector. The
|
||||
* vector must be normalized (unit length).
|
||||
*/
|
||||
constexpr std::array<float,MaxAmbiChannels> CalcDirectionCoeffs(const float (&dir)[3])
|
||||
{
|
||||
/* Convert from OpenAL coords to Ambisonics. */
|
||||
return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* CalcAngleCoeffs
|
||||
*
|
||||
|
|
@ -78,24 +104,6 @@ inline std::array<float,MaxAmbiChannels> CalcAngleCoeffs(const float azimuth,
|
|||
* scale and orient the sound samples.
|
||||
*/
|
||||
void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain,
|
||||
const al::span<float,MAX_OUTPUT_CHANNELS> gains);
|
||||
|
||||
|
||||
/** Helper to set an identity/pass-through panning for ambisonic mixing (3D input). */
|
||||
template<typename T, typename I, typename F>
|
||||
auto SetAmbiPanIdentity(T iter, I count, F func) -> std::enable_if_t<std::is_integral<I>::value>
|
||||
{
|
||||
if(count < 1) return;
|
||||
|
||||
std::array<float,MaxAmbiChannels> coeffs{{1.0f}};
|
||||
func(*iter, coeffs);
|
||||
++iter;
|
||||
for(I i{1};i < count;++i,++iter)
|
||||
{
|
||||
coeffs[i-1] = 0.0f;
|
||||
coeffs[i ] = 1.0f;
|
||||
func(*iter, coeffs);
|
||||
}
|
||||
}
|
||||
const al::span<float,MaxAmbiChannels> gains);
|
||||
|
||||
#endif /* CORE_MIXER_H */
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "core/bufferline.h"
|
||||
#include "core/resampler_limits.h"
|
||||
|
||||
struct CubicCoefficients;
|
||||
struct HrtfChannelState;
|
||||
struct HrtfFilter;
|
||||
struct MixHrtfFilter;
|
||||
|
|
@ -16,14 +17,15 @@ using uint = unsigned int;
|
|||
using float2 = std::array<float,2>;
|
||||
|
||||
|
||||
constexpr int MixerFracBits{12};
|
||||
constexpr int MixerFracBits{16};
|
||||
constexpr int MixerFracOne{1 << MixerFracBits};
|
||||
constexpr int MixerFracMask{MixerFracOne - 1};
|
||||
constexpr int MixerFracHalf{MixerFracOne >> 1};
|
||||
|
||||
constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */
|
||||
|
||||
|
||||
enum class Resampler {
|
||||
enum class Resampler : uint8_t {
|
||||
Point,
|
||||
Linear,
|
||||
Cubic,
|
||||
|
|
@ -50,23 +52,34 @@ struct BsincState {
|
|||
const float *filter;
|
||||
};
|
||||
|
||||
struct CubicState {
|
||||
/* Filter coefficients, and coefficient deltas. Starting at phase index 0,
|
||||
* each subsequent phase index follows contiguously.
|
||||
*/
|
||||
const CubicCoefficients *filter;
|
||||
};
|
||||
|
||||
union InterpState {
|
||||
CubicState cubic;
|
||||
BsincState bsinc;
|
||||
};
|
||||
|
||||
using ResamplerFunc = float*(*)(const InterpState *state, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst);
|
||||
using ResamplerFunc = void(*)(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst);
|
||||
|
||||
ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state);
|
||||
|
||||
|
||||
template<typename TypeTag, typename InstTag>
|
||||
float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, uint increment,
|
||||
const al::span<float> dst);
|
||||
void Resample_(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst);
|
||||
|
||||
template<typename InstTag>
|
||||
void Mix_(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer,
|
||||
float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos);
|
||||
template<typename InstTag>
|
||||
void Mix_(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter);
|
||||
|
||||
template<typename InstTag>
|
||||
void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl
|
|||
const ConstHrirSpan NewCoeffs{newparams->Coeffs};
|
||||
const float newGainStep{newparams->GainStep};
|
||||
|
||||
if LIKELY(oldparams->Gain > GainSilenceThreshold)
|
||||
if(oldparams->Gain > GainSilenceThreshold) LIKELY
|
||||
{
|
||||
size_t ldelay{HrtfHistoryLength - oldparams->Delay[0]};
|
||||
size_t rdelay{HrtfHistoryLength - oldparams->Delay[1]};
|
||||
|
|
@ -66,7 +66,7 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl
|
|||
}
|
||||
}
|
||||
|
||||
if LIKELY(newGainStep*static_cast<float>(BufferSize) > GainSilenceThreshold)
|
||||
if(newGainStep*static_cast<float>(BufferSize) > GainSilenceThreshold) LIKELY
|
||||
{
|
||||
size_t ldelay{HrtfHistoryLength+1 - newparams->Delay[0]};
|
||||
size_t rdelay{HrtfHistoryLength+1 - newparams->Delay[1]};
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@
|
|||
#include <limits>
|
||||
|
||||
#include "alnumeric.h"
|
||||
#include "core/bsinc_tables.h"
|
||||
#include "core/bsinc_defs.h"
|
||||
#include "core/cubic_defs.h"
|
||||
#include "defs.h"
|
||||
#include "hrtfbase.h"
|
||||
|
||||
struct CTag;
|
||||
struct CopyTag;
|
||||
struct PointTag;
|
||||
struct LerpTag;
|
||||
struct CubicTag;
|
||||
|
|
@ -20,30 +20,46 @@ struct FastBSincTag;
|
|||
|
||||
namespace {
|
||||
|
||||
constexpr uint FracPhaseBitDiff{MixerFracBits - BSincPhaseBits};
|
||||
constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff};
|
||||
constexpr uint BsincPhaseDiffBits{MixerFracBits - BSincPhaseBits};
|
||||
constexpr uint BsincPhaseDiffOne{1 << BsincPhaseDiffBits};
|
||||
constexpr uint BsincPhaseDiffMask{BsincPhaseDiffOne - 1u};
|
||||
|
||||
constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits};
|
||||
constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits};
|
||||
constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u};
|
||||
|
||||
inline float do_point(const InterpState&, const float *RESTRICT vals, const uint)
|
||||
{ return vals[0]; }
|
||||
inline float do_lerp(const InterpState&, const float *RESTRICT vals, const uint frac)
|
||||
{ return lerpf(vals[0], vals[1], static_cast<float>(frac)*(1.0f/MixerFracOne)); }
|
||||
inline float do_cubic(const InterpState&, const float *RESTRICT vals, const uint frac)
|
||||
{ return cubic(vals[0], vals[1], vals[2], vals[3], static_cast<float>(frac)*(1.0f/MixerFracOne)); }
|
||||
inline float do_cubic(const InterpState &istate, const float *RESTRICT vals, const uint frac)
|
||||
{
|
||||
/* Calculate the phase index and factor. */
|
||||
const uint pi{frac >> CubicPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
|
||||
|
||||
const float *RESTRICT fil{al::assume_aligned<16>(istate.cubic.filter[pi].mCoeffs)};
|
||||
const float *RESTRICT phd{al::assume_aligned<16>(istate.cubic.filter[pi].mDeltas)};
|
||||
|
||||
/* Apply the phase interpolated filter. */
|
||||
return (fil[0] + pf*phd[0])*vals[0] + (fil[1] + pf*phd[1])*vals[1]
|
||||
+ (fil[2] + pf*phd[2])*vals[2] + (fil[3] + pf*phd[3])*vals[3];
|
||||
}
|
||||
inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac)
|
||||
{
|
||||
const size_t m{istate.bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> FracPhaseBitDiff};
|
||||
const float pf{static_cast<float>(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)};
|
||||
/* Calculate the phase index and factor. */
|
||||
const uint pi{frac >> BsincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)};
|
||||
|
||||
const float *RESTRICT fil{istate.bsinc.filter + m*pi*2};
|
||||
const float *RESTRICT phd{fil + m};
|
||||
const float *RESTRICT scd{fil + BSincPhaseCount*2*m};
|
||||
const float *RESTRICT spd{scd + m};
|
||||
|
||||
// Apply the scale and phase interpolated filter.
|
||||
/* Apply the scale and phase interpolated filter. */
|
||||
float r{0.0f};
|
||||
for(size_t j_f{0};j_f < m;j_f++)
|
||||
r += (fil[j_f] + istate.bsinc.sf*scd[j_f] + pf*(phd[j_f] + istate.bsinc.sf*spd[j_f])) * vals[j_f];
|
||||
|
|
@ -54,14 +70,14 @@ inline float do_fastbsinc(const InterpState &istate, const float *RESTRICT vals,
|
|||
const size_t m{istate.bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> FracPhaseBitDiff};
|
||||
const float pf{static_cast<float>(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)};
|
||||
/* Calculate the phase index and factor. */
|
||||
const uint pi{frac >> BsincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)};
|
||||
|
||||
const float *RESTRICT fil{istate.bsinc.filter + m*pi*2};
|
||||
const float *RESTRICT phd{fil + m};
|
||||
|
||||
// Apply the phase interpolated filter.
|
||||
/* Apply the phase interpolated filter. */
|
||||
float r{0.0f};
|
||||
for(size_t j_f{0};j_f < m;j_f++)
|
||||
r += (fil[j_f] + pf*phd[j_f]) * vals[j_f];
|
||||
|
|
@ -70,10 +86,11 @@ inline float do_fastbsinc(const InterpState &istate, const float *RESTRICT vals,
|
|||
|
||||
using SamplerT = float(&)(const InterpState&, const float*RESTRICT, const uint);
|
||||
template<SamplerT Sampler>
|
||||
float *DoResample(const InterpState *state, float *RESTRICT src, uint frac, uint increment,
|
||||
const al::span<float> dst)
|
||||
void DoResample(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
const InterpState istate{*state};
|
||||
ASSUME(frac < MixerFracOne);
|
||||
for(float &out : dst)
|
||||
{
|
||||
out = Sampler(istate, src, frac);
|
||||
|
|
@ -82,7 +99,6 @@ float *DoResample(const InterpState *state, float *RESTRICT src, uint frac, uint
|
|||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
return dst.data();
|
||||
}
|
||||
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs,
|
||||
|
|
@ -96,45 +112,63 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Cons
|
|||
}
|
||||
}
|
||||
|
||||
force_inline void MixLine(const al::span<const float> InSamples, float *RESTRICT dst,
|
||||
float &CurrentGain, const float TargetGain, const float delta, const size_t min_len,
|
||||
size_t Counter)
|
||||
{
|
||||
float gain{CurrentGain};
|
||||
const float step{(TargetGain-gain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = TargetGain;
|
||||
else
|
||||
{
|
||||
float step_count{0.0f};
|
||||
for(;pos != min_len;++pos)
|
||||
{
|
||||
dst[pos] += InSamples[pos] * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
}
|
||||
if(pos == Counter)
|
||||
gain = TargetGain;
|
||||
else
|
||||
gain += step*step_count;
|
||||
}
|
||||
CurrentGain = gain;
|
||||
|
||||
if(!(std::abs(gain) > GainSilenceThreshold))
|
||||
return;
|
||||
for(;pos != InSamples.size();++pos)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template<>
|
||||
float *Resample_<CopyTag,CTag>(const InterpState*, float *RESTRICT src, uint, uint,
|
||||
const al::span<float> dst)
|
||||
{
|
||||
#if defined(HAVE_SSE) || defined(HAVE_NEON)
|
||||
/* Avoid copying the source data if it's aligned like the destination. */
|
||||
if((reinterpret_cast<intptr_t>(src)&15) == (reinterpret_cast<intptr_t>(dst.data())&15))
|
||||
return src;
|
||||
#endif
|
||||
std::copy_n(src, dst.size(), dst.begin());
|
||||
return dst.data();
|
||||
}
|
||||
void Resample_<PointTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_point>(state, src, frac, increment, dst); }
|
||||
|
||||
template<>
|
||||
float *Resample_<PointTag,CTag>(const InterpState *state, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
{ return DoResample<do_point>(state, src, frac, increment, dst); }
|
||||
void Resample_<LerpTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_lerp>(state, src, frac, increment, dst); }
|
||||
|
||||
template<>
|
||||
float *Resample_<LerpTag,CTag>(const InterpState *state, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
{ return DoResample<do_lerp>(state, src, frac, increment, dst); }
|
||||
void Resample_<CubicTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_cubic>(state, src-1, frac, increment, dst); }
|
||||
|
||||
template<>
|
||||
float *Resample_<CubicTag,CTag>(const InterpState *state, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
{ return DoResample<do_cubic>(state, src-1, frac, increment, dst); }
|
||||
void Resample_<BSincTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_bsinc>(state, src-state->bsinc.l, frac, increment, dst); }
|
||||
|
||||
template<>
|
||||
float *Resample_<BSincTag,CTag>(const InterpState *state, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
{ return DoResample<do_bsinc>(state, src-state->bsinc.l, frac, increment, dst); }
|
||||
|
||||
template<>
|
||||
float *Resample_<FastBSincTag,CTag>(const InterpState *state, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
{ return DoResample<do_fastbsinc>(state, src-state->bsinc.l, frac, increment, dst); }
|
||||
void Resample_<FastBSincTag,CTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{ DoResample<do_fastbsinc>(state, src-state->bsinc.l, frac, increment, dst); }
|
||||
|
||||
|
||||
template<>
|
||||
|
|
@ -166,35 +200,19 @@ void Mix_<CTag>(const al::span<const float> InSamples, const al::span<FloatBuffe
|
|||
{
|
||||
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
|
||||
const auto min_len = minz(Counter, InSamples.size());
|
||||
|
||||
for(FloatBufferLine &output : OutBuffer)
|
||||
{
|
||||
float *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)};
|
||||
float gain{*CurrentGains};
|
||||
const float step{(*TargetGains-gain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = *TargetGains;
|
||||
else
|
||||
{
|
||||
float step_count{0.0f};
|
||||
for(;pos != min_len;++pos)
|
||||
{
|
||||
dst[pos] += InSamples[pos] * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
}
|
||||
if(pos == Counter)
|
||||
gain = *TargetGains;
|
||||
else
|
||||
gain += step*step_count;
|
||||
}
|
||||
*CurrentGains = gain;
|
||||
++CurrentGains;
|
||||
++TargetGains;
|
||||
|
||||
if(!(std::abs(gain) > GainSilenceThreshold))
|
||||
continue;
|
||||
for(;pos != InSamples.size();++pos)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++,
|
||||
*TargetGains++, delta, min_len, Counter);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Mix_<CTag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter)
|
||||
{
|
||||
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
|
||||
const auto min_len = minz(Counter, InSamples.size());
|
||||
|
||||
MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain,
|
||||
TargetGain, delta, min_len, Counter);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
#include "alnumeric.h"
|
||||
#include "core/bsinc_defs.h"
|
||||
#include "core/cubic_defs.h"
|
||||
#include "defs.h"
|
||||
#include "hrtfbase.h"
|
||||
|
||||
struct NEONTag;
|
||||
struct LerpTag;
|
||||
struct CubicTag;
|
||||
struct BSincTag;
|
||||
struct FastBSincTag;
|
||||
|
||||
|
|
@ -22,6 +24,14 @@ struct FastBSincTag;
|
|||
|
||||
namespace {
|
||||
|
||||
constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits};
|
||||
constexpr uint BSincPhaseDiffOne{1 << BSincPhaseDiffBits};
|
||||
constexpr uint BSincPhaseDiffMask{BSincPhaseDiffOne - 1u};
|
||||
|
||||
constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits};
|
||||
constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits};
|
||||
constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u};
|
||||
|
||||
inline float32x4_t set_f4(float l0, float l1, float l2, float l3)
|
||||
{
|
||||
float32x4_t ret{vmovq_n_f32(l0)};
|
||||
|
|
@ -31,9 +41,6 @@ inline float32x4_t set_f4(float l0, float l1, float l2, float l3)
|
|||
return ret;
|
||||
}
|
||||
|
||||
constexpr uint FracPhaseBitDiff{MixerFracBits - BSincPhaseBits};
|
||||
constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff};
|
||||
|
||||
inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs,
|
||||
const float left, const float right)
|
||||
{
|
||||
|
|
@ -56,12 +63,86 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Cons
|
|||
}
|
||||
}
|
||||
|
||||
force_inline void MixLine(const al::span<const float> InSamples, float *RESTRICT dst,
|
||||
float &CurrentGain, const float TargetGain, const float delta, const size_t min_len,
|
||||
const size_t aligned_len, size_t Counter)
|
||||
{
|
||||
float gain{CurrentGain};
|
||||
const float step{(TargetGain-gain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = TargetGain;
|
||||
else
|
||||
{
|
||||
float step_count{0.0f};
|
||||
/* Mix with applying gain steps in aligned multiples of 4. */
|
||||
if(size_t todo{min_len >> 2})
|
||||
{
|
||||
const float32x4_t four4{vdupq_n_f32(4.0f)};
|
||||
const float32x4_t step4{vdupq_n_f32(step)};
|
||||
const float32x4_t gain4{vdupq_n_f32(gain)};
|
||||
float32x4_t step_count4{vdupq_n_f32(0.0f)};
|
||||
step_count4 = vsetq_lane_f32(1.0f, step_count4, 1);
|
||||
step_count4 = vsetq_lane_f32(2.0f, step_count4, 2);
|
||||
step_count4 = vsetq_lane_f32(3.0f, step_count4, 3);
|
||||
|
||||
do {
|
||||
const float32x4_t val4 = vld1q_f32(&InSamples[pos]);
|
||||
float32x4_t dry4 = vld1q_f32(&dst[pos]);
|
||||
dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4));
|
||||
step_count4 = vaddq_f32(step_count4, four4);
|
||||
vst1q_f32(&dst[pos], dry4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
/* NOTE: step_count4 now represents the next four counts after the
|
||||
* last four mixed samples, so the lowest element represents the
|
||||
* next step count to apply.
|
||||
*/
|
||||
step_count = vgetq_lane_f32(step_count4, 0);
|
||||
}
|
||||
/* Mix with applying left over gain steps that aren't aligned multiples of 4. */
|
||||
for(size_t leftover{min_len&3};leftover;++pos,--leftover)
|
||||
{
|
||||
dst[pos] += InSamples[pos] * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
}
|
||||
if(pos == Counter)
|
||||
gain = TargetGain;
|
||||
else
|
||||
gain += step*step_count;
|
||||
|
||||
/* Mix until pos is aligned with 4 or the mix is done. */
|
||||
for(size_t leftover{aligned_len&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
CurrentGain = gain;
|
||||
|
||||
if(!(std::abs(gain) > GainSilenceThreshold))
|
||||
return;
|
||||
if(size_t todo{(InSamples.size()-pos) >> 2})
|
||||
{
|
||||
const float32x4_t gain4 = vdupq_n_f32(gain);
|
||||
do {
|
||||
const float32x4_t val4 = vld1q_f32(&InSamples[pos]);
|
||||
float32x4_t dry4 = vld1q_f32(&dst[pos]);
|
||||
dry4 = vmlaq_f32(dry4, val4, gain4);
|
||||
vst1q_f32(&dst[pos], dry4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
}
|
||||
for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template<>
|
||||
float *Resample_<LerpTag,NEONTag>(const InterpState*, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
void Resample_<LerpTag,NEONTag>(const InterpState*, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const int32x4_t increment4 = vdupq_n_s32(static_cast<int>(increment*4));
|
||||
const float32x4_t fracOne4 = vdupq_n_f32(1.0f/MixerFracOne);
|
||||
const int32x4_t fracMask4 = vdupq_n_s32(MixerFracMask);
|
||||
|
|
@ -108,24 +189,56 @@ float *Resample_<LerpTag,NEONTag>(const InterpState*, float *RESTRICT src, uint
|
|||
frac &= MixerFracMask;
|
||||
} while(--todo);
|
||||
}
|
||||
return dst.data();
|
||||
}
|
||||
|
||||
template<>
|
||||
float *Resample_<BSincTag,NEONTag>(const InterpState *state, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
void Resample_<CubicTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const CubicCoefficients *RESTRICT filter = al::assume_aligned<16>(state->cubic.filter);
|
||||
|
||||
src -= 1;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
const uint pi{frac >> CubicPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
|
||||
const float32x4_t pf4{vdupq_n_f32(pf)};
|
||||
|
||||
/* Apply the phase interpolated filter. */
|
||||
|
||||
/* f = fil + pf*phd */
|
||||
const float32x4_t f4 = vmlaq_f32(vld1q_f32(filter[pi].mCoeffs), pf4,
|
||||
vld1q_f32(filter[pi].mDeltas));
|
||||
/* r = f*src */
|
||||
float32x4_t r4{vmulq_f32(f4, vld1q_f32(src))};
|
||||
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<BSincTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
const float *const filter{state->bsinc.filter};
|
||||
const float32x4_t sf4{vdupq_n_f32(state->bsinc.sf)};
|
||||
const size_t m{state->bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> FracPhaseBitDiff};
|
||||
const float pf{static_cast<float>(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)};
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
|
||||
|
||||
// Apply the scale and phase interpolated filter.
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
|
|
@ -155,23 +268,23 @@ float *Resample_<BSincTag,NEONTag>(const InterpState *state, float *RESTRICT src
|
|||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
return dst.data();
|
||||
}
|
||||
|
||||
template<>
|
||||
float *Resample_<FastBSincTag,NEONTag>(const InterpState *state, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
void Resample_<FastBSincTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
const float *const filter{state->bsinc.filter};
|
||||
const size_t m{state->bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> FracPhaseBitDiff};
|
||||
const float pf{static_cast<float>(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)};
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
|
||||
|
||||
// Apply the phase interpolated filter.
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
|
|
@ -197,7 +310,6 @@ float *Resample_<FastBSincTag,NEONTag>(const InterpState *state, float *RESTRICT
|
|||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
return dst.data();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -233,75 +345,18 @@ void Mix_<NEONTag>(const al::span<const float> InSamples, const al::span<FloatBu
|
|||
const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
|
||||
|
||||
for(FloatBufferLine &output : OutBuffer)
|
||||
{
|
||||
float *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)};
|
||||
float gain{*CurrentGains};
|
||||
const float step{(*TargetGains-gain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = *TargetGains;
|
||||
else
|
||||
{
|
||||
float step_count{0.0f};
|
||||
/* Mix with applying gain steps in aligned multiples of 4. */
|
||||
if(size_t todo{min_len >> 2})
|
||||
{
|
||||
const float32x4_t four4{vdupq_n_f32(4.0f)};
|
||||
const float32x4_t step4{vdupq_n_f32(step)};
|
||||
const float32x4_t gain4{vdupq_n_f32(gain)};
|
||||
float32x4_t step_count4{vdupq_n_f32(0.0f)};
|
||||
step_count4 = vsetq_lane_f32(1.0f, step_count4, 1);
|
||||
step_count4 = vsetq_lane_f32(2.0f, step_count4, 2);
|
||||
step_count4 = vsetq_lane_f32(3.0f, step_count4, 3);
|
||||
|
||||
do {
|
||||
const float32x4_t val4 = vld1q_f32(&InSamples[pos]);
|
||||
float32x4_t dry4 = vld1q_f32(&dst[pos]);
|
||||
dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4));
|
||||
step_count4 = vaddq_f32(step_count4, four4);
|
||||
vst1q_f32(&dst[pos], dry4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
/* NOTE: step_count4 now represents the next four counts after
|
||||
* the last four mixed samples, so the lowest element
|
||||
* represents the next step count to apply.
|
||||
*/
|
||||
step_count = vgetq_lane_f32(step_count4, 0);
|
||||
}
|
||||
/* Mix with applying left over gain steps that aren't aligned multiples of 4. */
|
||||
for(size_t leftover{min_len&3};leftover;++pos,--leftover)
|
||||
{
|
||||
dst[pos] += InSamples[pos] * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
}
|
||||
if(pos == Counter)
|
||||
gain = *TargetGains;
|
||||
else
|
||||
gain += step*step_count;
|
||||
|
||||
/* Mix until pos is aligned with 4 or the mix is done. */
|
||||
for(size_t leftover{aligned_len&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
*CurrentGains = gain;
|
||||
++CurrentGains;
|
||||
++TargetGains;
|
||||
|
||||
if(!(std::abs(gain) > GainSilenceThreshold))
|
||||
continue;
|
||||
if(size_t todo{(InSamples.size()-pos) >> 2})
|
||||
{
|
||||
const float32x4_t gain4 = vdupq_n_f32(gain);
|
||||
do {
|
||||
const float32x4_t val4 = vld1q_f32(&InSamples[pos]);
|
||||
float32x4_t dry4 = vld1q_f32(&dst[pos]);
|
||||
dry4 = vmlaq_f32(dry4, val4, gain4);
|
||||
vst1q_f32(&dst[pos], dry4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
}
|
||||
for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++,
|
||||
*TargetGains++, delta, min_len, aligned_len, Counter);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Mix_<NEONTag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter)
|
||||
{
|
||||
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
|
||||
const auto min_len = minz(Counter, InSamples.size());
|
||||
const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
|
||||
|
||||
MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len,
|
||||
aligned_len, Counter);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@
|
|||
|
||||
#include "alnumeric.h"
|
||||
#include "core/bsinc_defs.h"
|
||||
#include "core/cubic_defs.h"
|
||||
#include "defs.h"
|
||||
#include "hrtfbase.h"
|
||||
|
||||
struct SSETag;
|
||||
struct CubicTag;
|
||||
struct BSincTag;
|
||||
struct FastBSincTag;
|
||||
|
||||
|
|
@ -21,8 +23,13 @@ struct FastBSincTag;
|
|||
|
||||
namespace {
|
||||
|
||||
constexpr uint FracPhaseBitDiff{MixerFracBits - BSincPhaseBits};
|
||||
constexpr uint FracPhaseDiffOne{1 << FracPhaseBitDiff};
|
||||
constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits};
|
||||
constexpr uint BSincPhaseDiffOne{1 << BSincPhaseDiffBits};
|
||||
constexpr uint BSincPhaseDiffMask{BSincPhaseDiffOne - 1u};
|
||||
|
||||
constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits};
|
||||
constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits};
|
||||
constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u};
|
||||
|
||||
#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z))
|
||||
|
||||
|
|
@ -40,56 +47,161 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Cons
|
|||
{
|
||||
for(size_t i{0};i < IrSize;i += 2)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&Coeffs[i][0])};
|
||||
__m128 vals{_mm_load_ps(&Values[i][0])};
|
||||
const __m128 coeffs{_mm_load_ps(Coeffs[i].data())};
|
||||
__m128 vals{_mm_load_ps(Values[i].data())};
|
||||
vals = MLA4(vals, lrlr, coeffs);
|
||||
_mm_store_ps(&Values[i][0], vals);
|
||||
_mm_store_ps(Values[i].data(), vals);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
__m128 imp0, imp1;
|
||||
__m128 coeffs{_mm_load_ps(&Coeffs[0][0])};
|
||||
__m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(&Values[0][0]))};
|
||||
__m128 coeffs{_mm_load_ps(Coeffs[0].data())};
|
||||
__m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(Values[0].data()))};
|
||||
imp0 = _mm_mul_ps(lrlr, coeffs);
|
||||
vals = _mm_add_ps(imp0, vals);
|
||||
_mm_storel_pi(reinterpret_cast<__m64*>(&Values[0][0]), vals);
|
||||
_mm_storel_pi(reinterpret_cast<__m64*>(Values[0].data()), vals);
|
||||
size_t td{((IrSize+1)>>1) - 1};
|
||||
size_t i{1};
|
||||
do {
|
||||
coeffs = _mm_load_ps(&Coeffs[i+1][0]);
|
||||
vals = _mm_load_ps(&Values[i][0]);
|
||||
coeffs = _mm_load_ps(Coeffs[i+1].data());
|
||||
vals = _mm_load_ps(Values[i].data());
|
||||
imp1 = _mm_mul_ps(lrlr, coeffs);
|
||||
imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2));
|
||||
vals = _mm_add_ps(imp0, vals);
|
||||
_mm_store_ps(&Values[i][0], vals);
|
||||
_mm_store_ps(Values[i].data(), vals);
|
||||
imp0 = imp1;
|
||||
i += 2;
|
||||
} while(--td);
|
||||
vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(&Values[i][0]));
|
||||
vals = _mm_loadl_pi(vals, reinterpret_cast<__m64*>(Values[i].data()));
|
||||
imp0 = _mm_movehl_ps(imp0, imp0);
|
||||
vals = _mm_add_ps(imp0, vals);
|
||||
_mm_storel_pi(reinterpret_cast<__m64*>(&Values[i][0]), vals);
|
||||
_mm_storel_pi(reinterpret_cast<__m64*>(Values[i].data()), vals);
|
||||
}
|
||||
}
|
||||
|
||||
force_inline void MixLine(const al::span<const float> InSamples, float *RESTRICT dst,
|
||||
float &CurrentGain, const float TargetGain, const float delta, const size_t min_len,
|
||||
const size_t aligned_len, size_t Counter)
|
||||
{
|
||||
float gain{CurrentGain};
|
||||
const float step{(TargetGain-gain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = TargetGain;
|
||||
else
|
||||
{
|
||||
float step_count{0.0f};
|
||||
/* Mix with applying gain steps in aligned multiples of 4. */
|
||||
if(size_t todo{min_len >> 2})
|
||||
{
|
||||
const __m128 four4{_mm_set1_ps(4.0f)};
|
||||
const __m128 step4{_mm_set1_ps(step)};
|
||||
const __m128 gain4{_mm_set1_ps(gain)};
|
||||
__m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)};
|
||||
do {
|
||||
const __m128 val4{_mm_load_ps(&InSamples[pos])};
|
||||
__m128 dry4{_mm_load_ps(&dst[pos])};
|
||||
|
||||
/* dry += val * (gain + step*step_count) */
|
||||
dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4));
|
||||
|
||||
_mm_store_ps(&dst[pos], dry4);
|
||||
step_count4 = _mm_add_ps(step_count4, four4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
/* NOTE: step_count4 now represents the next four counts after the
|
||||
* last four mixed samples, so the lowest element represents the
|
||||
* next step count to apply.
|
||||
*/
|
||||
step_count = _mm_cvtss_f32(step_count4);
|
||||
}
|
||||
/* Mix with applying left over gain steps that aren't aligned multiples of 4. */
|
||||
for(size_t leftover{min_len&3};leftover;++pos,--leftover)
|
||||
{
|
||||
dst[pos] += InSamples[pos] * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
}
|
||||
if(pos == Counter)
|
||||
gain = TargetGain;
|
||||
else
|
||||
gain += step*step_count;
|
||||
|
||||
/* Mix until pos is aligned with 4 or the mix is done. */
|
||||
for(size_t leftover{aligned_len&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
CurrentGain = gain;
|
||||
|
||||
if(!(std::abs(gain) > GainSilenceThreshold))
|
||||
return;
|
||||
if(size_t todo{(InSamples.size()-pos) >> 2})
|
||||
{
|
||||
const __m128 gain4{_mm_set1_ps(gain)};
|
||||
do {
|
||||
const __m128 val4{_mm_load_ps(&InSamples[pos])};
|
||||
__m128 dry4{_mm_load_ps(&dst[pos])};
|
||||
dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4));
|
||||
_mm_store_ps(&dst[pos], dry4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
}
|
||||
for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template<>
|
||||
float *Resample_<BSincTag,SSETag>(const InterpState *state, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
void Resample_<CubicTag,SSETag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const CubicCoefficients *RESTRICT filter = al::assume_aligned<16>(state->cubic.filter);
|
||||
|
||||
src -= 1;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
const uint pi{frac >> CubicPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
|
||||
const __m128 pf4{_mm_set1_ps(pf)};
|
||||
|
||||
/* Apply the phase interpolated filter. */
|
||||
|
||||
/* f = fil + pf*phd */
|
||||
const __m128 f4 = MLA4(_mm_load_ps(filter[pi].mCoeffs), pf4,
|
||||
_mm_load_ps(filter[pi].mDeltas));
|
||||
/* r = f*src */
|
||||
__m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(src))};
|
||||
|
||||
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
|
||||
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
|
||||
out_sample = _mm_cvtss_f32(r4);
|
||||
|
||||
frac += increment;
|
||||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Resample_<BSincTag,SSETag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
const float *const filter{state->bsinc.filter};
|
||||
const __m128 sf4{_mm_set1_ps(state->bsinc.sf)};
|
||||
const size_t m{state->bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> FracPhaseBitDiff};
|
||||
const float pf{static_cast<float>(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)};
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
|
||||
|
||||
// Apply the scale and phase interpolated filter.
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
|
|
@ -120,23 +232,23 @@ float *Resample_<BSincTag,SSETag>(const InterpState *state, float *RESTRICT src,
|
|||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
return dst.data();
|
||||
}
|
||||
|
||||
template<>
|
||||
float *Resample_<FastBSincTag,SSETag>(const InterpState *state, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
void Resample_<FastBSincTag,SSETag>(const InterpState *state, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
const float *const filter{state->bsinc.filter};
|
||||
const size_t m{state->bsinc.m};
|
||||
ASSUME(m > 0);
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
src -= state->bsinc.l;
|
||||
for(float &out_sample : dst)
|
||||
{
|
||||
// Calculate the phase index and factor.
|
||||
const uint pi{frac >> FracPhaseBitDiff};
|
||||
const float pf{static_cast<float>(frac & (FracPhaseDiffOne-1)) * (1.0f/FracPhaseDiffOne)};
|
||||
const uint pi{frac >> BSincPhaseDiffBits};
|
||||
const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
|
||||
|
||||
// Apply the phase interpolated filter.
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
|
|
@ -163,7 +275,6 @@ float *Resample_<FastBSincTag,SSETag>(const InterpState *state, float *RESTRICT
|
|||
src += frac>>MixerFracBits;
|
||||
frac &= MixerFracMask;
|
||||
}
|
||||
return dst.data();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -199,74 +310,18 @@ void Mix_<SSETag>(const al::span<const float> InSamples, const al::span<FloatBuf
|
|||
const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
|
||||
|
||||
for(FloatBufferLine &output : OutBuffer)
|
||||
{
|
||||
float *RESTRICT dst{al::assume_aligned<16>(output.data()+OutPos)};
|
||||
float gain{*CurrentGains};
|
||||
const float step{(*TargetGains-gain) * delta};
|
||||
|
||||
size_t pos{0};
|
||||
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
|
||||
gain = *TargetGains;
|
||||
else
|
||||
{
|
||||
float step_count{0.0f};
|
||||
/* Mix with applying gain steps in aligned multiples of 4. */
|
||||
if(size_t todo{min_len >> 2})
|
||||
{
|
||||
const __m128 four4{_mm_set1_ps(4.0f)};
|
||||
const __m128 step4{_mm_set1_ps(step)};
|
||||
const __m128 gain4{_mm_set1_ps(gain)};
|
||||
__m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)};
|
||||
do {
|
||||
const __m128 val4{_mm_load_ps(&InSamples[pos])};
|
||||
__m128 dry4{_mm_load_ps(&dst[pos])};
|
||||
|
||||
/* dry += val * (gain + step*step_count) */
|
||||
dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4));
|
||||
|
||||
_mm_store_ps(&dst[pos], dry4);
|
||||
step_count4 = _mm_add_ps(step_count4, four4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
/* NOTE: step_count4 now represents the next four counts after
|
||||
* the last four mixed samples, so the lowest element
|
||||
* represents the next step count to apply.
|
||||
*/
|
||||
step_count = _mm_cvtss_f32(step_count4);
|
||||
}
|
||||
/* Mix with applying left over gain steps that aren't aligned multiples of 4. */
|
||||
for(size_t leftover{min_len&3};leftover;++pos,--leftover)
|
||||
{
|
||||
dst[pos] += InSamples[pos] * (gain + step*step_count);
|
||||
step_count += 1.0f;
|
||||
}
|
||||
if(pos == Counter)
|
||||
gain = *TargetGains;
|
||||
else
|
||||
gain += step*step_count;
|
||||
|
||||
/* Mix until pos is aligned with 4 or the mix is done. */
|
||||
for(size_t leftover{aligned_len&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
*CurrentGains = gain;
|
||||
++CurrentGains;
|
||||
++TargetGains;
|
||||
|
||||
if(!(std::abs(gain) > GainSilenceThreshold))
|
||||
continue;
|
||||
if(size_t todo{(InSamples.size()-pos) >> 2})
|
||||
{
|
||||
const __m128 gain4{_mm_set1_ps(gain)};
|
||||
do {
|
||||
const __m128 val4{_mm_load_ps(&InSamples[pos])};
|
||||
__m128 dry4{_mm_load_ps(&dst[pos])};
|
||||
dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4));
|
||||
_mm_store_ps(&dst[pos], dry4);
|
||||
pos += 4;
|
||||
} while(--todo);
|
||||
}
|
||||
for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover)
|
||||
dst[pos] += InSamples[pos] * gain;
|
||||
}
|
||||
MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++,
|
||||
*TargetGains++, delta, min_len, aligned_len, Counter);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Mix_<SSETag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
|
||||
const float TargetGain, const size_t Counter)
|
||||
{
|
||||
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
|
||||
const auto min_len = minz(Counter, InSamples.size());
|
||||
const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
|
||||
|
||||
MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len,
|
||||
aligned_len, Counter);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,9 +35,11 @@ struct LerpTag;
|
|||
#endif
|
||||
|
||||
template<>
|
||||
float *Resample_<LerpTag,SSE2Tag>(const InterpState*, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
void Resample_<LerpTag,SSE2Tag>(const InterpState*, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const __m128i increment4{_mm_set1_epi32(static_cast<int>(increment*4))};
|
||||
const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)};
|
||||
const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)};
|
||||
|
|
@ -85,5 +87,4 @@ float *Resample_<LerpTag,SSE2Tag>(const InterpState*, float *RESTRICT src, uint
|
|||
frac &= MixerFracMask;
|
||||
} while(--todo);
|
||||
}
|
||||
return dst.data();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,9 +36,11 @@ struct LerpTag;
|
|||
#endif
|
||||
|
||||
template<>
|
||||
float *Resample_<LerpTag,SSE4Tag>(const InterpState*, float *RESTRICT src, uint frac,
|
||||
uint increment, const al::span<float> dst)
|
||||
void Resample_<LerpTag,SSE4Tag>(const InterpState*, const float *RESTRICT src, uint frac,
|
||||
const uint increment, const al::span<float> dst)
|
||||
{
|
||||
ASSUME(frac < MixerFracOne);
|
||||
|
||||
const __m128i increment4{_mm_set1_epi32(static_cast<int>(increment*4))};
|
||||
const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)};
|
||||
const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)};
|
||||
|
|
@ -90,5 +92,4 @@ float *Resample_<LerpTag,SSE4Tag>(const InterpState*, float *RESTRICT src, uint
|
|||
frac &= MixerFracMask;
|
||||
} while(--todo);
|
||||
}
|
||||
return dst.data();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,11 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef __linux__
|
||||
#include <sys/syscall.h>
|
||||
#elif defined(__FreeBSD__)
|
||||
#include <sys/thr.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace dbus {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,64 @@
|
|||
#include "phase_shifter.h"
|
||||
|
||||
|
||||
UhjQualityType UhjDecodeQuality{UhjQualityType::Default};
|
||||
UhjQualityType UhjEncodeQuality{UhjQualityType::Default};
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
const PhaseShifterT<UhjFilterBase::sFilterDelay*2> PShift{};
|
||||
const PhaseShifterT<UhjLength256> PShiftLq{};
|
||||
const PhaseShifterT<UhjLength512> PShiftHq{};
|
||||
|
||||
template<size_t N>
|
||||
struct GetPhaseShifter;
|
||||
template<>
|
||||
struct GetPhaseShifter<UhjLength256> { static auto& Get() noexcept { return PShiftLq; } };
|
||||
template<>
|
||||
struct GetPhaseShifter<UhjLength512> { static auto& Get() noexcept { return PShiftHq; } };
|
||||
|
||||
|
||||
constexpr float square(float x) noexcept
|
||||
{ return x*x; }
|
||||
|
||||
/* Filter coefficients for the 'base' all-pass IIR, which applies a frequency-
|
||||
* dependent phase-shift of N degrees. The output of the filter requires a 1-
|
||||
* sample delay.
|
||||
*/
|
||||
constexpr std::array<float,4> Filter1Coeff{{
|
||||
square(0.6923878f), square(0.9360654322959f), square(0.9882295226860f),
|
||||
square(0.9987488452737f)
|
||||
}};
|
||||
/* Filter coefficients for the offset all-pass IIR, which applies a frequency-
|
||||
* dependent phase-shift of N+90 degrees.
|
||||
*/
|
||||
constexpr std::array<float,4> Filter2Coeff{{
|
||||
square(0.4021921162426f), square(0.8561710882420f), square(0.9722909545651f),
|
||||
square(0.9952884791278f)
|
||||
}};
|
||||
|
||||
} // namespace
|
||||
|
||||
void UhjAllPassFilter::process(const al::span<const float,4> coeffs,
|
||||
const al::span<const float> src, const bool updateState, float *RESTRICT dst)
|
||||
{
|
||||
auto state = mState;
|
||||
|
||||
auto proc_sample = [&state,coeffs](float x) noexcept -> float
|
||||
{
|
||||
for(size_t i{0};i < 4;++i)
|
||||
{
|
||||
const float y{x*coeffs[i] + state[i].z[0]};
|
||||
state[i].z[0] = state[i].z[1];
|
||||
state[i].z[1] = y*coeffs[i] - x;
|
||||
x = y;
|
||||
}
|
||||
return x;
|
||||
};
|
||||
std::transform(src.begin(), src.end(), dst, proc_sample);
|
||||
if(updateState) LIKELY mState = state;
|
||||
}
|
||||
|
||||
|
||||
/* Encoding UHJ from B-Format is done as:
|
||||
*
|
||||
|
|
@ -36,55 +88,136 @@ const PhaseShifterT<UhjFilterBase::sFilterDelay*2> PShift{};
|
|||
* impulse with the desired shift.
|
||||
*/
|
||||
|
||||
void UhjEncoder::encode(float *LeftOut, float *RightOut, const FloatBufferLine *InSamples,
|
||||
const size_t SamplesToDo)
|
||||
template<size_t N>
|
||||
void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
|
||||
const al::span<const float*const,3> InSamples, const size_t SamplesToDo)
|
||||
{
|
||||
const auto &PShift = GetPhaseShifter<N>::Get();
|
||||
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])};
|
||||
const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])};
|
||||
const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])};
|
||||
|
||||
std::copy_n(winput, SamplesToDo, mW.begin()+sFilterDelay);
|
||||
std::copy_n(xinput, SamplesToDo, mX.begin()+sFilterDelay);
|
||||
std::copy_n(yinput, SamplesToDo, mY.begin()+sFilterDelay);
|
||||
|
||||
/* S = 0.9396926*W + 0.1855740*X */
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
mS[i] = 0.9396926f*mW[i] + 0.1855740f*mX[i];
|
||||
|
||||
/* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */
|
||||
std::transform(winput, winput+SamplesToDo, xinput, mWX.begin() + sWXInOffset,
|
||||
[](const float w, const float x) noexcept -> float
|
||||
{ return -0.3420201f*w + 0.5098604f*x; });
|
||||
PShift.process({mD.data(), SamplesToDo}, mWX.data());
|
||||
|
||||
/* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
mD[i] = mD[i] + 0.6554516f*mY[i];
|
||||
|
||||
/* Copy the future samples to the front for next time. */
|
||||
std::copy(mW.cbegin()+SamplesToDo, mW.cbegin()+SamplesToDo+sFilterDelay, mW.begin());
|
||||
std::copy(mX.cbegin()+SamplesToDo, mX.cbegin()+SamplesToDo+sFilterDelay, mX.begin());
|
||||
std::copy(mY.cbegin()+SamplesToDo, mY.cbegin()+SamplesToDo+sFilterDelay, mY.begin());
|
||||
std::copy(mWX.cbegin()+SamplesToDo, mWX.cbegin()+SamplesToDo+sWXInOffset, mWX.begin());
|
||||
|
||||
/* Apply a delay to the existing output to align with the input delay. */
|
||||
auto *delayBuffer = mDirectDelay.data();
|
||||
for(float *buffer : {LeftOut, RightOut})
|
||||
{
|
||||
float *distbuf{al::assume_aligned<16>(delayBuffer->data())};
|
||||
++delayBuffer;
|
||||
|
||||
float *inout{al::assume_aligned<16>(buffer)};
|
||||
auto inout_end = inout + SamplesToDo;
|
||||
if(SamplesToDo >= sFilterDelay) LIKELY
|
||||
{
|
||||
auto delay_end = std::rotate(inout, inout_end - sFilterDelay, inout_end);
|
||||
std::swap_ranges(inout, delay_end, distbuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto delay_start = std::swap_ranges(inout, inout_end, distbuf);
|
||||
std::rotate(distbuf, delay_start, distbuf + sFilterDelay);
|
||||
}
|
||||
}
|
||||
|
||||
/* Combine the direct signal with the produced output. */
|
||||
|
||||
/* Left = (S + D)/2.0 */
|
||||
float *RESTRICT left{al::assume_aligned<16>(LeftOut)};
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
left[i] += (mS[i] + mD[i]) * 0.5f;
|
||||
/* Right = (S - D)/2.0 */
|
||||
float *RESTRICT right{al::assume_aligned<16>(RightOut)};
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
right[i] += (mS[i] - mD[i]) * 0.5f;
|
||||
}
|
||||
|
||||
/* This encoding implementation uses two sets of four chained IIR filters to
|
||||
* produce the desired relative phase shift. The first filter chain produces a
|
||||
* phase shift of varying degrees over a wide range of frequencies, while the
|
||||
* second filter chain produces a phase shift 90 degrees ahead of the first
|
||||
* over the same range. Further details are described here:
|
||||
*
|
||||
* https://web.archive.org/web/20060708031958/http://www.biochem.oulu.fi/~oniemita/dsp/hilbert/
|
||||
*
|
||||
* 2-channel UHJ output requires the use of three filter chains. The S channel
|
||||
* output uses a Filter1 chain on the W and X channel mix, while the D channel
|
||||
* output uses a Filter1 chain on the Y channel plus a Filter2 chain on the W
|
||||
* and X channel mix. This results in the W and X input mix on the D channel
|
||||
* output having the required +90 degree phase shift relative to the other
|
||||
* inputs.
|
||||
*/
|
||||
void UhjEncoderIIR::encode(float *LeftOut, float *RightOut,
|
||||
const al::span<const float *const, 3> InSamples, const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
float *RESTRICT left{al::assume_aligned<16>(LeftOut)};
|
||||
float *RESTRICT right{al::assume_aligned<16>(RightOut)};
|
||||
|
||||
const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0].data())};
|
||||
const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1].data())};
|
||||
const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())};
|
||||
|
||||
/* Combine the previously delayed S/D signal with the input. Include any
|
||||
* existing direct signal with it.
|
||||
*/
|
||||
const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])};
|
||||
const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])};
|
||||
const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])};
|
||||
|
||||
/* S = 0.9396926*W + 0.1855740*X */
|
||||
auto miditer = mS.begin() + sFilterDelay;
|
||||
std::transform(winput, winput+SamplesToDo, xinput, miditer,
|
||||
[](const float w, const float x) noexcept -> float
|
||||
{ return 0.9396926f*w + 0.1855740f*x; });
|
||||
for(size_t i{0};i < SamplesToDo;++i,++miditer)
|
||||
*miditer += left[i] + right[i];
|
||||
std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(),
|
||||
[](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; });
|
||||
mFilter1WX.process(Filter1Coeff, {mTemp.data(), SamplesToDo}, true, mS.data()+1);
|
||||
mS[0] = mDelayWX; mDelayWX = mS[SamplesToDo];
|
||||
|
||||
/* D = 0.6554516*Y */
|
||||
auto sideiter = mD.begin() + sFilterDelay;
|
||||
std::transform(yinput, yinput+SamplesToDo, sideiter,
|
||||
[](const float y) noexcept -> float { return 0.6554516f*y; });
|
||||
for(size_t i{0};i < SamplesToDo;++i,++sideiter)
|
||||
*sideiter += left[i] - right[i];
|
||||
/* Precompute j(-0.3420201*W + 0.5098604*X) and store in mWX. */
|
||||
std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(),
|
||||
[](const float w, const float x) noexcept { return -0.3420201f*w + 0.5098604f*x; });
|
||||
mFilter2WX.process(Filter2Coeff, {mTemp.data(), SamplesToDo}, true, mWX.data());
|
||||
|
||||
/* D += j(-0.3420201*W + 0.5098604*X) */
|
||||
auto tmpiter = std::copy(mWXHistory.cbegin(), mWXHistory.cend(), mTemp.begin());
|
||||
std::transform(winput, winput+SamplesToDo, xinput, tmpiter,
|
||||
[](const float w, const float x) noexcept -> float
|
||||
{ return -0.3420201f*w + 0.5098604f*x; });
|
||||
std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory.size(), mWXHistory.begin());
|
||||
PShift.processAccum({mD.data(), SamplesToDo}, mTemp.data());
|
||||
/* Apply filter1 to Y and store in mD. */
|
||||
mFilter1Y.process(Filter1Coeff, {yinput, SamplesToDo}, SamplesToDo, mD.data()+1);
|
||||
mD[0] = mDelayY; mDelayY = mD[SamplesToDo];
|
||||
|
||||
/* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
mD[i] = mWX[i] + 0.6554516f*mD[i];
|
||||
|
||||
/* Apply the base filter to the existing output to align with the processed
|
||||
* signal.
|
||||
*/
|
||||
mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true, mTemp.data()+1);
|
||||
mTemp[0] = mDirectDelay[0]; mDirectDelay[0] = mTemp[SamplesToDo];
|
||||
|
||||
/* Left = (S + D)/2.0 */
|
||||
float *RESTRICT left{al::assume_aligned<16>(LeftOut)};
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
left[i] = (mS[i] + mD[i]) * 0.5f;
|
||||
/* Right = (S - D)/2.0 */
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
right[i] = (mS[i] - mD[i]) * 0.5f;
|
||||
left[i] = (mS[i] + mD[i])*0.5f + mTemp[i];
|
||||
|
||||
/* Copy the future samples to the front for next time. */
|
||||
std::copy(mS.cbegin()+SamplesToDo, mS.cbegin()+SamplesToDo+sFilterDelay, mS.begin());
|
||||
std::copy(mD.cbegin()+SamplesToDo, mD.cbegin()+SamplesToDo+sFilterDelay, mD.begin());
|
||||
mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, true, mTemp.data()+1);
|
||||
mTemp[0] = mDirectDelay[1]; mDirectDelay[1] = mTemp[SamplesToDo];
|
||||
|
||||
/* Right = (S - D)/2.0 */
|
||||
float *RESTRICT right{al::assume_aligned<16>(RightOut)};
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
right[i] = (mS[i] - mD[i])*0.5f + mTemp[i];
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -101,9 +234,14 @@ void UhjEncoder::encode(float *LeftOut, float *RightOut, const FloatBufferLine *
|
|||
* where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2-
|
||||
* channel excludes Q and T.
|
||||
*/
|
||||
void UhjDecoder::decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const size_t forwardSamples)
|
||||
template<size_t N>
|
||||
void UhjDecoder<N>::decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState)
|
||||
{
|
||||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
const auto &PShift = GetPhaseShifter<N>::Get();
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
|
||||
{
|
||||
|
|
@ -112,15 +250,15 @@ void UhjDecoder::decode(const al::span<float*> samples, const size_t samplesToDo
|
|||
const float *RESTRICT t{al::assume_aligned<16>(samples[2])};
|
||||
|
||||
/* S = Left + Right */
|
||||
for(size_t i{0};i < samplesToDo+sFilterDelay;++i)
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
|
||||
/* D = Left - Right */
|
||||
for(size_t i{0};i < samplesToDo+sFilterDelay;++i)
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++i)
|
||||
mD[i] = left[i] - right[i];
|
||||
|
||||
/* T */
|
||||
for(size_t i{0};i < samplesToDo+sFilterDelay;++i)
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++i)
|
||||
mT[i] = t[i];
|
||||
}
|
||||
|
||||
|
|
@ -130,9 +268,10 @@ void UhjDecoder::decode(const al::span<float*> samples, const size_t samplesToDo
|
|||
|
||||
/* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */
|
||||
auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin());
|
||||
std::transform(mD.cbegin(), mD.cbegin()+samplesToDo+sFilterDelay, mT.cbegin(), tmpiter,
|
||||
std::transform(mD.cbegin(), mD.cbegin()+samplesToDo+sInputPadding, mT.cbegin(), tmpiter,
|
||||
[](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; });
|
||||
std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.begin());
|
||||
if(updateState) LIKELY
|
||||
std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin());
|
||||
PShift.process({xoutput, samplesToDo}, mTemp.data());
|
||||
|
||||
/* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */
|
||||
|
|
@ -144,8 +283,9 @@ void UhjDecoder::decode(const al::span<float*> samples, const size_t samplesToDo
|
|||
|
||||
/* Precompute j*S and store in youtput. */
|
||||
tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin());
|
||||
std::copy_n(mS.cbegin(), samplesToDo+sFilterDelay, tmpiter);
|
||||
std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.begin());
|
||||
std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter);
|
||||
if(updateState) LIKELY
|
||||
std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin());
|
||||
PShift.process({youtput, samplesToDo}, mTemp.data());
|
||||
|
||||
/* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */
|
||||
|
|
@ -161,6 +301,78 @@ void UhjDecoder::decode(const al::span<float*> samples, const size_t samplesToDo
|
|||
}
|
||||
}
|
||||
|
||||
void UhjDecoderIIR::decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState)
|
||||
{
|
||||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
|
||||
{
|
||||
const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
|
||||
const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
|
||||
|
||||
/* S = Left + Right */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
|
||||
/* D = Left - Right */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
mD[i] = left[i] - right[i];
|
||||
}
|
||||
|
||||
float *RESTRICT woutput{al::assume_aligned<16>(samples[0])};
|
||||
float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])};
|
||||
float *RESTRICT youtput{al::assume_aligned<16>(samples[2])};
|
||||
|
||||
/* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */
|
||||
std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, mTemp.begin(),
|
||||
[](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; });
|
||||
mFilter2DT.process(Filter2Coeff, {mTemp.data(), samplesToDo}, updateState, xoutput);
|
||||
|
||||
/* Apply filter1 to S and store in mTemp. */
|
||||
mTemp[0] = mDelayS;
|
||||
mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1);
|
||||
if(updateState) LIKELY mDelayS = mTemp[samplesToDo];
|
||||
|
||||
/* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
woutput[i] = 0.981532f*mTemp[i] + 0.197484f*xoutput[i];
|
||||
/* X = 0.418496*S - j(0.828331*D + 0.767820*T) */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
xoutput[i] = 0.418496f*mTemp[i] - xoutput[i];
|
||||
|
||||
|
||||
/* Apply filter1 to (0.795968*D - 0.676392*T) and store in mTemp. */
|
||||
std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, youtput,
|
||||
[](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; });
|
||||
mTemp[0] = mDelayDT;
|
||||
mFilter1DT.process(Filter1Coeff, {youtput, samplesToDo}, updateState, mTemp.data()+1);
|
||||
if(updateState) LIKELY mDelayDT = mTemp[samplesToDo];
|
||||
|
||||
/* Precompute j*S and store in youtput. */
|
||||
mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput);
|
||||
|
||||
/* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
youtput[i] = mTemp[i] + 0.186633f*youtput[i];
|
||||
|
||||
|
||||
if(samples.size() > 3)
|
||||
{
|
||||
float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])};
|
||||
|
||||
/* Apply filter1 to Q and store in mTemp. */
|
||||
mTemp[0] = mDelayQ;
|
||||
mFilter1Q.process(Filter1Coeff, {zoutput, samplesToDo}, updateState, mTemp.data()+1);
|
||||
if(updateState) LIKELY mDelayQ = mTemp[samplesToDo];
|
||||
|
||||
/* Z = 1.023332*Q */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
zoutput[i] = 1.023332f*mTemp[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Super Stereo processing is done as:
|
||||
*
|
||||
|
|
@ -174,39 +386,44 @@ void UhjDecoder::decode(const al::span<float*> samples, const size_t samplesToDo
|
|||
* where j is a +90 degree phase shift. w is a variable control for the
|
||||
* resulting stereo width, with the range 0 <= w <= 0.7.
|
||||
*/
|
||||
void UhjDecoder::decodeStereo(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const size_t forwardSamples)
|
||||
template<size_t N>
|
||||
void UhjStereoDecoder<N>::decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState)
|
||||
{
|
||||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
const auto &PShift = GetPhaseShifter<N>::Get();
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
|
||||
{
|
||||
const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
|
||||
const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
|
||||
|
||||
for(size_t i{0};i < samplesToDo+sFilterDelay;++i)
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
|
||||
/* Pre-apply the width factor to the difference signal D. Smoothly
|
||||
* interpolate when it changes.
|
||||
*/
|
||||
const float wtarget{mWidthControl};
|
||||
const float wcurrent{unlikely(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth};
|
||||
if(likely(wtarget == wcurrent) || unlikely(forwardSamples == 0))
|
||||
const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth};
|
||||
if(wtarget == wcurrent || !updateState)
|
||||
{
|
||||
for(size_t i{0};i < samplesToDo+sFilterDelay;++i)
|
||||
for(size_t i{0};i < samplesToDo+sInputPadding;++i)
|
||||
mD[i] = (left[i] - right[i]) * wcurrent;
|
||||
mCurrentWidth = wcurrent;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float wstep{(wtarget - wcurrent) / static_cast<float>(forwardSamples)};
|
||||
const float wstep{(wtarget - wcurrent) / static_cast<float>(samplesToDo)};
|
||||
float fi{0.0f};
|
||||
size_t i{0};
|
||||
for(;i < forwardSamples;++i)
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
{
|
||||
mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi);
|
||||
fi += 1.0f;
|
||||
}
|
||||
for(;i < samplesToDo+sFilterDelay;++i)
|
||||
for(size_t i{samplesToDo};i < samplesToDo+sInputPadding;++i)
|
||||
mD[i] = (left[i] - right[i]) * wtarget;
|
||||
mCurrentWidth = wtarget;
|
||||
}
|
||||
|
|
@ -218,8 +435,9 @@ void UhjDecoder::decodeStereo(const al::span<float*> samples, const size_t sampl
|
|||
|
||||
/* Precompute j*D and store in xoutput. */
|
||||
auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin());
|
||||
std::copy_n(mD.cbegin(), samplesToDo+sFilterDelay, tmpiter);
|
||||
std::copy_n(mTemp.cbegin()+forwardSamples, mDTHistory.size(), mDTHistory.begin());
|
||||
std::copy_n(mD.cbegin(), samplesToDo+sInputPadding, tmpiter);
|
||||
if(updateState) LIKELY
|
||||
std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin());
|
||||
PShift.process({xoutput, samplesToDo}, mTemp.data());
|
||||
|
||||
/* W = 0.6098637*S - 0.6896511*j*w*D */
|
||||
|
|
@ -231,11 +449,91 @@ void UhjDecoder::decodeStereo(const al::span<float*> samples, const size_t sampl
|
|||
|
||||
/* Precompute j*S and store in youtput. */
|
||||
tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin());
|
||||
std::copy_n(mS.cbegin(), samplesToDo+sFilterDelay, tmpiter);
|
||||
std::copy_n(mTemp.cbegin()+forwardSamples, mSHistory.size(), mSHistory.begin());
|
||||
std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter);
|
||||
if(updateState) LIKELY
|
||||
std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin());
|
||||
PShift.process({youtput, samplesToDo}, mTemp.data());
|
||||
|
||||
/* Y = 1.6822415*w*D - 0.2156194*j*S */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
youtput[i] = 1.6822415f*mD[i] - 0.2156194f*youtput[i];
|
||||
}
|
||||
|
||||
void UhjStereoDecoderIIR::decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState)
|
||||
{
|
||||
static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large");
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
|
||||
{
|
||||
const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
|
||||
const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
|
||||
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
mS[i] = left[i] + right[i];
|
||||
|
||||
/* Pre-apply the width factor to the difference signal D. Smoothly
|
||||
* interpolate when it changes.
|
||||
*/
|
||||
const float wtarget{mWidthControl};
|
||||
const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth};
|
||||
if(wtarget == wcurrent || !updateState)
|
||||
{
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
mD[i] = (left[i] - right[i]) * wcurrent;
|
||||
mCurrentWidth = wcurrent;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float wstep{(wtarget - wcurrent) / static_cast<float>(samplesToDo)};
|
||||
float fi{0.0f};
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
{
|
||||
mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi);
|
||||
fi += 1.0f;
|
||||
}
|
||||
mCurrentWidth = wtarget;
|
||||
}
|
||||
}
|
||||
|
||||
float *RESTRICT woutput{al::assume_aligned<16>(samples[0])};
|
||||
float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])};
|
||||
float *RESTRICT youtput{al::assume_aligned<16>(samples[2])};
|
||||
|
||||
/* Apply filter1 to S and store in mTemp. */
|
||||
mTemp[0] = mDelayS;
|
||||
mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1);
|
||||
if(updateState) LIKELY mDelayS = mTemp[samplesToDo];
|
||||
|
||||
/* Precompute j*D and store in xoutput. */
|
||||
mFilter2D.process(Filter2Coeff, {mD.data(), samplesToDo}, updateState, xoutput);
|
||||
|
||||
/* W = 0.6098637*S - 0.6896511*j*w*D */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
woutput[i] = 0.6098637f*mTemp[i] - 0.6896511f*xoutput[i];
|
||||
/* X = 0.8624776*S + 0.7626955*j*w*D */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
xoutput[i] = 0.8624776f*mTemp[i] + 0.7626955f*xoutput[i];
|
||||
|
||||
/* Precompute j*S and store in youtput. */
|
||||
mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput);
|
||||
|
||||
/* Apply filter1 to D and store in mTemp. */
|
||||
mTemp[0] = mDelayD;
|
||||
mFilter1D.process(Filter1Coeff, {mD.data(), samplesToDo}, updateState, mTemp.data()+1);
|
||||
if(updateState) LIKELY mDelayD = mTemp[samplesToDo];
|
||||
|
||||
/* Y = 1.6822415*w*D - 0.2156194*j*S */
|
||||
for(size_t i{0};i < samplesToDo;++i)
|
||||
youtput[i] = 1.6822415f*mTemp[i] - 0.2156194f*youtput[i];
|
||||
}
|
||||
|
||||
|
||||
template struct UhjEncoder<UhjLength256>;
|
||||
template struct UhjDecoder<UhjLength256>;
|
||||
template struct UhjStereoDecoder<UhjLength256>;
|
||||
|
||||
template struct UhjEncoder<UhjLength512>;
|
||||
template struct UhjDecoder<UhjLength512>;
|
||||
template struct UhjStereoDecoder<UhjLength512>;
|
||||
|
|
|
|||
|
|
@ -4,56 +4,146 @@
|
|||
#include <array>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "bufferline.h"
|
||||
#include "resampler_limits.h"
|
||||
|
||||
|
||||
struct UhjFilterBase {
|
||||
/* The filter delay is half it's effective size, so a delay of 128 has a
|
||||
* FIR length of 256.
|
||||
*/
|
||||
static constexpr size_t sFilterDelay{128};
|
||||
static constexpr size_t UhjLength256{256};
|
||||
static constexpr size_t UhjLength512{512};
|
||||
|
||||
enum class UhjQualityType : uint8_t {
|
||||
IIR = 0,
|
||||
FIR256,
|
||||
FIR512,
|
||||
Default = IIR
|
||||
};
|
||||
|
||||
struct UhjEncoder : public UhjFilterBase {
|
||||
/* Delays and processing storage for the unfiltered signal. */
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mD{};
|
||||
extern UhjQualityType UhjDecodeQuality;
|
||||
extern UhjQualityType UhjEncodeQuality;
|
||||
|
||||
/* History for the FIR filter. */
|
||||
alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mTemp{};
|
||||
struct UhjAllPassFilter {
|
||||
struct AllPassState {
|
||||
/* Last two delayed components for direct form II. */
|
||||
float z[2];
|
||||
};
|
||||
std::array<AllPassState,4> mState;
|
||||
|
||||
void process(const al::span<const float,4> coeffs, const al::span<const float> src,
|
||||
const bool update, float *RESTRICT dst);
|
||||
};
|
||||
|
||||
|
||||
struct UhjEncoderBase {
|
||||
virtual ~UhjEncoderBase() = default;
|
||||
|
||||
virtual size_t getDelay() noexcept = 0;
|
||||
|
||||
/**
|
||||
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
|
||||
* signal. The input must use FuMa channel ordering and UHJ scaling (FuMa
|
||||
* with an additional +3dB boost).
|
||||
*/
|
||||
void encode(float *LeftOut, float *RightOut, const FloatBufferLine *InSamples,
|
||||
const size_t SamplesToDo);
|
||||
virtual void encode(float *LeftOut, float *RightOut,
|
||||
const al::span<const float*const,3> InSamples, const size_t SamplesToDo) = 0;
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct UhjEncoder final : public UhjEncoderBase {
|
||||
static constexpr size_t sFilterDelay{N/2};
|
||||
|
||||
/* Delays and processing storage for the input signal. */
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mW{};
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mX{};
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mY{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize> mD{};
|
||||
|
||||
/* History and temp storage for the FIR filter. New samples should be
|
||||
* written to index sFilterDelay*2 - 1.
|
||||
*/
|
||||
static constexpr size_t sWXInOffset{sFilterDelay*2 - 1};
|
||||
alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mWX{};
|
||||
|
||||
alignas(16) std::array<std::array<float,sFilterDelay>,2> mDirectDelay{};
|
||||
|
||||
size_t getDelay() noexcept override { return sFilterDelay; }
|
||||
|
||||
/**
|
||||
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
|
||||
* signal. The input must use FuMa channel ordering and UHJ scaling (FuMa
|
||||
* with an additional +3dB boost).
|
||||
*/
|
||||
void encode(float *LeftOut, float *RightOut, const al::span<const float*const,3> InSamples,
|
||||
const size_t SamplesToDo) override;
|
||||
|
||||
DEF_NEWDEL(UhjEncoder)
|
||||
};
|
||||
|
||||
struct UhjEncoderIIR final : public UhjEncoderBase {
|
||||
static constexpr size_t sFilterDelay{1};
|
||||
|
||||
struct UhjDecoder : public UhjFilterBase {
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge+sFilterDelay> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge+sFilterDelay> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge+sFilterDelay> mT{};
|
||||
/* Processing storage for the input signal. */
|
||||
alignas(16) std::array<float,BufferLineSize+1> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+1> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mWX{};
|
||||
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mTemp{};
|
||||
float mDelayWX{}, mDelayY{};
|
||||
|
||||
alignas(16) std::array<float,sFilterDelay-1> mDTHistory{};
|
||||
alignas(16) std::array<float,sFilterDelay-1> mSHistory{};
|
||||
UhjAllPassFilter mFilter1WX;
|
||||
UhjAllPassFilter mFilter2WX;
|
||||
UhjAllPassFilter mFilter1Y;
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge + sFilterDelay*2> mTemp{};
|
||||
std::array<UhjAllPassFilter,2> mFilter1Direct;
|
||||
std::array<float,2> mDirectDelay{};
|
||||
|
||||
float mCurrentWidth{-1.0f};
|
||||
size_t getDelay() noexcept override { return sFilterDelay; }
|
||||
|
||||
/**
|
||||
* Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input
|
||||
* signal. The input must use FuMa channel ordering and UHJ scaling (FuMa
|
||||
* with an additional +3dB boost).
|
||||
*/
|
||||
void encode(float *LeftOut, float *RightOut, const al::span<const float*const,3> InSamples,
|
||||
const size_t SamplesToDo) override;
|
||||
|
||||
DEF_NEWDEL(UhjEncoderIIR)
|
||||
};
|
||||
|
||||
|
||||
struct DecoderBase {
|
||||
static constexpr size_t sMaxPadding{256};
|
||||
|
||||
/* For 2-channel UHJ, shelf filters should use these LF responses. */
|
||||
static constexpr float sWLFScale{0.661f};
|
||||
static constexpr float sXYLFScale{1.293f};
|
||||
|
||||
virtual ~DecoderBase() = default;
|
||||
|
||||
virtual void decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState) = 0;
|
||||
|
||||
/**
|
||||
* The width factor for Super Stereo processing. Can be changed in between
|
||||
* calls to decodeStereo, with valid values being between 0...0.7.
|
||||
* calls to decode, with valid values being between 0...0.7.
|
||||
*/
|
||||
float mWidthControl{0.593f};
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct UhjDecoder final : public DecoderBase {
|
||||
/* The number of extra sample frames needed for input. */
|
||||
static constexpr size_t sInputPadding{N/2};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mT{};
|
||||
|
||||
alignas(16) std::array<float,sInputPadding-1> mDTHistory{};
|
||||
alignas(16) std::array<float,sInputPadding-1> mSHistory{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize + sInputPadding*2> mTemp{};
|
||||
|
||||
/**
|
||||
* Decodes a 3- or 4-channel UHJ signal into a B-Format signal with FuMa
|
||||
|
|
@ -64,7 +154,49 @@ struct UhjDecoder : public UhjFilterBase {
|
|||
* B-Format decoder, as it needs different shelf filters.
|
||||
*/
|
||||
void decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const size_t forwardSamples);
|
||||
const bool updateState) override;
|
||||
|
||||
DEF_NEWDEL(UhjDecoder)
|
||||
};
|
||||
|
||||
struct UhjDecoderIIR final : public DecoderBase {
|
||||
/* FIXME: These IIR decoder filters actually have a 1-sample delay on the
|
||||
* non-filtered components, which is not reflected in the source latency
|
||||
* value. sInputPadding is 0, however, because it doesn't need any extra
|
||||
* input samples.
|
||||
*/
|
||||
static constexpr size_t sInputPadding{0};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+1> mTemp{};
|
||||
float mDelayS{}, mDelayDT{}, mDelayQ{};
|
||||
|
||||
UhjAllPassFilter mFilter1S;
|
||||
UhjAllPassFilter mFilter2DT;
|
||||
UhjAllPassFilter mFilter1DT;
|
||||
UhjAllPassFilter mFilter2S;
|
||||
UhjAllPassFilter mFilter1Q;
|
||||
|
||||
void decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState) override;
|
||||
|
||||
DEF_NEWDEL(UhjDecoderIIR)
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
struct UhjStereoDecoder final : public DecoderBase {
|
||||
static constexpr size_t sInputPadding{N/2};
|
||||
|
||||
float mCurrentWidth{-1.0f};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
|
||||
|
||||
alignas(16) std::array<float,sInputPadding-1> mDTHistory{};
|
||||
alignas(16) std::array<float,sInputPadding-1> mSHistory{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize + sInputPadding*2> mTemp{};
|
||||
|
||||
/**
|
||||
* Applies Super Stereo processing on a stereo signal to create a B-Format
|
||||
|
|
@ -72,13 +204,31 @@ struct UhjDecoder : public UhjFilterBase {
|
|||
* should contain 3 channels, the first two being the left and right stereo
|
||||
* channels, and the third left empty.
|
||||
*/
|
||||
void decodeStereo(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const size_t forwardSamples);
|
||||
void decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState) override;
|
||||
|
||||
using DecoderFunc = void (UhjDecoder::*)(const al::span<float*> samples,
|
||||
const size_t samplesToDo, const size_t forwardSamples);
|
||||
DEF_NEWDEL(UhjStereoDecoder)
|
||||
};
|
||||
|
||||
DEF_NEWDEL(UhjDecoder)
|
||||
struct UhjStereoDecoderIIR final : public DecoderBase {
|
||||
static constexpr size_t sInputPadding{0};
|
||||
|
||||
float mCurrentWidth{-1.0f};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+1> mTemp{};
|
||||
float mDelayS{}, mDelayD{};
|
||||
|
||||
UhjAllPassFilter mFilter1S;
|
||||
UhjAllPassFilter mFilter2D;
|
||||
UhjAllPassFilter mFilter1D;
|
||||
UhjAllPassFilter mFilter2S;
|
||||
|
||||
void decode(const al::span<float*> samples, const size_t samplesToDo,
|
||||
const bool updateState) override;
|
||||
|
||||
DEF_NEWDEL(UhjStereoDecoderIIR)
|
||||
};
|
||||
|
||||
#endif /* CORE_UHJFILTER_H */
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,7 @@
|
|||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
|
|
@ -48,10 +49,7 @@ enum class DirectMode : unsigned char {
|
|||
};
|
||||
|
||||
|
||||
/* Maximum number of extra source samples that may need to be loaded, for
|
||||
* resampling or conversion purposes.
|
||||
*/
|
||||
constexpr uint MaxPostVoiceLoad{MaxResamplerEdge + UhjDecoder::sFilterDelay};
|
||||
constexpr uint MaxPitch{10};
|
||||
|
||||
|
||||
enum {
|
||||
|
|
@ -85,8 +83,8 @@ struct SendParams {
|
|||
BiquadFilter HighPass;
|
||||
|
||||
struct {
|
||||
std::array<float,MAX_OUTPUT_CHANNELS> Current;
|
||||
std::array<float,MAX_OUTPUT_CHANNELS> Target;
|
||||
std::array<float,MaxAmbiChannels> Current;
|
||||
std::array<float,MaxAmbiChannels> Target;
|
||||
} Gains;
|
||||
};
|
||||
|
||||
|
|
@ -97,6 +95,7 @@ struct VoiceBufferItem {
|
|||
CallbackType mCallback{nullptr};
|
||||
void *mUserData{nullptr};
|
||||
|
||||
uint mBlockAlign{0u};
|
||||
uint mSampleLen{0u};
|
||||
uint mLoopStart{0u};
|
||||
uint mLoopEnd{0u};
|
||||
|
|
@ -197,7 +196,7 @@ struct Voice {
|
|||
* Source offset in samples, relative to the currently playing buffer, NOT
|
||||
* the whole queue.
|
||||
*/
|
||||
std::atomic<uint> mPosition;
|
||||
std::atomic<int> mPosition;
|
||||
/** Fractional (fixed-point) offset to the next sample. */
|
||||
std::atomic<uint> mPositionFrac;
|
||||
|
||||
|
|
@ -209,18 +208,21 @@ struct Voice {
|
|||
*/
|
||||
std::atomic<VoiceBufferItem*> mLoopBuffer;
|
||||
|
||||
std::chrono::nanoseconds mStartTime{};
|
||||
|
||||
/* Properties for the attached buffer(s). */
|
||||
FmtChannels mFmtChannels;
|
||||
FmtType mFmtType;
|
||||
uint mFrequency;
|
||||
uint mFrameStep; /**< In steps of the sample type size. */
|
||||
uint mFrameSize; /**< In bytes. */
|
||||
uint mBytesPerBlock; /**< Or for PCM formats, BytesPerFrame. */
|
||||
uint mSamplesPerBlock; /**< Always 1 for PCM formats. */
|
||||
AmbiLayout mAmbiLayout;
|
||||
AmbiScaling mAmbiScaling;
|
||||
uint mAmbiOrder;
|
||||
|
||||
std::unique_ptr<UhjDecoder> mDecoder;
|
||||
UhjDecoder::DecoderFunc mDecoderFunc{};
|
||||
std::unique_ptr<DecoderBase> mDecoder;
|
||||
uint mDecoderPadding{};
|
||||
|
||||
/** Current target parameters used for mixing. */
|
||||
uint mStep{0};
|
||||
|
|
@ -230,7 +232,8 @@ struct Voice {
|
|||
InterpState mResampleState;
|
||||
|
||||
std::bitset<VoiceFlagCount> mFlags{};
|
||||
uint mNumCallbackSamples{0};
|
||||
uint mNumCallbackBlocks{0};
|
||||
uint mCallbackBlockBase{0};
|
||||
|
||||
struct TargetData {
|
||||
int FilterType;
|
||||
|
|
@ -262,7 +265,8 @@ struct Voice {
|
|||
Voice(const Voice&) = delete;
|
||||
Voice& operator=(const Voice&) = delete;
|
||||
|
||||
void mix(const State vstate, ContextBase *Context, const uint SamplesToDo);
|
||||
void mix(const State vstate, ContextBase *Context, const std::chrono::nanoseconds deviceTime,
|
||||
const uint SamplesToDo);
|
||||
|
||||
void prepare(DeviceBase *device);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue