update OpenAL-Soft to 1.24.3.

This commit is contained in:
Sasha Szpakowski
2025-05-03 12:51:37 -03:00
parent 375c6f88cd
commit 5e4f3241ac
322 changed files with 54386 additions and 12885 deletions
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -2,12 +2,15 @@
#define LOADDEF_H
#include <istream>
#include <string_view>
#include "alspan.h"
#include "makemhr.h"
bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount,
const char *filename, const uint fftSize, const uint truncSize, const uint outRate,
bool LoadDefInput(std::istream &istream, const al::span<const char> startbytes,
const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate,
const ChannelModeT chanMode, HrirDataT *hData);
#endif /* LOADDEF_H */
+94 -85
View File
@@ -32,7 +32,6 @@
#include <future>
#include <iterator>
#include <memory>
#include <numeric>
#include <optional>
#include <string>
#include <string_view>
@@ -41,6 +40,7 @@
#include "alspan.h"
#include "alnumeric.h"
#include "fmt/core.h"
#include "makemhr.h"
#include "polyphase_resampler.h"
#include "sofa-support.h"
@@ -59,14 +59,14 @@ using uint = unsigned int;
* possible. Those sets that contain purely random measurements or use
* different major axes will fail.
*/
bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData)
auto PrepareLayout(const al::span<const float> xyzs, HrirDataT *hData) -> bool
{
fprintf(stdout, "Detecting compatible layout...\n");
fmt::println("Detecting compatible layout...");
auto fds = GetCompatibleLayout(m, xyzs);
auto fds = GetCompatibleLayout(xyzs);
if(fds.size() > MAX_FD_COUNT)
{
fprintf(stdout, "Incompatible layout (inumerable radii).\n");
fmt::println("Incompatible layout (inumerable radii).");
return false;
}
@@ -91,7 +91,7 @@ bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData)
++fi;
}
fprintf(stdout, "Using %u of %u IRs.\n", ir_total, m);
fmt::println("Using {} of {} IRs.", ir_total, xyzs.size()/3);
const auto azs = al::span{azCounts}.first<MAX_FD_COUNT>();
return PrepareHrirData(al::span{distances}.first(fi), evCounts, azs, hData);
}
@@ -108,7 +108,7 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf)
{
if(srate_dim)
{
fprintf(stderr, "Duplicate SampleRate.DIMENSION_LIST\n");
fmt::println(stderr, "Duplicate SampleRate.DIMENSION_LIST");
return 0.0f;
}
srate_dim = srate_attrs->value;
@@ -117,53 +117,53 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf)
{
if(srate_units)
{
fprintf(stderr, "Duplicate SampleRate.Units\n");
fmt::println(stderr, "Duplicate SampleRate.Units");
return 0.0f;
}
srate_units = srate_attrs->value;
}
else
fprintf(stderr, "Unexpected sample rate attribute: %s = %s\n", srate_attrs->name,
fmt::println(stderr, "Unexpected sample rate attribute: {} = {}", srate_attrs->name,
srate_attrs->value);
srate_attrs = srate_attrs->next;
}
if(!srate_dim)
{
fprintf(stderr, "Missing sample rate dimensions\n");
fmt::println(stderr, "Missing sample rate dimensions");
return 0.0f;
}
if(srate_dim != "I"sv)
{
fprintf(stderr, "Unsupported sample rate dimensions: %s\n", srate_dim);
fmt::println(stderr, "Unsupported sample rate dimensions: {}", srate_dim);
return 0.0f;
}
if(!srate_units)
{
fprintf(stderr, "Missing sample rate unit type\n");
fmt::println(stderr, "Missing sample rate unit type");
return 0.0f;
}
if(srate_units != "hertz"sv)
{
fprintf(stderr, "Unsupported sample rate unit type: %s\n", srate_units);
fmt::println(stderr, "Unsupported sample rate unit type: {}", srate_units);
return 0.0f;
}
/* I dimensions guarantees 1 element, so just extract it. */
if(srate_array->values[0] < float{MIN_RATE} || srate_array->values[0] > float{MAX_RATE})
const auto values = al::span{srate_array->values, sofaHrtf->I};
if(values[0] < float{MIN_RATE} || values[0] > float{MAX_RATE})
{
fprintf(stderr, "Sample rate out of range: %f (expected %u to %u)", srate_array->values[0],
fmt::println(stderr, "Sample rate out of range: {:f} (expected {} to {})", values[0],
MIN_RATE, MAX_RATE);
return 0.0f;
}
return srate_array->values[0];
return values[0];
}
enum class DelayType : uint8_t {
None,
I_R, /* [1][Channels] */
M_R, /* [HRIRs][Channels] */
Invalid,
};
DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf)
auto PrepareDelay(MYSOFA_HRTF *sofaHrtf) -> std::optional<DelayType>
{
const char *delay_dim{nullptr};
MYSOFA_ARRAY *delay_array{&sofaHrtf->DataDelay};
@@ -174,19 +174,19 @@ DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf)
{
if(delay_dim)
{
fprintf(stderr, "Duplicate Delay.DIMENSION_LIST\n");
return DelayType::Invalid;
fmt::println(stderr, "Duplicate Delay.DIMENSION_LIST");
return std::nullopt;
}
delay_dim = delay_attrs->value;
}
else
fprintf(stderr, "Unexpected delay attribute: %s = %s\n", delay_attrs->name,
fmt::println(stderr, "Unexpected delay attribute: {} = {}", delay_attrs->name,
delay_attrs->value ? delay_attrs->value : "<null>");
delay_attrs = delay_attrs->next;
}
if(!delay_dim)
{
fprintf(stderr, "Missing delay dimensions\n");
fmt::println(stderr, "Missing delay dimensions");
return DelayType::None;
}
if(delay_dim == "I,R"sv)
@@ -194,8 +194,8 @@ DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf)
if(delay_dim == "M,R"sv)
return DelayType::M_R;
fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim);
return DelayType::Invalid;
fmt::println(stderr, "Unsupported delay dimensions: {}", delay_dim);
return std::nullopt;
}
bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
@@ -209,24 +209,24 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
{
if(ir_dim)
{
fprintf(stderr, "Duplicate IR.DIMENSION_LIST\n");
fmt::println(stderr, "Duplicate IR.DIMENSION_LIST");
return false;
}
ir_dim = ir_attrs->value;
}
else
fprintf(stderr, "Unexpected IR attribute: %s = %s\n", ir_attrs->name,
fmt::println(stderr, "Unexpected IR attribute: {} = {}", ir_attrs->name,
ir_attrs->value ? ir_attrs->value : "<null>");
ir_attrs = ir_attrs->next;
}
if(!ir_dim)
{
fprintf(stderr, "Missing IR dimensions\n");
fmt::println(stderr, "Missing IR dimensions");
return false;
}
if(ir_dim != "M,R,N"sv)
{
fprintf(stderr, "Unsupported IR dimensions: %s\n", ir_dim);
fmt::println(stderr, "Unsupported IR dimensions: {}", ir_dim);
return false;
}
return true;
@@ -235,12 +235,12 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
/* Calculate the onset time of a HRIR. */
constexpr int OnsetRateMultiple{10};
double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n,
al::span<double> upsampled, const double *hrir)
auto CalcHrirOnset(PPhaseResampler &rs, const uint rate, al::span<double> upsampled,
const al::span<const double> hrir) -> double
{
rs.process({hrir, n}, upsampled);
rs.process(hrir, upsampled);
auto abs_lt = [](const double &lhs, const double &rhs) -> bool
auto abs_lt = [](const double lhs, const double rhs) -> bool
{ return std::abs(lhs) < std::abs(rhs); };
auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt);
return static_cast<double>(std::distance(upsampled.cbegin(), iter)) /
@@ -248,13 +248,13 @@ double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n,
}
/* Calculate the magnitude response of a HRIR. */
void CalcHrirMagnitude(const uint points, const uint n, al::span<complex_d> h, double *hrir)
void CalcHrirMagnitude(const uint points, al::span<complex_d> h, const al::span<double> hrir)
{
auto iter = std::copy_n(hrir, points, h.begin());
auto iter = std::copy_n(hrir.cbegin(), points, h.begin());
std::fill(iter, h.end(), complex_d{0.0, 0.0});
FftForward(n, h.data());
MagnitudeResponse(n, h.data(), hrir);
forward_fft(h);
MagnitudeResponse(h, hrir.first((h.size()/2) + 1));
}
bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType delayType,
@@ -266,7 +266,7 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela
{
const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
hData->mHrirsBase.resize(channels * size_t{hData->mIrCount} * hData->mIrSize, 0.0);
double *hrirs = hData->mHrirsBase.data();
const auto hrirs = al::span{hData->mHrirsBase};
std::vector<double> restmp;
std::optional<PPhaseResampler> resampler;
@@ -276,15 +276,15 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela
restmp.resize(sofaHrtf->N);
}
const auto srcPosValues = al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz};
const auto irValues = al::span{sofaHrtf->DataIR.values,
size_t{sofaHrtf->M}*sofaHrtf->R*sofaHrtf->N};
for(uint si{0u};si < sofaHrtf->M;++si)
{
loaded_count.fetch_add(1u);
std::array aer{
sofaHrtf->SourcePosition.values[3_uz*si],
sofaHrtf->SourcePosition.values[3_uz*si + 1],
sofaHrtf->SourcePosition.values[3_uz*si + 2]
};
std::array aer{srcPosValues[3_uz*si], srcPosValues[3_uz*si + 1],
srcPosValues[3_uz*si + 2]};
mysofa_c2s(aer.data());
if(std::abs(aer[1]) >= 89.999f)
@@ -311,40 +311,43 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela
ai %= static_cast<uint>(field->mEvs[ei].mAzs.size());
if(std::abs(af) >= 0.1) continue;
HrirAzT *azd = &field->mEvs[ei].mAzs[ai];
if(azd->mIrs[0] != nullptr)
HrirAzT &azd = field->mEvs[ei].mAzs[ai];
if(!azd.mIrs[0].empty())
{
fprintf(stderr, "\nMultiple measurements near [ a=%f, e=%f, r=%f ].\n",
fmt::println(stderr, "\nMultiple measurements near [ a={:f}, e={:f}, r={:f} ].",
aer[0], aer[1], aer[2]);
return false;
}
for(uint ti{0u};ti < channels;++ti)
{
azd->mIrs[ti] = &hrirs[(size_t{hData->mIrCount}*ti + azd->mIndex)*hData->mIrSize];
azd.mIrs[ti] = hrirs.subspan(
(size_t{hData->mIrCount}*ti + azd.mIndex) * hData->mIrSize, hData->mIrSize);
const auto ir = irValues.subspan((size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N,
sofaHrtf->N);
if(!resampler)
std::copy_n(&sofaHrtf->DataIR.values[(size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N],
sofaHrtf->N, azd->mIrs[ti]);
std::copy_n(ir.cbegin(), ir.size(), azd.mIrs[ti].begin());
else
{
std::copy_n(&sofaHrtf->DataIR.values[(size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N],
sofaHrtf->N, restmp.begin());
resampler->process(restmp, {azd->mIrs[ti], hData->mIrSize});
std::copy_n(ir.cbegin(), ir.size(), restmp.begin());
resampler->process(restmp, azd.mIrs[ti]);
}
}
/* Include any per-channel or per-HRIR delays. */
if(delayType == DelayType::I_R)
{
const float *delayValues{sofaHrtf->DataDelay.values};
const auto delayValues = al::span{sofaHrtf->DataDelay.values,
size_t{sofaHrtf->I}*sofaHrtf->R};
for(uint ti{0u};ti < channels;++ti)
azd->mDelays[ti] = delayValues[ti] / static_cast<float>(hData->mIrRate);
azd.mDelays[ti] = delayValues[ti] / static_cast<float>(hData->mIrRate);
}
else if(delayType == DelayType::M_R)
{
const float *delayValues{sofaHrtf->DataDelay.values};
const auto delayValues = al::span{sofaHrtf->DataDelay.values,
size_t{sofaHrtf->M}*sofaHrtf->R};
for(uint ti{0u};ti < channels;++ti)
azd->mDelays[ti] = delayValues[si*sofaHrtf->R + ti] /
azd.mDelays[ti] = delayValues[si*sofaHrtf->R + ti] /
static_cast<float>(hData->mIrRate);
}
}
@@ -363,10 +366,10 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela
auto load_future = std::async(std::launch::async, load_proc);
do {
load_status = load_future.wait_for(std::chrono::milliseconds{50});
printf("\rLoading HRIRs... %u of %u", loaded_count.load(), sofaHrtf->M);
fmt::print("\rLoading HRIRs... {} of {}", loaded_count.load(), sofaHrtf->M);
fflush(stdout);
} while(load_status != std::future_status::ready);
fputc('\n', stdout);
fmt::println("");
return load_future.get();
}
@@ -378,10 +381,13 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela
struct MagCalculator {
const uint mFftSize{};
const uint mIrPoints{};
std::vector<double*> mIrs{};
std::vector<al::span<double>> mIrs;
std::atomic<size_t> mCurrent{};
std::atomic<size_t> mDone{};
MagCalculator(const uint fftsize, const uint irpoints) : mFftSize{fftsize}, mIrPoints{irpoints}
{ }
void Worker()
{
auto htemp = std::vector<complex_d>(mFftSize);
@@ -401,7 +407,7 @@ struct MagCalculator {
*/
} while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed));
CalcHrirMagnitude(mIrPoints, mFftSize, htemp, mIrs[idx]);
CalcHrirMagnitude(mIrPoints, htemp, mIrs[idx]);
/* Increment the number of IRs done. */
mDone.fetch_add(1);
@@ -411,34 +417,34 @@ struct MagCalculator {
} // namespace
bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize,
bool LoadSofaFile(const std::string_view filename, const uint numThreads, const uint fftSize,
const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData)
{
int err;
MySofaHrtfPtr sofaHrtf{mysofa_load(filename, &err)};
MySofaHrtfPtr sofaHrtf{mysofa_load(std::string{filename}.c_str(), &err)};
if(!sofaHrtf)
{
fprintf(stdout, "Error: Could not load %s: %s\n", filename, SofaErrorStr(err));
fmt::println("Error: Could not load {}: {} ({})", filename, SofaErrorStr(err), err);
return false;
}
/* NOTE: Some valid SOFA files are failing this check. */
err = mysofa_check(sofaHrtf.get());
if(err != MYSOFA_OK)
fprintf(stderr, "Warning: Supposedly malformed source file '%s' (%s).\n", filename,
SofaErrorStr(err));
fmt::println(stderr, "Warning: Supposedly malformed source file '{}': {} ({})", filename,
SofaErrorStr(err), err);
mysofa_tocartesian(sofaHrtf.get());
/* Make sure emitter and receiver counts are sane. */
if(sofaHrtf->E != 1)
{
fprintf(stderr, "%u emitters not supported\n", sofaHrtf->E);
fmt::println(stderr, "{} emitters not supported", sofaHrtf->E);
return false;
}
if(sofaHrtf->R > 2 || sofaHrtf->R < 1)
{
fprintf(stderr, "%u receivers not supported\n", sofaHrtf->R);
fmt::println(stderr, "{} receivers not supported", sofaHrtf->R);
return false;
}
/* Assume R=2 is a stereo measurement, and R=1 is mono left-ear-only. */
@@ -450,12 +456,14 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
/* Check and set the FFT and IR size. */
if(sofaHrtf->N > fftSize)
{
fprintf(stderr, "Sample points exceeds the FFT size.\n");
fmt::println(stderr, "Sample points exceeds the FFT size ({} > {}).", sofaHrtf->N,
fftSize);
return false;
}
if(sofaHrtf->N < truncSize)
{
fprintf(stderr, "Sample points is below the truncation size.\n");
fmt::println(stderr, "Sample points is below the truncation size ({} < {}).", sofaHrtf->N,
truncSize);
return false;
}
hData->mIrPoints = sofaHrtf->N;
@@ -469,15 +477,15 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
if(!hData->mIrRate)
return false;
DelayType delayType = PrepareDelay(sofaHrtf.get());
if(delayType == DelayType::Invalid)
const auto delayType = PrepareDelay(sofaHrtf.get());
if(!delayType)
return false;
if(!CheckIrData(sofaHrtf.get()))
return false;
if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData))
if(!PrepareLayout(al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz}, hData))
return false;
if(!LoadResponses(sofaHrtf.get(), hData, delayType, outRate))
if(!LoadResponses(sofaHrtf.get(), hData, *delayType, outRate))
return false;
sofaHrtf = nullptr;
@@ -490,14 +498,14 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
for(;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
if(azd.mIrs[0] != nullptr) break;
if(!azd.mIrs[0].empty()) break;
}
if(ai < hData->mFds[fi].mEvs[ei].mAzs.size())
break;
}
if(ei >= hData->mFds[fi].mEvs.size())
{
fprintf(stderr, "Missing source references [ %d, *, * ].\n", fi);
fmt::println(stderr, "Missing source references [ {}, *, * ].", fi);
return false;
}
hData->mFds[fi].mEvStart = ei;
@@ -506,9 +514,9 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
if(azd.mIrs[0] == nullptr)
if(azd.mIrs[0].empty())
{
fprintf(stderr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai);
fmt::println(stderr, "Missing source reference [ {}, {}, {} ].", fi, ei, ai);
return false;
}
}
@@ -518,7 +526,7 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
size_t hrir_total{0};
const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
double *hrirs = hData->mHrirsBase.data();
const auto hrirs = al::span{hData->mHrirsBase};
for(uint fi{0u};fi < hData->mFds.size();fi++)
{
for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++)
@@ -527,7 +535,8 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
for(size_t ti{0u};ti < channels;ti++)
azd.mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd.mIndex)];
azd.mIrs[ti] = hrirs.subspan((hData->mIrCount*ti + azd.mIndex)*hData->mIrSize,
hData->mIrSize);
}
}
@@ -553,8 +562,8 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
for(uint ti{0};ti < channels;ti++)
{
hrir_done.fetch_add(1u, std::memory_order_acq_rel);
azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, hData->mIrPoints,
upsampled, azd.mIrs[ti]);
azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, upsampled,
azd.mIrs[ti].first(hData->mIrPoints));
}
}
}
@@ -566,10 +575,10 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
auto load_future = std::async(std::launch::async, onset_proc);
do {
load_status = load_future.wait_for(std::chrono::milliseconds{50});
printf("\rCalculating HRIR onsets... %zu of %zu", hrir_done.load(), hrir_total);
fmt::print("\rCalculating HRIR onsets... {} of {}", hrir_done.load(), hrir_total);
fflush(stdout);
} while(load_status != std::future_status::ready);
fputc('\n', stdout);
fmt::println("");
if(!load_future.get())
return false;
@@ -589,16 +598,16 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
std::vector<std::thread> thrds;
thrds.reserve(numThreads);
for(size_t i{0};i < numThreads;++i)
thrds.emplace_back(std::mem_fn(&MagCalculator::Worker), &calculator);
thrds.emplace_back(&MagCalculator::Worker, &calculator);
size_t count;
do {
std::this_thread::sleep_for(std::chrono::milliseconds{50});
count = calculator.mDone.load();
printf("\rCalculating HRIR magnitudes... %zu of %zu", count, calculator.mIrs.size());
fmt::print("\rCalculating HRIR magnitudes... {} of {}", count, calculator.mIrs.size());
fflush(stdout);
} while(count != calculator.mIrs.size());
fputc('\n', stdout);
fmt::println("");
for(auto &thrd : thrds)
{
+3 -1
View File
@@ -1,10 +1,12 @@
#ifndef LOADSOFA_H
#define LOADSOFA_H
#include <string_view>
#include "makemhr.h"
bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize,
bool LoadSofaFile(const std::string_view filename, const uint numThreads, const uint fftSize,
const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData);
#endif /* LOADSOFA_H */
+264 -215
View File
@@ -73,7 +73,6 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iostream>
@@ -82,20 +81,15 @@
#include <numeric>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#ifdef HAVE_GETOPT
#include <unistd.h>
#else
#include "../getopt.h"
#endif
#include "alcomplex.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "alstring.h"
#include "filesystem.h"
#include "fmt/core.h"
#include "loaddef.h"
#include "loadsofa.h"
@@ -169,29 +163,28 @@ enum ChannelIndex : uint {
* pattern string are replaced with the replacement string. The result is
* truncated if necessary.
*/
std::string StrSubst(al::span<const char> in, const al::span<const char> pat,
const al::span<const char> rep)
auto StrSubst(std::string_view in, const std::string_view pat, const std::string_view rep) -> std::string
{
std::string ret;
ret.reserve(in.size() + pat.size());
while(in.size() >= pat.size())
{
if(al::strncasecmp(in.data(), pat.data(), pat.size()) == 0)
if(al::starts_with(in, pat))
{
in = in.subspan(pat.size());
ret.append(rep.data(), rep.size());
in = in.substr(pat.size());
ret += rep;
}
else
{
size_t endpos{1};
while(endpos < in.size() && in[endpos] != pat.front())
while(endpos < in.size() && std::toupper(in[endpos]) != std::toupper(pat.front()))
++endpos;
ret.append(in.data(), endpos);
in = in.subspan(endpos);
ret += in.substr(0, endpos);
in = in.substr(endpos);
}
}
ret.append(in.data(), in.size());
ret += in;
return ret;
}
@@ -215,73 +208,44 @@ inline uint dither_rng(uint *seed)
// Performs a triangular probability density function dither. The input samples
// should be normalized (-1 to +1).
void TpdfDither(double *RESTRICT out, const double *RESTRICT in, const double scale,
const uint count, const uint step, uint *seed)
void TpdfDither(const al::span<double> out, const al::span<const double> in, const double scale,
const size_t channel, const size_t step, uint *seed)
{
static constexpr double PRNG_SCALE = 1.0 / std::numeric_limits<uint>::max();
assert(channel < step);
for(uint i{0};i < count;i++)
for(size_t i{0};i < in.size();++i)
{
uint prn0{dither_rng(seed)};
uint prn1{dither_rng(seed)};
*out = std::round(*(in++)*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE));
out += step;
out[i*step + channel] = std::round(in[i]*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE));
}
}
/* Calculate the complex helical sequence (or discrete-time analytical signal)
* of the given input using the Hilbert transform. Given the natural logarithm
* of a signal's magnitude response, the imaginary components can be used as
* the angles for minimum-phase reconstruction.
*/
inline void Hilbert(const uint n, complex_d *inout)
{ complex_hilbert({inout, n}); }
} // namespace
/* Calculate the magnitude response of the given input. This is used in
* place of phase decomposition, since the phase residuals are discarded for
* minimum phase reconstruction. The mirrored half of the response is also
* discarded.
*/
void MagnitudeResponse(const uint n, const complex_d *in, double *out)
{
const uint m = 1 + (n / 2);
uint i;
for(i = 0;i < m;i++)
out[i] = std::max(std::abs(in[i]), Epsilon);
}
namespace {
/* Apply a range limit (in dB) to the given magnitude response. This is used
* to adjust the effects of the diffuse-field average on the equalization
* process.
*/
void LimitMagnitudeResponse(const uint n, const uint m, const double limit, const double *in, double *out)
void LimitMagnitudeResponse(const uint n, const uint m, const double limit,
const al::span<double> inout)
{
double halfLim;
uint i, lower, upper;
double ave;
halfLim = limit / 2.0;
const double halfLim{limit / 2.0};
// Convert the response to dB.
for(i = 0;i < m;i++)
out[i] = 20.0 * std::log10(in[i]);
for(uint i{0};i < m;++i)
inout[i] = 20.0 * std::log10(inout[i]);
// Use six octaves to calculate the average magnitude of the signal.
lower = (static_cast<uint>(std::ceil(n / std::pow(2.0, 8.0)))) - 1;
upper = (static_cast<uint>(std::floor(n / std::pow(2.0, 2.0)))) - 1;
ave = 0.0;
for(i = lower;i <= upper;i++)
ave += out[i];
const auto lower = (static_cast<uint>(std::ceil(n / std::pow(2.0, 8.0)))) - 1;
const auto upper = (static_cast<uint>(std::floor(n / std::pow(2.0, 2.0)))) - 1;
double ave{0.0};
for(uint i{lower};i <= upper;++i)
ave += inout[i];
ave /= upper - lower + 1;
// Keep the response within range of the average magnitude.
for(i = 0;i < m;i++)
out[i] = Clamp(out[i], ave - halfLim, ave + halfLim);
for(uint i{0};i < m;++i)
inout[i] = Clamp(inout[i], ave - halfLim, ave + halfLim);
// Convert the response back to linear magnitude.
for(i = 0;i < m;i++)
out[i] = std::pow(10.0, out[i] / 20.0);
for(uint i{0};i < m;++i)
inout[i] = std::pow(10.0, inout[i] / 20.0);
}
/* Reconstructs the minimum-phase component for the given magnitude response
@@ -289,22 +253,23 @@ void LimitMagnitudeResponse(const uint n, const uint m, const double limit, cons
* residuals (which were discarded). The mirrored half of the response is
* reconstructed.
*/
void MinimumPhase(const uint n, double *mags, complex_d *out)
void MinimumPhase(const al::span<double> mags, const al::span<complex_d> out)
{
const uint m{(n/2) + 1};
assert(mags.size() == out.size());
const size_t m{(mags.size()/2) + 1};
uint i;
size_t i;
for(i = 0;i < m;i++)
out[i] = std::log(mags[i]);
for(;i < n;i++)
for(;i < mags.size();++i)
{
mags[i] = mags[n - i];
out[i] = out[n - i];
mags[i] = mags[mags.size() - i];
out[i] = out[mags.size() - i];
}
Hilbert(n, out);
complex_hilbert(out);
// Remove any DC offset the filter has.
mags[0] = Epsilon;
for(i = 0;i < n;i++)
for(i = 0;i < mags.size();++i)
out[i] = std::polar(mags[i], out[i].imag());
}
@@ -314,11 +279,11 @@ void MinimumPhase(const uint n, double *mags, complex_d *out)
***************************/
// Write an ASCII string to a file.
int WriteAscii(const std::string_view out, FILE *fp, const char *filename)
auto WriteAscii(const std::string_view out, std::ostream &ostream, const std::string_view filename) -> int
{
if(fwrite(out.data(), 1, out.size(), fp) != out.size())
if(!ostream.write(out.data(), std::streamsize(out.size())) || ostream.bad())
{
fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename);
fmt::println(stderr, "\nError: Bad write to file '{}'.", filename);
return 0;
}
return 1;
@@ -326,54 +291,55 @@ int WriteAscii(const std::string_view out, FILE *fp, const char *filename)
// Write a binary value of the given byte order and byte size to a file,
// loading it from a 32-bit unsigned integer.
int WriteBin4(const uint bytes, const uint32_t in, FILE *fp, const char *filename)
auto WriteBin4(const uint bytes, const uint32_t in, std::ostream &ostream,
const std::string_view filename) -> int
{
std::array<uint8_t,4> out{};
std::array<char,4> out{};
for(uint i{0};i < bytes;i++)
out[i] = (in>>(i*8)) & 0x000000FF;
out[i] = static_cast<char>((in>>(i*8)) & 0x000000FF);
if(fwrite(out.data(), 1, bytes, fp) != bytes)
if(!ostream.write(out.data(), std::streamsize(bytes)) || ostream.bad())
{
fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename);
fmt::println(stderr, "\nError: Bad write to file '{}'.", filename);
return 0;
}
return 1;
}
// Store the OpenAL Soft HRTF data set.
bool StoreMhr(const HrirDataT *hData, const char *filename)
auto StoreMhr(const HrirDataT *hData, const std::string_view filename) -> bool
{
const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
const uint n{hData->mIrPoints};
uint dither_seed{22222};
FilePtr fp{fopen(filename, "wb")};
if(!fp)
auto ostream = fs::ofstream{fs::u8path(filename), std::ios::binary};
if(!ostream.is_open())
{
fprintf(stderr, "\nError: Could not open MHR file '%s'.\n", filename);
fmt::println(stderr, "\nError: Could not open MHR file '{}'.", filename);
return false;
}
if(!WriteAscii(GetMHRMarker(), fp.get(), filename))
if(!WriteAscii(GetMHRMarker(), ostream, filename))
return false;
if(!WriteBin4(4, hData->mIrRate, fp.get(), filename))
if(!WriteBin4(4, hData->mIrRate, ostream, filename))
return false;
if(!WriteBin4(1, static_cast<uint32_t>(hData->mChannelType), fp.get(), filename))
if(!WriteBin4(1, static_cast<uint32_t>(hData->mChannelType), ostream, filename))
return false;
if(!WriteBin4(1, hData->mIrPoints, fp.get(), filename))
if(!WriteBin4(1, hData->mIrPoints, ostream, filename))
return false;
if(!WriteBin4(1, static_cast<uint>(hData->mFds.size()), fp.get(), filename))
if(!WriteBin4(1, static_cast<uint>(hData->mFds.size()), ostream, filename))
return false;
for(size_t fi{hData->mFds.size()-1};fi < hData->mFds.size();--fi)
{
auto fdist = static_cast<uint32_t>(std::round(1000.0 * hData->mFds[fi].mDistance));
if(!WriteBin4(2, fdist, fp.get(), filename))
if(!WriteBin4(2, fdist, ostream, filename))
return false;
if(!WriteBin4(1, static_cast<uint32_t>(hData->mFds[fi].mEvs.size()), fp.get(), filename))
if(!WriteBin4(1, static_cast<uint32_t>(hData->mFds[fi].mEvs.size()), ostream, filename))
return false;
for(size_t ei{0};ei < hData->mFds[fi].mEvs.size();++ei)
{
const auto &elev = hData->mFds[fi].mEvs[ei];
if(!WriteBin4(1, static_cast<uint32_t>(elev.mAzs.size()), fp.get(), filename))
if(!WriteBin4(1, static_cast<uint32_t>(elev.mAzs.size()), ostream, filename))
return false;
}
}
@@ -389,14 +355,14 @@ bool StoreMhr(const HrirDataT *hData, const char *filename)
{
std::array<double,MaxTruncSize*2_uz> out{};
TpdfDither(out.data(), azd.mIrs[0], scale, n, channels, &dither_seed);
TpdfDither(out, azd.mIrs[0].first(n), scale, 0, channels, &dither_seed);
if(hData->mChannelType == CT_STEREO)
TpdfDither(out.data()+1, azd.mIrs[1], scale, n, channels, &dither_seed);
TpdfDither(out, azd.mIrs[1].first(n), scale, 1, channels, &dither_seed);
const size_t numsamples{size_t{channels} * n};
for(size_t i{0};i < numsamples;i++)
{
const auto v = static_cast<int>(Clamp(out[i], -scale-1.0, scale));
if(!WriteBin4(bps, static_cast<uint32_t>(v), fp.get(), filename))
if(!WriteBin4(bps, static_cast<uint32_t>(v), ostream, filename))
return false;
}
}
@@ -411,11 +377,11 @@ bool StoreMhr(const HrirDataT *hData, const char *filename)
for(const auto &azd : evd.mAzs)
{
auto v = static_cast<uint>(std::round(azd.mDelays[0]*DelayPrecScale));
if(!WriteBin4(1, v, fp.get(), filename)) return false;
if(!WriteBin4(1, v, ostream, filename)) return false;
if(hData->mChannelType == CT_STEREO)
{
v = static_cast<uint>(std::round(azd.mDelays[1]*DelayPrecScale));
if(!WriteBin4(1, v, fp.get(), filename)) return false;
if(!WriteBin4(1, v, ostream, filename)) return false;
}
}
}
@@ -476,7 +442,7 @@ void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, const u
* on its coverage volume. All volumes are centered at the spherical HRIR
* coordinates and measured by extruded solid angle.
*/
void CalculateDfWeights(const HrirDataT *hData, double *weights)
void CalculateDfWeights(const HrirDataT *hData, const al::span<double> weights)
{
double sum, innerRa, outerRa, evs, ev, upperEv, lowerEv;
double solidAngle, solidVolume;
@@ -533,7 +499,7 @@ void CalculateDfWeights(const HrirDataT *hData, double *weights)
* specified magnitude range (in positive dB; 0.0 to skip).
*/
void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, const uint m,
const bool weighted, const double limit, double *dfa)
const bool weighted, const double limit, const al::span<double> dfa)
{
std::vector<double> weights(hData->mFds.size() * MAX_EV_COUNT);
uint count;
@@ -541,7 +507,7 @@ void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, c
if(weighted)
{
// Use coverage weighting to calculate the average.
CalculateDfWeights(hData, weights.data());
CalculateDfWeights(hData, weights);
}
else
{
@@ -589,13 +555,14 @@ void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, c
// Apply a limit to the magnitude range of the diffuse-field average
// if desired.
if(limit > 0.0)
LimitMagnitudeResponse(hData->mFftSize, m, limit, &dfa[ti * m], &dfa[ti * m]);
LimitMagnitudeResponse(hData->mFftSize, m, limit, dfa.subspan(ti * m));
}
}
// Perform diffuse-field equalization on the magnitude responses of the HRIR
// set using the given average response.
void DiffuseFieldEqualize(const uint channels, const uint m, const double *dfa, const HrirDataT *hData)
void DiffuseFieldEqualize(const uint channels, const uint m, const al::span<const double> dfa,
const HrirDataT *hData)
{
for(size_t fi{0};fi < hData->mFds.size();++fi)
{
@@ -814,7 +781,7 @@ void SynthesizeHrirs(HrirDataT *hData)
*/
FftForward(static_cast<uint>(htemp.size()), htemp.data());
std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(),
[](const complex_d &c) -> double { return std::abs(c); });
[](const complex_d c) -> double { return std::abs(c); });
for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++)
{
@@ -854,7 +821,7 @@ void SynthesizeHrirs(HrirDataT *hData)
}
FftForward(static_cast<uint>(htemp.size()), htemp.data());
std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(),
[](const complex_d &c) -> double { return std::abs(c); });
[](const complex_d c) -> double { return std::abs(c); });
for(uint ti{0u};ti < channels;ti++)
{
@@ -872,7 +839,7 @@ void SynthesizeHrirs(HrirDataT *hData)
* or more threads (sharing the same reconstructor object).
*/
struct HrirReconstructor {
std::vector<double*> mIrs;
std::vector<al::span<double>> mIrs;
std::atomic<size_t> mCurrent{};
std::atomic<size_t> mDone{};
uint mFftSize{};
@@ -904,7 +871,7 @@ struct HrirReconstructor {
*/
for(size_t i{0};i < m;++i)
mags[i] = std::max(mIrs[idx][i], Epsilon);
MinimumPhase(mFftSize, mags.data(), h.data());
MinimumPhase(mags, h);
FftInverse(mFftSize, h.data());
for(uint i{0u};i < mIrPoints;++i)
mIrs[idx][i] = h[i].real();
@@ -943,7 +910,7 @@ void ReconstructHrirs(const HrirDataT *hData, const uint numThreads)
std::vector<std::thread> thrds;
thrds.reserve(numThreads);
for(size_t i{0};i < numThreads;++i)
thrds.emplace_back(std::mem_fn(&HrirReconstructor::Worker), &reconstructor);
thrds.emplace_back(&HrirReconstructor::Worker, &reconstructor);
/* Keep track of the number of IRs done, periodically reporting it. */
size_t count;
@@ -953,10 +920,10 @@ void ReconstructHrirs(const HrirDataT *hData, const uint numThreads)
count = reconstructor.mDone.load();
size_t pcdone{count * 100 / reconstructor.mIrs.size()};
printf("\r%3zu%% done (%zu of %zu)", pcdone, count, reconstructor.mIrs.size());
fmt::print("\r{:3}% done ({} of {})", pcdone, count, reconstructor.mIrs.size());
fflush(stdout);
} while(count < reconstructor.mIrs.size());
fputc('\n', stdout);
fmt::println("");
for(auto &thrd : thrds)
{
@@ -973,10 +940,11 @@ void NormalizeHrirs(HrirDataT *hData)
/* Find the maximum amplitude and RMS out of all the IRs. */
struct LevelPair { double amp, rms; };
auto mesasure_channel = [irSize](const LevelPair levels, const double *ir)
auto mesasure_channel = [irSize](const LevelPair levels, al::span<const double> ir)
{
/* Calculate the peak amplitude and RMS of this IR. */
auto current = std::accumulate(ir, ir+irSize, LevelPair{0.0, 0.0},
ir = ir.first(irSize);
auto current = std::accumulate(ir.cbegin(), ir.cend(), LevelPair{0.0, 0.0},
[](const LevelPair cur, const double impulse)
{
return LevelPair{std::max(std::abs(impulse), cur.amp), cur.rms + impulse*impulse};
@@ -1011,8 +979,12 @@ void NormalizeHrirs(HrirDataT *hData)
factor = std::min(factor, 0.99/maxlev.amp);
/* Now scale all IRs by the given factor. */
auto proc_channel = [irSize,factor](double *ir)
{ std::transform(ir, ir+irSize, ir, [factor](double s){ return s * factor; }); };
auto proc_channel = [irSize,factor](al::span<double> ir)
{
ir = ir.first(irSize);
std::transform(ir.cbegin(), ir.cend(), ir.begin(),
[factor](double s) { return s * factor; });
};
auto proc_azi = [channels,proc_channel](HrirAzT &azi)
{ std::for_each(azi.mIrs.begin(), azi.mIrs.begin()+channels, proc_channel); };
auto proc_elev = [proc_azi](HrirEvT &elev)
@@ -1101,7 +1073,7 @@ void CalculateHrtds(const HeadModelT model, const double radius, HrirDataT *hDat
}
if(maxHrtd > MaxHrtd)
{
fprintf(stdout, " Scaling for max delay of %f samples to %f\n...\n", maxHrtd, MaxHrtd);
fmt::println(" Scaling for max delay of {:f} samples to {:f}\n...", maxHrtd, MaxHrtd);
const double scale{MaxHrtd / maxHrtd};
for(auto &field : hData->mFds)
{
@@ -1145,7 +1117,7 @@ bool PrepareHrirData(const al::span<const double> distances,
{
hData->mFds[fi].mDistance = distances[fi];
hData->mFds[fi].mEvStart = 0;
hData->mFds[fi].mEvs = {&hData->mEvsBase[evTotal], evCounts[fi]};
hData->mFds[fi].mEvs = al::span{hData->mEvsBase}.subspan(evTotal, evCounts[fi]);
evTotal += evCounts[fi];
for(uint ei{0};ei < evCounts[fi];++ei)
{
@@ -1153,15 +1125,15 @@ bool PrepareHrirData(const al::span<const double> distances,
hData->mFds[fi].mEvs[ei].mElevation = -al::numbers::pi / 2.0 + al::numbers::pi * ei /
(evCounts[fi] - 1);
hData->mFds[fi].mEvs[ei].mAzs = {&hData->mAzsBase[azTotal], azCount};
hData->mFds[fi].mEvs[ei].mAzs = al::span{hData->mAzsBase}.subspan(azTotal, azCount);
for(uint ai{0};ai < azCount;ai++)
{
hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth = 2.0 * al::numbers::pi * ai / azCount;
hData->mFds[fi].mEvs[ei].mAzs[ai].mIndex = azTotal + ai;
hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[0] = 0.0;
hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[1] = 0.0;
hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = nullptr;
hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = nullptr;
hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = {};
hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = {};
}
azTotal += azCount;
}
@@ -1176,36 +1148,35 @@ namespace {
* resulting data set as desired. If the input name is NULL it will read
* from standard input.
*/
bool ProcessDefinition(const char *inName, const uint outRate, const ChannelModeT chanMode,
bool ProcessDefinition(std::string_view inName, const uint outRate, const ChannelModeT chanMode,
const bool farfield, const uint numThreads, const uint fftSize, const bool equalize,
const bool surface, const double limit, const uint truncSize, const HeadModelT model,
const double radius, const char *outName)
const double radius, const std::string_view outName)
{
HrirDataT hData;
fprintf(stdout, "Using %u thread%s.\n", numThreads, (numThreads==1)?"":"s");
if(!inName)
fmt::println("Using {} thread{}.", numThreads, (numThreads==1)?"":"s");
if(inName.empty() || inName == "-"sv)
{
inName = "stdin";
fprintf(stdout, "Reading HRIR definition from %s...\n", inName);
if(!LoadDefInput(std::cin, nullptr, 0, inName, fftSize, truncSize, outRate, chanMode, &hData))
inName = "stdin"sv;
fmt::println("Reading HRIR definition from {}...", inName);
if(!LoadDefInput(std::cin, {}, inName, fftSize, truncSize, outRate, chanMode, &hData))
return false;
}
else
{
auto input = std::make_unique<std::ifstream>(std::filesystem::u8path(inName));
auto input = std::make_unique<fs::ifstream>(fs::u8path(inName));
if(!input->is_open())
{
fprintf(stderr, "Error: Could not open input file '%s'\n", inName);
fmt::println(stderr, "Error: Could not open input file '{}'", inName);
return false;
}
std::array<char,4> startbytes{};
input->read(startbytes.data(), startbytes.size());
std::streamsize startbytecount{input->gcount()};
if(startbytecount != startbytes.size() || !input->good())
if(input->gcount() != startbytes.size() || !input->good())
{
fprintf(stderr, "Error: Could not read input file '%s'\n", inName);
fmt::println(stderr, "Error: Could not read input file '{}'", inName);
return false;
}
@@ -1213,15 +1184,15 @@ bool ProcessDefinition(const char *inName, const uint outRate, const ChannelMode
&& startbytes[3] == 'F')
{
input = nullptr;
fprintf(stdout, "Reading HRTF data from %s...\n", inName);
fmt::println("Reading HRTF data from {}...", inName);
if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, outRate, chanMode, &hData))
return false;
}
else
{
fprintf(stdout, "Reading HRIR definition from %s...\n", inName);
if(!LoadDefInput(*input, startbytes.data(), startbytecount, inName, fftSize, truncSize,
outRate, chanMode, &hData))
fmt::println("Reading HRIR definition from {}...", inName);
if(!LoadDefInput(*input, startbytes, inName, fftSize, truncSize, outRate, chanMode,
&hData))
return false;
}
}
@@ -1234,85 +1205,82 @@ bool ProcessDefinition(const char *inName, const uint outRate, const ChannelMode
if(hData.mFds.size() > 1)
{
fprintf(stdout, "Balancing field magnitudes...\n");
fmt::println("Balancing field magnitudes...");
BalanceFieldMagnitudes(&hData, c, m);
}
fprintf(stdout, "Calculating diffuse-field average...\n");
CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa.data());
fprintf(stdout, "Performing diffuse-field equalization...\n");
DiffuseFieldEqualize(c, m, dfa.data(), &hData);
fmt::println("Calculating diffuse-field average...");
CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa);
fmt::println("Performing diffuse-field equalization...");
DiffuseFieldEqualize(c, m, dfa, &hData);
}
if(hData.mFds.size() > 1)
{
fprintf(stdout, "Sorting %zu fields...\n", hData.mFds.size());
fmt::println("Sorting {} fields...", hData.mFds.size());
std::sort(hData.mFds.begin(), hData.mFds.end(),
[](const HrirFdT &lhs, const HrirFdT &rhs) noexcept
{ return lhs.mDistance < rhs.mDistance; });
if(farfield)
{
fprintf(stdout, "Clearing %zu near field%s...\n", hData.mFds.size()-1,
fmt::println("Clearing {} near field{}...", hData.mFds.size()-1,
(hData.mFds.size()-1 != 1) ? "s" : "");
hData.mFds.erase(hData.mFds.cbegin(), hData.mFds.cend()-1);
}
}
fprintf(stdout, "Synthesizing missing elevations...\n");
fmt::println("Synthesizing missing elevations...");
if(model == HM_Dataset)
SynthesizeOnsets(&hData);
SynthesizeHrirs(&hData);
fprintf(stdout, "Performing minimum phase reconstruction...\n");
fmt::println("Performing minimum phase reconstruction...");
ReconstructHrirs(&hData, numThreads);
fprintf(stdout, "Truncating minimum-phase HRIRs...\n");
fmt::println("Truncating minimum-phase HRIRs...");
hData.mIrPoints = truncSize;
fprintf(stdout, "Normalizing final HRIRs...\n");
fmt::println("Normalizing final HRIRs...");
NormalizeHrirs(&hData);
fprintf(stdout, "Calculating impulse delays...\n");
fmt::println("Calculating impulse delays...");
CalculateHrtds(model, (radius > DefaultCustomRadius) ? radius : hData.mRadius, &hData);
const auto rateStr = std::to_string(hData.mIrRate);
const auto expName = StrSubst({outName, strlen(outName)}, {"%r", 2},
{rateStr.data(), rateStr.size()});
fprintf(stdout, "Creating MHR data set %s...\n", expName.c_str());
return StoreMhr(&hData, expName.c_str());
const auto expName = StrSubst(outName, "%r"sv, rateStr);
fmt::println("Creating MHR data set {}...", expName);
return StoreMhr(&hData, expName);
}
void PrintHelp(const char *argv0, FILE *ofile)
void PrintHelp(const std::string_view argv0, FILE *ofile)
{
fprintf(ofile, "Usage: %s [<option>...]\n\n", argv0);
fprintf(ofile, "Options:\n");
fprintf(ofile, " -r <rate> Change the data set sample rate to the specified value and\n");
fprintf(ofile, " resample the HRIRs accordingly.\n");
fprintf(ofile, " -m Change the data set to mono, mirroring the left ear for the\n");
fprintf(ofile, " right ear.\n");
fprintf(ofile, " -a Change the data set to single field, using the farthest field.\n");
fprintf(ofile, " -j <threads> Number of threads used to process HRIRs (default: 2).\n");
fprintf(ofile, " -f <points> Override the FFT window size (default: %u).\n", DefaultFftSize);
fprintf(ofile, " -e {on|off} Toggle diffuse-field equalization (default: %s).\n", (DefaultEqualize ? "on" : "off"));
fprintf(ofile, " -s {on|off} Toggle surface-weighted diffuse-field average (default: %s).\n", (DefaultSurface ? "on" : "off"));
fprintf(ofile, " -l {<dB>|none} Specify a limit to the magnitude range of the diffuse-field\n");
fprintf(ofile, " average (default: %.2f).\n", DefaultLimit);
fprintf(ofile, " -w <points> Specify the size of the truncation window that's applied\n");
fprintf(ofile, " after minimum-phase reconstruction (default: %u).\n", DefaultTruncSize);
fprintf(ofile, " -d {dataset| Specify the model used for calculating the head-delay timing\n");
fprintf(ofile, " sphere} values (default: %s).\n", ((HM_Default == HM_Dataset) ? "dataset" : "sphere"));
fprintf(ofile, " -c <radius> Use a customized head radius measured to-ear in meters.\n");
fprintf(ofile, " -i <filename> Specify an HRIR definition file to use (defaults to stdin).\n");
fprintf(ofile, " -o <filename> Specify an output file. Use of '%%r' will be substituted with\n");
fprintf(ofile, " the data set sample rate.\n");
fmt::println(ofile, "Usage: {} [<option>...]\n", argv0);
fmt::println(ofile, "Options:");
fmt::println(ofile, " -r <rate> Change the data set sample rate to the specified value and");
fmt::println(ofile, " resample the HRIRs accordingly.");
fmt::println(ofile, " -m Change the data set to mono, mirroring the left ear for the");
fmt::println(ofile, " right ear.");
fmt::println(ofile, " -a Change the data set to single field, using the farthest field.");
fmt::println(ofile, " -j <threads> Number of threads used to process HRIRs (default: 2).");
fmt::println(ofile, " -f <points> Override the FFT window size (default: {}).", DefaultFftSize);
fmt::println(ofile, " -e {{on|off}} Toggle diffuse-field equalization (default: {}).", (DefaultEqualize ? "on" : "off"));
fmt::println(ofile, " -s {{on|off}} Toggle surface-weighted diffuse-field average (default: {}).", (DefaultSurface ? "on" : "off"));
fmt::println(ofile, " -l {{<dB>|none}} Specify a limit to the magnitude range of the diffuse-field");
fmt::println(ofile, " average (default: {:.2f}).", DefaultLimit);
fmt::println(ofile, " -w <points> Specify the size of the truncation window that's applied");
fmt::println(ofile, " after minimum-phase reconstruction (default: {}).", DefaultTruncSize);
fmt::println(ofile, " -d {{dataset| Specify the model used for calculating the head-delay timing");
fmt::println(ofile, " sphere}} values (default: {}).", ((HM_Default == HM_Dataset) ? "dataset" : "sphere"));
fmt::println(ofile, " -c <radius> Use a customized head radius measured to-ear in meters.");
fmt::println(ofile, " -i <filename> Specify an HRIR definition file to use (defaults to stdin).");
fmt::println(ofile, " -o <filename> Specify an output file. Use of '%r' will be substituted with");
fmt::println(ofile, " the data set sample rate.");
}
} // namespace
// Standard command line dispatch.
int main(int argc, char *argv[])
int main(al::span<std::string_view> args)
{
if(argc < 2)
if(args.size() < 2)
{
fprintf(stdout, "HRTF Processing and Composition Utility\n\n");
PrintHelp(argv[0], stdout);
fmt::println("HRTF Processing and Composition Utility\n");
PrintHelp(args[0], stdout);
exit(EXIT_SUCCESS);
}
const char *outName{"./oalsoft_hrtf_%r.mhr"};
std::string_view outName{"./oalsoft_hrtf_%r.mhr"sv};
uint outRate{0};
ChannelModeT chanMode{CM_AllowStereo};
uint fftSize{DefaultFftSize};
@@ -1324,19 +1292,73 @@ int main(int argc, char *argv[])
HeadModelT model{HM_Default};
double radius{DefaultCustomRadius};
bool farfield{false};
std::string_view inName;
const char *inName{};
int opt;
while((opt=getopt(argc, argv, "r:maj:f:e:s:l:w:d:c:e:i:o:h")) != -1)
const std::string_view optlist{"r:maj:f:e:s:l:w:d:c:e:i:o:h"sv};
const auto arg0 = args[0];
args = args.subspan(1);
std::string_view optarg;
size_t argplace{0};
auto getarg = [&args,&argplace,&optarg,optlist]
{
char *end{};
while(!args.empty() && argplace >= args[0].size())
{
argplace = 0;
args = args.subspan(1);
}
if(args.empty())
return 0;
if(argplace == 0)
{
if(args[0] == "--"sv)
return 0;
if(args[0][0] != '-' || args[0].size() == 1)
{
fmt::println(stderr, "Invalid argument: {}", args[0]);
return -1;
}
++argplace;
}
const char nextopt{args[0][argplace]};
const auto listidx = optlist.find(nextopt);
if(listidx >= optlist.size())
{
fmt::println(stderr, "Unknown argument: -{:c}", nextopt);
return -1;
}
const bool needsarg{listidx+1 < optlist.size() && optlist[listidx+1] == ':'};
if(needsarg && (argplace+1 < args[0].size() || args.size() < 2))
{
fmt::println(stderr, "Missing parameter for argument: -{:c}", nextopt);
return -1;
}
if(++argplace == args[0].size())
{
if(needsarg)
optarg = args[1];
argplace = 0;
args = args.subspan(1u + needsarg);
}
return int{nextopt};
};
while(auto opt = getarg())
{
std::size_t endpos{};
switch(opt)
{
case 'r':
outRate = static_cast<uint>(strtoul(optarg, &end, 10));
if(end[0] != '\0' || outRate < MIN_RATE || outRate > MAX_RATE)
outRate = static_cast<uint>(std::stoul(std::string{optarg}, &endpos, 10));
if(endpos != optarg.size() || outRate < MIN_RATE || outRate > MAX_RATE)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, MIN_RATE, MAX_RATE);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected between {} to {}.",
optarg, opt, MIN_RATE, MAX_RATE);
exit(EXIT_FAILURE);
}
break;
@@ -1350,10 +1372,12 @@ int main(int argc, char *argv[])
break;
case 'j':
numThreads = static_cast<uint>(strtoul(optarg, &end, 10));
if(end[0] != '\0' || numThreads > 64)
numThreads = static_cast<uint>(std::stoul(std::string{optarg}, &endpos, 10));
if(endpos != optarg.size() || numThreads > 64)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, 0, 64);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected between {} to {}.",
optarg, opt, 0, 64);
exit(EXIT_FAILURE);
}
if(numThreads == 0)
@@ -1361,78 +1385,93 @@ int main(int argc, char *argv[])
break;
case 'f':
fftSize = static_cast<uint>(strtoul(optarg, &end, 10));
if(end[0] != '\0' || (fftSize&(fftSize-1)) || fftSize < MinFftSize || fftSize > MaxFftSize)
fftSize = static_cast<uint>(std::stoul(std::string{optarg}, &endpos, 10));
if(endpos != optarg.size() || (fftSize&(fftSize-1)) || fftSize < MinFftSize
|| fftSize > MaxFftSize)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected a power-of-two between %u to %u.\n", optarg, opt, MinFftSize, MaxFftSize);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected a power-of-two between {} to {}.",
optarg, opt, MinFftSize, MaxFftSize);
exit(EXIT_FAILURE);
}
break;
case 'e':
if(strcmp(optarg, "on") == 0)
if(optarg == "on"sv)
equalize = true;
else if(strcmp(optarg, "off") == 0)
else if(optarg == "off"sv)
equalize = false;
else
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected on or off.",
optarg, opt);
exit(EXIT_FAILURE);
}
break;
case 's':
if(strcmp(optarg, "on") == 0)
if(optarg == "on"sv)
surface = true;
else if(strcmp(optarg, "off") == 0)
else if(optarg == "off"sv)
surface = false;
else
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected on or off.",
optarg, opt);
exit(EXIT_FAILURE);
}
break;
case 'l':
if(strcmp(optarg, "none") == 0)
if(optarg == "none"sv)
limit = 0.0;
else
{
limit = strtod(optarg, &end);
if(end[0] != '\0' || limit < MinLimit || limit > MaxLimit)
limit = std::stod(std::string{optarg}, &endpos);
if(endpos != optarg.size() || limit < MinLimit || limit > MaxLimit)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %.0f to %.0f.\n", optarg, opt, MinLimit, MaxLimit);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected between {:.0f} to {:.0f}.",
optarg, opt, MinLimit, MaxLimit);
exit(EXIT_FAILURE);
}
}
break;
case 'w':
truncSize = static_cast<uint>(strtoul(optarg, &end, 10));
if(end[0] != '\0' || truncSize < MinTruncSize || truncSize > MaxTruncSize)
truncSize = static_cast<uint>(std::stoul(std::string{optarg}, &endpos, 10));
if(endpos != optarg.size() || truncSize < MinTruncSize || truncSize > MaxTruncSize)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, MinTruncSize, MaxTruncSize);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected between {} to {}.",
optarg, opt, MinTruncSize, MaxTruncSize);
exit(EXIT_FAILURE);
}
break;
case 'd':
if(strcmp(optarg, "dataset") == 0)
if(optarg == "dataset"sv)
model = HM_Dataset;
else if(strcmp(optarg, "sphere") == 0)
else if(optarg == "sphere"sv)
model = HM_Sphere;
else
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected dataset or sphere.\n", optarg, opt);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected dataset or sphere.",
optarg, opt);
exit(EXIT_FAILURE);
}
break;
case 'c':
radius = strtod(optarg, &end);
if(end[0] != '\0' || radius < MinCustomRadius || radius > MaxCustomRadius)
radius = std::stod(std::string{optarg}, &endpos);
if(endpos != optarg.size() || radius < MinCustomRadius || radius > MaxCustomRadius)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %.2f to %.2f.\n", optarg, opt, MinCustomRadius, MaxCustomRadius);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected between {:.2f} to {:.2f}.",
optarg, opt, MinCustomRadius, MaxCustomRadius);
exit(EXIT_FAILURE);
}
break;
@@ -1446,11 +1485,11 @@ int main(int argc, char *argv[])
break;
case 'h':
PrintHelp(argv[0], stdout);
PrintHelp(arg0, stdout);
exit(EXIT_SUCCESS);
default: /* '?' */
PrintHelp(argv[0], stderr);
PrintHelp(arg0, stderr);
exit(EXIT_FAILURE);
}
}
@@ -1458,7 +1497,17 @@ int main(int argc, char *argv[])
const int ret{ProcessDefinition(inName, outRate, chanMode, farfield, numThreads, fftSize,
equalize, surface, limit, truncSize, model, radius, outName)};
if(!ret) return -1;
fprintf(stdout, "Operation completed.\n");
fmt::println("Operation completed.");
return EXIT_SUCCESS;
}
} /* namespace */
int main(int argc, char **argv)
{
assert(argc >= 0);
auto args = std::vector<std::string_view>(static_cast<unsigned int>(argc));
std::copy_n(argv, args.size(), args.begin());
return main(al::span{args});
}
+33 -27
View File
@@ -1,52 +1,46 @@
#ifndef MAKEMHR_H
#define MAKEMHR_H
#include <algorithm>
#include <array>
#include <complex>
#include <vector>
#include "alcomplex.h"
#include "alspan.h"
#include "polyphase_resampler.h"
// The maximum path length used when processing filenames.
enum { MAX_PATH_LEN = 256u };
inline constexpr auto MAX_PATH_LEN = 256u;
// The limit to the number of 'distances' listed in the data set definition.
// Must be less than 256
enum { MAX_FD_COUNT = 16u };
inline constexpr auto MAX_FD_COUNT = 16u;
// The limits to the number of 'elevations' listed in the data set definition.
// Must be less than 256.
enum {
MIN_EV_COUNT = 5u,
MAX_EV_COUNT = 181u
};
inline constexpr auto MIN_EV_COUNT = 5u;
inline constexpr auto MAX_EV_COUNT = 181u;
// The limits for each of the 'azimuths' listed in the data set definition.
// Must be less than 256.
enum {
MIN_AZ_COUNT = 1u,
MAX_AZ_COUNT = 255u
};
inline constexpr auto MIN_AZ_COUNT = 1u;
inline constexpr auto MAX_AZ_COUNT = 255u;
// The limits for the 'distance' from source to listener for each field in
// the definition file.
inline constexpr double MIN_DISTANCE{0.05};
inline constexpr double MAX_DISTANCE{2.50};
inline constexpr auto MIN_DISTANCE = 0.05;
inline constexpr auto MAX_DISTANCE = 2.50;
// The limits for the sample 'rate' metric in the data set definition and for
// resampling.
enum {
MIN_RATE = 32000u,
MAX_RATE = 96000u
};
inline constexpr auto MIN_RATE = 32000u;
inline constexpr auto MAX_RATE = 96000u;
// The limits for the HRIR 'points' metric in the data set definition.
enum {
MIN_POINTS = 16u,
MAX_POINTS = 8192u
};
inline constexpr auto MIN_POINTS = 16u;
inline constexpr auto MAX_POINTS = 8192u;
using uint = unsigned int;
@@ -78,7 +72,7 @@ struct HrirAzT {
double mAzimuth{0.0};
uint mIndex{0u};
std::array<double,2> mDelays{};
std::array<double*,2> mIrs{};
std::array<al::span<double>,2> mIrs{};
};
struct HrirEvT {
@@ -118,19 +112,31 @@ struct HrirDataT {
bool PrepareHrirData(const al::span<const double> distances,
const al::span<const uint,MAX_FD_COUNT> evCounts,
const al::span<const std::array<uint,MAX_EV_COUNT>,MAX_FD_COUNT> azCounts, HrirDataT *hData);
void MagnitudeResponse(const uint n, const complex_d *in, double *out);
/* Calculate the magnitude response of the given input. This is used in
* place of phase decomposition, since the phase residuals are discarded for
* minimum phase reconstruction. The mirrored half of the response is also
* discarded.
*/
inline void MagnitudeResponse(const al::span<const complex_d> in, const al::span<double> out)
{
static constexpr double Epsilon{1e-9};
for(size_t i{0};i < out.size();++i)
out[i] = std::max(std::abs(in[i]), Epsilon);
}
// Performs a forward FFT.
inline void FftForward(const uint n, complex_d *inout)
{ forward_fft(al::span{inout, n}); }
// Performs an inverse FFT.
// Performs an inverse FFT, scaling the result by the number of elements.
inline void FftInverse(const uint n, complex_d *inout)
{
inverse_fft(al::span{inout, n});
double f{1.0 / n};
for(uint i{0};i < n;i++)
inout[i] *= f;
const auto values = al::span{inout, n};
inverse_fft(values);
const double f{1.0 / n};
std::for_each(values.begin(), values.end(), [f](complex_d &value) { value *= f; });
}
// Performs linear interpolation.