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:
marauder2k7 2024-03-21 17:33:47 +00:00
parent 05a083ca6f
commit a745fc3757
1954 changed files with 431332 additions and 21037 deletions

View file

@ -15,7 +15,8 @@ if(Qt5Widgets_FOUND)
target_link_libraries(alsoft-config Qt5::Widgets)
target_include_directories(alsoft-config PRIVATE "${alsoft-config_BINARY_DIR}"
"${OpenAL_BINARY_DIR}")
set_target_properties(alsoft-config PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR})
set_target_properties(alsoft-config PROPERTIES ${DEFAULT_TARGET_PROPS}
RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR})
if(TARGET build_version)
add_dependencies(alsoft-config build_version)
endif()

View file

@ -21,7 +21,7 @@
namespace {
static const struct {
const struct {
char backend_name[16];
char full_string[32];
} backendList[] = {
@ -75,7 +75,7 @@ static const struct {
{ "", "" }
};
static const struct NameValuePair {
const struct NameValuePair {
const char name[64];
const char value[16];
} speakerModeList[] = {
@ -86,6 +86,7 @@ static const struct NameValuePair {
{ "5.1 Surround", "surround51" },
{ "6.1 Surround", "surround61" },
{ "7.1 Surround", "surround71" },
{ "3D7.1 Surround", "surround3d71" },
{ "Ambisonic, 1st Order", "ambi1" },
{ "Ambisonic, 2nd Order", "ambi2" },
@ -106,8 +107,8 @@ static const struct NameValuePair {
}, resamplerList[] = {
{ "Point", "point" },
{ "Linear", "linear" },
{ "Default (Linear)", "" },
{ "Cubic Spline", "cubic" },
{ "Default (Cubic Spline)", "" },
{ "11th order Sinc (fast)", "fast_bsinc12" },
{ "11th order Sinc", "bsinc12" },
{ "23rd order Sinc (fast)", "fast_bsinc24" },
@ -122,7 +123,7 @@ static const struct NameValuePair {
{ "", "" }
}, stereoEncList[] = {
{ "Default", "" },
{ "Pan Pot", "panpot" },
{ "Basic", "panpot" },
{ "UHJ", "uhj" },
{ "Binaural", "hrtf" },
@ -145,7 +146,7 @@ static const struct NameValuePair {
{ "", "" }
};
static QString getDefaultConfigName()
QString getDefaultConfigName()
{
#ifdef Q_OS_WIN32
static const char fname[] = "alsoft.ini";
@ -172,7 +173,7 @@ static QString getDefaultConfigName()
return fname;
}
static QString getBaseDataPath()
QString getBaseDataPath()
{
#ifdef Q_OS_WIN32
auto get_appdata_path = []() noexcept -> QString
@ -195,7 +196,7 @@ static QString getBaseDataPath()
return base;
}
static QStringList getAllDataPaths(const QString &append)
QStringList getAllDataPaths(const QString &append)
{
QStringList list;
list.append(getBaseDataPath());
@ -226,7 +227,7 @@ static QStringList getAllDataPaths(const QString &append)
}
template<size_t N>
static QString getValueFromName(const NameValuePair (&list)[N], const QString &str)
QString getValueFromName(const NameValuePair (&list)[N], const QString &str)
{
for(size_t i = 0;i < N-1;i++)
{
@ -237,7 +238,7 @@ static QString getValueFromName(const NameValuePair (&list)[N], const QString &s
}
template<size_t N>
static QString getNameFromValue(const NameValuePair (&list)[N], const QString &str)
QString getNameFromValue(const NameValuePair (&list)[N], const QString &str)
{
for(size_t i = 0;i < N-1;i++)
{
@ -307,7 +308,6 @@ MainWindow::MainWindow(QWidget *parent) :
for(count = 0;hrtfModeList[count].name[0];count++) {
}
ui->hrtfmodeSlider->setRange(0, count-1);
ui->hrtfStateComboBox->adjustSize();
#if !defined(HAVE_NEON) && !defined(HAVE_SSE)
ui->cpuExtDisabledLabel->move(ui->cpuExtDisabledLabel->x(), ui->cpuExtDisabledLabel->y() - 60);
@ -347,6 +347,12 @@ MainWindow::MainWindow(QWidget *parent) :
ui->enableSSE41CheckBox->setVisible(false);
#endif /* !SSE4.1 */
#endif
#ifndef ALSOFT_EAX
ui->enableEaxCheck->setChecked(Qt::Unchecked);
ui->enableEaxCheck->setEnabled(false);
ui->enableEaxCheck->setVisible(false);
#endif
mPeriodSizeValidator = new QIntValidator{64, 8192, this};
@ -397,7 +403,7 @@ MainWindow::MainWindow(QWidget *parent) :
connect(ui->decoderDistCompCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
connect(ui->decoderNFEffectsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
auto qdsb_vcd = static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged);
connect(ui->decoderNFRefDelaySpinBox, qdsb_vcd, this, &MainWindow::enableApplyButton);
connect(ui->decoderSpeakerDistSpinBox, qdsb_vcd, this, &MainWindow::enableApplyButton);
connect(ui->decoderQuadLineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
connect(ui->decoderQuadButton, &QPushButton::clicked, this, &MainWindow::selectQuadDecoderFile);
connect(ui->decoder51LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
@ -406,9 +412,10 @@ MainWindow::MainWindow(QWidget *parent) :
connect(ui->decoder61Button, &QPushButton::clicked, this, &MainWindow::select61DecoderFile);
connect(ui->decoder71LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
connect(ui->decoder71Button, &QPushButton::clicked, this, &MainWindow::select71DecoderFile);
connect(ui->decoder3D71LineEdit, &QLineEdit::textChanged, this, &MainWindow::enableApplyButton);
connect(ui->decoder3D71Button, &QPushButton::clicked, this, &MainWindow::select3D71DecoderFile);
connect(ui->preferredHrtfComboBox, qcb_cicint, this, &MainWindow::enableApplyButton);
connect(ui->hrtfStateComboBox, qcb_cicint, this, &MainWindow::enableApplyButton);
connect(ui->hrtfmodeSlider, &QSlider::valueChanged, this, &MainWindow::updateHrtfModeLabel);
connect(ui->hrtfAddButton, &QPushButton::clicked, this, &MainWindow::addHrtfFile);
@ -448,6 +455,7 @@ MainWindow::MainWindow(QWidget *parent) :
connect(ui->enableDedicatedCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
connect(ui->enablePitchShifterCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
connect(ui->enableVocalMorpherCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
connect(ui->enableEaxCheck, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
connect(ui->pulseAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
connect(ui->pulseAllowMovesCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
@ -455,6 +463,9 @@ MainWindow::MainWindow(QWidget *parent) :
connect(ui->pulseAdjLatencyCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
connect(ui->pwireAssumeAudioCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
connect(ui->pwireRtMixCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
connect(ui->wasapiResamplerCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
connect(ui->jackAutospawnCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
connect(ui->jackConnectPortsCheckBox, &QCheckBox::stateChanged, this, &MainWindow::enableApplyButton);
@ -752,13 +763,14 @@ void MainWindow::loadConfig(const QString &fname)
ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value("decoder/hq-mode")));
ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value("decoder/distance-comp")));
ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value("decoder/nfc")));
double refdelay{settings.value("decoder/nfc-ref-delay", 0.0).toDouble()};
ui->decoderNFRefDelaySpinBox->setValue(refdelay);
double speakerdist{settings.value("decoder/speaker-dist", 1.0).toDouble()};
ui->decoderSpeakerDistSpinBox->setValue(speakerdist);
ui->decoderQuadLineEdit->setText(settings.value("decoder/quad").toString());
ui->decoder51LineEdit->setText(settings.value("decoder/surround51").toString());
ui->decoder61LineEdit->setText(settings.value("decoder/surround61").toString());
ui->decoder71LineEdit->setText(settings.value("decoder/surround71").toString());
ui->decoder3D71LineEdit->setText(settings.value("decoder/surround3d71").toString());
QStringList disabledCpuExts{settings.value("disable-cpu-exts").toStringList()};
if(disabledCpuExts.size() == 1)
@ -804,14 +816,6 @@ void MainWindow::loadConfig(const QString &fname)
ui->hrtfFileList->addItems(hrtf_paths);
updateHrtfRemoveButton();
QString hrtfstate{settings.value("hrtf").toString().toLower()};
if(hrtfstate == "true")
ui->hrtfStateComboBox->setCurrentIndex(1);
else if(hrtfstate == "false")
ui->hrtfStateComboBox->setCurrentIndex(2);
else
ui->hrtfStateComboBox->setCurrentIndex(0);
ui->preferredHrtfComboBox->clear();
ui->preferredHrtfComboBox->addItem("- Any -");
if(ui->defaultHrtfPathsCheckBox->isChecked())
@ -840,7 +844,7 @@ void MainWindow::loadConfig(const QString &fname)
ui->enabledBackendList->clear();
ui->disabledBackendList->clear();
QStringList drivers{settings.value("drivers").toStringList()};
if(drivers.size() == 0)
if(drivers.empty())
ui->backendCheckBox->setChecked(true);
else
{
@ -922,14 +926,18 @@ void MainWindow::loadConfig(const QString &fname)
ui->enableDedicatedCheck->setChecked(!excludefx.contains("dedicated", Qt::CaseInsensitive));
ui->enablePitchShifterCheck->setChecked(!excludefx.contains("pshifter", Qt::CaseInsensitive));
ui->enableVocalMorpherCheck->setChecked(!excludefx.contains("vmorpher", Qt::CaseInsensitive));
if(ui->enableEaxCheck->isEnabled())
ui->enableEaxCheck->setChecked(getCheckState(settings.value("eax/enable")) != Qt::Unchecked);
ui->pulseAutospawnCheckBox->setCheckState(getCheckState(settings.value("pulse/spawn-server")));
ui->pulseAllowMovesCheckBox->setCheckState(getCheckState(settings.value("pulse/allow-moves")));
ui->pulseFixRateCheckBox->setCheckState(getCheckState(settings.value("pulse/fix-rate")));
ui->pulseAdjLatencyCheckBox->setCheckState(getCheckState(settings.value("pulse/adjust-latency")));
ui->pwireAssumeAudioCheckBox->setCheckState(settings.value("pipewire/assume-audio").toBool()
? Qt::Checked : Qt::Unchecked);
ui->pwireAssumeAudioCheckBox->setCheckState(getCheckState(settings.value("pipewire/assume-audio")));
ui->pwireRtMixCheckBox->setCheckState(getCheckState(settings.value("pipewire/rt-mix")));
ui->wasapiResamplerCheckBox->setCheckState(getCheckState(settings.value("wasapi/allow-resampler")));
ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value("jack/spawn-server")));
ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value("jack/connect-ports")));
@ -1016,15 +1024,16 @@ void MainWindow::saveConfig(const QString &fname) const
settings.setValue("decoder/hq-mode", getCheckValue(ui->decoderHQModeCheckBox));
settings.setValue("decoder/distance-comp", getCheckValue(ui->decoderDistCompCheckBox));
settings.setValue("decoder/nfc", getCheckValue(ui->decoderNFEffectsCheckBox));
double refdelay = ui->decoderNFRefDelaySpinBox->value();
settings.setValue("decoder/nfc-ref-delay",
(refdelay > 0.0) ? QString::number(refdelay) : QString{}
double speakerdist{ui->decoderSpeakerDistSpinBox->value()};
settings.setValue("decoder/speaker-dist",
(speakerdist != 1.0) ? QString::number(speakerdist) : QString{}
);
settings.setValue("decoder/quad", ui->decoderQuadLineEdit->text());
settings.setValue("decoder/surround51", ui->decoder51LineEdit->text());
settings.setValue("decoder/surround61", ui->decoder61LineEdit->text());
settings.setValue("decoder/surround71", ui->decoder71LineEdit->text());
settings.setValue("decoder/surround3d71", ui->decoder3D71LineEdit->text());
QStringList strlist;
if(!ui->enableSSECheckBox->isChecked())
@ -1041,13 +1050,6 @@ void MainWindow::saveConfig(const QString &fname) const
settings.setValue("hrtf-mode", hrtfModeList[ui->hrtfmodeSlider->value()].value);
if(ui->hrtfStateComboBox->currentIndex() == 1)
settings.setValue("hrtf", "true");
else if(ui->hrtfStateComboBox->currentIndex() == 2)
settings.setValue("hrtf", "false");
else
settings.setValue("hrtf", QString{});
if(ui->preferredHrtfComboBox->currentIndex() == 0)
settings.setValue("default-hrtf", QString{});
else
@ -1089,7 +1091,7 @@ void MainWindow::saveConfig(const QString &fname) const
}
}
}
if(strlist.size() == 0 && !ui->backendCheckBox->isChecked())
if(strlist.empty() && !ui->backendCheckBox->isChecked())
strlist.append("-all");
else if(ui->backendCheckBox->isChecked())
strlist.append(QString{});
@ -1134,15 +1136,20 @@ void MainWindow::saveConfig(const QString &fname) const
if(!ui->enableVocalMorpherCheck->isChecked())
strlist.append("vmorpher");
settings.setValue("excludefx", strlist.join(QChar{','}));
settings.setValue("eax/enable",
(!ui->enableEaxCheck->isEnabled() || ui->enableEaxCheck->isChecked())
? QString{/*"true"*/} : QString{"false"});
settings.setValue("pipewire/assume-audio", getCheckValue(ui->pwireAssumeAudioCheckBox));
settings.setValue("pipewire/rt-mix", getCheckValue(ui->pwireRtMixCheckBox));
settings.setValue("wasapi/allow-resampler", getCheckValue(ui->wasapiResamplerCheckBox));
settings.setValue("pulse/spawn-server", getCheckValue(ui->pulseAutospawnCheckBox));
settings.setValue("pulse/allow-moves", getCheckValue(ui->pulseAllowMovesCheckBox));
settings.setValue("pulse/fix-rate", getCheckValue(ui->pulseFixRateCheckBox));
settings.setValue("pulse/adjust-latency", getCheckValue(ui->pulseAdjLatencyCheckBox));
settings.setValue("pipewire/assume-audio", ui->pwireAssumeAudioCheckBox->isChecked()
? QString{"true"} : QString{/*"false"*/});
settings.setValue("jack/spawn-server", getCheckValue(ui->jackAutospawnCheckBox));
settings.setValue("jack/connect-ports", getCheckValue(ui->jackConnectPortsCheckBox));
settings.setValue("jack/rt-mix", getCheckValue(ui->jackRtMixCheckBox));
@ -1240,6 +1247,8 @@ void MainWindow::select61DecoderFile()
{ selectDecoderFile(ui->decoder61LineEdit, "Select 6.1 Surround Decoder");}
void MainWindow::select71DecoderFile()
{ selectDecoderFile(ui->decoder71LineEdit, "Select 7.1 Surround Decoder");}
void MainWindow::select3D71DecoderFile()
{ selectDecoderFile(ui->decoder3D71LineEdit, "Select 3D7.1 Surround Decoder");}
void MainWindow::selectDecoderFile(QLineEdit *line, const char *caption)
{
QString dir{line->text()};
@ -1313,7 +1322,7 @@ void MainWindow::removeHrtfFile()
void MainWindow::updateHrtfRemoveButton()
{
ui->hrtfRemoveButton->setEnabled(ui->hrtfFileList->selectedItems().size() != 0);
ui->hrtfRemoveButton->setEnabled(!ui->hrtfFileList->selectedItems().empty());
}
void MainWindow::showEnabledBackendMenu(QPoint pt)
@ -1324,7 +1333,7 @@ void MainWindow::showEnabledBackendMenu(QPoint pt)
QMenu ctxmenu;
QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")};
if(ui->enabledBackendList->selectedItems().size() == 0)
if(ui->enabledBackendList->selectedItems().empty())
removeAction->setEnabled(false);
ctxmenu.addSeparator();
for(size_t i = 0;backendList[i].backend_name[0];i++)
@ -1332,8 +1341,8 @@ void MainWindow::showEnabledBackendMenu(QPoint pt)
QString backend{backendList[i].full_string};
QAction *action{ctxmenu.addAction(QString("Add ")+backend)};
actionMap[action] = backend;
if(ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0 ||
ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0)
if(!ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).empty() ||
!ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).empty())
action->setEnabled(false);
}
@ -1362,7 +1371,7 @@ void MainWindow::showDisabledBackendMenu(QPoint pt)
QMenu ctxmenu;
QAction *removeAction{ctxmenu.addAction(QIcon::fromTheme("list-remove"), "Remove")};
if(ui->disabledBackendList->selectedItems().size() == 0)
if(ui->disabledBackendList->selectedItems().empty())
removeAction->setEnabled(false);
ctxmenu.addSeparator();
for(size_t i = 0;backendList[i].backend_name[0];i++)
@ -1370,8 +1379,8 @@ void MainWindow::showDisabledBackendMenu(QPoint pt)
QString backend{backendList[i].full_string};
QAction *action{ctxmenu.addAction(QString("Add ")+backend)};
actionMap[action] = backend;
if(ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0 ||
ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).size() != 0)
if(!ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).empty() ||
!ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).empty())
action->setEnabled(false);
}

View file

@ -39,6 +39,7 @@ private slots:
void select51DecoderFile();
void select61DecoderFile();
void select71DecoderFile();
void select3D71DecoderFile();
void updateJackBufferSizeEdit(int size);
void updateJackBufferSizeSlider();

View file

@ -116,9 +116,9 @@ float and converted to the output sample type as needed.</string>
</rect>
</property>
<property name="toolTip">
<string>The output channel configuration. Note that not all backends
can properly detect the channel configuration and may default
to stereo output.</string>
<string>The default output channel configuration. Note that not all
backends can properly detect the channel configuration and
may default to stereo output.</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
@ -436,7 +436,7 @@ frames needed for each mixing update.</string>
</rect>
</property>
<property name="toolTip">
<string>Pan Pot uses standard amplitude panning (aka
<string>Basic uses standard amplitude panning (aka
pair-wise, stereo pair, etc).
UHJ creates a stereo-compatible two-channel
@ -444,8 +444,8 @@ UHJ mix, which encodes some surround sound
information into stereo output that can be
decoded with a surround sound receiver.
Binaural applies HRTF filters to create a sense
of 3D space with headphones.</string>
Binaural applies HRTF filters to create an
illusion of 3D placement with headphones.</string>
</property>
</widget>
<widget class="QLabel" name="label_19">
@ -625,9 +625,7 @@ quantization with low-level whitenoise.</string>
<string>Enables high-quality ambisonic rendering. This mode is
capable of frequency-dependent processing, creating a
better reproduction of 3D sound rendering over
surround sound speakers. Enabling this also requires
specifying decoder configuration files for the
appropriate speaker configuration you intend to use.</string>
surround sound speakers.</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
@ -670,9 +668,9 @@ configuration file.</string>
<property name="geometry">
<rect>
<x>-10</x>
<y>160</y>
<y>140</y>
<width>551</width>
<height>161</height>
<height>231</height>
</rect>
</property>
<property name="title">
@ -684,10 +682,10 @@ configuration file.</string>
<widget class="QLineEdit" name="decoderQuadLineEdit">
<property name="geometry">
<rect>
<x>120</x>
<x>130</x>
<y>30</y>
<width>311</width>
<height>21</height>
<width>301</width>
<height>25</height>
</rect>
</property>
</widget>
@ -696,8 +694,8 @@ configuration file.</string>
<rect>
<x>20</x>
<y>30</y>
<width>91</width>
<height>21</height>
<width>101</width>
<height>25</height>
</rect>
</property>
<property name="text">
@ -713,7 +711,7 @@ configuration file.</string>
<x>440</x>
<y>30</y>
<width>91</width>
<height>21</height>
<height>25</height>
</rect>
</property>
<property name="text">
@ -723,10 +721,10 @@ configuration file.</string>
<widget class="QLineEdit" name="decoder51LineEdit">
<property name="geometry">
<rect>
<x>120</x>
<y>60</y>
<width>311</width>
<height>21</height>
<x>130</x>
<y>70</y>
<width>301</width>
<height>25</height>
</rect>
</property>
</widget>
@ -734,9 +732,9 @@ configuration file.</string>
<property name="geometry">
<rect>
<x>440</x>
<y>60</y>
<y>70</y>
<width>91</width>
<height>21</height>
<height>25</height>
</rect>
</property>
<property name="text">
@ -747,9 +745,9 @@ configuration file.</string>
<property name="geometry">
<rect>
<x>20</x>
<y>60</y>
<width>91</width>
<height>21</height>
<y>70</y>
<width>101</width>
<height>25</height>
</rect>
</property>
<property name="text">
@ -763,9 +761,9 @@ configuration file.</string>
<property name="geometry">
<rect>
<x>20</x>
<y>90</y>
<width>91</width>
<height>21</height>
<y>110</y>
<width>101</width>
<height>25</height>
</rect>
</property>
<property name="text">
@ -778,10 +776,10 @@ configuration file.</string>
<widget class="QLineEdit" name="decoder61LineEdit">
<property name="geometry">
<rect>
<x>120</x>
<y>90</y>
<width>311</width>
<height>21</height>
<x>130</x>
<y>110</y>
<width>301</width>
<height>25</height>
</rect>
</property>
</widget>
@ -789,9 +787,9 @@ configuration file.</string>
<property name="geometry">
<rect>
<x>440</x>
<y>90</y>
<y>110</y>
<width>91</width>
<height>21</height>
<height>25</height>
</rect>
</property>
<property name="text">
@ -802,9 +800,9 @@ configuration file.</string>
<property name="geometry">
<rect>
<x>440</x>
<y>120</y>
<y>150</y>
<width>91</width>
<height>21</height>
<height>25</height>
</rect>
</property>
<property name="text">
@ -814,10 +812,10 @@ configuration file.</string>
<widget class="QLineEdit" name="decoder71LineEdit">
<property name="geometry">
<rect>
<x>120</x>
<y>120</y>
<width>311</width>
<height>21</height>
<x>130</x>
<y>150</y>
<width>301</width>
<height>25</height>
</rect>
</property>
</widget>
@ -825,9 +823,9 @@ configuration file.</string>
<property name="geometry">
<rect>
<x>20</x>
<y>120</y>
<width>91</width>
<height>21</height>
<y>150</y>
<width>101</width>
<height>25</height>
</rect>
</property>
<property name="text">
@ -837,6 +835,45 @@ configuration file.</string>
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QLabel" name="label_33">
<property name="geometry">
<rect>
<x>20</x>
<y>190</y>
<width>101</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>3D7.1 Surround:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QLineEdit" name="decoder3D71LineEdit">
<property name="geometry">
<rect>
<x>130</x>
<y>190</y>
<width>301</width>
<height>25</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="decoder3D71Button">
<property name="geometry">
<rect>
<x>440</x>
<y>190</y>
<width>91</width>
<height>25</height>
</rect>
</property>
<property name="text">
<string>Browse...</string>
</property>
</widget>
</widget>
<widget class="QCheckBox" name="decoderNFEffectsCheckBox">
<property name="geometry">
@ -869,60 +906,63 @@ are set in the decoder configuration file.</string>
<widget class="QWidget" name="widget_3" native="true">
<property name="geometry">
<rect>
<x>-10</x>
<x>30</x>
<y>110</y>
<width>281</width>
<width>471</width>
<height>21</height>
</rect>
</property>
<property name="toolTip">
<string>The reference delay value for ambisonic output. When
Channels is set to one of the Ambisonic formats, this
option enables NFC-HOA output with the specified
Reference Delay parameter. The specified value can then
be shared with an appropriate NFC-HOA decoder to
reproduce correct near-field effects. Keep in mind that
despite being designed for higher-order ambisonics, this
applies to first-order output all the same. When left unset,
normal output is created with no near-field simulation.</string>
<string>Specifies the speaker distance in meters, used by the near-field control
filters with surround sound output. For ambisonic output modes, this value
is the basis for the NFC-HOA Reference Delay parameter (calculated as
delay_seconds = speaker_dist/343.3). This value is not used when a decoder
configuration is set for the output mode (since they specify the per-speaker
distances, overriding this setting), or when the NFC filters are off.</string>
</property>
<widget class="QLabel" name="label_27">
<property name="geometry">
<rect>
<x>20</x>
<x>45</x>
<y>0</y>
<width>171</width>
<width>111</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Near-Field Reference Delay:</string>
<string>Speaker Distance:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QDoubleSpinBox" name="decoderNFRefDelaySpinBox">
<widget class="QDoubleSpinBox" name="decoderSpeakerDistSpinBox">
<property name="geometry">
<rect>
<x>200</x>
<x>165</x>
<y>0</y>
<width>81</width>
<width>101</width>
<height>21</height>
</rect>
</property>
<property name="suffix">
<string>sec</string>
<string> meters</string>
</property>
<property name="decimals">
<number>4</number>
<number>2</number>
</property>
<property name="minimum">
<double>0.100000000000000</double>
</property>
<property name="maximum">
<double>1000.000000000000000</double>
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</widget>
</widget>
@ -1050,54 +1090,6 @@ listed above.</string>
</widget>
</widget>
</widget>
<widget class="QLabel" name="label_16">
<property name="geometry">
<rect>
<x>50</x>
<y>60</y>
<width>71</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>HRTF Mode:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QComboBox" name="hrtfStateComboBox">
<property name="geometry">
<rect>
<x>130</x>
<y>60</y>
<width>161</width>
<height>31</height>
</rect>
</property>
<property name="toolTip">
<string>Forces HRTF processing on or off, or leaves it to the
application or system to determine if it should be used.</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContentsOnFirstShow</enum>
</property>
<item>
<property name="text">
<string>Application preference</string>
</property>
</item>
<item>
<property name="text">
<string>Force on</string>
</property>
</item>
<item>
<property name="text">
<string>Force off</string>
</property>
</item>
</widget>
<widget class="QLabel" name="label_12">
<property name="geometry">
<rect>
@ -1222,12 +1214,17 @@ application or system to determine if it should be used.</string>
</item>
<item>
<property name="text">
<string>PulseAudio</string>
<string>PipeWire</string>
</property>
</item>
<item>
<property name="text">
<string>PipeWire</string>
<string>WASAPI</string>
</property>
</item>
<item>
<property name="text">
<string>PulseAudio</string>
</property>
</item>
<item>
@ -1345,7 +1342,79 @@ duplicated names are ignored.</string>
</property>
</widget>
</widget>
<widget class="QWidget" name="page_1">
<widget class="QCheckBox" name="pwireAssumeAudioCheckBox">
<property name="geometry">
<rect>
<x>20</x>
<y>10</y>
<width>161</width>
<height>21</height>
</rect>
</property>
<property name="toolTip">
<string>Assumes PipeWire has support for audio, allowing
the backend to initialize even when no audio devices
are reported.</string>
</property>
<property name="text">
<string>Assume audio support</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
<widget class="QCheckBox" name="pwireRtMixCheckBox">
<property name="geometry">
<rect>
<x>20</x>
<y>40</y>
<width>161</width>
<height>21</height>
</rect>
</property>
<property name="toolTip">
<string>Renders samples directly in the real-time
processing callback. This allows for lower
latency and less overall CPU utilization, but
can increase the risk of underruns when
increasing the amount of processing the
mixer needs to do.</string>
</property>
<property name="text">
<string>Real-time Mixing</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</widget>
<widget class="QWidget" name="page_2">
<widget class="QCheckBox" name="wasapiResamplerCheckBox">
<property name="geometry">
<rect>
<x>20</x>
<y>10</y>
<width>191</width>
<height>21</height>
</rect>
</property>
<property name="toolTip">
<string>Specifies whether to allow an extra resampler pass on the output. Enabling
this will allow the playback device to be set to a different sample rate
than the actual output can accept, causing the backend to apply its own
resampling pass after OpenAL Soft mixes the sources and processes effects
for output.</string>
</property>
<property name="text">
<string>Allow Resampler</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</widget>
<widget class="QWidget" name="page_8">
<widget class="QCheckBox" name="pulseAutospawnCheckBox">
<property name="geometry">
<rect>
@ -1432,26 +1501,6 @@ drop-outs.</string>
</property>
</widget>
</widget>
<widget class="QWidget" name="page_8">
<widget class="QCheckBox" name="pwireAssumeAudioCheckBox">
<property name="geometry">
<rect>
<x>20</x>
<y>10</y>
<width>161</width>
<height>21</height>
</rect>
</property>
<property name="toolTip">
<string>Assumes PipeWire has support for audio, allowing
the backend to initialize even when no audio devices
are reported.</string>
</property>
<property name="text">
<string>Assume audio support</string>
</property>
</widget>
</widget>
<widget class="QWidget" name="page_7">
<widget class="QCheckBox" name="jackAutospawnCheckBox">
<property name="geometry">
@ -2110,7 +2159,7 @@ be useful for preventing those extensions from being used.</string>
<property name="geometry">
<rect>
<x>10</x>
<y>100</y>
<y>60</y>
<width>511</width>
<height>241</height>
</rect>
@ -2516,6 +2565,22 @@ added by the ALC_EXT_DEDICATED extension.</string>
</property>
</item>
</widget>
<widget class="QCheckBox" name="enableEaxCheck">
<property name="geometry">
<rect>
<x>30</x>
<y>320</y>
<width>231</width>
<height>21</height>
</rect>
</property>
<property name="toolTip">
<string>Enables legacy EAX API support.</string>
</property>
<property name="text">
<string>Enable EAX API support</string>
</property>
</widget>
</widget>
</widget>
<widget class="QPushButton" name="closeCancelButton">

View file

@ -37,8 +37,11 @@
#include <vector>
#include "alfstream.h"
#include "aloptional.h"
#include "alspan.h"
#include "alstring.h"
#include "makemhr.h"
#include "polyphase_resampler.h"
#include "mysofa.h"
@ -1236,7 +1239,8 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc
double distances[MAX_FD_COUNT];
uint fdCount = 0;
uint evCounts[MAX_FD_COUNT];
std::vector<uint> azCounts(MAX_FD_COUNT * MAX_EV_COUNT);
auto azCounts = std::vector<std::array<uint,MAX_EV_COUNT>>(MAX_FD_COUNT);
for(auto &azs : azCounts) azs.fill(0u);
TrIndication(tr, &line, &col);
while(TrIsIdent(tr))
@ -1385,7 +1389,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc
{
if(!TrReadInt(tr, MIN_AZ_COUNT, MAX_AZ_COUNT, &intVal))
return 0;
azCounts[(count * MAX_EV_COUNT) + evCounts[count]++] = static_cast<uint>(intVal);
azCounts[count][evCounts[count]++] = static_cast<uint>(intVal);
if(TrIsOperator(tr, ","))
{
if(evCounts[count] >= MAX_EV_COUNT)
@ -1402,7 +1406,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc
TrErrorAt(tr, line, col, "Did not reach the minimum of %d azimuth counts.\n", MIN_EV_COUNT);
return 0;
}
if(azCounts[count * MAX_EV_COUNT] != 1 || azCounts[(count * MAX_EV_COUNT) + evCounts[count] - 1] != 1)
if(azCounts[count][0] != 1 || azCounts[count][evCounts[count] - 1] != 1)
{
TrError(tr, "Poles are not singular for field %d.\n", count - 1);
return 0;
@ -1447,7 +1451,8 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc
}
if(hData->mChannelType == CT_NONE)
hData->mChannelType = CT_MONO;
if(!PrepareHrirData(fdCount, distances, evCounts, azCounts.data(), hData))
const auto azs = al::as_span(azCounts).first<MAX_FD_COUNT>();
if(!PrepareHrirData({distances, fdCount}, evCounts, azs, hData))
{
fprintf(stderr, "Error: Out of memory.\n");
exit(-1);
@ -1460,9 +1465,9 @@ static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi,
{
int intVal;
if(hData->mFdCount > 1)
if(hData->mFds.size() > 1)
{
if(!TrReadInt(tr, 0, static_cast<int>(hData->mFdCount) - 1, &intVal))
if(!TrReadInt(tr, 0, static_cast<int>(hData->mFds.size()-1), &intVal))
return 0;
*fi = static_cast<uint>(intVal);
if(!TrReadOperator(tr, ","))
@ -1472,12 +1477,12 @@ static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi,
{
*fi = 0;
}
if(!TrReadInt(tr, 0, static_cast<int>(hData->mFds[*fi].mEvCount) - 1, &intVal))
if(!TrReadInt(tr, 0, static_cast<int>(hData->mFds[*fi].mEvs.size()-1), &intVal))
return 0;
*ei = static_cast<uint>(intVal);
if(!TrReadOperator(tr, ","))
return 0;
if(!TrReadInt(tr, 0, static_cast<int>(hData->mFds[*fi].mEvs[*ei].mAzCount) - 1, &intVal))
if(!TrReadInt(tr, 0, static_cast<int>(hData->mFds[*fi].mEvs[*ei].mAzs.size()-1), &intVal))
return 0;
*ai = static_cast<uint>(intVal);
return 1;
@ -1707,14 +1712,11 @@ static int MatchTargetEar(const char *ident)
// Calculate the onset time of an HRIR and average it with any existing
// timing for its field, elevation, azimuth, and ear.
static double AverageHrirOnset(const uint rate, const uint n, const double *hrir, const double f, const double onset)
static constexpr int OnsetRateMultiple{10};
static double AverageHrirOnset(PPhaseResampler &rs, al::span<double> upsampled, const uint rate,
const uint n, const double *hrir, const double f, const double onset)
{
std::vector<double> upsampled(10 * n);
{
PPhaseResampler rs;
rs.init(rate, 10 * rate);
rs.process(n, hrir, 10 * n, upsampled.data());
}
rs.process(n, hrir, static_cast<uint>(upsampled.size()), upsampled.data());
auto abs_lt = [](const double &lhs, const double &rhs) -> bool
{ return std::abs(lhs) < std::abs(rhs); };
@ -1731,9 +1733,9 @@ static void AverageHrirMagnitude(const uint points, const uint n, const double *
std::vector<double> r(n);
for(i = 0;i < points;i++)
h[i] = complex_d{hrir[i], 0.0};
h[i] = hrir[i];
for(;i < n;i++)
h[i] = complex_d{0.0, 0.0};
h[i] = 0.0;
FftForward(n, h.data());
MagnitudeResponse(n, h.data(), r.data());
for(i = 0;i < m;i++)
@ -1741,18 +1743,29 @@ static void AverageHrirMagnitude(const uint points, const uint n, const double *
}
// Process the list of sources in the data set definition.
static int ProcessSources(TokenReaderT *tr, HrirDataT *hData)
static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate)
{
uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize);
double *hrirs = hData->mHrirsBase.data();
std::vector<double> hrir(hData->mIrPoints);
auto hrir = std::make_unique<double[]>(hData->mIrSize);
uint line, col, fi, ei, ai;
int count;
std::vector<double> onsetSamples(OnsetRateMultiple * hData->mIrPoints);
PPhaseResampler onsetResampler;
onsetResampler.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate);
al::optional<PPhaseResampler> resampler;
if(outRate && outRate != hData->mIrRate)
resampler.emplace().init(hData->mIrRate, outRate);
const double rateScale{outRate ? static_cast<double>(outRate) / hData->mIrRate : 1.0};
const uint irPoints{outRate
? std::min(static_cast<uint>(std::ceil(hData->mIrPoints*rateScale)), hData->mIrPoints)
: hData->mIrPoints};
printf("Loading sources...");
fflush(stdout);
count = 0;
int count{0};
while(TrIsOperator(tr, "["))
{
double factor[2]{ 1.0, 1.0 };
@ -1834,45 +1847,53 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData)
else
aer[0] = std::fmod(360.0f - aer[0], 360.0f);
for(fi = 0;fi < hData->mFdCount;fi++)
{
double delta = aer[2] - hData->mFds[fi].mDistance;
if(std::abs(delta) < 0.001) break;
}
if(fi >= hData->mFdCount)
auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(),
[&aer](const HrirFdT &fld) -> bool
{ return (std::abs(aer[2] - fld.mDistance) < 0.001); });
if(field == hData->mFds.cend())
continue;
fi = static_cast<uint>(std::distance(hData->mFds.cbegin(), field));
double ef{(90.0 + aer[1]) / 180.0 * (hData->mFds[fi].mEvCount - 1)};
const double evscale{180.0 / static_cast<double>(field->mEvs.size()-1)};
double ef{(90.0 + aer[1]) / evscale};
ei = static_cast<uint>(std::round(ef));
ef = (ef - ei) * 180.0 / (hData->mFds[fi].mEvCount - 1);
ef = (ef - ei) * evscale;
if(std::abs(ef) >= 0.1)
continue;
double af{aer[0] / 360.0 * hData->mFds[fi].mEvs[ei].mAzCount};
const double azscale{360.0 / static_cast<double>(field->mEvs[ei].mAzs.size())};
double af{aer[0] / azscale};
ai = static_cast<uint>(std::round(af));
af = (af - ai) * 360.0 / hData->mFds[fi].mEvs[ei].mAzCount;
ai = ai % hData->mFds[fi].mEvs[ei].mAzCount;
af = (af - ai) * azscale;
ai %= static_cast<uint>(field->mEvs[ei].mAzs.size());
if(std::abs(af) >= 0.1)
continue;
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
HrirAzT *azd = &field->mEvs[ei].mAzs[ai];
if(azd->mIrs[0] != nullptr)
{
TrErrorAt(tr, line, col, "Redefinition of source [ %d, %d, %d ].\n", fi, ei, ai);
return 0;
}
ExtractSofaHrir(sofa, si, 0, src.mOffset, hData->mIrPoints, hrir.data());
ExtractSofaHrir(sofa, si, 0, src.mOffset, hData->mIrPoints, hrir.get());
azd->mIrs[0] = &hrirs[hData->mIrSize * azd->mIndex];
azd->mDelays[0] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[0]);
AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir.data(), 1.0, azd->mIrs[0]);
azd->mDelays[0] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate,
hData->mIrPoints, hrir.get(), 1.0, azd->mDelays[0]);
if(resampler)
resampler->process(hData->mIrPoints, hrir.get(), hData->mIrSize, hrir.get());
AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.get(), 1.0, azd->mIrs[0]);
if(src.mChannel == 1)
{
ExtractSofaHrir(sofa, si, 1, src.mOffset, hData->mIrPoints, hrir.data());
ExtractSofaHrir(sofa, si, 1, src.mOffset, hData->mIrPoints, hrir.get());
azd->mIrs[1] = &hrirs[hData->mIrSize * (hData->mIrCount + azd->mIndex)];
azd->mDelays[1] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0, azd->mDelays[1]);
AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir.data(), 1.0, azd->mIrs[1]);
azd->mDelays[1] = AverageHrirOnset(onsetResampler, onsetSamples,
hData->mIrRate, hData->mIrPoints, hrir.get(), 1.0, azd->mDelays[1]);
if(resampler)
resampler->process(hData->mIrPoints, hrir.get(), hData->mIrSize,
hrir.get());
AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.get(), 1.0, azd->mIrs[1]);
}
// TODO: Since some SOFA files contain minimum phase HRIRs,
@ -1911,7 +1932,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData)
printf("\rLoading sources... %d file%s", count, (count==1)?"":"s");
fflush(stdout);
if(!LoadSource(&src, hData->mIrRate, hData->mIrPoints, hrir.data()))
if(!LoadSource(&src, hData->mIrRate, hData->mIrPoints, hrir.get()))
return 0;
uint ti{0};
@ -1929,8 +1950,12 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData)
}
}
azd->mIrs[ti] = &hrirs[hData->mIrSize * (ti * hData->mIrCount + azd->mIndex)];
azd->mDelays[ti] = AverageHrirOnset(hData->mIrRate, hData->mIrPoints, hrir.data(), 1.0 / factor[ti], azd->mDelays[ti]);
AverageHrirMagnitude(hData->mIrPoints, hData->mFftSize, hrir.data(), 1.0 / factor[ti], azd->mIrs[ti]);
azd->mDelays[ti] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate,
hData->mIrPoints, hrir.get(), 1.0 / factor[ti], azd->mDelays[ti]);
if(resampler)
resampler->process(hData->mIrPoints, hrir.get(), hData->mIrSize, hrir.get());
AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.get(), 1.0 / factor[ti],
azd->mIrs[ti]);
factor[ti] += 1.0;
if(!TrIsOperator(tr, "+"))
break;
@ -1951,28 +1976,35 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData)
}
}
printf("\n");
for(fi = 0;fi < hData->mFdCount;fi++)
hrir = nullptr;
if(resampler)
{
for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
hData->mIrRate = outRate;
hData->mIrPoints = irPoints;
resampler.reset();
}
for(fi = 0;fi < hData->mFds.size();fi++)
{
for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(ai = 0;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(ai < hData->mFds[fi].mEvs[ei].mAzCount)
if(ai < hData->mFds[fi].mEvs[ei].mAzs.size())
break;
}
if(ei >= hData->mFds[fi].mEvCount)
if(ei >= hData->mFds[fi].mEvs.size())
{
TrError(tr, "Missing source references [ %d, *, * ].\n", fi);
return 0;
}
hData->mFds[fi].mEvStart = ei;
for(;ei < hData->mFds[fi].mEvCount;ei++)
for(;ei < hData->mFds[fi].mEvs.size();ei++)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
{
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
@ -1986,11 +2018,11 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData)
}
for(uint ti{0};ti < channels;ti++)
{
for(fi = 0;fi < hData->mFdCount;fi++)
for(fi = 0;fi < hData->mFds.size();fi++)
{
for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
{
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
@ -2012,14 +2044,14 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData)
bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount,
const char *filename, const uint fftSize, const uint truncSize, const ChannelModeT chanMode,
HrirDataT *hData)
const char *filename, const uint fftSize, const uint truncSize, const uint outRate,
const ChannelModeT chanMode, HrirDataT *hData)
{
TokenReaderT tr{istream};
TrSetup(startbytes, startbytecount, filename, &tr);
if(!ProcessMetrics(&tr, fftSize, truncSize, chanMode, hData)
|| !ProcessSources(&tr, hData))
|| !ProcessSources(&tr, hData, outRate))
return false;
return true;

View file

@ -7,7 +7,7 @@
bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount,
const char *filename, const uint fftSize, const uint truncSize, const ChannelModeT chanMode,
HrirDataT *hData);
const char *filename, const uint fftSize, const uint truncSize, const uint outRate,
const ChannelModeT chanMode, HrirDataT *hData);
#endif /* LOADDEF_H */

View file

@ -37,6 +37,8 @@
#include <thread>
#include <vector>
#include "aloptional.h"
#include "alspan.h"
#include "makemhr.h"
#include "polyphase_resampler.h"
#include "sofa-support.h"
@ -65,7 +67,8 @@ static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData)
double distances[MAX_FD_COUNT]{};
uint evCounts[MAX_FD_COUNT]{};
auto azCounts = std::vector<uint>(MAX_FD_COUNT*MAX_EV_COUNT, 0u);
auto azCounts = std::vector<std::array<uint,MAX_EV_COUNT>>(MAX_FD_COUNT);
for(auto &azs : azCounts) azs.fill(0u);
uint fi{0u}, ir_total{0u};
for(const auto &field : fds)
@ -74,21 +77,22 @@ static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData)
evCounts[fi] = field.mEvCount;
for(uint ei{0u};ei < field.mEvStart;ei++)
azCounts[fi*MAX_EV_COUNT + ei] = field.mAzCounts[field.mEvCount-ei-1];
azCounts[fi][ei] = field.mAzCounts[field.mEvCount-ei-1];
for(uint ei{field.mEvStart};ei < field.mEvCount;ei++)
{
azCounts[fi*MAX_EV_COUNT + ei] = field.mAzCounts[ei];
azCounts[fi][ei] = field.mAzCounts[ei];
ir_total += field.mAzCounts[ei];
}
++fi;
}
fprintf(stdout, "Using %u of %u IRs.\n", ir_total, m);
return PrepareHrirData(fi, distances, evCounts, azCounts.data(), hData) != 0;
const auto azs = al::as_span(azCounts).first<MAX_FD_COUNT>();
return PrepareHrirData({distances, fi}, evCounts, azs, hData);
}
bool PrepareSampleRate(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
float GetSampleRate(MYSOFA_HRTF *sofaHrtf)
{
const char *srate_dim{nullptr};
const char *srate_units{nullptr};
@ -101,7 +105,7 @@ bool PrepareSampleRate(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
if(srate_dim)
{
fprintf(stderr, "Duplicate SampleRate.DIMENSION_LIST\n");
return false;
return 0.0f;
}
srate_dim = srate_attrs->value;
}
@ -110,7 +114,7 @@ bool PrepareSampleRate(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
if(srate_units)
{
fprintf(stderr, "Duplicate SampleRate.Units\n");
return false;
return 0.0f;
}
srate_units = srate_attrs->value;
}
@ -122,35 +126,40 @@ bool PrepareSampleRate(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
if(!srate_dim)
{
fprintf(stderr, "Missing sample rate dimensions\n");
return false;
return 0.0f;
}
if(srate_dim != std::string{"I"})
{
fprintf(stderr, "Unsupported sample rate dimensions: %s\n", srate_dim);
return false;
return 0.0f;
}
if(!srate_units)
{
fprintf(stderr, "Missing sample rate unit type\n");
return false;
return 0.0f;
}
if(srate_units != std::string{"hertz"})
{
fprintf(stderr, "Unsupported sample rate unit type: %s\n", srate_units);
return false;
return 0.0f;
}
/* I dimensions guarantees 1 element, so just extract it. */
hData->mIrRate = static_cast<uint>(srate_array->values[0] + 0.5f);
if(hData->mIrRate < MIN_RATE || hData->mIrRate > MAX_RATE)
if(srate_array->values[0] < MIN_RATE || srate_array->values[0] > MAX_RATE)
{
fprintf(stderr, "Sample rate out of range: %u (expected %u to %u)", hData->mIrRate,
fprintf(stderr, "Sample rate out of range: %f (expected %u to %u)", srate_array->values[0],
MIN_RATE, MAX_RATE);
return false;
return 0.0f;
}
return true;
return srate_array->values[0];
}
bool PrepareDelay(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
enum class DelayType : uint8_t {
None,
I_R, /* [1][Channels] */
M_R, /* [HRIRs][Channels] */
Invalid,
};
DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf)
{
const char *delay_dim{nullptr};
MYSOFA_ARRAY *delay_array{&sofaHrtf->DataDelay};
@ -162,38 +171,27 @@ bool PrepareDelay(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
if(delay_dim)
{
fprintf(stderr, "Duplicate Delay.DIMENSION_LIST\n");
return false;
return DelayType::Invalid;
}
delay_dim = delay_attrs->value;
}
else
fprintf(stderr, "Unexpected delay attribute: %s = %s\n", delay_attrs->name,
delay_attrs->value);
delay_attrs->value ? delay_attrs->value : "<null>");
delay_attrs = delay_attrs->next;
}
if(!delay_dim)
{
fprintf(stderr, "Missing delay dimensions\n");
/*return false;*/
return DelayType::None;
}
else if(delay_dim != std::string{"I,R"})
{
fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim);
return false;
}
else if(hData->mChannelType == CT_STEREO)
{
/* I,R is 1xChannelCount. Makemhr currently removes any delay constant,
* so we can ignore this as long as it's equal.
*/
if(delay_array->values[0] != delay_array->values[1])
{
fprintf(stderr, "Mismatched delays not supported: %f, %f\n", delay_array->values[0],
delay_array->values[1]);
return false;
}
}
return true;
if(delay_dim == std::string{"I,R"})
return DelayType::I_R;
else if(delay_dim == std::string{"M,R"})
return DelayType::M_R;
fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim);
return DelayType::Invalid;
}
bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
@ -214,7 +212,7 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
}
else
fprintf(stderr, "Unexpected IR attribute: %s = %s\n", ir_attrs->name,
ir_attrs->value);
ir_attrs->value ? ir_attrs->value : "<null>");
ir_attrs = ir_attrs->next;
}
if(!ir_dim)
@ -234,7 +232,7 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
/* Calculate the onset time of a HRIR. */
static constexpr int OnsetRateMultiple{10};
static double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n,
std::vector<double> &upsampled, const double *hrir)
al::span<double> upsampled, const double *hrir)
{
rs.process(n, hrir, static_cast<uint>(upsampled.size()), upsampled.data());
@ -246,8 +244,7 @@ static double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n,
}
/* Calculate the magnitude response of a HRIR. */
static void CalcHrirMagnitude(const uint points, const uint n, std::vector<complex_d> &h,
double *hrir)
static void CalcHrirMagnitude(const uint points, const uint n, al::span<complex_d> h, double *hrir)
{
auto iter = std::copy_n(hrir, points, h.begin());
std::fill(iter, h.end(), complex_d{0.0, 0.0});
@ -256,16 +253,25 @@ static void CalcHrirMagnitude(const uint points, const uint n, std::vector<compl
MagnitudeResponse(n, h.data(), hrir);
}
static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType delayType,
const uint outRate)
{
std::atomic<uint> loaded_count{0u};
auto load_proc = [sofaHrtf,hData,&loaded_count]() -> bool
auto load_proc = [sofaHrtf,hData,delayType,outRate,&loaded_count]() -> bool
{
const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize, 0.0);
double *hrirs = hData->mHrirsBase.data();
std::unique_ptr<double[]> restmp;
al::optional<PPhaseResampler> resampler;
if(outRate && outRate != hData->mIrRate)
{
resampler.emplace().init(hData->mIrRate, outRate);
restmp = std::make_unique<double[]>(sofaHrtf->N);
}
for(uint si{0u};si < sofaHrtf->M;++si)
{
loaded_count.fetch_add(1u);
@ -284,22 +290,21 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(),
[&aer](const HrirFdT &fld) -> bool
{
double delta = aer[2] - fld.mDistance;
return (std::abs(delta) < 0.001);
});
{ return (std::abs(aer[2] - fld.mDistance) < 0.001); });
if(field == hData->mFds.cend())
continue;
double ef{(90.0+aer[1]) / 180.0 * (field->mEvCount-1)};
auto ei = static_cast<int>(std::round(ef));
ef = (ef-ei) * 180.0 / (field->mEvCount-1);
const double evscale{180.0 / static_cast<double>(field->mEvs.size()-1)};
double ef{(90.0 + aer[1]) / evscale};
auto ei = static_cast<uint>(std::round(ef));
ef = (ef - ei) * evscale;
if(std::abs(ef) >= 0.1) continue;
double af{aer[0] / 360.0 * field->mEvs[ei].mAzCount};
auto ai = static_cast<int>(std::round(af));
af = (af-ai) * 360.0 / field->mEvs[ei].mAzCount;
ai %= field->mEvs[ei].mAzCount;
const double azscale{360.0 / static_cast<double>(field->mEvs[ei].mAzs.size())};
double af{aer[0] / azscale};
auto ai = static_cast<uint>(std::round(af));
af = (af-ai) * azscale;
ai %= static_cast<uint>(field->mEvs[ei].mAzs.size());
if(std::abs(af) >= 0.1) continue;
HrirAzT *azd = &field->mEvs[ei].mAzs[ai];
@ -313,14 +318,39 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
for(uint ti{0u};ti < channels;++ti)
{
azd->mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd->mIndex)];
std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N],
hData->mIrPoints, azd->mIrs[ti]);
if(!resampler)
std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N],
sofaHrtf->N, azd->mIrs[ti]);
else
{
std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N],
sofaHrtf->N, restmp.get());
resampler->process(sofaHrtf->N, restmp.get(), hData->mIrSize, azd->mIrs[ti]);
}
}
/* TODO: Since some SOFA files contain minimum phase HRIRs,
* it would be beneficial to check for per-measurement delays
* (when available) to reconstruct the HRTDs.
*/
/* Include any per-channel or per-HRIR delays. */
if(delayType == DelayType::I_R)
{
const float *delayValues{sofaHrtf->DataDelay.values};
for(uint ti{0u};ti < channels;++ti)
azd->mDelays[ti] = delayValues[ti] / static_cast<float>(hData->mIrRate);
}
else if(delayType == DelayType::M_R)
{
const float *delayValues{sofaHrtf->DataDelay.values};
for(uint ti{0u};ti < channels;++ti)
azd->mDelays[ti] = delayValues[si*sofaHrtf->R + ti] /
static_cast<float>(hData->mIrRate);
}
}
if(outRate && outRate != hData->mIrRate)
{
const double scale{static_cast<double>(outRate) / hData->mIrRate};
hData->mIrRate = outRate;
hData->mIrPoints = std::min(static_cast<uint>(std::ceil(hData->mIrPoints*scale)),
hData->mIrSize);
}
return true;
};
@ -376,7 +406,7 @@ struct MagCalculator {
};
bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize,
const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData)
const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData)
{
int err;
MySofaHrtfPtr sofaHrtf{mysofa_load(filename, &err)};
@ -429,39 +459,45 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
/* Assume a default head radius of 9cm. */
hData->mRadius = 0.09;
if(!PrepareSampleRate(sofaHrtf.get(), hData) || !PrepareDelay(sofaHrtf.get(), hData)
|| !CheckIrData(sofaHrtf.get()))
hData->mIrRate = static_cast<uint>(GetSampleRate(sofaHrtf.get()) + 0.5f);
if(!hData->mIrRate)
return false;
DelayType delayType = PrepareDelay(sofaHrtf.get());
if(delayType == DelayType::Invalid)
return false;
if(!CheckIrData(sofaHrtf.get()))
return false;
if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData))
return false;
if(!LoadResponses(sofaHrtf.get(), hData))
if(!LoadResponses(sofaHrtf.get(), hData, delayType, outRate))
return false;
sofaHrtf = nullptr;
for(uint fi{0u};fi < hData->mFdCount;fi++)
for(uint fi{0u};fi < hData->mFds.size();fi++)
{
uint ei{0u};
for(;ei < hData->mFds[fi].mEvCount;ei++)
for(;ei < hData->mFds[fi].mEvs.size();ei++)
{
uint ai{0u};
for(;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
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(ai < hData->mFds[fi].mEvs[ei].mAzCount)
if(ai < hData->mFds[fi].mEvs[ei].mAzs.size())
break;
}
if(ei >= hData->mFds[fi].mEvCount)
if(ei >= hData->mFds[fi].mEvs.size())
{
fprintf(stderr, "Missing source references [ %d, *, * ].\n", fi);
return false;
}
hData->mFds[fi].mEvStart = ei;
for(;ei < hData->mFds[fi].mEvCount;ei++)
for(;ei < hData->mFds[fi].mEvs.size();ei++)
{
for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
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)
@ -477,11 +513,11 @@ 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();
for(uint fi{0u};fi < hData->mFdCount;fi++)
for(uint fi{0u};fi < hData->mFds.size();fi++)
{
for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++)
{
for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
for(uint ti{0u};ti < channels;ti++)
@ -489,8 +525,8 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
}
}
for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++)
hrir_total += hData->mFds[fi].mEvs[ei].mAzCount * channels;
for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();ei++)
hrir_total += hData->mFds[fi].mEvs[ei].mAzs.size() * channels;
}
std::atomic<size_t> hrir_done{0};
@ -502,17 +538,16 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
PPhaseResampler rs;
rs.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate);
for(uint fi{0u};fi < hData->mFdCount;fi++)
for(auto &field : hData->mFds)
{
for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++)
for(auto &elev : field.mEvs.subspan(field.mEvStart))
{
for(uint ai{0};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(auto &azd : elev.mAzs)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
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,
azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, hData->mIrPoints,
upsampled, azd.mIrs[ti]);
}
}
@ -533,13 +568,12 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
return false;
MagCalculator calculator{hData->mFftSize, hData->mIrPoints};
for(uint fi{0u};fi < hData->mFdCount;fi++)
for(auto &field : hData->mFds)
{
for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++)
for(auto &elev : field.mEvs.subspan(field.mEvStart))
{
for(uint ai{0};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(auto &azd : elev.mAzs)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
for(uint ti{0};ti < channels;ti++)
calculator.mIrs.push_back(azd.mIrs[ti]);
}

View file

@ -5,6 +5,6 @@
bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize,
const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData);
const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData);
#endif /* LOADSOFA_H */

View file

@ -88,6 +88,7 @@
#include "../getopt.h"
#endif
#include "alcomplex.h"
#include "alfstream.h"
#include "alspan.h"
#include "alstring.h"
@ -108,6 +109,8 @@ using namespace std::placeholders;
#endif
HrirDataT::~HrirDataT() = default;
// Head model used for calculating the impulse delays.
enum HeadModelT {
HM_NONE,
@ -140,7 +143,7 @@ enum HeadModelT {
#define DEFAULT_EQUALIZE (1)
#define DEFAULT_SURFACE (1)
#define DEFAULT_LIMIT (24.0)
#define DEFAULT_TRUNCSIZE (32)
#define DEFAULT_TRUNCSIZE (64)
#define DEFAULT_HEAD_MODEL (HM_DATASET)
#define DEFAULT_CUSTOM_RADIUS (0.0)
@ -222,94 +225,14 @@ static void TpdfDither(double *RESTRICT out, const double *RESTRICT in, const do
}
}
/* Fast Fourier transform routines. The number of points must be a power of
* two.
*/
// Performs bit-reversal ordering.
static void FftArrange(const uint n, complex_d *inout)
{
// Handle in-place arrangement.
uint rk{0u};
for(uint k{0u};k < n;k++)
{
if(rk > k)
std::swap(inout[rk], inout[k]);
uint m{n};
while(rk&(m >>= 1))
rk &= ~m;
rk |= m;
}
}
// Performs the summation.
static void FftSummation(const uint n, const double s, complex_d *cplx)
{
double pi;
uint m, m2;
uint i, k, mk;
pi = s * M_PI;
for(m = 1, m2 = 2;m < n; m <<= 1, m2 <<= 1)
{
// v = Complex (-2.0 * sin (0.5 * pi / m) * sin (0.5 * pi / m), -sin (pi / m))
double sm = std::sin(0.5 * pi / m);
auto v = complex_d{-2.0*sm*sm, -std::sin(pi / m)};
auto w = complex_d{1.0, 0.0};
for(i = 0;i < m;i++)
{
for(k = i;k < n;k += m2)
{
mk = k + m;
auto t = w * cplx[mk];
cplx[mk] = cplx[k] - t;
cplx[k] = cplx[k] + t;
}
w += v*w;
}
}
}
// Performs a forward FFT.
void FftForward(const uint n, complex_d *inout)
{
FftArrange(n, inout);
FftSummation(n, 1.0, inout);
}
// Performs an inverse FFT.
void FftInverse(const uint n, complex_d *inout)
{
FftArrange(n, inout);
FftSummation(n, -1.0, inout);
double f{1.0 / n};
for(uint i{0};i < n;i++)
inout[i] *= f;
}
/* 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.
*/
static void Hilbert(const uint n, complex_d *inout)
{
uint i;
// Handle in-place operation.
for(i = 0;i < n;i++)
inout[i].imag(0.0);
FftInverse(n, inout);
for(i = 1;i < (n+1)/2;i++)
inout[i] *= 2.0;
/* Increment i if n is even. */
i += (n&1)^1;
for(;i < n;i++)
inout[i] = complex_d{0.0, 0.0};
FftForward(n, inout);
}
inline static void Hilbert(const uint n, complex_d *inout)
{ complex_hilbert({inout, n}); }
/* Calculate the magnitude response of the given input. This is used in
* place of phase decomposition, since the phase residuals are discarded for
@ -440,30 +363,31 @@ static int StoreMhr(const HrirDataT *hData, const char *filename)
return 0;
if(!WriteBin4(1, hData->mIrPoints, fp, filename))
return 0;
if(!WriteBin4(1, hData->mFdCount, fp, filename))
if(!WriteBin4(1, static_cast<uint>(hData->mFds.size()), fp, filename))
return 0;
for(fi = hData->mFdCount-1;fi < hData->mFdCount;fi--)
for(fi = static_cast<uint>(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, filename))
return 0;
if(!WriteBin4(1, hData->mFds[fi].mEvCount, fp, filename))
if(!WriteBin4(1, static_cast<uint32_t>(hData->mFds[fi].mEvs.size()), fp, filename))
return 0;
for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++)
{
if(!WriteBin4(1, hData->mFds[fi].mEvs[ei].mAzCount, fp, filename))
const auto &elev = hData->mFds[fi].mEvs[ei];
if(!WriteBin4(1, static_cast<uint32_t>(elev.mAzs.size()), fp, filename))
return 0;
}
}
for(fi = hData->mFdCount-1;fi < hData->mFdCount;fi--)
for(fi = static_cast<uint>(hData->mFds.size()-1);fi < hData->mFds.size();fi--)
{
constexpr double scale{8388607.0};
constexpr uint bps{3u};
for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
{
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
double out[2 * MAX_TRUNCSIZE];
@ -480,16 +404,14 @@ static int StoreMhr(const HrirDataT *hData, const char *filename)
}
}
}
for(fi = hData->mFdCount-1;fi < hData->mFdCount;fi--)
for(fi = static_cast<uint>(hData->mFds.size()-1);fi < hData->mFds.size();fi--)
{
/* Delay storage has 2 bits of extra precision. */
constexpr double DelayPrecScale{4.0};
for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs)
{
const HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
auto v = static_cast<uint>(std::round(azd.mDelays[0]*DelayPrecScale));
if(!WriteBin4(1, v, fp, filename)) return 0;
if(hData->mChannelType == CT_STEREO)
@ -516,22 +438,21 @@ static int StoreMhr(const HrirDataT *hData, const char *filename)
static void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, const uint m)
{
double maxMags[MAX_FD_COUNT];
uint fi, ei, ai, ti, i;
uint fi, ei, ti, i;
double maxMag{0.0};
for(fi = 0;fi < hData->mFdCount;fi++)
for(fi = 0;fi < hData->mFds.size();fi++)
{
maxMags[fi] = 0.0;
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs)
{
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
for(ti = 0;ti < channels;ti++)
{
for(i = 0;i < m;i++)
maxMags[fi] = std::max(azd->mIrs[ti][i], maxMags[fi]);
maxMags[fi] = std::max(azd.mIrs[ti][i], maxMags[fi]);
}
}
}
@ -539,19 +460,18 @@ static void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels,
maxMag = std::max(maxMags[fi], maxMag);
}
for(fi = 0;fi < hData->mFdCount;fi++)
for(fi = 0;fi < hData->mFds.size();fi++)
{
const double magFactor{maxMag / maxMags[fi]};
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs)
{
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
for(ti = 0;ti < channels;ti++)
{
for(i = 0;i < m;i++)
azd->mIrs[ti][i] *= magFactor;
azd.mIrs[ti][i] *= magFactor;
}
}
}
@ -571,30 +491,32 @@ static void CalculateDfWeights(const HrirDataT *hData, double *weights)
sum = 0.0;
// The head radius acts as the limit for the inner radius.
innerRa = hData->mRadius;
for(fi = 0;fi < hData->mFdCount;fi++)
for(fi = 0;fi < hData->mFds.size();fi++)
{
// Each volume ends half way between progressive field measurements.
if((fi + 1) < hData->mFdCount)
if((fi + 1) < hData->mFds.size())
outerRa = 0.5f * (hData->mFds[fi].mDistance + hData->mFds[fi + 1].mDistance);
// The final volume has its limit extended to some practical value.
// This is done to emphasize the far-field responses in the average.
else
outerRa = 10.0f;
evs = M_PI / 2.0 / (hData->mFds[fi].mEvCount - 1);
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
const double raPowDiff{std::pow(outerRa, 3.0) - std::pow(innerRa, 3.0)};
evs = M_PI / 2.0 / static_cast<double>(hData->mFds[fi].mEvs.size() - 1);
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++)
{
const auto &elev = hData->mFds[fi].mEvs[ei];
// For each elevation, calculate the upper and lower limits of
// the patch band.
ev = hData->mFds[fi].mEvs[ei].mElevation;
ev = elev.mElevation;
lowerEv = std::max(-M_PI / 2.0, ev - evs);
upperEv = std::min(M_PI / 2.0, ev + evs);
// Calculate the surface area of the patch band.
solidAngle = 2.0 * M_PI * (std::sin(upperEv) - std::sin(lowerEv));
// Then the volume of the extruded patch band.
solidVolume = solidAngle * (std::pow(outerRa, 3.0) - std::pow(innerRa, 3.0)) / 3.0;
solidVolume = solidAngle * raPowDiff / 3.0;
// Each weight is the volume of one extruded patch.
weights[(fi * MAX_EV_COUNT) + ei] = solidVolume / hData->mFds[fi].mEvs[ei].mAzCount;
weights[(fi*MAX_EV_COUNT) + ei] = solidVolume / static_cast<double>(elev.mAzs.size());
// Sum the total coverage volume of the HRIRs for all fields.
sum += solidAngle;
}
@ -602,11 +524,11 @@ static void CalculateDfWeights(const HrirDataT *hData, double *weights)
innerRa = outerRa;
}
for(fi = 0;fi < hData->mFdCount;fi++)
for(fi = 0;fi < hData->mFds.size();fi++)
{
// Normalize the weights given the total surface coverage for all
// fields.
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++)
weights[(fi * MAX_EV_COUNT) + ei] /= sum;
}
}
@ -616,9 +538,10 @@ static void CalculateDfWeights(const HrirDataT *hData, double *weights)
* coverage of each HRIR. The final average can then be limited by the
* specified magnitude range (in positive dB; 0.0 to skip).
*/
static void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, const uint m, const int weighted, const double limit, double *dfa)
static void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, const uint m,
const int weighted, const double limit, double *dfa)
{
std::vector<double> weights(hData->mFdCount * MAX_EV_COUNT);
std::vector<double> weights(hData->mFds.size() * MAX_EV_COUNT);
uint count, ti, fi, ei, i, ai;
if(weighted)
@ -633,16 +556,16 @@ static void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint chan
// If coverage weighting is not used, the weights still need to be
// averaged by the number of existing HRIRs.
count = hData->mIrCount;
for(fi = 0;fi < hData->mFdCount;fi++)
for(fi = 0;fi < hData->mFds.size();fi++)
{
for(ei = 0;ei < hData->mFds[fi].mEvStart;ei++)
count -= hData->mFds[fi].mEvs[ei].mAzCount;
count -= static_cast<uint>(hData->mFds[fi].mEvs[ei].mAzs.size());
}
weight = 1.0 / count;
for(fi = 0;fi < hData->mFdCount;fi++)
for(fi = 0;fi < hData->mFds.size();fi++)
{
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++)
weights[(fi * MAX_EV_COUNT) + ei] = weight;
}
}
@ -650,11 +573,11 @@ static void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint chan
{
for(i = 0;i < m;i++)
dfa[(ti * m) + i] = 0.0;
for(fi = 0;fi < hData->mFdCount;fi++)
for(fi = 0;fi < hData->mFds.size();fi++)
{
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
{
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
// Get the weight for this HRIR's contribution.
@ -680,126 +603,36 @@ static void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint chan
// set using the given average response.
static void DiffuseFieldEqualize(const uint channels, const uint m, const double *dfa, const HrirDataT *hData)
{
uint ti, fi, ei, ai, i;
uint ti, fi, ei, i;
for(fi = 0;fi < hData->mFdCount;fi++)
for(fi = 0;fi < hData->mFds.size();fi++)
{
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvCount;ei++)
for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(auto &azd : hData->mFds[fi].mEvs[ei].mAzs)
{
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
for(ti = 0;ti < channels;ti++)
{
for(i = 0;i < m;i++)
azd->mIrs[ti][i] /= dfa[(ti * m) + i];
azd.mIrs[ti][i] /= dfa[(ti * m) + i];
}
}
}
}
}
// Resamples the HRIRs for use at the given sampling rate.
static void ResampleHrirs(const uint rate, HrirDataT *hData)
{
struct Resampler {
const double scale;
const size_t m;
/* Resampling from a lower rate to a higher rate. This likely only
* works properly when 1 <= scale <= 2.
*/
void upsample(double *resampled, const double *ir) const
{
std::fill_n(resampled, m, 0.0);
resampled[0] = ir[0];
for(size_t in{1};in < m;++in)
{
const auto offset = static_cast<double>(in) / scale;
const auto out = static_cast<size_t>(offset);
const double a{offset - static_cast<double>(out)};
if(out == m-1)
resampled[out] += ir[in]*(1.0-a);
else
{
resampled[out ] += ir[in]*(1.0-a);
resampled[out+1] += ir[in]*a;
}
}
}
/* Resampling from a higher rate to a lower rate. This likely only
* works properly when 0.5 <= scale <= 1.0.
*/
void downsample(double *resampled, const double *ir) const
{
resampled[0] = ir[0];
for(size_t out{1};out < m;++out)
{
const auto offset = static_cast<double>(out) * scale;
const auto in = static_cast<size_t>(offset);
const double a{offset - static_cast<double>(in)};
if(in == m-1)
resampled[out] = ir[in]*(1.0-a);
else
resampled[out] = ir[in]*(1.0-a) + ir[in+1]*a;
}
}
};
while(rate > hData->mIrRate*2)
ResampleHrirs(hData->mIrRate*2, hData);
while(rate < (hData->mIrRate+1)/2)
ResampleHrirs((hData->mIrRate+1)/2, hData);
const auto scale = static_cast<double>(rate) / hData->mIrRate;
const size_t m{hData->mFftSize/2u + 1u};
auto resampled = std::vector<double>(m);
const Resampler resampler{scale, m};
auto do_resample = std::bind(
std::mem_fn((scale > 1.0) ? &Resampler::upsample : &Resampler::downsample), &resampler,
_1, _2);
const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
for(uint fi{0};fi < hData->mFdCount;++fi)
{
for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;++ei)
{
for(uint ai{0};ai < hData->mFds[fi].mEvs[ei].mAzCount;++ai)
{
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
for(uint ti{0};ti < channels;++ti)
{
do_resample(resampled.data(), azd->mIrs[ti]);
/* This should probably be rescaled according to the scale,
* however it'll all be normalized in the end so a constant
* scalar is fine to leave.
*/
std::transform(resampled.cbegin(), resampled.cend(), azd->mIrs[ti],
[](const double d) { return std::max(d, EPSILON); });
}
}
}
}
hData->mIrRate = rate;
}
/* Given field and elevation indices and an azimuth, calculate the indices of
* the two HRIRs that bound the coordinate along with a factor for
* calculating the continuous HRIR using interpolation.
*/
static void CalcAzIndices(const HrirFdT &field, const uint ei, const double az, uint *a0, uint *a1, double *af)
{
double f{(2.0*M_PI + az) * field.mEvs[ei].mAzCount / (2.0*M_PI)};
uint i{static_cast<uint>(f) % field.mEvs[ei].mAzCount};
double f{(2.0*M_PI + az) * static_cast<double>(field.mEvs[ei].mAzs.size()) / (2.0*M_PI)};
const uint i{static_cast<uint>(f) % static_cast<uint>(field.mEvs[ei].mAzs.size())};
f -= std::floor(f);
*a0 = i;
*a1 = (i + 1) % field.mEvs[ei].mAzCount;
*a1 = (i + 1) % static_cast<uint>(field.mEvs[ei].mAzs.size());
*af = f;
}
@ -829,13 +662,13 @@ static void SynthesizeOnsets(HrirDataT *hData)
/* Take the polar opposite position of the desired measurement and
* swap the ears.
*/
field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvCount-1].mAzs[0].mDelays[1];
field.mEvs[0].mAzs[0].mDelays[1] = field.mEvs[field.mEvCount-1].mAzs[0].mDelays[0];
field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvs.size()-1].mAzs[0].mDelays[1];
field.mEvs[0].mAzs[0].mDelays[1] = field.mEvs[field.mEvs.size()-1].mAzs[0].mDelays[0];
for(ei = 1u;ei < (upperElevReal+1)/2;++ei)
{
const uint topElev{field.mEvCount-ei-1};
const uint topElev{static_cast<uint>(field.mEvs.size()-ei-1)};
for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++)
for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++)
{
uint a0, a1;
double af;
@ -859,12 +692,12 @@ static void SynthesizeOnsets(HrirDataT *hData)
}
else
{
field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvCount-1].mAzs[0].mDelays[0];
field.mEvs[0].mAzs[0].mDelays[0] = field.mEvs[field.mEvs.size()-1].mAzs[0].mDelays[0];
for(ei = 1u;ei < (upperElevReal+1)/2;++ei)
{
const uint topElev{field.mEvCount-ei-1};
const uint topElev{static_cast<uint>(field.mEvs.size()-ei-1)};
for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++)
for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++)
{
uint a0, a1;
double af;
@ -897,7 +730,7 @@ static void SynthesizeOnsets(HrirDataT *hData)
const double ef{(field.mEvs[upperElevReal].mElevation - field.mEvs[ei].mElevation) /
(field.mEvs[upperElevReal].mElevation - field.mEvs[lowerElevFake].mElevation)};
for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++)
for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++)
{
uint a0, a1, a2, a3;
double af0, af1;
@ -923,7 +756,7 @@ static void SynthesizeOnsets(HrirDataT *hData)
}
}
};
std::for_each(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, proc_field);
std::for_each(hData->mFds.begin(), hData->mFds.end(), proc_field);
}
/* Attempt to synthesize any missing HRIRs at the bottom elevations of each
@ -990,7 +823,7 @@ static void SynthesizeHrirs(HrirDataT *hData)
std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(),
[](const complex_d &c) -> double { return std::abs(c); });
for(uint ai{0u};ai < field.mEvs[ei].mAzCount;ai++)
for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++)
{
uint a0, a1;
double af;
@ -1036,7 +869,7 @@ static void SynthesizeHrirs(HrirDataT *hData)
field.mEvs[0].mAzs[0].mIrs[ti][i] *= filter[i];
}
};
std::for_each(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, proc_field);
std::for_each(hData->mFds.begin(), hData->mFds.end(), proc_field);
}
// The following routines assume a full set of HRIRs for all elevations.
@ -1101,15 +934,12 @@ static void ReconstructHrirs(const HrirDataT *hData, const uint numThreads)
reconstructor.mDone.store(0, std::memory_order_relaxed);
reconstructor.mFftSize = hData->mFftSize;
reconstructor.mIrPoints = hData->mIrPoints;
for(uint fi{0u};fi < hData->mFdCount;fi++)
for(const auto &field : hData->mFds)
{
const HrirFdT &field = hData->mFds[fi];
for(uint ei{0};ei < field.mEvCount;ei++)
for(auto &elev : field.mEvs)
{
const HrirEvT &elev = field.mEvs[ei];
for(uint ai{0u};ai < elev.mAzCount;ai++)
for(const auto &azd : elev.mAzs)
{
const HrirAzT &azd = elev.mAzs[ai];
for(uint ti{0u};ti < channels;ti++)
reconstructor.mIrs.push_back(azd.mIrs[ti]);
}
@ -1150,35 +980,28 @@ static void NormalizeHrirs(HrirDataT *hData)
/* Find the maximum amplitude and RMS out of all the IRs. */
struct LevelPair { double amp, rms; };
auto proc0_field = [channels,irSize](const LevelPair levels0, const HrirFdT &field) -> LevelPair
auto mesasure_channel = [irSize](const LevelPair levels, const double *ir)
{
auto proc_elev = [channels,irSize](const LevelPair levels1, const HrirEvT &elev) -> LevelPair
{
auto proc_azi = [channels,irSize](const LevelPair levels2, const HrirAzT &azi) -> LevelPair
/* Calculate the peak amplitude and RMS of this IR. */
auto current = std::accumulate(ir, ir+irSize, LevelPair{0.0, 0.0},
[](const LevelPair cur, const double impulse)
{
auto proc_channel = [irSize](const LevelPair levels3, const double *ir) -> LevelPair
{
/* Calculate the peak amplitude and RMS of this IR. */
auto current = std::accumulate(ir, ir+irSize, LevelPair{0.0, 0.0},
[](const LevelPair cur, const double impulse) -> LevelPair
{
return {std::max(std::abs(impulse), cur.amp),
cur.rms + impulse*impulse};
});
current.rms = std::sqrt(current.rms / irSize);
return LevelPair{std::max(std::abs(impulse), cur.amp), cur.rms + impulse*impulse};
});
current.rms = std::sqrt(current.rms / irSize);
/* Accumulate levels by taking the maximum amplitude and RMS. */
return LevelPair{std::max(current.amp, levels3.amp),
std::max(current.rms, levels3.rms)};
};
return std::accumulate(azi.mIrs, azi.mIrs+channels, levels2, proc_channel);
};
return std::accumulate(elev.mAzs, elev.mAzs+elev.mAzCount, levels1, proc_azi);
};
return std::accumulate(field.mEvs, field.mEvs+field.mEvCount, levels0, proc_elev);
/* Accumulate levels by taking the maximum amplitude and RMS. */
return LevelPair{std::max(current.amp, levels.amp), std::max(current.rms, levels.rms)};
};
const auto maxlev = std::accumulate(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount,
LevelPair{0.0, 0.0}, proc0_field);
auto measure_azi = [channels,mesasure_channel](const LevelPair levels, const HrirAzT &azi)
{ return std::accumulate(azi.mIrs, azi.mIrs+channels, levels, mesasure_channel); };
auto measure_elev = [measure_azi](const LevelPair levels, const HrirEvT &elev)
{ return std::accumulate(elev.mAzs.cbegin(), elev.mAzs.cend(), levels, measure_azi); };
auto measure_field = [measure_elev](const LevelPair levels, const HrirFdT &field)
{ return std::accumulate(field.mEvs.cbegin(), field.mEvs.cend(), levels, measure_elev); };
const auto maxlev = std::accumulate(hData->mFds.begin(), hData->mFds.end(),
LevelPair{0.0, 0.0}, measure_field);
/* Normalize using the maximum RMS of the HRIRs. The RMS measure for the
* non-filtered signal is of an impulse with equal length (to the filter):
@ -1195,24 +1018,16 @@ static void NormalizeHrirs(HrirDataT *hData)
factor = std::min(factor, 0.99/maxlev.amp);
/* Now scale all IRs by the given factor. */
auto proc1_field = [channels,irSize,factor](HrirFdT &field) -> void
{
auto proc_elev = [channels,irSize,factor](HrirEvT &elev) -> void
{
auto proc_azi = [channels,irSize,factor](HrirAzT &azi) -> void
{
auto proc_channel = [irSize,factor](double *ir) -> void
{
std::transform(ir, ir+irSize, ir,
std::bind(std::multiplies<double>{}, _1, factor));
};
std::for_each(azi.mIrs, azi.mIrs+channels, proc_channel);
};
std::for_each(elev.mAzs, elev.mAzs+elev.mAzCount, proc_azi);
};
std::for_each(field.mEvs, field.mEvs+field.mEvCount, proc_elev);
};
std::for_each(hData->mFds.begin(), hData->mFds.begin()+hData->mFdCount, proc1_field);
auto proc_channel = [irSize,factor](double *ir)
{ std::transform(ir, ir+irSize, ir, [factor](double s){ return s * factor; }); };
auto proc_azi = [channels,proc_channel](HrirAzT &azi)
{ std::for_each(azi.mIrs, azi.mIrs+channels, proc_channel); };
auto proc_elev = [proc_azi](HrirEvT &elev)
{ std::for_each(elev.mAzs.begin(), elev.mAzs.end(), proc_azi); };
auto proc1_field = [proc_elev](HrirFdT &field)
{ std::for_each(field.mEvs.begin(), field.mEvs.end(), proc_elev); };
std::for_each(hData->mFds.begin(), hData->mFds.end(), proc1_field);
}
// Calculate the left-ear time delay using a spherical head model.
@ -1235,69 +1050,58 @@ static void CalculateHrtds(const HeadModelT model, const double radius, HrirData
{
uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1;
double customRatio{radius / hData->mRadius};
uint ti, fi, ei, ai;
uint ti;
if(model == HM_SPHERE)
{
for(fi = 0;fi < hData->mFdCount;fi++)
for(auto &field : hData->mFds)
{
for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
for(auto &elev : field.mEvs)
{
HrirEvT *evd = &hData->mFds[fi].mEvs[ei];
for(ai = 0;ai < evd->mAzCount;ai++)
for(auto &azd : elev.mAzs)
{
HrirAzT *azd = &evd->mAzs[ai];
for(ti = 0;ti < channels;ti++)
azd->mDelays[ti] = CalcLTD(evd->mElevation, azd->mAzimuth, radius, hData->mFds[fi].mDistance);
azd.mDelays[ti] = CalcLTD(elev.mElevation, azd.mAzimuth, radius, field.mDistance);
}
}
}
}
else if(customRatio != 1.0)
{
for(fi = 0;fi < hData->mFdCount;fi++)
for(auto &field : hData->mFds)
{
for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
for(auto &elev : field.mEvs)
{
HrirEvT *evd = &hData->mFds[fi].mEvs[ei];
for(ai = 0;ai < evd->mAzCount;ai++)
for(auto &azd : elev.mAzs)
{
HrirAzT *azd = &evd->mAzs[ai];
for(ti = 0;ti < channels;ti++)
azd->mDelays[ti] *= customRatio;
azd.mDelays[ti] *= customRatio;
}
}
}
}
double maxHrtd{0.0};
for(fi = 0;fi < hData->mFdCount;fi++)
for(auto &field : hData->mFds)
{
double minHrtd{std::numeric_limits<double>::infinity()};
for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
for(auto &elev : field.mEvs)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(auto &azd : elev.mAzs)
{
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
for(ti = 0;ti < channels;ti++)
minHrtd = std::min(azd->mDelays[ti], minHrtd);
minHrtd = std::min(azd.mDelays[ti], minHrtd);
}
}
for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
for(auto &elev : field.mEvs)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(auto &azd : elev.mAzs)
{
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
for(ti = 0;ti < channels;ti++)
{
azd->mDelays[ti] = (azd->mDelays[ti]-minHrtd) * hData->mIrRate;
maxHrtd = std::max(maxHrtd, azd->mDelays[ti]);
azd.mDelays[ti] = (azd.mDelays[ti]-minHrtd) * hData->mIrRate;
maxHrtd = std::max(maxHrtd, azd.mDelays[ti]);
}
}
}
@ -1306,15 +1110,14 @@ static void CalculateHrtds(const HeadModelT model, const double radius, HrirData
{
fprintf(stdout, " Scaling for max delay of %f samples to %f\n...\n", maxHrtd, MAX_HRTD);
const double scale{MAX_HRTD / maxHrtd};
for(fi = 0;fi < hData->mFdCount;fi++)
for(auto &field : hData->mFds)
{
for(ei = 0;ei < hData->mFds[fi].mEvCount;ei++)
for(auto &elev : field.mEvs)
{
for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
for(auto &azd : elev.mAzs)
{
HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai];
for(ti = 0;ti < channels;ti++)
azd->mDelays[ti] *= scale;
azd.mDelays[ti] *= scale;
}
}
}
@ -1322,45 +1125,40 @@ static void CalculateHrtds(const HeadModelT model, const double radius, HrirData
}
// Allocate and configure dynamic HRIR structures.
int PrepareHrirData(const uint fdCount, const double (&distances)[MAX_FD_COUNT],
const uint (&evCounts)[MAX_FD_COUNT], const uint azCounts[MAX_FD_COUNT * MAX_EV_COUNT],
HrirDataT *hData)
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)
{
uint evTotal = 0, azTotal = 0, fi, ei, ai;
uint evTotal{0}, azTotal{0};
for(fi = 0;fi < fdCount;fi++)
for(size_t fi{0};fi < distances.size();++fi)
{
evTotal += evCounts[fi];
for(ei = 0;ei < evCounts[fi];ei++)
azTotal += azCounts[(fi * MAX_EV_COUNT) + ei];
for(size_t ei{0};ei < evCounts[fi];++ei)
azTotal += azCounts[fi][ei];
}
if(!fdCount || !evTotal || !azTotal)
return 0;
if(!evTotal || !azTotal)
return false;
hData->mEvsBase.resize(evTotal);
hData->mAzsBase.resize(azTotal);
hData->mFds.resize(fdCount);
hData->mFds.resize(distances.size());
hData->mIrCount = azTotal;
hData->mFdCount = fdCount;
evTotal = 0;
azTotal = 0;
for(fi = 0;fi < fdCount;fi++)
for(size_t fi{0};fi < distances.size();++fi)
{
hData->mFds[fi].mDistance = distances[fi];
hData->mFds[fi].mEvCount = evCounts[fi];
hData->mFds[fi].mEvStart = 0;
hData->mFds[fi].mEvs = &hData->mEvsBase[evTotal];
hData->mFds[fi].mEvs = {&hData->mEvsBase[evTotal], evCounts[fi]};
evTotal += evCounts[fi];
for(ei = 0;ei < evCounts[fi];ei++)
for(uint ei{0};ei < evCounts[fi];++ei)
{
uint azCount = azCounts[(fi * MAX_EV_COUNT) + ei];
uint azCount = azCounts[fi][ei];
hData->mFds[fi].mIrCount += azCount;
hData->mFds[fi].mEvs[ei].mElevation = -M_PI / 2.0 + M_PI * ei / (evCounts[fi] - 1);
hData->mFds[fi].mEvs[ei].mIrCount += azCount;
hData->mFds[fi].mEvs[ei].mAzCount = azCount;
hData->mFds[fi].mEvs[ei].mAzs = &hData->mAzsBase[azTotal];
for(ai = 0;ai < azCount;ai++)
hData->mFds[fi].mEvs[ei].mAzs = {&hData->mAzsBase[azTotal], azCount};
for(uint ai{0};ai < azCount;ai++)
{
hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth = 2.0 * M_PI * ai / azCount;
hData->mFds[fi].mEvs[ei].mAzs[ai].mIndex = azTotal + ai;
@ -1372,7 +1170,7 @@ int PrepareHrirData(const uint fdCount, const double (&distances)[MAX_FD_COUNT],
azTotal += azCount;
}
}
return 1;
return true;
}
@ -1392,7 +1190,7 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann
{
inName = "stdin";
fprintf(stdout, "Reading HRIR definition from %s...\n", inName);
if(!LoadDefInput(std::cin, nullptr, 0, inName, fftSize, truncSize, chanMode, &hData))
if(!LoadDefInput(std::cin, nullptr, 0, inName, fftSize, truncSize, outRate, chanMode, &hData))
return 0;
}
else
@ -1418,13 +1216,13 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann
{
input = nullptr;
fprintf(stdout, "Reading HRTF data from %s...\n", inName);
if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, chanMode, &hData))
if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, outRate, chanMode, &hData))
return 0;
}
else
{
fprintf(stdout, "Reading HRIR definition from %s...\n", inName);
if(!LoadDefInput(*input, startbytes, startbytecount, inName, fftSize, truncSize, chanMode, &hData))
if(!LoadDefInput(*input, startbytes, startbytecount, inName, fftSize, truncSize, outRate, chanMode, &hData))
return 0;
}
}
@ -1435,7 +1233,7 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann
uint m{hData.mFftSize/2u + 1u};
auto dfa = std::vector<double>(c * m);
if(hData.mFdCount > 1)
if(hData.mFds.size() > 1)
{
fprintf(stdout, "Balancing field magnitudes...\n");
BalanceFieldMagnitudes(&hData, c, m);
@ -1456,14 +1254,8 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann
fprintf(stdout, "Clearing %zu near field%s...\n", hData.mFds.size()-1,
(hData.mFds.size()-1 != 1) ? "s" : "");
hData.mFds.erase(hData.mFds.cbegin(), hData.mFds.cend()-1);
hData.mFdCount = 1;
}
}
if(outRate != 0 && outRate != hData.mIrRate)
{
fprintf(stdout, "Resampling HRIRs...\n");
ResampleHrirs(outRate, &hData);
}
fprintf(stdout, "Synthesizing missing elevations...\n");
if(model == HM_DATASET)
SynthesizeOnsets(&hData);

View file

@ -4,6 +4,7 @@
#include <vector>
#include <complex>
#include "alcomplex.h"
#include "polyphase_resampler.h"
@ -73,17 +74,13 @@ struct HrirAzT {
struct HrirEvT {
double mElevation{0.0};
uint mIrCount{0u};
uint mAzCount{0u};
HrirAzT *mAzs{nullptr};
al::span<HrirAzT> mAzs;
};
struct HrirFdT {
double mDistance{0.0};
uint mIrCount{0u};
uint mEvCount{0u};
uint mEvStart{0u};
HrirEvT *mEvs{nullptr};
al::span<HrirEvT> mEvs;
};
// The HRIR metrics and data set used when loading, processing, and storing
@ -97,21 +94,35 @@ struct HrirDataT {
uint mIrSize{0u};
double mRadius{0.0};
uint mIrCount{0u};
uint mFdCount{0u};
std::vector<double> mHrirsBase;
std::vector<HrirEvT> mEvsBase;
std::vector<HrirAzT> mAzsBase;
std::vector<HrirFdT> mFds;
/* GCC warns when it tries to inline this. */
~HrirDataT();
};
int PrepareHrirData(const uint fdCount, const double (&distances)[MAX_FD_COUNT], const uint (&evCounts)[MAX_FD_COUNT], const uint azCounts[MAX_FD_COUNT * MAX_EV_COUNT], HrirDataT *hData);
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);
void FftForward(const uint n, complex_d *inout);
void FftInverse(const uint n, complex_d *inout);
// Performs a forward FFT.
inline void FftForward(const uint n, complex_d *inout)
{ forward_fft(al::as_span(inout, n)); }
// Performs an inverse FFT.
inline void FftInverse(const uint n, complex_d *inout)
{
inverse_fft(al::as_span(inout, n));
double f{1.0 / n};
for(uint i{0};i < n;i++)
inout[i] *= f;
}
// Performs linear interpolation.
inline double Lerp(const double a, const double b, const double f)

View file

@ -183,6 +183,8 @@ static void printHRTFInfo(ALCdevice *device)
static void printModeInfo(ALCdevice *device)
{
ALCint srate = 0;
if(alcIsExtensionPresent(device, "ALC_SOFT_output_mode"))
{
const char *modename = "(error)";
@ -203,10 +205,14 @@ static void printModeInfo(ALCdevice *device)
case ALC_SURROUND_6_1_SOFT: modename = "6.1 Surround"; break;
case ALC_SURROUND_7_1_SOFT: modename = "7.1 Surround"; break;
}
printf("Output channel mode: %s\n", modename);
printf("Device output mode: %s\n", modename);
}
else
printf("Output mode extension not available\n");
alcGetIntegerv(device, ALC_FREQUENCY, 1, &srate);
if(checkALCErrors(device) == ALC_NO_ERROR)
printf("Device sample rate: %dhz\n", srate);
}
static void printALInfo(void)

View file

@ -128,7 +128,7 @@ struct UhjDecoder {
void decode(const float *RESTRICT InSamples, const size_t InChannels,
const al::span<FloatBufferLine> OutSamples, const size_t SamplesToDo);
void decode2(const float *RESTRICT InSamples, const al::span<FloatBufferLine,3> OutSamples,
void decode2(const float *RESTRICT InSamples, const al::span<FloatBufferLine> OutSamples,
const size_t SamplesToDo);
DEF_NEWDEL(UhjDecoder)
@ -305,7 +305,7 @@ void UhjDecoder::decode(const float *RESTRICT InSamples, const size_t InChannels
* halving here is merely a -6dB reduction in output, but it's still incorrect.
*/
void UhjDecoder::decode2(const float *RESTRICT InSamples,
const al::span<FloatBufferLine,3> OutSamples, const size_t SamplesToDo)
const al::span<FloatBufferLine> OutSamples, const size_t SamplesToDo)
{
ASSUME(SamplesToDo > 0);

View file

@ -65,10 +65,14 @@ struct UhjEncoder {
constexpr static size_t sFilterDelay{1024};
/* Delays and processing storage for the unfiltered signal. */
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mS{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mD{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mT{};
alignas(16) std::array<float,BufferLineSize+sFilterDelay> mQ{};
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+sFilterDelay> mZ{};
alignas(16) std::array<float,BufferLineSize> mS{};
alignas(16) std::array<float,BufferLineSize> mD{};
alignas(16) std::array<float,BufferLineSize> mT{};
/* History for the FIR filter. */
alignas(16) std::array<float,sFilterDelay*2 - 1> mWXHistory1{};
@ -106,26 +110,27 @@ void UhjEncoder::encode(const al::span<FloatBufferLine> OutSamples,
const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())};
const float *RESTRICT zinput{al::assume_aligned<16>(InSamples[3].data())};
/* Combine the previously delayed S/D signal with the input. */
/* Combine the previously delayed input signal with the new input. */
std::copy_n(winput, SamplesToDo, mW.begin()+sFilterDelay);
std::copy_n(xinput, SamplesToDo, mX.begin()+sFilterDelay);
std::copy_n(yinput, SamplesToDo, mY.begin()+sFilterDelay);
std::copy_n(zinput, SamplesToDo, mZ.begin()+sFilterDelay);
/* 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)
mS[i] = 0.9396926f*mW[i] + 0.1855740f*mX[i];
/* D = 0.6554516*Y */
auto sideiter = mD.begin() + sFilterDelay;
std::transform(yinput, yinput+SamplesToDo, sideiter,
[](const float y) noexcept -> float { return 0.6554516f*y; });
/* D += j(-0.3420201*W + 0.5098604*X) */
/* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */
auto tmpiter = std::copy(mWXHistory1.cbegin(), mWXHistory1.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, mWXHistory1.size(), mWXHistory1.begin());
PShift.processAccum({mD.data(), SamplesToDo}, mTemp.data());
PShift.process({mD.data(), SamplesToDo}, mTemp.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];
/* Left = (S + D)/2.0 */
float *RESTRICT left{al::assume_aligned<16>(OutSamples[0].data())};
@ -138,40 +143,32 @@ void UhjEncoder::encode(const al::span<FloatBufferLine> OutSamples,
if(OutSamples.size() > 2)
{
/* T = -0.7071068*Y */
sideiter = mT.begin() + sFilterDelay;
std::transform(yinput, yinput+SamplesToDo, sideiter,
[](const float y) noexcept -> float { return -0.7071068f*y; });
/* T += j(-0.1432*W + 0.6512*X) */
/* Precompute j(-0.1432*W + 0.6512*X) and store in mT. */
tmpiter = std::copy(mWXHistory2.cbegin(), mWXHistory2.cend(), mTemp.begin());
std::transform(winput, winput+SamplesToDo, xinput, tmpiter,
[](const float w, const float x) noexcept -> float
{ return -0.1432f*w + 0.6512f*x; });
std::copy_n(mTemp.cbegin()+SamplesToDo, mWXHistory2.size(), mWXHistory2.begin());
PShift.processAccum({mT.data(), SamplesToDo}, mTemp.data());
PShift.process({mT.data(), SamplesToDo}, mTemp.data());
/* T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y */
float *RESTRICT t{al::assume_aligned<16>(OutSamples[2].data())};
for(size_t i{0};i < SamplesToDo;i++)
t[i] = mT[i];
t[i] = mT[i] - 0.7071068f*mY[i];
}
if(OutSamples.size() > 3)
{
/* Q = 0.9772*Z */
sideiter = mQ.begin() + sFilterDelay;
std::transform(zinput, zinput+SamplesToDo, sideiter,
[](const float z) noexcept -> float { return 0.9772f*z; });
float *RESTRICT q{al::assume_aligned<16>(OutSamples[3].data())};
for(size_t i{0};i < SamplesToDo;i++)
q[i] = mQ[i];
q[i] = 0.9772f*mZ[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());
std::copy(mT.cbegin()+SamplesToDo, mT.cbegin()+SamplesToDo+sFilterDelay, mT.begin());
std::copy(mQ.cbegin()+SamplesToDo, mQ.cbegin()+SamplesToDo+sFilterDelay, mQ.begin());
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(mZ.cbegin()+SamplesToDo, mZ.cbegin()+SamplesToDo+sFilterDelay, mZ.begin());
}
@ -291,9 +288,6 @@ int main(int argc, char **argv)
/* Work out the channel map, preferably using the actual channel map
* from the file/format, but falling back to assuming WFX order.
*
* TODO: Map indices when the channel order differs from the virtual
* speaker position maps.
*/
al::span<const SpeakerPos> spkrs;
auto chanmap = std::vector<int>(static_cast<uint>(ininfo.channels), SF_CHANNEL_MAP_INVALID);
@ -327,8 +321,14 @@ int main(int argc, char **argv)
auto match_chanmap = [](const al::span<int> a, const al::span<const int> b) -> bool
{
return a.size() == b.size()
&& std::mismatch(a.begin(), a.end(), b.begin(), b.end()).first == a.end();
if(a.size() != b.size())
return false;
for(const int id : a)
{
if(std::find(b.begin(), b.end(), id) != b.end())
return false;
}
return true;
};
if(match_chanmap(chanmap, stereomap))
spkrs = StereoMap;
@ -349,7 +349,7 @@ int main(int argc, char **argv)
else
{
std::string mapstr;
if(chanmap.size() > 0)
if(!chanmap.empty())
{
mapstr = std::to_string(chanmap[0]);
for(int idx : al::span<int>{chanmap}.subspan<1>())
@ -367,16 +367,32 @@ int main(int argc, char **argv)
{
fprintf(stderr, " ... assuming WFX order stereo\n");
spkrs = StereoMap;
chanmap[0] = SF_CHANNEL_MAP_FRONT_LEFT;
chanmap[1] = SF_CHANNEL_MAP_FRONT_RIGHT;
}
else if(ininfo.channels == 6)
{
fprintf(stderr, " ... assuming WFX order 5.1\n");
spkrs = X51Map;
chanmap[0] = SF_CHANNEL_MAP_FRONT_LEFT;
chanmap[1] = SF_CHANNEL_MAP_FRONT_RIGHT;
chanmap[2] = SF_CHANNEL_MAP_FRONT_CENTER;
chanmap[3] = SF_CHANNEL_MAP_LFE;
chanmap[4] = SF_CHANNEL_MAP_SIDE_LEFT;
chanmap[5] = SF_CHANNEL_MAP_SIDE_RIGHT;
}
else if(ininfo.channels == 8)
{
fprintf(stderr, " ... assuming WFX order 7.1\n");
spkrs = X71Map;
chanmap[0] = SF_CHANNEL_MAP_FRONT_LEFT;
chanmap[1] = SF_CHANNEL_MAP_FRONT_RIGHT;
chanmap[2] = SF_CHANNEL_MAP_FRONT_CENTER;
chanmap[3] = SF_CHANNEL_MAP_LFE;
chanmap[4] = SF_CHANNEL_MAP_REAR_LEFT;
chanmap[5] = SF_CHANNEL_MAP_REAR_RIGHT;
chanmap[6] = SF_CHANNEL_MAP_SIDE_LEFT;
chanmap[7] = SF_CHANNEL_MAP_SIDE_RIGHT;
}
else
{
@ -398,7 +414,7 @@ int main(int argc, char **argv)
auto encoder = std::make_unique<UhjEncoder>();
auto splbuf = al::vector<FloatBufferLine, 16>(static_cast<uint>(9+ininfo.channels)+uhjchans);
auto ambmem = al::span<FloatBufferLine,4>{&splbuf[0], 4};
auto ambmem = al::span<FloatBufferLine,4>{splbuf.data(), 4};
auto encmem = al::span<FloatBufferLine,4>{&splbuf[4], 4};
auto srcmem = al::span<float,BufferLineSize>{splbuf[8].data(), BufferLineSize};
auto outmem = al::span<float>{splbuf[9].data(), BufferLineSize*uhjchans};
@ -434,7 +450,7 @@ int main(int argc, char **argv)
/* B-Format is already in the correct order. It just needs a
* +3dB boost.
*/
constexpr float scale{al::numbers::sqrt2_v<float>};
static constexpr float scale{al::numbers::sqrt2_v<float>};
const size_t chans{std::min<size_t>(static_cast<uint>(ininfo.channels), 4u)};
for(size_t c{0};c < chans;++c)
{
@ -443,24 +459,32 @@ int main(int argc, char **argv)
++inmem;
}
}
else for(auto&& spkr : spkrs)
else for(const int chanid : chanmap)
{
/* Skip LFE. Or mix directly into W? Or W+X? */
if(spkr.mChannelID == SF_CHANNEL_MAP_LFE)
if(chanid == SF_CHANNEL_MAP_LFE)
{
++inmem;
continue;
}
const auto spkr = std::find_if(spkrs.cbegin(), spkrs.cend(),
[chanid](const SpeakerPos &pos){return pos.mChannelID == chanid;});
if(spkr == spkrs.cend())
{
fprintf(stderr, " ... failed to find channel ID %d\n", chanid);
continue;
}
for(size_t i{0};i < got;++i)
srcmem[i] = inmem[i * static_cast<uint>(ininfo.channels)];
++inmem;
constexpr auto Deg2Rad = al::numbers::pi / 180.0;
static constexpr auto Deg2Rad = al::numbers::pi / 180.0;
const auto coeffs = GenCoeffs(
std::cos(spkr.mAzimuth*Deg2Rad) * std::cos(spkr.mElevation*Deg2Rad),
std::sin(spkr.mAzimuth*Deg2Rad) * std::cos(spkr.mElevation*Deg2Rad),
std::sin(spkr.mElevation*Deg2Rad));
std::cos(spkr->mAzimuth*Deg2Rad) * std::cos(spkr->mElevation*Deg2Rad),
std::sin(spkr->mAzimuth*Deg2Rad) * std::cos(spkr->mElevation*Deg2Rad),
std::sin(spkr->mElevation*Deg2Rad));
for(size_t c{0};c < 4;++c)
{
for(size_t i{0};i < got;++i)