update OpenAL-Soft to 1.24.3.

This commit is contained in:
Sasha Szpakowski
2025-05-03 12:51:37 -03:00
parent 375c6f88cd
commit 5e4f3241ac
322 changed files with 54386 additions and 12885 deletions
@@ -12,13 +12,14 @@ if(Qt5Widgets_FOUND)
verstr.cpp
verstr.h
${UIS} ${RSCS} ${TRS} ${MOCS})
target_link_libraries(alsoft-config PUBLIC Qt5::Widgets PRIVATE alcommon)
target_link_libraries(alsoft-config PUBLIC Qt5::Widgets PRIVATE alsoft.common)
target_include_directories(alsoft-config PRIVATE "${alsoft-config_BINARY_DIR}"
"${OpenAL_BINARY_DIR}")
target_compile_definitions(alsoft-config PRIVATE QT_NO_KEYWORDS)
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)
if(TARGET alsoft.build_version)
add_dependencies(alsoft-config alsoft.build_version)
endif()
message(STATUS "Building configuration program")
@@ -1,11 +1,13 @@
#include "config.h"
#include "config_backends.h"
#include "config_simd.h"
#include "mainwindow.h"
#include <array>
#include <cmath>
#include <iostream>
#include <memory>
#include <QFileDialog>
#include <QMessageBox>
@@ -32,48 +34,48 @@ struct BackendNamePair {
/* NOLINTEND(*-avoid-c-arrays) */
};
constexpr std::array backendList{
#ifdef HAVE_PIPEWIRE
#if HAVE_PIPEWIRE
BackendNamePair{ "pipewire", "PipeWire" },
#endif
#ifdef HAVE_PULSEAUDIO
#if HAVE_PULSEAUDIO
BackendNamePair{ "pulse", "PulseAudio" },
#endif
#ifdef HAVE_ALSA
BackendNamePair{ "alsa", "ALSA" },
#endif
#ifdef HAVE_JACK
BackendNamePair{ "jack", "JACK" },
#endif
#ifdef HAVE_COREAUDIO
BackendNamePair{ "core", "CoreAudio" },
#endif
#ifdef HAVE_OSS
BackendNamePair{ "oss", "OSS" },
#endif
#ifdef HAVE_SOLARIS
BackendNamePair{ "solaris", "Solaris" },
#endif
#ifdef HAVE_SNDIO
BackendNamePair{ "sndio", "SoundIO" },
#endif
#ifdef HAVE_WASAPI
#if HAVE_WASAPI
BackendNamePair{ "wasapi", "WASAPI" },
#endif
#ifdef HAVE_DSOUND
#if HAVE_COREAUDIO
BackendNamePair{ "core", "CoreAudio" },
#endif
#if HAVE_OPENSL
BackendNamePair{ "opensl", "OpenSL" },
#endif
#if HAVE_ALSA
BackendNamePair{ "alsa", "ALSA" },
#endif
#if HAVE_SOLARIS
BackendNamePair{ "solaris", "Solaris" },
#endif
#if HAVE_SNDIO
BackendNamePair{ "sndio", "SndIO" },
#endif
#if HAVE_OSS
BackendNamePair{ "oss", "OSS" },
#endif
#if HAVE_DSOUND
BackendNamePair{ "dsound", "DirectSound" },
#endif
#ifdef HAVE_WINMM
#if HAVE_WINMM
BackendNamePair{ "winmm", "Windows Multimedia" },
#endif
#ifdef HAVE_PORTAUDIO
#if HAVE_PORTAUDIO
BackendNamePair{ "port", "PortAudio" },
#endif
#ifdef HAVE_OPENSL
BackendNamePair{ "opensl", "OpenSL" },
#if HAVE_JACK
BackendNamePair{ "jack", "JACK" },
#endif
BackendNamePair{ "null", "Null Output" },
#ifdef HAVE_WAVE
#if HAVE_WAVE
BackendNamePair{ "wave", "Wave Writer" },
#endif
};
@@ -111,12 +113,15 @@ constexpr std::array sampleTypeList{
constexpr std::array resamplerList{
NameValuePair{ "Point", "point" },
NameValuePair{ "Linear", "linear" },
NameValuePair{ "Cubic Spline", "cubic" },
NameValuePair{ "Cubic Spline", "spline" },
NameValuePair{ "Default (Cubic Spline)", "" },
NameValuePair{ "4-point Gaussian", "gaussian" },
NameValuePair{ "11th order Sinc (fast)", "fast_bsinc12" },
NameValuePair{ "11th order Sinc", "bsinc12" },
NameValuePair{ "23rd order Sinc (fast)", "fast_bsinc24" },
NameValuePair{ "23rd order Sinc", "bsinc24" },
NameValuePair{ "47th order Sinc (fast)", "fast_bsinc48" },
NameValuePair{ "47th order Sinc", "bsinc48" },
};
constexpr std::array stereoModeList{
NameValuePair{ "Autodetect", "" },
@@ -144,19 +149,35 @@ constexpr std::array hrtfModeList{
NameValuePair{ "Full", "full" },
};
constexpr auto GetDefaultIndex(const al::span<const NameValuePair> list) -> size_t
{
for(size_t i{0};i < list.size();++i)
{
if(!list[i].value[0])
return i;
}
throw std::runtime_error{"Failed to find default entry"};
}
#ifdef Q_OS_WIN32
struct CoTaskMemDeleter {
void operator()(void *buffer) { CoTaskMemFree(buffer); }
};
/* NOLINTNEXTLINE(*-avoid-c-arrays) */
using WCharBufferPtr = std::unique_ptr<WCHAR[],CoTaskMemDeleter>;
#endif
QString getDefaultConfigName()
{
#ifdef Q_OS_WIN32
const char *fname{"alsoft.ini"};
auto get_appdata_path = []() noexcept -> QString
static constexpr auto get_appdata_path = []() -> QString
{
QString ret;
WCHAR *buffer{};
auto buffer = WCharBufferPtr{};
if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND,
nullptr, &buffer)}; SUCCEEDED(hr))
ret = QString::fromWCharArray(buffer);
CoTaskMemFree(buffer);
return ret;
nullptr, al::out_ptr(buffer))}; SUCCEEDED(hr))
return QString::fromWCharArray(buffer.get());
return QString{};
};
QString base = get_appdata_path();
#else
@@ -177,15 +198,13 @@ QString getDefaultConfigName()
QString getBaseDataPath()
{
#ifdef Q_OS_WIN32
auto get_appdata_path = []() noexcept -> QString
static constexpr auto get_appdata_path = []() -> QString
{
QString ret;
WCHAR *buffer{};
auto buffer = WCharBufferPtr{};
if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND,
nullptr, &buffer)}; SUCCEEDED(hr))
ret = QString::fromWCharArray(buffer);
CoTaskMemFree(buffer);
return ret;
nullptr, al::out_ptr(buffer))}; SUCCEEDED(hr))
return QString::fromWCharArray(buffer.get());
return QString{};
};
QString base = get_appdata_path();
#else
@@ -264,9 +283,9 @@ QString getCheckValue(const QCheckBox *checkbox)
{
const Qt::CheckState state{checkbox->checkState()};
if(state == Qt::Checked)
return QString{"true"};
return QStringLiteral("true");
if(state == Qt::Unchecked)
return QString{"false"};
return QStringLiteral("false");
return QString{};
}
@@ -296,18 +315,18 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}
ui->resamplerSlider->setRange(0, resamplerList.size()-1);
ui->hrtfmodeSlider->setRange(0, hrtfModeList.size()-1);
#if !defined(HAVE_NEON) && !defined(HAVE_SSE)
#if !HAVE_NEON && !HAVE_SSE
ui->cpuExtDisabledLabel->move(ui->cpuExtDisabledLabel->x(), ui->cpuExtDisabledLabel->y() - 60);
#else
ui->cpuExtDisabledLabel->setVisible(false);
#endif
#ifndef HAVE_NEON
#if !HAVE_NEON
#ifndef HAVE_SSE4_1
#ifndef HAVE_SSE3
#ifndef HAVE_SSE2
#ifndef HAVE_SSE
#if !HAVE_SSE4_1
#if !HAVE_SSE3
#if !HAVE_SSE2
#if !HAVE_SSE
ui->enableSSECheckBox->setVisible(false);
#endif /* !SSE */
ui->enableSSE2CheckBox->setVisible(false);
@@ -320,10 +339,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}
#else /* !Neon */
#ifndef HAVE_SSE4_1
#ifndef HAVE_SSE3
#ifndef HAVE_SSE2
#ifndef HAVE_SSE
#if !HAVE_SSE4_1
#if !HAVE_SSE3
#if !HAVE_SSE2
#if !HAVE_SSE
ui->enableNeonCheckBox->move(ui->enableNeonCheckBox->x(), ui->enableNeonCheckBox->y() - 30);
ui->enableSSECheckBox->setVisible(false);
#endif /* !SSE */
@@ -336,7 +355,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}
#endif
#ifndef ALSOFT_EAX
#if !ALSOFT_EAX
ui->enableEaxCheck->setChecked(Qt::Unchecked);
ui->enableEaxCheck->setEnabled(false);
ui->enableEaxCheck->setVisible(false);
@@ -486,7 +505,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}
{
QList<QListWidgetItem*> items = ui->backendListWidget->findItems(
std::data(backendList[i].full_string), Qt::MatchFixedString);
foreach(QListWidgetItem *item, items)
Q_FOREACH(QListWidgetItem *item, items)
item->setHidden(false);
}
@@ -537,9 +556,9 @@ QStringList MainWindow::collectHrtfs()
{
QDir dir(ui->hrtfFileList->item(i)->text());
QStringList fnames = dir.entryList(QDir::Files | QDir::Readable, QDir::Name);
foreach(const QString &fname, fnames)
Q_FOREACH(const QString &fname, fnames)
{
if(!fname.endsWith(".mhr", Qt::CaseInsensitive))
if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive))
continue;
QString fullname{dir.absoluteFilePath(fname)};
if(processed.contains(fullname))
@@ -567,14 +586,14 @@ QStringList MainWindow::collectHrtfs()
if(ui->defaultHrtfPathsCheckBox->isChecked())
{
QStringList paths = getAllDataPaths("/openal/hrtf");
foreach(const QString &name, paths)
QStringList paths = getAllDataPaths(QStringLiteral("/openal/hrtf"));
Q_FOREACH(const QString &name, paths)
{
QDir dir{name};
QStringList fnames{dir.entryList(QDir::Files | QDir::Readable, QDir::Name)};
foreach(const QString &fname, fnames)
Q_FOREACH(const QString &fname, fnames)
{
if(!fname.endsWith(".mhr", Qt::CaseInsensitive))
if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive))
continue;
QString fullname{dir.absoluteFilePath(fname)};
if(processed.contains(fullname))
@@ -601,7 +620,7 @@ QStringList MainWindow::collectHrtfs()
}
#ifdef ALSOFT_EMBED_HRTF_DATA
ret.push_back("Built-In HRTF");
ret.push_back(QStringLiteral("Built-In HRTF"));
#endif
}
return ret;
@@ -619,7 +638,7 @@ void MainWindow::loadConfig(const QString &fname)
{
QSettings settings{fname, QSettings::IniFormat};
QString sampletype = settings.value("sample-type").toString();
QString sampletype{settings.value(QStringLiteral("sample-type")).toString()};
ui->sampleFormatCombo->setCurrentIndex(0);
if(sampletype.isEmpty() == false)
{
@@ -631,12 +650,12 @@ void MainWindow::loadConfig(const QString &fname)
}
}
QString channelconfig{settings.value("channels").toString()};
QString channelconfig{settings.value(QStringLiteral("channels")).toString()};
ui->channelConfigCombo->setCurrentIndex(0);
if(channelconfig.isEmpty() == false)
{
if(channelconfig == "surround51rear")
channelconfig = "surround51";
if(channelconfig == QStringLiteral("surround51rear"))
channelconfig = QStringLiteral("surround51");
QString str{getNameFromValue(speakerModeList, channelconfig)};
if(!str.isEmpty())
{
@@ -645,7 +664,7 @@ void MainWindow::loadConfig(const QString &fname)
}
}
QString srate{settings.value("frequency").toString()};
QString srate{settings.value(QStringLiteral("frequency")).toString()};
if(srate.isEmpty())
ui->sampleRateCombo->setCurrentIndex(0);
else
@@ -655,23 +674,26 @@ void MainWindow::loadConfig(const QString &fname)
}
ui->srcCountLineEdit->clear();
ui->srcCountLineEdit->insert(settings.value("sources").toString());
ui->srcCountLineEdit->insert(settings.value(QStringLiteral("sources")).toString());
ui->effectSlotLineEdit->clear();
ui->effectSlotLineEdit->insert(settings.value("slots").toString());
ui->effectSlotLineEdit->insert(settings.value(QStringLiteral("slots")).toString());
ui->srcSendLineEdit->clear();
ui->srcSendLineEdit->insert(settings.value("sends").toString());
ui->srcSendLineEdit->insert(settings.value(QStringLiteral("sends")).toString());
QString resampler = settings.value("resampler").toString().trimmed();
ui->resamplerSlider->setValue(2);
ui->resamplerLabel->setText(std::data(resamplerList[2].name));
/* The "sinc4" and "sinc8" resamplers are no longer supported. Use "cubic"
* as a fallback.
auto resampler = settings.value(QStringLiteral("resampler")).toString().trimmed();
static constexpr auto defaultResamplerIndex = GetDefaultIndex(resamplerList);
ui->resamplerSlider->setValue(defaultResamplerIndex);
ui->resamplerLabel->setText(std::data(resamplerList[defaultResamplerIndex].name));
/* "Cubic" is an alias for the 4-point spline resampler. The "sinc4" and
* "sinc8" resamplers are unsupported, use "gaussian" as a fallback.
*/
if(resampler == "sinc4" || resampler == "sinc8")
resampler = "cubic";
if(resampler == QLatin1String{"cubic"})
resampler = QStringLiteral("spline");
else if(resampler == QLatin1String{"sinc4"} || resampler == QLatin1String{"sinc8"})
resampler = QStringLiteral("gaussian");
/* The "bsinc" resampler name is an alias for "bsinc12". */
else if(resampler == "bsinc")
resampler = "bsinc12";
else if(resampler == QLatin1String{"bsinc"})
resampler = QStringLiteral("bsinc12");
for(int i = 0;resamplerList[i].name[0];i++)
{
if(resampler == std::data(resamplerList[i].value))
@@ -682,7 +704,7 @@ void MainWindow::loadConfig(const QString &fname)
}
}
QString stereomode = settings.value("stereo-mode").toString().trimmed();
QString stereomode{settings.value(QStringLiteral("stereo-mode")).toString().trimmed()};
ui->stereoModeCombo->setCurrentIndex(0);
if(stereomode.isEmpty() == false)
{
@@ -710,10 +732,10 @@ void MainWindow::loadConfig(const QString &fname)
updatePeriodCountSlider();
}
ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value("output-limiter")));
ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value("dither")));
ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("output-limiter"))));
ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("dither"))));
QString stereopan{settings.value("stereo-encoding").toString()};
QString stereopan{settings.value(QStringLiteral("stereo-encoding")).toString()};
ui->stereoEncodingComboBox->setCurrentIndex(0);
if(stereopan.isEmpty() == false)
{
@@ -725,7 +747,7 @@ void MainWindow::loadConfig(const QString &fname)
}
}
QString ambiformat{settings.value("ambi-format").toString()};
QString ambiformat{settings.value(QStringLiteral("ambi-format")).toString()};
ui->ambiFormatComboBox->setCurrentIndex(0);
if(ambiformat.isEmpty() == false)
{
@@ -737,35 +759,36 @@ 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 speakerdist{settings.value("decoder/speaker-dist", 1.0).toDouble()};
ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value(QStringLiteral("decoder/hq-mode"))));
ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("decoder/distance-comp"))));
ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("decoder/nfc"))));
double speakerdist{settings.value(QStringLiteral("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());
ui->decoderQuadLineEdit->setText(settings.value(QStringLiteral("decoder/quad")).toString());
ui->decoder51LineEdit->setText(settings.value(QStringLiteral("decoder/surround51")).toString());
ui->decoder61LineEdit->setText(settings.value(QStringLiteral("decoder/surround61")).toString());
ui->decoder71LineEdit->setText(settings.value(QStringLiteral("decoder/surround71")).toString());
ui->decoder3D71LineEdit->setText(settings.value(QStringLiteral("decoder/surround3d71")).toString());
QStringList disabledCpuExts{settings.value("disable-cpu-exts").toStringList()};
QStringList disabledCpuExts{settings.value(QStringLiteral("disable-cpu-exts")).toStringList()};
if(disabledCpuExts.size() == 1)
disabledCpuExts = disabledCpuExts[0].split(QChar(','));
for(QString &name : disabledCpuExts)
name = name.trimmed();
ui->enableSSECheckBox->setChecked(!disabledCpuExts.contains("sse", Qt::CaseInsensitive));
ui->enableSSE2CheckBox->setChecked(!disabledCpuExts.contains("sse2", Qt::CaseInsensitive));
ui->enableSSE3CheckBox->setChecked(!disabledCpuExts.contains("sse3", Qt::CaseInsensitive));
ui->enableSSE41CheckBox->setChecked(!disabledCpuExts.contains("sse4.1", Qt::CaseInsensitive));
ui->enableNeonCheckBox->setChecked(!disabledCpuExts.contains("neon", Qt::CaseInsensitive));
ui->enableSSECheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse"), Qt::CaseInsensitive));
ui->enableSSE2CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse2"), Qt::CaseInsensitive));
ui->enableSSE3CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse3"), Qt::CaseInsensitive));
ui->enableSSE41CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse4.1"), Qt::CaseInsensitive));
ui->enableNeonCheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("neon"), Qt::CaseInsensitive));
QString hrtfmode{settings.value("hrtf-mode").toString().trimmed()};
ui->hrtfmodeSlider->setValue(2);
ui->hrtfmodeLabel->setText(std::data(hrtfModeList[3].name));
auto hrtfmode = settings.value(QStringLiteral("hrtf-mode")).toString().trimmed();
static constexpr auto defaultHrtfModeIndex = GetDefaultIndex(hrtfModeList);
ui->hrtfmodeSlider->setValue(defaultHrtfModeIndex);
ui->hrtfmodeLabel->setText(std::data(hrtfModeList[defaultHrtfModeIndex].name));
/* The "basic" mode name is no longer supported. Use "ambi2" instead. */
if(hrtfmode == "basic")
hrtfmode = "ambi2";
if(hrtfmode == QLatin1String{"basic"})
hrtfmode = QStringLiteral("ambi2");
for(size_t i{0};i < hrtfModeList.size();++i)
{
if(hrtfmode == std::data(hrtfModeList[i].value))
@@ -776,7 +799,7 @@ void MainWindow::loadConfig(const QString &fname)
}
}
QStringList hrtf_paths{settings.value("hrtf-paths").toStringList()};
QStringList hrtf_paths{settings.value(QStringLiteral("hrtf-paths")).toStringList()};
if(hrtf_paths.size() == 1)
hrtf_paths = hrtf_paths[0].split(QChar(','));
for(QString &name : hrtf_paths)
@@ -794,15 +817,15 @@ void MainWindow::loadConfig(const QString &fname)
updateHrtfRemoveButton();
ui->preferredHrtfComboBox->clear();
ui->preferredHrtfComboBox->addItem("- Any -");
ui->preferredHrtfComboBox->addItem(QStringLiteral("- Any -"));
if(ui->defaultHrtfPathsCheckBox->isChecked())
{
QStringList hrtfs{collectHrtfs()};
foreach(const QString &name, hrtfs)
Q_FOREACH(const QString &name, hrtfs)
ui->preferredHrtfComboBox->addItem(name);
}
QString defaulthrtf{settings.value("default-hrtf").toString()};
QString defaulthrtf{settings.value(QStringLiteral("default-hrtf")).toString()};
ui->preferredHrtfComboBox->setCurrentIndex(0);
if(defaulthrtf.isEmpty() == false)
{
@@ -820,7 +843,7 @@ void MainWindow::loadConfig(const QString &fname)
ui->enabledBackendList->clear();
ui->disabledBackendList->clear();
QStringList drivers{settings.value("drivers").toStringList()};
QStringList drivers{settings.value(QStringLiteral("drivers")).toStringList()};
if(drivers.empty())
ui->backendCheckBox->setChecked(true);
else
@@ -833,14 +856,14 @@ void MainWindow::loadConfig(const QString &fname)
/* Convert "mmdevapi" references to "wasapi" for backwards
* compatibility.
*/
if(name == "-mmdevapi")
name = "-wasapi";
else if(name == "mmdevapi")
name = "wasapi";
if(name == QLatin1String{"-mmdevapi"})
name = QStringLiteral("-wasapi");
else if(name == QLatin1String{"mmdevapi"})
name = QStringLiteral("wasapi");
}
bool lastWasEmpty = false;
foreach(const QString &backend, drivers)
bool lastWasEmpty{false};
Q_FOREACH(const QString &backend, drivers)
{
lastWasEmpty = backend.isEmpty();
if(lastWasEmpty) continue;
@@ -872,7 +895,7 @@ void MainWindow::loadConfig(const QString &fname)
ui->backendCheckBox->setChecked(lastWasEmpty);
}
QString defaultreverb{settings.value("default-reverb").toString().toLower()};
QString defaultreverb{settings.value(QStringLiteral("default-reverb")).toString().toLower()};
ui->defaultReverbComboBox->setCurrentIndex(0);
if(defaultreverb.isEmpty() == false)
{
@@ -886,56 +909,56 @@ void MainWindow::loadConfig(const QString &fname)
}
}
QStringList excludefx{settings.value("excludefx").toStringList()};
QStringList excludefx{settings.value(QStringLiteral("excludefx")).toStringList()};
if(excludefx.size() == 1)
excludefx = excludefx[0].split(QChar(','));
for(QString &name : excludefx)
name = name.trimmed();
ui->enableEaxReverbCheck->setChecked(!excludefx.contains("eaxreverb", Qt::CaseInsensitive));
ui->enableStdReverbCheck->setChecked(!excludefx.contains("reverb", Qt::CaseInsensitive));
ui->enableAutowahCheck->setChecked(!excludefx.contains("autowah", Qt::CaseInsensitive));
ui->enableChorusCheck->setChecked(!excludefx.contains("chorus", Qt::CaseInsensitive));
ui->enableCompressorCheck->setChecked(!excludefx.contains("compressor", Qt::CaseInsensitive));
ui->enableDistortionCheck->setChecked(!excludefx.contains("distortion", Qt::CaseInsensitive));
ui->enableEchoCheck->setChecked(!excludefx.contains("echo", Qt::CaseInsensitive));
ui->enableEqualizerCheck->setChecked(!excludefx.contains("equalizer", Qt::CaseInsensitive));
ui->enableFlangerCheck->setChecked(!excludefx.contains("flanger", Qt::CaseInsensitive));
ui->enableFrequencyShifterCheck->setChecked(!excludefx.contains("fshifter", Qt::CaseInsensitive));
ui->enableModulatorCheck->setChecked(!excludefx.contains("modulator", Qt::CaseInsensitive));
ui->enableDedicatedCheck->setChecked(!excludefx.contains("dedicated", Qt::CaseInsensitive));
ui->enablePitchShifterCheck->setChecked(!excludefx.contains("pshifter", Qt::CaseInsensitive));
ui->enableVocalMorpherCheck->setChecked(!excludefx.contains("vmorpher", Qt::CaseInsensitive));
ui->enableEaxReverbCheck->setChecked(!excludefx.contains(QStringLiteral("eaxreverb"), Qt::CaseInsensitive));
ui->enableStdReverbCheck->setChecked(!excludefx.contains(QStringLiteral("reverb"), Qt::CaseInsensitive));
ui->enableAutowahCheck->setChecked(!excludefx.contains(QStringLiteral("autowah"), Qt::CaseInsensitive));
ui->enableChorusCheck->setChecked(!excludefx.contains(QStringLiteral("chorus"), Qt::CaseInsensitive));
ui->enableCompressorCheck->setChecked(!excludefx.contains(QStringLiteral("compressor"), Qt::CaseInsensitive));
ui->enableDistortionCheck->setChecked(!excludefx.contains(QStringLiteral("distortion"), Qt::CaseInsensitive));
ui->enableEchoCheck->setChecked(!excludefx.contains(QStringLiteral("echo"), Qt::CaseInsensitive));
ui->enableEqualizerCheck->setChecked(!excludefx.contains(QStringLiteral("equalizer"), Qt::CaseInsensitive));
ui->enableFlangerCheck->setChecked(!excludefx.contains(QStringLiteral("flanger"), Qt::CaseInsensitive));
ui->enableFrequencyShifterCheck->setChecked(!excludefx.contains(QStringLiteral("fshifter"), Qt::CaseInsensitive));
ui->enableModulatorCheck->setChecked(!excludefx.contains(QStringLiteral("modulator"), Qt::CaseInsensitive));
ui->enableDedicatedCheck->setChecked(!excludefx.contains(QStringLiteral("dedicated"), Qt::CaseInsensitive));
ui->enablePitchShifterCheck->setChecked(!excludefx.contains(QStringLiteral("pshifter"), Qt::CaseInsensitive));
ui->enableVocalMorpherCheck->setChecked(!excludefx.contains(QStringLiteral("vmorpher"), Qt::CaseInsensitive));
if(ui->enableEaxCheck->isEnabled())
ui->enableEaxCheck->setChecked(getCheckState(settings.value("eax/enable")) != Qt::Unchecked);
ui->enableEaxCheck->setChecked(getCheckState(settings.value(QStringLiteral("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->pulseAutospawnCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/spawn-server"))));
ui->pulseAllowMovesCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/allow-moves"))));
ui->pulseFixRateCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/fix-rate"))));
ui->pulseAdjLatencyCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/adjust-latency"))));
ui->pwireAssumeAudioCheckBox->setCheckState(getCheckState(settings.value("pipewire/assume-audio")));
ui->pwireRtMixCheckBox->setCheckState(getCheckState(settings.value("pipewire/rt-mix")));
ui->pwireAssumeAudioCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pipewire/assume-audio"))));
ui->pwireRtMixCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pipewire/rt-mix"))));
ui->wasapiResamplerCheckBox->setCheckState(getCheckState(settings.value("wasapi/allow-resampler")));
ui->wasapiResamplerCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("wasapi/allow-resampler"))));
ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value("jack/spawn-server")));
ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value("jack/connect-ports")));
ui->jackRtMixCheckBox->setCheckState(getCheckState(settings.value("jack/rt-mix")));
ui->jackBufferSizeLine->setText(settings.value("jack/buffer-size", QString()).toString());
ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/spawn-server"))));
ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/connect-ports"))));
ui->jackRtMixCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/rt-mix"))));
ui->jackBufferSizeLine->setText(settings.value(QStringLiteral("jack/buffer-size"), QString()).toString());
updateJackBufferSizeSlider();
ui->alsaDefaultDeviceLine->setText(settings.value("alsa/device", QString()).toString());
ui->alsaDefaultCaptureLine->setText(settings.value("alsa/capture", QString()).toString());
ui->alsaResamplerCheckBox->setCheckState(getCheckState(settings.value("alsa/allow-resampler")));
ui->alsaMmapCheckBox->setCheckState(getCheckState(settings.value("alsa/mmap")));
ui->alsaDefaultDeviceLine->setText(settings.value(QStringLiteral("alsa/device"), QString()).toString());
ui->alsaDefaultCaptureLine->setText(settings.value(QStringLiteral("alsa/capture"), QString()).toString());
ui->alsaResamplerCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("alsa/allow-resampler"))));
ui->alsaMmapCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("alsa/mmap"))));
ui->ossDefaultDeviceLine->setText(settings.value("oss/device", QString()).toString());
ui->ossDefaultCaptureLine->setText(settings.value("oss/capture", QString()).toString());
ui->ossDefaultDeviceLine->setText(settings.value(QStringLiteral("oss/device"), QString()).toString());
ui->ossDefaultCaptureLine->setText(settings.value(QStringLiteral("oss/capture"), QString()).toString());
ui->solarisDefaultDeviceLine->setText(settings.value("solaris/device", QString()).toString());
ui->solarisDefaultDeviceLine->setText(settings.value(QStringLiteral("solaris/device"), QString()).toString());
ui->waveOutputLine->setText(settings.value("wave/file", QString()).toString());
ui->waveBFormatCheckBox->setChecked(settings.value("wave/bformat", false).toBool());
ui->waveOutputLine->setText(settings.value(QStringLiteral("wave/file"), QString()).toString());
ui->waveBFormatCheckBox->setChecked(settings.value(QStringLiteral("wave/bformat"), false).toBool());
ui->applyButton->setEnabled(false);
ui->closeCancelButton->setText(tr("Close"));
@@ -969,72 +992,72 @@ void MainWindow::saveConfig(const QString &fname) const
/* HACK: Compound any stringlist values into a comma-separated string. */
QStringList allkeys{settings.allKeys()};
foreach(const QString &key, allkeys)
Q_FOREACH(const QString &key, allkeys)
{
QStringList vals{settings.value(key).toStringList()};
if(vals.size() > 1)
settings.setValue(key, vals.join(QChar(',')));
}
settings.setValue("sample-type", getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText()));
settings.setValue("channels", getValueFromName(speakerModeList, ui->channelConfigCombo->currentText()));
settings.setValue(QStringLiteral("sample-type"), getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText()));
settings.setValue(QStringLiteral("channels"), getValueFromName(speakerModeList, ui->channelConfigCombo->currentText()));
uint rate{ui->sampleRateCombo->currentText().toUInt()};
if(rate <= 0)
settings.setValue("frequency", QString{});
settings.setValue(QStringLiteral("frequency"), QString{});
else
settings.setValue("frequency", rate);
settings.setValue(QStringLiteral("frequency"), rate);
settings.setValue("period_size", ui->periodSizeEdit->text());
settings.setValue("periods", ui->periodCountEdit->text());
settings.setValue(QStringLiteral("period_size"), ui->periodSizeEdit->text());
settings.setValue(QStringLiteral("periods"), ui->periodCountEdit->text());
settings.setValue("sources", ui->srcCountLineEdit->text());
settings.setValue("slots", ui->effectSlotLineEdit->text());
settings.setValue(QStringLiteral("sources"), ui->srcCountLineEdit->text());
settings.setValue(QStringLiteral("slots"), ui->effectSlotLineEdit->text());
settings.setValue("resampler", std::data(resamplerList[ui->resamplerSlider->value()].value));
settings.setValue(QStringLiteral("resampler"), std::data(resamplerList[ui->resamplerSlider->value()].value));
settings.setValue("stereo-mode", getValueFromName(stereoModeList, ui->stereoModeCombo->currentText()));
settings.setValue("stereo-encoding", getValueFromName(stereoEncList, ui->stereoEncodingComboBox->currentText()));
settings.setValue("ambi-format", getValueFromName(ambiFormatList, ui->ambiFormatComboBox->currentText()));
settings.setValue(QStringLiteral("stereo-mode"), getValueFromName(stereoModeList, ui->stereoModeCombo->currentText()));
settings.setValue(QStringLiteral("stereo-encoding"), getValueFromName(stereoEncList, ui->stereoEncodingComboBox->currentText()));
settings.setValue(QStringLiteral("ambi-format"), getValueFromName(ambiFormatList, ui->ambiFormatComboBox->currentText()));
settings.setValue("output-limiter", getCheckValue(ui->outputLimiterCheckBox));
settings.setValue("dither", getCheckValue(ui->outputDitherCheckBox));
settings.setValue(QStringLiteral("output-limiter"), getCheckValue(ui->outputLimiterCheckBox));
settings.setValue(QStringLiteral("dither"), getCheckValue(ui->outputDitherCheckBox));
settings.setValue("decoder/hq-mode", getCheckValue(ui->decoderHQModeCheckBox));
settings.setValue("decoder/distance-comp", getCheckValue(ui->decoderDistCompCheckBox));
settings.setValue("decoder/nfc", getCheckValue(ui->decoderNFEffectsCheckBox));
settings.setValue(QStringLiteral("decoder/hq-mode"), getCheckValue(ui->decoderHQModeCheckBox));
settings.setValue(QStringLiteral("decoder/distance-comp"), getCheckValue(ui->decoderDistCompCheckBox));
settings.setValue(QStringLiteral("decoder/nfc"), getCheckValue(ui->decoderNFEffectsCheckBox));
double speakerdist{ui->decoderSpeakerDistSpinBox->value()};
settings.setValue("decoder/speaker-dist",
settings.setValue(QStringLiteral("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());
settings.setValue(QStringLiteral("decoder/quad"), ui->decoderQuadLineEdit->text());
settings.setValue(QStringLiteral("decoder/surround51"), ui->decoder51LineEdit->text());
settings.setValue(QStringLiteral("decoder/surround61"), ui->decoder61LineEdit->text());
settings.setValue(QStringLiteral("decoder/surround71"), ui->decoder71LineEdit->text());
settings.setValue(QStringLiteral("decoder/surround3d71"), ui->decoder3D71LineEdit->text());
QStringList strlist;
if(!ui->enableSSECheckBox->isChecked())
strlist.append("sse");
strlist.append(QStringLiteral("sse"));
if(!ui->enableSSE2CheckBox->isChecked())
strlist.append("sse2");
strlist.append(QStringLiteral("sse2"));
if(!ui->enableSSE3CheckBox->isChecked())
strlist.append("sse3");
strlist.append(QStringLiteral("sse3"));
if(!ui->enableSSE41CheckBox->isChecked())
strlist.append("sse4.1");
strlist.append(QStringLiteral("sse4.1"));
if(!ui->enableNeonCheckBox->isChecked())
strlist.append("neon");
settings.setValue("disable-cpu-exts", strlist.join(QChar(',')));
strlist.append(QStringLiteral("neon"));
settings.setValue(QStringLiteral("disable-cpu-exts"), strlist.join(QChar(',')));
settings.setValue("hrtf-mode", std::data(hrtfModeList[ui->hrtfmodeSlider->value()].value));
settings.setValue(QStringLiteral("hrtf-mode"), std::data(hrtfModeList[ui->hrtfmodeSlider->value()].value));
if(ui->preferredHrtfComboBox->currentIndex() == 0)
settings.setValue("default-hrtf", QString{});
settings.setValue(QStringLiteral("default-hrtf"), QString{});
else
{
QString str{ui->preferredHrtfComboBox->currentText()};
settings.setValue("default-hrtf", str);
settings.setValue(QStringLiteral("default-hrtf"), str);
}
strlist.clear();
@@ -1043,7 +1066,7 @@ void MainWindow::saveConfig(const QString &fname) const
strlist.append(ui->hrtfFileList->item(i)->text());
if(!strlist.empty() && ui->defaultHrtfPathsCheckBox->isChecked())
strlist.append(QString{});
settings.setValue("hrtf-paths", strlist.join(QChar{','}));
settings.setValue(QStringLiteral("hrtf-paths"), strlist.join(QChar{','}));
strlist.clear();
for(int i = 0;i < ui->enabledBackendList->count();i++)
@@ -1071,92 +1094,92 @@ void MainWindow::saveConfig(const QString &fname) const
}
}
if(strlist.empty() && !ui->backendCheckBox->isChecked())
strlist.append("-all");
strlist.append(QStringLiteral("-all"));
else if(ui->backendCheckBox->isChecked())
strlist.append(QString{});
settings.setValue("drivers", strlist.join(QChar(',')));
settings.setValue(QStringLiteral("drivers"), strlist.join(QChar(',')));
// TODO: Remove check when we can properly match global values.
if(ui->defaultReverbComboBox->currentIndex() == 0)
settings.setValue("default-reverb", QString{});
settings.setValue(QStringLiteral("default-reverb"), QString{});
else
{
QString str{ui->defaultReverbComboBox->currentText().toLower()};
settings.setValue("default-reverb", str);
settings.setValue(QStringLiteral("default-reverb"), str);
}
strlist.clear();
if(!ui->enableEaxReverbCheck->isChecked())
strlist.append("eaxreverb");
strlist.append(QStringLiteral("eaxreverb"));
if(!ui->enableStdReverbCheck->isChecked())
strlist.append("reverb");
strlist.append(QStringLiteral("reverb"));
if(!ui->enableAutowahCheck->isChecked())
strlist.append("autowah");
strlist.append(QStringLiteral("autowah"));
if(!ui->enableChorusCheck->isChecked())
strlist.append("chorus");
strlist.append(QStringLiteral("chorus"));
if(!ui->enableDistortionCheck->isChecked())
strlist.append("distortion");
strlist.append(QStringLiteral("distortion"));
if(!ui->enableCompressorCheck->isChecked())
strlist.append("compressor");
strlist.append(QStringLiteral("compressor"));
if(!ui->enableEchoCheck->isChecked())
strlist.append("echo");
strlist.append(QStringLiteral("echo"));
if(!ui->enableEqualizerCheck->isChecked())
strlist.append("equalizer");
strlist.append(QStringLiteral("equalizer"));
if(!ui->enableFlangerCheck->isChecked())
strlist.append("flanger");
strlist.append(QStringLiteral("flanger"));
if(!ui->enableFrequencyShifterCheck->isChecked())
strlist.append("fshifter");
strlist.append(QStringLiteral("fshifter"));
if(!ui->enableModulatorCheck->isChecked())
strlist.append("modulator");
strlist.append(QStringLiteral("modulator"));
if(!ui->enableDedicatedCheck->isChecked())
strlist.append("dedicated");
strlist.append(QStringLiteral("dedicated"));
if(!ui->enablePitchShifterCheck->isChecked())
strlist.append("pshifter");
strlist.append(QStringLiteral("pshifter"));
if(!ui->enableVocalMorpherCheck->isChecked())
strlist.append("vmorpher");
settings.setValue("excludefx", strlist.join(QChar{','}));
settings.setValue("eax/enable",
strlist.append(QStringLiteral("vmorpher"));
settings.setValue(QStringLiteral("excludefx"), strlist.join(QChar{','}));
settings.setValue(QStringLiteral("eax/enable"),
(!ui->enableEaxCheck->isEnabled() || ui->enableEaxCheck->isChecked())
? QString{/*"true"*/} : QString{"false"});
? QString{/*"true"*/} : QStringLiteral("false"));
settings.setValue("pipewire/assume-audio", getCheckValue(ui->pwireAssumeAudioCheckBox));
settings.setValue("pipewire/rt-mix", getCheckValue(ui->pwireRtMixCheckBox));
settings.setValue(QStringLiteral("pipewire/assume-audio"), getCheckValue(ui->pwireAssumeAudioCheckBox));
settings.setValue(QStringLiteral("pipewire/rt-mix"), getCheckValue(ui->pwireRtMixCheckBox));
settings.setValue("wasapi/allow-resampler", getCheckValue(ui->wasapiResamplerCheckBox));
settings.setValue(QStringLiteral("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(QStringLiteral("pulse/spawn-server"), getCheckValue(ui->pulseAutospawnCheckBox));
settings.setValue(QStringLiteral("pulse/allow-moves"), getCheckValue(ui->pulseAllowMovesCheckBox));
settings.setValue(QStringLiteral("pulse/fix-rate"), getCheckValue(ui->pulseFixRateCheckBox));
settings.setValue(QStringLiteral("pulse/adjust-latency"), getCheckValue(ui->pulseAdjLatencyCheckBox));
settings.setValue("jack/spawn-server", getCheckValue(ui->jackAutospawnCheckBox));
settings.setValue("jack/connect-ports", getCheckValue(ui->jackConnectPortsCheckBox));
settings.setValue("jack/rt-mix", getCheckValue(ui->jackRtMixCheckBox));
settings.setValue("jack/buffer-size", ui->jackBufferSizeLine->text());
settings.setValue(QStringLiteral("jack/spawn-server"), getCheckValue(ui->jackAutospawnCheckBox));
settings.setValue(QStringLiteral("jack/connect-ports"), getCheckValue(ui->jackConnectPortsCheckBox));
settings.setValue(QStringLiteral("jack/rt-mix"), getCheckValue(ui->jackRtMixCheckBox));
settings.setValue(QStringLiteral("jack/buffer-size"), ui->jackBufferSizeLine->text());
settings.setValue("alsa/device", ui->alsaDefaultDeviceLine->text());
settings.setValue("alsa/capture", ui->alsaDefaultCaptureLine->text());
settings.setValue("alsa/allow-resampler", getCheckValue(ui->alsaResamplerCheckBox));
settings.setValue("alsa/mmap", getCheckValue(ui->alsaMmapCheckBox));
settings.setValue(QStringLiteral("alsa/device"), ui->alsaDefaultDeviceLine->text());
settings.setValue(QStringLiteral("alsa/capture"), ui->alsaDefaultCaptureLine->text());
settings.setValue(QStringLiteral("alsa/allow-resampler"), getCheckValue(ui->alsaResamplerCheckBox));
settings.setValue(QStringLiteral("alsa/mmap"), getCheckValue(ui->alsaMmapCheckBox));
settings.setValue("oss/device", ui->ossDefaultDeviceLine->text());
settings.setValue("oss/capture", ui->ossDefaultCaptureLine->text());
settings.setValue(QStringLiteral("oss/device"), ui->ossDefaultDeviceLine->text());
settings.setValue(QStringLiteral("oss/capture"), ui->ossDefaultCaptureLine->text());
settings.setValue("solaris/device", ui->solarisDefaultDeviceLine->text());
settings.setValue(QStringLiteral("solaris/device"), ui->solarisDefaultDeviceLine->text());
settings.setValue("wave/file", ui->waveOutputLine->text());
settings.setValue("wave/bformat",
ui->waveBFormatCheckBox->isChecked() ? QString{"true"} : QString{/*"false"*/}
settings.setValue(QStringLiteral("wave/file"), ui->waveOutputLine->text());
settings.setValue(QStringLiteral("wave/bformat"),
ui->waveBFormatCheckBox->isChecked() ? QStringLiteral("true") : QString{/*"false"*/}
);
/* Remove empty keys
* FIXME: Should only remove keys whose value matches the globally-specified value.
*/
allkeys = settings.allKeys();
foreach(const QString &key, allkeys)
Q_FOREACH(const QString &key, allkeys)
{
QString str{settings.value(key).toString()};
if(str == QString{})
if(str.isEmpty())
settings.remove(key);
}
}
@@ -1190,11 +1213,7 @@ void MainWindow::updatePeriodSizeSlider()
{
int pos = ui->periodSizeEdit->text().toInt();
if(pos >= 64)
{
if(pos > 8192)
pos = 8192;
ui->periodSizeSlider->setSliderPosition(pos);
}
ui->periodSizeSlider->setSliderPosition(std::min(pos, 8192));
enableApplyButton();
}
@@ -1281,7 +1300,7 @@ void MainWindow::updateHrtfModeLabel(int num)
void MainWindow::addHrtfFile()
{
QString path{QFileDialog::getExistingDirectory(this, tr("Select HRTF Path"))};
if(path.isEmpty() == false && !getAllDataPaths("/openal/hrtf").contains(path))
if(path.isEmpty() == false && !getAllDataPaths(QStringLiteral("/openal/hrtf")).contains(path))
{
ui->hrtfFileList->addItem(path);
enableApplyButton();
@@ -13,7 +13,7 @@ class MainWindow;
class MainWindow : public QMainWindow {
Q_OBJECT
private slots:
private Q_SLOTS:
void cancelCloseAction();
void saveCurrentConfig();
@@ -29,7 +29,7 @@ private slots:
void updatePeriodSizeEdit(int size);
void updatePeriodSizeSlider();
void updatePeriodCountEdit(int size);
void updatePeriodCountEdit(int count);
void updatePeriodCountSlider();
void selectQuadDecoderFile();
@@ -76,7 +76,7 @@ private:
void closeEvent(QCloseEvent *event) override;
void selectDecoderFile(QLineEdit *line, const char *name);
void selectDecoderFile(QLineEdit *line, const char *caption);
QStringList collectHrtfs();
-137
View File
@@ -1,137 +0,0 @@
/* $NetBSD: getopt.c,v 1.26 2003/08/07 16:43:40 agc Exp $ */
/*
* Copyright (c) 1987, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)getopt.c 8.3 (Berkeley) 4/27/95";
#endif /* LIBC_SCCS and not lint */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "getopt.h"
int opterr = 1, /* if error message should be printed */
optind = 1, /* index into parent argv vector */
optopt, /* character checked for validity */
optreset; /* reset getopt */
char *optarg; /* argument associated with option */
#define BADCH (int)'?'
#define BADARG (int)':'
#define EMSG ""
/*
* Get program name in Windows
*/
const char * _getprogname(void);
/*
* getopt --
* Parse argc/argv argument vector.
*/
int
getopt(int nargc, char * const nargv[], const char *ostr)
{
static char *place = EMSG; /* option letter processing */
char *oli; /* option letter list index */
if (optreset || *place == 0) { /* update scanning pointer */
optreset = 0;
place = nargv[optind];
if (optind >= nargc || *place++ != '-') {
/* Argument is absent or is not an option */
place = EMSG;
return (-1);
}
optopt = *place++;
if (optopt == '-' && *place == 0) {
/* "--" => end of options */
++optind;
place = EMSG;
return (-1);
}
if (optopt == 0) {
/* Solitary '-', treat as a '-' option
if the program (eg su) is looking for it. */
place = EMSG;
if (strchr(ostr, '-') == NULL)
return (-1);
optopt = '-';
}
} else
optopt = *place++;
/* See if option letter is one the caller wanted... */
if (optopt == ':' || (oli = strchr(ostr, optopt)) == NULL) {
if (*place == 0)
++optind;
if (opterr && *ostr != ':')
(void)fprintf(stderr,
"%s: illegal option -- %c\n", _getprogname(),
optopt);
return (BADCH);
}
/* Does this option need an argument? */
if (oli[1] != ':') {
/* don't need argument */
optarg = NULL;
if (*place == 0)
++optind;
} else {
/* Option-argument is either the rest of this argument or the
entire next argument. */
if (*place)
optarg = place;
else if (nargc > ++optind)
optarg = nargv[optind];
else {
/* option-argument absent */
place = EMSG;
if (*ostr == ':')
return (BADARG);
if (opterr)
(void)fprintf(stderr,
"%s: option requires an argument -- %c\n",
_getprogname(), optopt);
return (BADCH);
}
place = EMSG;
++optind;
}
return (optopt); /* return option letter */
}
const char * _getprogname() {
char *pgmptr = NULL;
_get_pgmptr(&pgmptr);
return strrchr(pgmptr,'\\')+1;
}
-26
View File
@@ -1,26 +0,0 @@
#ifndef GETOPT_H
#define GETOPT_H
#ifndef _WIN32
#include <unistd.h>
#else /* _WIN32 */
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
extern char *optarg;
extern int optind, opterr, optopt, optreset;
int getopt(int nargc, char * const nargv[], const char *ostr);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* !_WIN32 */
#endif /* !GETOPT_H */
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -2,12 +2,15 @@
#define LOADDEF_H
#include <istream>
#include <string_view>
#include "alspan.h"
#include "makemhr.h"
bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount,
const char *filename, const uint fftSize, const uint truncSize, const uint outRate,
bool LoadDefInput(std::istream &istream, const al::span<const char> startbytes,
const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate,
const ChannelModeT chanMode, HrirDataT *hData);
#endif /* LOADDEF_H */
+94 -85
View File
@@ -32,7 +32,6 @@
#include <future>
#include <iterator>
#include <memory>
#include <numeric>
#include <optional>
#include <string>
#include <string_view>
@@ -41,6 +40,7 @@
#include "alspan.h"
#include "alnumeric.h"
#include "fmt/core.h"
#include "makemhr.h"
#include "polyphase_resampler.h"
#include "sofa-support.h"
@@ -59,14 +59,14 @@ using uint = unsigned int;
* possible. Those sets that contain purely random measurements or use
* different major axes will fail.
*/
bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData)
auto PrepareLayout(const al::span<const float> xyzs, HrirDataT *hData) -> bool
{
fprintf(stdout, "Detecting compatible layout...\n");
fmt::println("Detecting compatible layout...");
auto fds = GetCompatibleLayout(m, xyzs);
auto fds = GetCompatibleLayout(xyzs);
if(fds.size() > MAX_FD_COUNT)
{
fprintf(stdout, "Incompatible layout (inumerable radii).\n");
fmt::println("Incompatible layout (inumerable radii).");
return false;
}
@@ -91,7 +91,7 @@ bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData)
++fi;
}
fprintf(stdout, "Using %u of %u IRs.\n", ir_total, m);
fmt::println("Using {} of {} IRs.", ir_total, xyzs.size()/3);
const auto azs = al::span{azCounts}.first<MAX_FD_COUNT>();
return PrepareHrirData(al::span{distances}.first(fi), evCounts, azs, hData);
}
@@ -108,7 +108,7 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf)
{
if(srate_dim)
{
fprintf(stderr, "Duplicate SampleRate.DIMENSION_LIST\n");
fmt::println(stderr, "Duplicate SampleRate.DIMENSION_LIST");
return 0.0f;
}
srate_dim = srate_attrs->value;
@@ -117,53 +117,53 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf)
{
if(srate_units)
{
fprintf(stderr, "Duplicate SampleRate.Units\n");
fmt::println(stderr, "Duplicate SampleRate.Units");
return 0.0f;
}
srate_units = srate_attrs->value;
}
else
fprintf(stderr, "Unexpected sample rate attribute: %s = %s\n", srate_attrs->name,
fmt::println(stderr, "Unexpected sample rate attribute: {} = {}", srate_attrs->name,
srate_attrs->value);
srate_attrs = srate_attrs->next;
}
if(!srate_dim)
{
fprintf(stderr, "Missing sample rate dimensions\n");
fmt::println(stderr, "Missing sample rate dimensions");
return 0.0f;
}
if(srate_dim != "I"sv)
{
fprintf(stderr, "Unsupported sample rate dimensions: %s\n", srate_dim);
fmt::println(stderr, "Unsupported sample rate dimensions: {}", srate_dim);
return 0.0f;
}
if(!srate_units)
{
fprintf(stderr, "Missing sample rate unit type\n");
fmt::println(stderr, "Missing sample rate unit type");
return 0.0f;
}
if(srate_units != "hertz"sv)
{
fprintf(stderr, "Unsupported sample rate unit type: %s\n", srate_units);
fmt::println(stderr, "Unsupported sample rate unit type: {}", srate_units);
return 0.0f;
}
/* I dimensions guarantees 1 element, so just extract it. */
if(srate_array->values[0] < float{MIN_RATE} || srate_array->values[0] > float{MAX_RATE})
const auto values = al::span{srate_array->values, sofaHrtf->I};
if(values[0] < float{MIN_RATE} || values[0] > float{MAX_RATE})
{
fprintf(stderr, "Sample rate out of range: %f (expected %u to %u)", srate_array->values[0],
fmt::println(stderr, "Sample rate out of range: {:f} (expected {} to {})", values[0],
MIN_RATE, MAX_RATE);
return 0.0f;
}
return srate_array->values[0];
return values[0];
}
enum class DelayType : uint8_t {
None,
I_R, /* [1][Channels] */
M_R, /* [HRIRs][Channels] */
Invalid,
};
DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf)
auto PrepareDelay(MYSOFA_HRTF *sofaHrtf) -> std::optional<DelayType>
{
const char *delay_dim{nullptr};
MYSOFA_ARRAY *delay_array{&sofaHrtf->DataDelay};
@@ -174,19 +174,19 @@ DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf)
{
if(delay_dim)
{
fprintf(stderr, "Duplicate Delay.DIMENSION_LIST\n");
return DelayType::Invalid;
fmt::println(stderr, "Duplicate Delay.DIMENSION_LIST");
return std::nullopt;
}
delay_dim = delay_attrs->value;
}
else
fprintf(stderr, "Unexpected delay attribute: %s = %s\n", delay_attrs->name,
fmt::println(stderr, "Unexpected delay attribute: {} = {}", delay_attrs->name,
delay_attrs->value ? delay_attrs->value : "<null>");
delay_attrs = delay_attrs->next;
}
if(!delay_dim)
{
fprintf(stderr, "Missing delay dimensions\n");
fmt::println(stderr, "Missing delay dimensions");
return DelayType::None;
}
if(delay_dim == "I,R"sv)
@@ -194,8 +194,8 @@ DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf)
if(delay_dim == "M,R"sv)
return DelayType::M_R;
fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim);
return DelayType::Invalid;
fmt::println(stderr, "Unsupported delay dimensions: {}", delay_dim);
return std::nullopt;
}
bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
@@ -209,24 +209,24 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
{
if(ir_dim)
{
fprintf(stderr, "Duplicate IR.DIMENSION_LIST\n");
fmt::println(stderr, "Duplicate IR.DIMENSION_LIST");
return false;
}
ir_dim = ir_attrs->value;
}
else
fprintf(stderr, "Unexpected IR attribute: %s = %s\n", ir_attrs->name,
fmt::println(stderr, "Unexpected IR attribute: {} = {}", ir_attrs->name,
ir_attrs->value ? ir_attrs->value : "<null>");
ir_attrs = ir_attrs->next;
}
if(!ir_dim)
{
fprintf(stderr, "Missing IR dimensions\n");
fmt::println(stderr, "Missing IR dimensions");
return false;
}
if(ir_dim != "M,R,N"sv)
{
fprintf(stderr, "Unsupported IR dimensions: %s\n", ir_dim);
fmt::println(stderr, "Unsupported IR dimensions: {}", ir_dim);
return false;
}
return true;
@@ -235,12 +235,12 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
/* Calculate the onset time of a HRIR. */
constexpr int OnsetRateMultiple{10};
double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n,
al::span<double> upsampled, const double *hrir)
auto CalcHrirOnset(PPhaseResampler &rs, const uint rate, al::span<double> upsampled,
const al::span<const double> hrir) -> double
{
rs.process({hrir, n}, upsampled);
rs.process(hrir, upsampled);
auto abs_lt = [](const double &lhs, const double &rhs) -> bool
auto abs_lt = [](const double lhs, const double rhs) -> bool
{ return std::abs(lhs) < std::abs(rhs); };
auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt);
return static_cast<double>(std::distance(upsampled.cbegin(), iter)) /
@@ -248,13 +248,13 @@ double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n,
}
/* Calculate the magnitude response of a HRIR. */
void CalcHrirMagnitude(const uint points, const uint n, al::span<complex_d> h, double *hrir)
void CalcHrirMagnitude(const uint points, al::span<complex_d> h, const al::span<double> hrir)
{
auto iter = std::copy_n(hrir, points, h.begin());
auto iter = std::copy_n(hrir.cbegin(), points, h.begin());
std::fill(iter, h.end(), complex_d{0.0, 0.0});
FftForward(n, h.data());
MagnitudeResponse(n, h.data(), hrir);
forward_fft(h);
MagnitudeResponse(h, hrir.first((h.size()/2) + 1));
}
bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType delayType,
@@ -266,7 +266,7 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela
{
const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
hData->mHrirsBase.resize(channels * size_t{hData->mIrCount} * hData->mIrSize, 0.0);
double *hrirs = hData->mHrirsBase.data();
const auto hrirs = al::span{hData->mHrirsBase};
std::vector<double> restmp;
std::optional<PPhaseResampler> resampler;
@@ -276,15 +276,15 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela
restmp.resize(sofaHrtf->N);
}
const auto srcPosValues = al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz};
const auto irValues = al::span{sofaHrtf->DataIR.values,
size_t{sofaHrtf->M}*sofaHrtf->R*sofaHrtf->N};
for(uint si{0u};si < sofaHrtf->M;++si)
{
loaded_count.fetch_add(1u);
std::array aer{
sofaHrtf->SourcePosition.values[3_uz*si],
sofaHrtf->SourcePosition.values[3_uz*si + 1],
sofaHrtf->SourcePosition.values[3_uz*si + 2]
};
std::array aer{srcPosValues[3_uz*si], srcPosValues[3_uz*si + 1],
srcPosValues[3_uz*si + 2]};
mysofa_c2s(aer.data());
if(std::abs(aer[1]) >= 89.999f)
@@ -311,40 +311,43 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela
ai %= static_cast<uint>(field->mEvs[ei].mAzs.size());
if(std::abs(af) >= 0.1) continue;
HrirAzT *azd = &field->mEvs[ei].mAzs[ai];
if(azd->mIrs[0] != nullptr)
HrirAzT &azd = field->mEvs[ei].mAzs[ai];
if(!azd.mIrs[0].empty())
{
fprintf(stderr, "\nMultiple measurements near [ a=%f, e=%f, r=%f ].\n",
fmt::println(stderr, "\nMultiple measurements near [ a={:f}, e={:f}, r={:f} ].",
aer[0], aer[1], aer[2]);
return false;
}
for(uint ti{0u};ti < channels;++ti)
{
azd->mIrs[ti] = &hrirs[(size_t{hData->mIrCount}*ti + azd->mIndex)*hData->mIrSize];
azd.mIrs[ti] = hrirs.subspan(
(size_t{hData->mIrCount}*ti + azd.mIndex) * hData->mIrSize, hData->mIrSize);
const auto ir = irValues.subspan((size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N,
sofaHrtf->N);
if(!resampler)
std::copy_n(&sofaHrtf->DataIR.values[(size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N],
sofaHrtf->N, azd->mIrs[ti]);
std::copy_n(ir.cbegin(), ir.size(), azd.mIrs[ti].begin());
else
{
std::copy_n(&sofaHrtf->DataIR.values[(size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N],
sofaHrtf->N, restmp.begin());
resampler->process(restmp, {azd->mIrs[ti], hData->mIrSize});
std::copy_n(ir.cbegin(), ir.size(), restmp.begin());
resampler->process(restmp, azd.mIrs[ti]);
}
}
/* Include any per-channel or per-HRIR delays. */
if(delayType == DelayType::I_R)
{
const float *delayValues{sofaHrtf->DataDelay.values};
const auto delayValues = al::span{sofaHrtf->DataDelay.values,
size_t{sofaHrtf->I}*sofaHrtf->R};
for(uint ti{0u};ti < channels;++ti)
azd->mDelays[ti] = delayValues[ti] / static_cast<float>(hData->mIrRate);
azd.mDelays[ti] = delayValues[ti] / static_cast<float>(hData->mIrRate);
}
else if(delayType == DelayType::M_R)
{
const float *delayValues{sofaHrtf->DataDelay.values};
const auto delayValues = al::span{sofaHrtf->DataDelay.values,
size_t{sofaHrtf->M}*sofaHrtf->R};
for(uint ti{0u};ti < channels;++ti)
azd->mDelays[ti] = delayValues[si*sofaHrtf->R + ti] /
azd.mDelays[ti] = delayValues[si*sofaHrtf->R + ti] /
static_cast<float>(hData->mIrRate);
}
}
@@ -363,10 +366,10 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela
auto load_future = std::async(std::launch::async, load_proc);
do {
load_status = load_future.wait_for(std::chrono::milliseconds{50});
printf("\rLoading HRIRs... %u of %u", loaded_count.load(), sofaHrtf->M);
fmt::print("\rLoading HRIRs... {} of {}", loaded_count.load(), sofaHrtf->M);
fflush(stdout);
} while(load_status != std::future_status::ready);
fputc('\n', stdout);
fmt::println("");
return load_future.get();
}
@@ -378,10 +381,13 @@ bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType dela
struct MagCalculator {
const uint mFftSize{};
const uint mIrPoints{};
std::vector<double*> mIrs{};
std::vector<al::span<double>> mIrs;
std::atomic<size_t> mCurrent{};
std::atomic<size_t> mDone{};
MagCalculator(const uint fftsize, const uint irpoints) : mFftSize{fftsize}, mIrPoints{irpoints}
{ }
void Worker()
{
auto htemp = std::vector<complex_d>(mFftSize);
@@ -401,7 +407,7 @@ struct MagCalculator {
*/
} while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed));
CalcHrirMagnitude(mIrPoints, mFftSize, htemp, mIrs[idx]);
CalcHrirMagnitude(mIrPoints, htemp, mIrs[idx]);
/* Increment the number of IRs done. */
mDone.fetch_add(1);
@@ -411,34 +417,34 @@ struct MagCalculator {
} // namespace
bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize,
bool LoadSofaFile(const std::string_view filename, const uint numThreads, const uint fftSize,
const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData)
{
int err;
MySofaHrtfPtr sofaHrtf{mysofa_load(filename, &err)};
MySofaHrtfPtr sofaHrtf{mysofa_load(std::string{filename}.c_str(), &err)};
if(!sofaHrtf)
{
fprintf(stdout, "Error: Could not load %s: %s\n", filename, SofaErrorStr(err));
fmt::println("Error: Could not load {}: {} ({})", filename, SofaErrorStr(err), err);
return false;
}
/* NOTE: Some valid SOFA files are failing this check. */
err = mysofa_check(sofaHrtf.get());
if(err != MYSOFA_OK)
fprintf(stderr, "Warning: Supposedly malformed source file '%s' (%s).\n", filename,
SofaErrorStr(err));
fmt::println(stderr, "Warning: Supposedly malformed source file '{}': {} ({})", filename,
SofaErrorStr(err), err);
mysofa_tocartesian(sofaHrtf.get());
/* Make sure emitter and receiver counts are sane. */
if(sofaHrtf->E != 1)
{
fprintf(stderr, "%u emitters not supported\n", sofaHrtf->E);
fmt::println(stderr, "{} emitters not supported", sofaHrtf->E);
return false;
}
if(sofaHrtf->R > 2 || sofaHrtf->R < 1)
{
fprintf(stderr, "%u receivers not supported\n", sofaHrtf->R);
fmt::println(stderr, "{} receivers not supported", sofaHrtf->R);
return false;
}
/* Assume R=2 is a stereo measurement, and R=1 is mono left-ear-only. */
@@ -450,12 +456,14 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
/* Check and set the FFT and IR size. */
if(sofaHrtf->N > fftSize)
{
fprintf(stderr, "Sample points exceeds the FFT size.\n");
fmt::println(stderr, "Sample points exceeds the FFT size ({} > {}).", sofaHrtf->N,
fftSize);
return false;
}
if(sofaHrtf->N < truncSize)
{
fprintf(stderr, "Sample points is below the truncation size.\n");
fmt::println(stderr, "Sample points is below the truncation size ({} < {}).", sofaHrtf->N,
truncSize);
return false;
}
hData->mIrPoints = sofaHrtf->N;
@@ -469,15 +477,15 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
if(!hData->mIrRate)
return false;
DelayType delayType = PrepareDelay(sofaHrtf.get());
if(delayType == DelayType::Invalid)
const auto delayType = PrepareDelay(sofaHrtf.get());
if(!delayType)
return false;
if(!CheckIrData(sofaHrtf.get()))
return false;
if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData))
if(!PrepareLayout(al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz}, hData))
return false;
if(!LoadResponses(sofaHrtf.get(), hData, delayType, outRate))
if(!LoadResponses(sofaHrtf.get(), hData, *delayType, outRate))
return false;
sofaHrtf = nullptr;
@@ -490,14 +498,14 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
for(;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
if(azd.mIrs[0] != nullptr) break;
if(!azd.mIrs[0].empty()) break;
}
if(ai < hData->mFds[fi].mEvs[ei].mAzs.size())
break;
}
if(ei >= hData->mFds[fi].mEvs.size())
{
fprintf(stderr, "Missing source references [ %d, *, * ].\n", fi);
fmt::println(stderr, "Missing source references [ {}, *, * ].", fi);
return false;
}
hData->mFds[fi].mEvStart = ei;
@@ -506,9 +514,9 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
if(azd.mIrs[0] == nullptr)
if(azd.mIrs[0].empty())
{
fprintf(stderr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai);
fmt::println(stderr, "Missing source reference [ {}, {}, {} ].", fi, ei, ai);
return false;
}
}
@@ -518,7 +526,7 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
size_t hrir_total{0};
const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
double *hrirs = hData->mHrirsBase.data();
const auto hrirs = al::span{hData->mHrirsBase};
for(uint fi{0u};fi < hData->mFds.size();fi++)
{
for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++)
@@ -527,7 +535,8 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
for(size_t ti{0u};ti < channels;ti++)
azd.mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd.mIndex)];
azd.mIrs[ti] = hrirs.subspan((hData->mIrCount*ti + azd.mIndex)*hData->mIrSize,
hData->mIrSize);
}
}
@@ -553,8 +562,8 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
for(uint ti{0};ti < channels;ti++)
{
hrir_done.fetch_add(1u, std::memory_order_acq_rel);
azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, hData->mIrPoints,
upsampled, azd.mIrs[ti]);
azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, upsampled,
azd.mIrs[ti].first(hData->mIrPoints));
}
}
}
@@ -566,10 +575,10 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
auto load_future = std::async(std::launch::async, onset_proc);
do {
load_status = load_future.wait_for(std::chrono::milliseconds{50});
printf("\rCalculating HRIR onsets... %zu of %zu", hrir_done.load(), hrir_total);
fmt::print("\rCalculating HRIR onsets... {} of {}", hrir_done.load(), hrir_total);
fflush(stdout);
} while(load_status != std::future_status::ready);
fputc('\n', stdout);
fmt::println("");
if(!load_future.get())
return false;
@@ -589,16 +598,16 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz
std::vector<std::thread> thrds;
thrds.reserve(numThreads);
for(size_t i{0};i < numThreads;++i)
thrds.emplace_back(std::mem_fn(&MagCalculator::Worker), &calculator);
thrds.emplace_back(&MagCalculator::Worker, &calculator);
size_t count;
do {
std::this_thread::sleep_for(std::chrono::milliseconds{50});
count = calculator.mDone.load();
printf("\rCalculating HRIR magnitudes... %zu of %zu", count, calculator.mIrs.size());
fmt::print("\rCalculating HRIR magnitudes... {} of {}", count, calculator.mIrs.size());
fflush(stdout);
} while(count != calculator.mIrs.size());
fputc('\n', stdout);
fmt::println("");
for(auto &thrd : thrds)
{
+3 -1
View File
@@ -1,10 +1,12 @@
#ifndef LOADSOFA_H
#define LOADSOFA_H
#include <string_view>
#include "makemhr.h"
bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize,
bool LoadSofaFile(const std::string_view filename, const uint numThreads, const uint fftSize,
const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData);
#endif /* LOADSOFA_H */
+264 -215
View File
@@ -73,7 +73,6 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iostream>
@@ -82,20 +81,15 @@
#include <numeric>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#ifdef HAVE_GETOPT
#include <unistd.h>
#else
#include "../getopt.h"
#endif
#include "alcomplex.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "alstring.h"
#include "filesystem.h"
#include "fmt/core.h"
#include "loaddef.h"
#include "loadsofa.h"
@@ -169,29 +163,28 @@ enum ChannelIndex : uint {
* pattern string are replaced with the replacement string. The result is
* truncated if necessary.
*/
std::string StrSubst(al::span<const char> in, const al::span<const char> pat,
const al::span<const char> rep)
auto StrSubst(std::string_view in, const std::string_view pat, const std::string_view rep) -> std::string
{
std::string ret;
ret.reserve(in.size() + pat.size());
while(in.size() >= pat.size())
{
if(al::strncasecmp(in.data(), pat.data(), pat.size()) == 0)
if(al::starts_with(in, pat))
{
in = in.subspan(pat.size());
ret.append(rep.data(), rep.size());
in = in.substr(pat.size());
ret += rep;
}
else
{
size_t endpos{1};
while(endpos < in.size() && in[endpos] != pat.front())
while(endpos < in.size() && std::toupper(in[endpos]) != std::toupper(pat.front()))
++endpos;
ret.append(in.data(), endpos);
in = in.subspan(endpos);
ret += in.substr(0, endpos);
in = in.substr(endpos);
}
}
ret.append(in.data(), in.size());
ret += in;
return ret;
}
@@ -215,73 +208,44 @@ inline uint dither_rng(uint *seed)
// Performs a triangular probability density function dither. The input samples
// should be normalized (-1 to +1).
void TpdfDither(double *RESTRICT out, const double *RESTRICT in, const double scale,
const uint count, const uint step, uint *seed)
void TpdfDither(const al::span<double> out, const al::span<const double> in, const double scale,
const size_t channel, const size_t step, uint *seed)
{
static constexpr double PRNG_SCALE = 1.0 / std::numeric_limits<uint>::max();
assert(channel < step);
for(uint i{0};i < count;i++)
for(size_t i{0};i < in.size();++i)
{
uint prn0{dither_rng(seed)};
uint prn1{dither_rng(seed)};
*out = std::round(*(in++)*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE));
out += step;
out[i*step + channel] = std::round(in[i]*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE));
}
}
/* Calculate the complex helical sequence (or discrete-time analytical signal)
* of the given input using the Hilbert transform. Given the natural logarithm
* of a signal's magnitude response, the imaginary components can be used as
* the angles for minimum-phase reconstruction.
*/
inline void Hilbert(const uint n, complex_d *inout)
{ complex_hilbert({inout, n}); }
} // namespace
/* Calculate the magnitude response of the given input. This is used in
* place of phase decomposition, since the phase residuals are discarded for
* minimum phase reconstruction. The mirrored half of the response is also
* discarded.
*/
void MagnitudeResponse(const uint n, const complex_d *in, double *out)
{
const uint m = 1 + (n / 2);
uint i;
for(i = 0;i < m;i++)
out[i] = std::max(std::abs(in[i]), Epsilon);
}
namespace {
/* Apply a range limit (in dB) to the given magnitude response. This is used
* to adjust the effects of the diffuse-field average on the equalization
* process.
*/
void LimitMagnitudeResponse(const uint n, const uint m, const double limit, const double *in, double *out)
void LimitMagnitudeResponse(const uint n, const uint m, const double limit,
const al::span<double> inout)
{
double halfLim;
uint i, lower, upper;
double ave;
halfLim = limit / 2.0;
const double halfLim{limit / 2.0};
// Convert the response to dB.
for(i = 0;i < m;i++)
out[i] = 20.0 * std::log10(in[i]);
for(uint i{0};i < m;++i)
inout[i] = 20.0 * std::log10(inout[i]);
// Use six octaves to calculate the average magnitude of the signal.
lower = (static_cast<uint>(std::ceil(n / std::pow(2.0, 8.0)))) - 1;
upper = (static_cast<uint>(std::floor(n / std::pow(2.0, 2.0)))) - 1;
ave = 0.0;
for(i = lower;i <= upper;i++)
ave += out[i];
const auto lower = (static_cast<uint>(std::ceil(n / std::pow(2.0, 8.0)))) - 1;
const auto upper = (static_cast<uint>(std::floor(n / std::pow(2.0, 2.0)))) - 1;
double ave{0.0};
for(uint i{lower};i <= upper;++i)
ave += inout[i];
ave /= upper - lower + 1;
// Keep the response within range of the average magnitude.
for(i = 0;i < m;i++)
out[i] = Clamp(out[i], ave - halfLim, ave + halfLim);
for(uint i{0};i < m;++i)
inout[i] = Clamp(inout[i], ave - halfLim, ave + halfLim);
// Convert the response back to linear magnitude.
for(i = 0;i < m;i++)
out[i] = std::pow(10.0, out[i] / 20.0);
for(uint i{0};i < m;++i)
inout[i] = std::pow(10.0, inout[i] / 20.0);
}
/* Reconstructs the minimum-phase component for the given magnitude response
@@ -289,22 +253,23 @@ void LimitMagnitudeResponse(const uint n, const uint m, const double limit, cons
* residuals (which were discarded). The mirrored half of the response is
* reconstructed.
*/
void MinimumPhase(const uint n, double *mags, complex_d *out)
void MinimumPhase(const al::span<double> mags, const al::span<complex_d> out)
{
const uint m{(n/2) + 1};
assert(mags.size() == out.size());
const size_t m{(mags.size()/2) + 1};
uint i;
size_t i;
for(i = 0;i < m;i++)
out[i] = std::log(mags[i]);
for(;i < n;i++)
for(;i < mags.size();++i)
{
mags[i] = mags[n - i];
out[i] = out[n - i];
mags[i] = mags[mags.size() - i];
out[i] = out[mags.size() - i];
}
Hilbert(n, out);
complex_hilbert(out);
// Remove any DC offset the filter has.
mags[0] = Epsilon;
for(i = 0;i < n;i++)
for(i = 0;i < mags.size();++i)
out[i] = std::polar(mags[i], out[i].imag());
}
@@ -314,11 +279,11 @@ void MinimumPhase(const uint n, double *mags, complex_d *out)
***************************/
// Write an ASCII string to a file.
int WriteAscii(const std::string_view out, FILE *fp, const char *filename)
auto WriteAscii(const std::string_view out, std::ostream &ostream, const std::string_view filename) -> int
{
if(fwrite(out.data(), 1, out.size(), fp) != out.size())
if(!ostream.write(out.data(), std::streamsize(out.size())) || ostream.bad())
{
fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename);
fmt::println(stderr, "\nError: Bad write to file '{}'.", filename);
return 0;
}
return 1;
@@ -326,54 +291,55 @@ int WriteAscii(const std::string_view out, FILE *fp, const char *filename)
// Write a binary value of the given byte order and byte size to a file,
// loading it from a 32-bit unsigned integer.
int WriteBin4(const uint bytes, const uint32_t in, FILE *fp, const char *filename)
auto WriteBin4(const uint bytes, const uint32_t in, std::ostream &ostream,
const std::string_view filename) -> int
{
std::array<uint8_t,4> out{};
std::array<char,4> out{};
for(uint i{0};i < bytes;i++)
out[i] = (in>>(i*8)) & 0x000000FF;
out[i] = static_cast<char>((in>>(i*8)) & 0x000000FF);
if(fwrite(out.data(), 1, bytes, fp) != bytes)
if(!ostream.write(out.data(), std::streamsize(bytes)) || ostream.bad())
{
fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename);
fmt::println(stderr, "\nError: Bad write to file '{}'.", filename);
return 0;
}
return 1;
}
// Store the OpenAL Soft HRTF data set.
bool StoreMhr(const HrirDataT *hData, const char *filename)
auto StoreMhr(const HrirDataT *hData, const std::string_view filename) -> bool
{
const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
const uint n{hData->mIrPoints};
uint dither_seed{22222};
FilePtr fp{fopen(filename, "wb")};
if(!fp)
auto ostream = fs::ofstream{fs::u8path(filename), std::ios::binary};
if(!ostream.is_open())
{
fprintf(stderr, "\nError: Could not open MHR file '%s'.\n", filename);
fmt::println(stderr, "\nError: Could not open MHR file '{}'.", filename);
return false;
}
if(!WriteAscii(GetMHRMarker(), fp.get(), filename))
if(!WriteAscii(GetMHRMarker(), ostream, filename))
return false;
if(!WriteBin4(4, hData->mIrRate, fp.get(), filename))
if(!WriteBin4(4, hData->mIrRate, ostream, filename))
return false;
if(!WriteBin4(1, static_cast<uint32_t>(hData->mChannelType), fp.get(), filename))
if(!WriteBin4(1, static_cast<uint32_t>(hData->mChannelType), ostream, filename))
return false;
if(!WriteBin4(1, hData->mIrPoints, fp.get(), filename))
if(!WriteBin4(1, hData->mIrPoints, ostream, filename))
return false;
if(!WriteBin4(1, static_cast<uint>(hData->mFds.size()), fp.get(), filename))
if(!WriteBin4(1, static_cast<uint>(hData->mFds.size()), ostream, filename))
return false;
for(size_t fi{hData->mFds.size()-1};fi < hData->mFds.size();--fi)
{
auto fdist = static_cast<uint32_t>(std::round(1000.0 * hData->mFds[fi].mDistance));
if(!WriteBin4(2, fdist, fp.get(), filename))
if(!WriteBin4(2, fdist, ostream, filename))
return false;
if(!WriteBin4(1, static_cast<uint32_t>(hData->mFds[fi].mEvs.size()), fp.get(), filename))
if(!WriteBin4(1, static_cast<uint32_t>(hData->mFds[fi].mEvs.size()), ostream, filename))
return false;
for(size_t ei{0};ei < hData->mFds[fi].mEvs.size();++ei)
{
const auto &elev = hData->mFds[fi].mEvs[ei];
if(!WriteBin4(1, static_cast<uint32_t>(elev.mAzs.size()), fp.get(), filename))
if(!WriteBin4(1, static_cast<uint32_t>(elev.mAzs.size()), ostream, filename))
return false;
}
}
@@ -389,14 +355,14 @@ bool StoreMhr(const HrirDataT *hData, const char *filename)
{
std::array<double,MaxTruncSize*2_uz> out{};
TpdfDither(out.data(), azd.mIrs[0], scale, n, channels, &dither_seed);
TpdfDither(out, azd.mIrs[0].first(n), scale, 0, channels, &dither_seed);
if(hData->mChannelType == CT_STEREO)
TpdfDither(out.data()+1, azd.mIrs[1], scale, n, channels, &dither_seed);
TpdfDither(out, azd.mIrs[1].first(n), scale, 1, channels, &dither_seed);
const size_t numsamples{size_t{channels} * n};
for(size_t i{0};i < numsamples;i++)
{
const auto v = static_cast<int>(Clamp(out[i], -scale-1.0, scale));
if(!WriteBin4(bps, static_cast<uint32_t>(v), fp.get(), filename))
if(!WriteBin4(bps, static_cast<uint32_t>(v), ostream, filename))
return false;
}
}
@@ -411,11 +377,11 @@ bool StoreMhr(const HrirDataT *hData, const char *filename)
for(const auto &azd : evd.mAzs)
{
auto v = static_cast<uint>(std::round(azd.mDelays[0]*DelayPrecScale));
if(!WriteBin4(1, v, fp.get(), filename)) return false;
if(!WriteBin4(1, v, ostream, filename)) return false;
if(hData->mChannelType == CT_STEREO)
{
v = static_cast<uint>(std::round(azd.mDelays[1]*DelayPrecScale));
if(!WriteBin4(1, v, fp.get(), filename)) return false;
if(!WriteBin4(1, v, ostream, filename)) return false;
}
}
}
@@ -476,7 +442,7 @@ void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, const u
* on its coverage volume. All volumes are centered at the spherical HRIR
* coordinates and measured by extruded solid angle.
*/
void CalculateDfWeights(const HrirDataT *hData, double *weights)
void CalculateDfWeights(const HrirDataT *hData, const al::span<double> weights)
{
double sum, innerRa, outerRa, evs, ev, upperEv, lowerEv;
double solidAngle, solidVolume;
@@ -533,7 +499,7 @@ void CalculateDfWeights(const HrirDataT *hData, double *weights)
* specified magnitude range (in positive dB; 0.0 to skip).
*/
void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, const uint m,
const bool weighted, const double limit, double *dfa)
const bool weighted, const double limit, const al::span<double> dfa)
{
std::vector<double> weights(hData->mFds.size() * MAX_EV_COUNT);
uint count;
@@ -541,7 +507,7 @@ void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, c
if(weighted)
{
// Use coverage weighting to calculate the average.
CalculateDfWeights(hData, weights.data());
CalculateDfWeights(hData, weights);
}
else
{
@@ -589,13 +555,14 @@ void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, c
// Apply a limit to the magnitude range of the diffuse-field average
// if desired.
if(limit > 0.0)
LimitMagnitudeResponse(hData->mFftSize, m, limit, &dfa[ti * m], &dfa[ti * m]);
LimitMagnitudeResponse(hData->mFftSize, m, limit, dfa.subspan(ti * m));
}
}
// Perform diffuse-field equalization on the magnitude responses of the HRIR
// set using the given average response.
void DiffuseFieldEqualize(const uint channels, const uint m, const double *dfa, const HrirDataT *hData)
void DiffuseFieldEqualize(const uint channels, const uint m, const al::span<const double> dfa,
const HrirDataT *hData)
{
for(size_t fi{0};fi < hData->mFds.size();++fi)
{
@@ -814,7 +781,7 @@ void SynthesizeHrirs(HrirDataT *hData)
*/
FftForward(static_cast<uint>(htemp.size()), htemp.data());
std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(),
[](const complex_d &c) -> double { return std::abs(c); });
[](const complex_d c) -> double { return std::abs(c); });
for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++)
{
@@ -854,7 +821,7 @@ void SynthesizeHrirs(HrirDataT *hData)
}
FftForward(static_cast<uint>(htemp.size()), htemp.data());
std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(),
[](const complex_d &c) -> double { return std::abs(c); });
[](const complex_d c) -> double { return std::abs(c); });
for(uint ti{0u};ti < channels;ti++)
{
@@ -872,7 +839,7 @@ void SynthesizeHrirs(HrirDataT *hData)
* or more threads (sharing the same reconstructor object).
*/
struct HrirReconstructor {
std::vector<double*> mIrs;
std::vector<al::span<double>> mIrs;
std::atomic<size_t> mCurrent{};
std::atomic<size_t> mDone{};
uint mFftSize{};
@@ -904,7 +871,7 @@ struct HrirReconstructor {
*/
for(size_t i{0};i < m;++i)
mags[i] = std::max(mIrs[idx][i], Epsilon);
MinimumPhase(mFftSize, mags.data(), h.data());
MinimumPhase(mags, h);
FftInverse(mFftSize, h.data());
for(uint i{0u};i < mIrPoints;++i)
mIrs[idx][i] = h[i].real();
@@ -943,7 +910,7 @@ void ReconstructHrirs(const HrirDataT *hData, const uint numThreads)
std::vector<std::thread> thrds;
thrds.reserve(numThreads);
for(size_t i{0};i < numThreads;++i)
thrds.emplace_back(std::mem_fn(&HrirReconstructor::Worker), &reconstructor);
thrds.emplace_back(&HrirReconstructor::Worker, &reconstructor);
/* Keep track of the number of IRs done, periodically reporting it. */
size_t count;
@@ -953,10 +920,10 @@ void ReconstructHrirs(const HrirDataT *hData, const uint numThreads)
count = reconstructor.mDone.load();
size_t pcdone{count * 100 / reconstructor.mIrs.size()};
printf("\r%3zu%% done (%zu of %zu)", pcdone, count, reconstructor.mIrs.size());
fmt::print("\r{:3}% done ({} of {})", pcdone, count, reconstructor.mIrs.size());
fflush(stdout);
} while(count < reconstructor.mIrs.size());
fputc('\n', stdout);
fmt::println("");
for(auto &thrd : thrds)
{
@@ -973,10 +940,11 @@ void NormalizeHrirs(HrirDataT *hData)
/* Find the maximum amplitude and RMS out of all the IRs. */
struct LevelPair { double amp, rms; };
auto mesasure_channel = [irSize](const LevelPair levels, const double *ir)
auto mesasure_channel = [irSize](const LevelPair levels, al::span<const double> ir)
{
/* Calculate the peak amplitude and RMS of this IR. */
auto current = std::accumulate(ir, ir+irSize, LevelPair{0.0, 0.0},
ir = ir.first(irSize);
auto current = std::accumulate(ir.cbegin(), ir.cend(), LevelPair{0.0, 0.0},
[](const LevelPair cur, const double impulse)
{
return LevelPair{std::max(std::abs(impulse), cur.amp), cur.rms + impulse*impulse};
@@ -1011,8 +979,12 @@ void NormalizeHrirs(HrirDataT *hData)
factor = std::min(factor, 0.99/maxlev.amp);
/* Now scale all IRs by the given factor. */
auto proc_channel = [irSize,factor](double *ir)
{ std::transform(ir, ir+irSize, ir, [factor](double s){ return s * factor; }); };
auto proc_channel = [irSize,factor](al::span<double> ir)
{
ir = ir.first(irSize);
std::transform(ir.cbegin(), ir.cend(), ir.begin(),
[factor](double s) { return s * factor; });
};
auto proc_azi = [channels,proc_channel](HrirAzT &azi)
{ std::for_each(azi.mIrs.begin(), azi.mIrs.begin()+channels, proc_channel); };
auto proc_elev = [proc_azi](HrirEvT &elev)
@@ -1101,7 +1073,7 @@ void CalculateHrtds(const HeadModelT model, const double radius, HrirDataT *hDat
}
if(maxHrtd > MaxHrtd)
{
fprintf(stdout, " Scaling for max delay of %f samples to %f\n...\n", maxHrtd, MaxHrtd);
fmt::println(" Scaling for max delay of {:f} samples to {:f}\n...", maxHrtd, MaxHrtd);
const double scale{MaxHrtd / maxHrtd};
for(auto &field : hData->mFds)
{
@@ -1145,7 +1117,7 @@ bool PrepareHrirData(const al::span<const double> distances,
{
hData->mFds[fi].mDistance = distances[fi];
hData->mFds[fi].mEvStart = 0;
hData->mFds[fi].mEvs = {&hData->mEvsBase[evTotal], evCounts[fi]};
hData->mFds[fi].mEvs = al::span{hData->mEvsBase}.subspan(evTotal, evCounts[fi]);
evTotal += evCounts[fi];
for(uint ei{0};ei < evCounts[fi];++ei)
{
@@ -1153,15 +1125,15 @@ bool PrepareHrirData(const al::span<const double> distances,
hData->mFds[fi].mEvs[ei].mElevation = -al::numbers::pi / 2.0 + al::numbers::pi * ei /
(evCounts[fi] - 1);
hData->mFds[fi].mEvs[ei].mAzs = {&hData->mAzsBase[azTotal], azCount};
hData->mFds[fi].mEvs[ei].mAzs = al::span{hData->mAzsBase}.subspan(azTotal, azCount);
for(uint ai{0};ai < azCount;ai++)
{
hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth = 2.0 * al::numbers::pi * ai / azCount;
hData->mFds[fi].mEvs[ei].mAzs[ai].mIndex = azTotal + ai;
hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[0] = 0.0;
hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[1] = 0.0;
hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = nullptr;
hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = nullptr;
hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = {};
hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = {};
}
azTotal += azCount;
}
@@ -1176,36 +1148,35 @@ namespace {
* resulting data set as desired. If the input name is NULL it will read
* from standard input.
*/
bool ProcessDefinition(const char *inName, const uint outRate, const ChannelModeT chanMode,
bool ProcessDefinition(std::string_view inName, const uint outRate, const ChannelModeT chanMode,
const bool farfield, const uint numThreads, const uint fftSize, const bool equalize,
const bool surface, const double limit, const uint truncSize, const HeadModelT model,
const double radius, const char *outName)
const double radius, const std::string_view outName)
{
HrirDataT hData;
fprintf(stdout, "Using %u thread%s.\n", numThreads, (numThreads==1)?"":"s");
if(!inName)
fmt::println("Using {} thread{}.", numThreads, (numThreads==1)?"":"s");
if(inName.empty() || inName == "-"sv)
{
inName = "stdin";
fprintf(stdout, "Reading HRIR definition from %s...\n", inName);
if(!LoadDefInput(std::cin, nullptr, 0, inName, fftSize, truncSize, outRate, chanMode, &hData))
inName = "stdin"sv;
fmt::println("Reading HRIR definition from {}...", inName);
if(!LoadDefInput(std::cin, {}, inName, fftSize, truncSize, outRate, chanMode, &hData))
return false;
}
else
{
auto input = std::make_unique<std::ifstream>(std::filesystem::u8path(inName));
auto input = std::make_unique<fs::ifstream>(fs::u8path(inName));
if(!input->is_open())
{
fprintf(stderr, "Error: Could not open input file '%s'\n", inName);
fmt::println(stderr, "Error: Could not open input file '{}'", inName);
return false;
}
std::array<char,4> startbytes{};
input->read(startbytes.data(), startbytes.size());
std::streamsize startbytecount{input->gcount()};
if(startbytecount != startbytes.size() || !input->good())
if(input->gcount() != startbytes.size() || !input->good())
{
fprintf(stderr, "Error: Could not read input file '%s'\n", inName);
fmt::println(stderr, "Error: Could not read input file '{}'", inName);
return false;
}
@@ -1213,15 +1184,15 @@ bool ProcessDefinition(const char *inName, const uint outRate, const ChannelMode
&& startbytes[3] == 'F')
{
input = nullptr;
fprintf(stdout, "Reading HRTF data from %s...\n", inName);
fmt::println("Reading HRTF data from {}...", inName);
if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, outRate, chanMode, &hData))
return false;
}
else
{
fprintf(stdout, "Reading HRIR definition from %s...\n", inName);
if(!LoadDefInput(*input, startbytes.data(), startbytecount, inName, fftSize, truncSize,
outRate, chanMode, &hData))
fmt::println("Reading HRIR definition from {}...", inName);
if(!LoadDefInput(*input, startbytes, inName, fftSize, truncSize, outRate, chanMode,
&hData))
return false;
}
}
@@ -1234,85 +1205,82 @@ bool ProcessDefinition(const char *inName, const uint outRate, const ChannelMode
if(hData.mFds.size() > 1)
{
fprintf(stdout, "Balancing field magnitudes...\n");
fmt::println("Balancing field magnitudes...");
BalanceFieldMagnitudes(&hData, c, m);
}
fprintf(stdout, "Calculating diffuse-field average...\n");
CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa.data());
fprintf(stdout, "Performing diffuse-field equalization...\n");
DiffuseFieldEqualize(c, m, dfa.data(), &hData);
fmt::println("Calculating diffuse-field average...");
CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa);
fmt::println("Performing diffuse-field equalization...");
DiffuseFieldEqualize(c, m, dfa, &hData);
}
if(hData.mFds.size() > 1)
{
fprintf(stdout, "Sorting %zu fields...\n", hData.mFds.size());
fmt::println("Sorting {} fields...", hData.mFds.size());
std::sort(hData.mFds.begin(), hData.mFds.end(),
[](const HrirFdT &lhs, const HrirFdT &rhs) noexcept
{ return lhs.mDistance < rhs.mDistance; });
if(farfield)
{
fprintf(stdout, "Clearing %zu near field%s...\n", hData.mFds.size()-1,
fmt::println("Clearing {} near field{}...", hData.mFds.size()-1,
(hData.mFds.size()-1 != 1) ? "s" : "");
hData.mFds.erase(hData.mFds.cbegin(), hData.mFds.cend()-1);
}
}
fprintf(stdout, "Synthesizing missing elevations...\n");
fmt::println("Synthesizing missing elevations...");
if(model == HM_Dataset)
SynthesizeOnsets(&hData);
SynthesizeHrirs(&hData);
fprintf(stdout, "Performing minimum phase reconstruction...\n");
fmt::println("Performing minimum phase reconstruction...");
ReconstructHrirs(&hData, numThreads);
fprintf(stdout, "Truncating minimum-phase HRIRs...\n");
fmt::println("Truncating minimum-phase HRIRs...");
hData.mIrPoints = truncSize;
fprintf(stdout, "Normalizing final HRIRs...\n");
fmt::println("Normalizing final HRIRs...");
NormalizeHrirs(&hData);
fprintf(stdout, "Calculating impulse delays...\n");
fmt::println("Calculating impulse delays...");
CalculateHrtds(model, (radius > DefaultCustomRadius) ? radius : hData.mRadius, &hData);
const auto rateStr = std::to_string(hData.mIrRate);
const auto expName = StrSubst({outName, strlen(outName)}, {"%r", 2},
{rateStr.data(), rateStr.size()});
fprintf(stdout, "Creating MHR data set %s...\n", expName.c_str());
return StoreMhr(&hData, expName.c_str());
const auto expName = StrSubst(outName, "%r"sv, rateStr);
fmt::println("Creating MHR data set {}...", expName);
return StoreMhr(&hData, expName);
}
void PrintHelp(const char *argv0, FILE *ofile)
void PrintHelp(const std::string_view argv0, FILE *ofile)
{
fprintf(ofile, "Usage: %s [<option>...]\n\n", argv0);
fprintf(ofile, "Options:\n");
fprintf(ofile, " -r <rate> Change the data set sample rate to the specified value and\n");
fprintf(ofile, " resample the HRIRs accordingly.\n");
fprintf(ofile, " -m Change the data set to mono, mirroring the left ear for the\n");
fprintf(ofile, " right ear.\n");
fprintf(ofile, " -a Change the data set to single field, using the farthest field.\n");
fprintf(ofile, " -j <threads> Number of threads used to process HRIRs (default: 2).\n");
fprintf(ofile, " -f <points> Override the FFT window size (default: %u).\n", DefaultFftSize);
fprintf(ofile, " -e {on|off} Toggle diffuse-field equalization (default: %s).\n", (DefaultEqualize ? "on" : "off"));
fprintf(ofile, " -s {on|off} Toggle surface-weighted diffuse-field average (default: %s).\n", (DefaultSurface ? "on" : "off"));
fprintf(ofile, " -l {<dB>|none} Specify a limit to the magnitude range of the diffuse-field\n");
fprintf(ofile, " average (default: %.2f).\n", DefaultLimit);
fprintf(ofile, " -w <points> Specify the size of the truncation window that's applied\n");
fprintf(ofile, " after minimum-phase reconstruction (default: %u).\n", DefaultTruncSize);
fprintf(ofile, " -d {dataset| Specify the model used for calculating the head-delay timing\n");
fprintf(ofile, " sphere} values (default: %s).\n", ((HM_Default == HM_Dataset) ? "dataset" : "sphere"));
fprintf(ofile, " -c <radius> Use a customized head radius measured to-ear in meters.\n");
fprintf(ofile, " -i <filename> Specify an HRIR definition file to use (defaults to stdin).\n");
fprintf(ofile, " -o <filename> Specify an output file. Use of '%%r' will be substituted with\n");
fprintf(ofile, " the data set sample rate.\n");
fmt::println(ofile, "Usage: {} [<option>...]\n", argv0);
fmt::println(ofile, "Options:");
fmt::println(ofile, " -r <rate> Change the data set sample rate to the specified value and");
fmt::println(ofile, " resample the HRIRs accordingly.");
fmt::println(ofile, " -m Change the data set to mono, mirroring the left ear for the");
fmt::println(ofile, " right ear.");
fmt::println(ofile, " -a Change the data set to single field, using the farthest field.");
fmt::println(ofile, " -j <threads> Number of threads used to process HRIRs (default: 2).");
fmt::println(ofile, " -f <points> Override the FFT window size (default: {}).", DefaultFftSize);
fmt::println(ofile, " -e {{on|off}} Toggle diffuse-field equalization (default: {}).", (DefaultEqualize ? "on" : "off"));
fmt::println(ofile, " -s {{on|off}} Toggle surface-weighted diffuse-field average (default: {}).", (DefaultSurface ? "on" : "off"));
fmt::println(ofile, " -l {{<dB>|none}} Specify a limit to the magnitude range of the diffuse-field");
fmt::println(ofile, " average (default: {:.2f}).", DefaultLimit);
fmt::println(ofile, " -w <points> Specify the size of the truncation window that's applied");
fmt::println(ofile, " after minimum-phase reconstruction (default: {}).", DefaultTruncSize);
fmt::println(ofile, " -d {{dataset| Specify the model used for calculating the head-delay timing");
fmt::println(ofile, " sphere}} values (default: {}).", ((HM_Default == HM_Dataset) ? "dataset" : "sphere"));
fmt::println(ofile, " -c <radius> Use a customized head radius measured to-ear in meters.");
fmt::println(ofile, " -i <filename> Specify an HRIR definition file to use (defaults to stdin).");
fmt::println(ofile, " -o <filename> Specify an output file. Use of '%r' will be substituted with");
fmt::println(ofile, " the data set sample rate.");
}
} // namespace
// Standard command line dispatch.
int main(int argc, char *argv[])
int main(al::span<std::string_view> args)
{
if(argc < 2)
if(args.size() < 2)
{
fprintf(stdout, "HRTF Processing and Composition Utility\n\n");
PrintHelp(argv[0], stdout);
fmt::println("HRTF Processing and Composition Utility\n");
PrintHelp(args[0], stdout);
exit(EXIT_SUCCESS);
}
const char *outName{"./oalsoft_hrtf_%r.mhr"};
std::string_view outName{"./oalsoft_hrtf_%r.mhr"sv};
uint outRate{0};
ChannelModeT chanMode{CM_AllowStereo};
uint fftSize{DefaultFftSize};
@@ -1324,19 +1292,73 @@ int main(int argc, char *argv[])
HeadModelT model{HM_Default};
double radius{DefaultCustomRadius};
bool farfield{false};
std::string_view inName;
const char *inName{};
int opt;
while((opt=getopt(argc, argv, "r:maj:f:e:s:l:w:d:c:e:i:o:h")) != -1)
const std::string_view optlist{"r:maj:f:e:s:l:w:d:c:e:i:o:h"sv};
const auto arg0 = args[0];
args = args.subspan(1);
std::string_view optarg;
size_t argplace{0};
auto getarg = [&args,&argplace,&optarg,optlist]
{
char *end{};
while(!args.empty() && argplace >= args[0].size())
{
argplace = 0;
args = args.subspan(1);
}
if(args.empty())
return 0;
if(argplace == 0)
{
if(args[0] == "--"sv)
return 0;
if(args[0][0] != '-' || args[0].size() == 1)
{
fmt::println(stderr, "Invalid argument: {}", args[0]);
return -1;
}
++argplace;
}
const char nextopt{args[0][argplace]};
const auto listidx = optlist.find(nextopt);
if(listidx >= optlist.size())
{
fmt::println(stderr, "Unknown argument: -{:c}", nextopt);
return -1;
}
const bool needsarg{listidx+1 < optlist.size() && optlist[listidx+1] == ':'};
if(needsarg && (argplace+1 < args[0].size() || args.size() < 2))
{
fmt::println(stderr, "Missing parameter for argument: -{:c}", nextopt);
return -1;
}
if(++argplace == args[0].size())
{
if(needsarg)
optarg = args[1];
argplace = 0;
args = args.subspan(1u + needsarg);
}
return int{nextopt};
};
while(auto opt = getarg())
{
std::size_t endpos{};
switch(opt)
{
case 'r':
outRate = static_cast<uint>(strtoul(optarg, &end, 10));
if(end[0] != '\0' || outRate < MIN_RATE || outRate > MAX_RATE)
outRate = static_cast<uint>(std::stoul(std::string{optarg}, &endpos, 10));
if(endpos != optarg.size() || outRate < MIN_RATE || outRate > MAX_RATE)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, MIN_RATE, MAX_RATE);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected between {} to {}.",
optarg, opt, MIN_RATE, MAX_RATE);
exit(EXIT_FAILURE);
}
break;
@@ -1350,10 +1372,12 @@ int main(int argc, char *argv[])
break;
case 'j':
numThreads = static_cast<uint>(strtoul(optarg, &end, 10));
if(end[0] != '\0' || numThreads > 64)
numThreads = static_cast<uint>(std::stoul(std::string{optarg}, &endpos, 10));
if(endpos != optarg.size() || numThreads > 64)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, 0, 64);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected between {} to {}.",
optarg, opt, 0, 64);
exit(EXIT_FAILURE);
}
if(numThreads == 0)
@@ -1361,78 +1385,93 @@ int main(int argc, char *argv[])
break;
case 'f':
fftSize = static_cast<uint>(strtoul(optarg, &end, 10));
if(end[0] != '\0' || (fftSize&(fftSize-1)) || fftSize < MinFftSize || fftSize > MaxFftSize)
fftSize = static_cast<uint>(std::stoul(std::string{optarg}, &endpos, 10));
if(endpos != optarg.size() || (fftSize&(fftSize-1)) || fftSize < MinFftSize
|| fftSize > MaxFftSize)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected a power-of-two between %u to %u.\n", optarg, opt, MinFftSize, MaxFftSize);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected a power-of-two between {} to {}.",
optarg, opt, MinFftSize, MaxFftSize);
exit(EXIT_FAILURE);
}
break;
case 'e':
if(strcmp(optarg, "on") == 0)
if(optarg == "on"sv)
equalize = true;
else if(strcmp(optarg, "off") == 0)
else if(optarg == "off"sv)
equalize = false;
else
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected on or off.",
optarg, opt);
exit(EXIT_FAILURE);
}
break;
case 's':
if(strcmp(optarg, "on") == 0)
if(optarg == "on"sv)
surface = true;
else if(strcmp(optarg, "off") == 0)
else if(optarg == "off"sv)
surface = false;
else
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected on or off.\n", optarg, opt);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected on or off.",
optarg, opt);
exit(EXIT_FAILURE);
}
break;
case 'l':
if(strcmp(optarg, "none") == 0)
if(optarg == "none"sv)
limit = 0.0;
else
{
limit = strtod(optarg, &end);
if(end[0] != '\0' || limit < MinLimit || limit > MaxLimit)
limit = std::stod(std::string{optarg}, &endpos);
if(endpos != optarg.size() || limit < MinLimit || limit > MaxLimit)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %.0f to %.0f.\n", optarg, opt, MinLimit, MaxLimit);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected between {:.0f} to {:.0f}.",
optarg, opt, MinLimit, MaxLimit);
exit(EXIT_FAILURE);
}
}
break;
case 'w':
truncSize = static_cast<uint>(strtoul(optarg, &end, 10));
if(end[0] != '\0' || truncSize < MinTruncSize || truncSize > MaxTruncSize)
truncSize = static_cast<uint>(std::stoul(std::string{optarg}, &endpos, 10));
if(endpos != optarg.size() || truncSize < MinTruncSize || truncSize > MaxTruncSize)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %u to %u.\n", optarg, opt, MinTruncSize, MaxTruncSize);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected between {} to {}.",
optarg, opt, MinTruncSize, MaxTruncSize);
exit(EXIT_FAILURE);
}
break;
case 'd':
if(strcmp(optarg, "dataset") == 0)
if(optarg == "dataset"sv)
model = HM_Dataset;
else if(strcmp(optarg, "sphere") == 0)
else if(optarg == "sphere"sv)
model = HM_Sphere;
else
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected dataset or sphere.\n", optarg, opt);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected dataset or sphere.",
optarg, opt);
exit(EXIT_FAILURE);
}
break;
case 'c':
radius = strtod(optarg, &end);
if(end[0] != '\0' || radius < MinCustomRadius || radius > MaxCustomRadius)
radius = std::stod(std::string{optarg}, &endpos);
if(endpos != optarg.size() || radius < MinCustomRadius || radius > MaxCustomRadius)
{
fprintf(stderr, "\nError: Got unexpected value \"%s\" for option -%c, expected between %.2f to %.2f.\n", optarg, opt, MinCustomRadius, MaxCustomRadius);
fmt::println(stderr,
"\nError: Got unexpected value \"{}\" for option -{:c}, expected between {:.2f} to {:.2f}.",
optarg, opt, MinCustomRadius, MaxCustomRadius);
exit(EXIT_FAILURE);
}
break;
@@ -1446,11 +1485,11 @@ int main(int argc, char *argv[])
break;
case 'h':
PrintHelp(argv[0], stdout);
PrintHelp(arg0, stdout);
exit(EXIT_SUCCESS);
default: /* '?' */
PrintHelp(argv[0], stderr);
PrintHelp(arg0, stderr);
exit(EXIT_FAILURE);
}
}
@@ -1458,7 +1497,17 @@ int main(int argc, char *argv[])
const int ret{ProcessDefinition(inName, outRate, chanMode, farfield, numThreads, fftSize,
equalize, surface, limit, truncSize, model, radius, outName)};
if(!ret) return -1;
fprintf(stdout, "Operation completed.\n");
fmt::println("Operation completed.");
return EXIT_SUCCESS;
}
} /* namespace */
int main(int argc, char **argv)
{
assert(argc >= 0);
auto args = std::vector<std::string_view>(static_cast<unsigned int>(argc));
std::copy_n(argv, args.size(), args.begin());
return main(al::span{args});
}
+33 -27
View File
@@ -1,52 +1,46 @@
#ifndef MAKEMHR_H
#define MAKEMHR_H
#include <algorithm>
#include <array>
#include <complex>
#include <vector>
#include "alcomplex.h"
#include "alspan.h"
#include "polyphase_resampler.h"
// The maximum path length used when processing filenames.
enum { MAX_PATH_LEN = 256u };
inline constexpr auto MAX_PATH_LEN = 256u;
// The limit to the number of 'distances' listed in the data set definition.
// Must be less than 256
enum { MAX_FD_COUNT = 16u };
inline constexpr auto MAX_FD_COUNT = 16u;
// The limits to the number of 'elevations' listed in the data set definition.
// Must be less than 256.
enum {
MIN_EV_COUNT = 5u,
MAX_EV_COUNT = 181u
};
inline constexpr auto MIN_EV_COUNT = 5u;
inline constexpr auto MAX_EV_COUNT = 181u;
// The limits for each of the 'azimuths' listed in the data set definition.
// Must be less than 256.
enum {
MIN_AZ_COUNT = 1u,
MAX_AZ_COUNT = 255u
};
inline constexpr auto MIN_AZ_COUNT = 1u;
inline constexpr auto MAX_AZ_COUNT = 255u;
// The limits for the 'distance' from source to listener for each field in
// the definition file.
inline constexpr double MIN_DISTANCE{0.05};
inline constexpr double MAX_DISTANCE{2.50};
inline constexpr auto MIN_DISTANCE = 0.05;
inline constexpr auto MAX_DISTANCE = 2.50;
// The limits for the sample 'rate' metric in the data set definition and for
// resampling.
enum {
MIN_RATE = 32000u,
MAX_RATE = 96000u
};
inline constexpr auto MIN_RATE = 32000u;
inline constexpr auto MAX_RATE = 96000u;
// The limits for the HRIR 'points' metric in the data set definition.
enum {
MIN_POINTS = 16u,
MAX_POINTS = 8192u
};
inline constexpr auto MIN_POINTS = 16u;
inline constexpr auto MAX_POINTS = 8192u;
using uint = unsigned int;
@@ -78,7 +72,7 @@ struct HrirAzT {
double mAzimuth{0.0};
uint mIndex{0u};
std::array<double,2> mDelays{};
std::array<double*,2> mIrs{};
std::array<al::span<double>,2> mIrs{};
};
struct HrirEvT {
@@ -118,19 +112,31 @@ struct HrirDataT {
bool PrepareHrirData(const al::span<const double> distances,
const al::span<const uint,MAX_FD_COUNT> evCounts,
const al::span<const std::array<uint,MAX_EV_COUNT>,MAX_FD_COUNT> azCounts, HrirDataT *hData);
void MagnitudeResponse(const uint n, const complex_d *in, double *out);
/* Calculate the magnitude response of the given input. This is used in
* place of phase decomposition, since the phase residuals are discarded for
* minimum phase reconstruction. The mirrored half of the response is also
* discarded.
*/
inline void MagnitudeResponse(const al::span<const complex_d> in, const al::span<double> out)
{
static constexpr double Epsilon{1e-9};
for(size_t i{0};i < out.size();++i)
out[i] = std::max(std::abs(in[i]), Epsilon);
}
// Performs a forward FFT.
inline void FftForward(const uint n, complex_d *inout)
{ forward_fft(al::span{inout, n}); }
// Performs an inverse FFT.
// Performs an inverse FFT, scaling the result by the number of elements.
inline void FftInverse(const uint n, complex_d *inout)
{
inverse_fft(al::span{inout, n});
double f{1.0 / n};
for(uint i{0};i < n;i++)
inout[i] *= f;
const auto values = al::span{inout, n};
inverse_fft(values);
const double f{1.0 / n};
std::for_each(values.begin(), values.end(), [f](complex_d &value) { value *= f; });
}
// Performs linear interpolation.
+2 -2
View File
@@ -55,7 +55,7 @@ static void printList(const char *list, char separator)
if(!list || *list == '\0')
{
fprintf(stdout, "\n%s!!! none !!!\n", indent);
printf("\n%s!!! none !!!\n", indent);
return;
}
@@ -73,7 +73,7 @@ static void printList(const char *list, char separator)
if(len + col + 2 >= MaxWidth)
{
fprintf(stdout, "\n%s", indent);
printf("\n%s", indent);
col = strlen(indent);
}
else
+55 -30
View File
@@ -23,30 +23,45 @@
#include <cstdio>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "alnumeric.h"
#include "alspan.h"
#include "fmt/core.h"
#include "sofa-support.h"
#include "mysofa.h"
#include "win_main_utf8.h"
namespace {
using namespace std::string_view_literals;
using uint = unsigned int;
static void PrintSofaAttributes(const char *prefix, struct MYSOFA_ATTRIBUTE *attribute)
void PrintSofaAttributes(const std::string_view prefix, MYSOFA_ATTRIBUTE *attribute)
{
while(attribute)
{
fprintf(stdout, "%s.%s: %s\n", prefix, attribute->name, attribute->value);
fmt::println("{}.{}: {}", prefix, attribute->name, attribute->value);
attribute = attribute->next;
}
}
static void PrintSofaArray(const char *prefix, struct MYSOFA_ARRAY *array)
void PrintSofaArray(const std::string_view prefix, MYSOFA_ARRAY *array, bool showValues=true)
{
PrintSofaAttributes(prefix, array->attributes);
for(uint i{0u};i < array->elements;i++)
fprintf(stdout, "%s[%u]: %.6f\n", prefix, i, array->values[i]);
if(showValues)
{
const auto values = al::span{array->values, array->elements};
for(size_t i{0u};i < values.size();++i)
fmt::println("{}[{}]: {:.6f}", prefix, i, values[i]);
}
else
fmt::println("{}[...]: <{} values suppressed>", prefix, array->elements);
}
/* Attempts to produce a compatible layout. Most data sets tend to be
@@ -55,14 +70,14 @@ static void PrintSofaArray(const char *prefix, struct MYSOFA_ARRAY *array)
* possible. Those sets that contain purely random measurements or use
* different major axes will fail.
*/
static void PrintCompatibleLayout(const uint m, const float *xyzs)
void PrintCompatibleLayout(const al::span<const float> xyzs)
{
fputc('\n', stdout);
fmt::println("");
auto fds = GetCompatibleLayout(m, xyzs);
auto fds = GetCompatibleLayout(xyzs);
if(fds.empty())
{
fprintf(stdout, "No compatible field layouts in SOFA file.\n");
fmt::println("No compatible field layouts in SOFA file.");
return;
}
@@ -73,31 +88,31 @@ static void PrintCompatibleLayout(const uint m, const float *xyzs)
used_elems += fds[fi].mAzCounts[ei];
}
fprintf(stdout, "Compatible Layout (%u of %u measurements):\n\ndistance = %.3f", used_elems, m,
fds[0].mDistance);
fmt::print("Compatible Layout ({} of {} measurements):\n\ndistance = {:.3f}", used_elems,
xyzs.size()/3, fds[0].mDistance);
for(size_t fi{1u};fi < fds.size();fi++)
fprintf(stdout, ", %.3f", fds[fi].mDistance);
fmt::print(", {:.3f}", fds[fi].mDistance);
fprintf(stdout, "\nazimuths = ");
fmt::print("\nazimuths = ");
for(size_t fi{0u};fi < fds.size();++fi)
{
for(uint ei{0u};ei < fds[fi].mEvStart;++ei)
fprintf(stdout, "%d%s", fds[fi].mAzCounts[fds[fi].mEvCount - 1 - ei], ", ");
fmt::print("{}{}", fds[fi].mAzCounts[fds[fi].mEvCount - 1 - ei], ", ");
for(uint ei{fds[fi].mEvStart};ei < fds[fi].mEvCount;++ei)
fprintf(stdout, "%d%s", fds[fi].mAzCounts[ei],
fmt::print("{}{}", fds[fi].mAzCounts[ei],
(ei < (fds[fi].mEvCount - 1)) ? ", " :
(fi < (fds.size() - 1)) ? ";\n " : "\n");
}
}
// Load and inspect the given SOFA file.
static void SofaInfo(const char *filename)
void SofaInfo(const std::string &filename)
{
int err;
MySofaHrtfPtr sofa{mysofa_load(filename, &err)};
MySofaHrtfPtr sofa{mysofa_load(filename.c_str(), &err)};
if(!sofa)
{
fprintf(stdout, "Error: Could not load source file '%s' (%s).\n", filename,
fmt::println("Error: Could not load source file '{}' ({}).", filename,
SofaErrorStr(err));
return;
}
@@ -105,34 +120,44 @@ static void SofaInfo(const char *filename)
/* NOTE: Some valid SOFA files are failing this check. */
err = mysofa_check(sofa.get());
if(err != MYSOFA_OK)
fprintf(stdout, "Warning: Supposedly malformed source file '%s' (%s).\n", filename,
fmt::println("Warning: Supposedly malformed source file '{}' ({}).", filename,
SofaErrorStr(err));
mysofa_tocartesian(sofa.get());
PrintSofaAttributes("Info", sofa->attributes);
fprintf(stdout, "Measurements: %u\n", sofa->M);
fprintf(stdout, "Receivers: %u\n", sofa->R);
fprintf(stdout, "Emitters: %u\n", sofa->E);
fprintf(stdout, "Samples: %u\n", sofa->N);
fmt::println("Measurements: {}", sofa->M);
fmt::println("Receivers: {}", sofa->R);
fmt::println("Emitters: {}", sofa->E);
fmt::println("Samples: {}", sofa->N);
PrintSofaArray("SampleRate", &sofa->DataSamplingRate);
PrintSofaArray("DataDelay", &sofa->DataDelay);
PrintSofaArray("SampleRate"sv, &sofa->DataSamplingRate);
PrintSofaArray("DataDelay"sv, &sofa->DataDelay);
PrintSofaArray("SourcePosition"sv, &sofa->SourcePosition, false);
PrintCompatibleLayout(sofa->M, sofa->SourcePosition.values);
PrintCompatibleLayout(al::span{sofa->SourcePosition.values, sofa->M*3_uz});
}
int main(int argc, char *argv[])
int main(al::span<std::string_view> args)
{
if(argc != 2)
if(args.size() != 2)
{
fprintf(stdout, "Usage: %s <sofa-file>\n", argv[0]);
fmt::println("Usage: {} <sofa-file>", args[0]);
return 0;
}
SofaInfo(argv[1]);
SofaInfo(std::string{args[1]});
return 0;
}
} /* namespace */
int main(int argc, char **argv)
{
assert(argc >= 0);
auto args = std::vector<std::string_view>(static_cast<unsigned int>(argc));
std::copy_n(argv, args.size(), args.begin());
return main(al::span{args});
}
+13 -12
View File
@@ -32,6 +32,7 @@
#include <utility>
#include <vector>
#include "fmt/core.h"
#include "mysofa.h"
@@ -47,7 +48,7 @@ using double3 = std::array<double,3>;
* equality of unique elements.
*/
std::vector<double> GetUniquelySortedElems(const std::vector<double3> &aers, const uint axis,
const std::array<const double*,3> filters, const std::array<double,3> epsilons)
const std::array<const double*,3> &filters, const std::array<double,3> &epsilons)
{
std::vector<double> elems;
for(const double3 &aer : aers)
@@ -178,10 +179,10 @@ const char *SofaErrorStr(int err)
return "Unknown";
}
std::vector<SofaField> GetCompatibleLayout(const size_t m, const float *xyzs)
auto GetCompatibleLayout(const al::span<const float> xyzs) -> std::vector<SofaField>
{
auto aers = std::vector<double3>(m, double3{});
for(size_t i{0u};i < m;++i)
auto aers = std::vector<double3>(xyzs.size()/3, double3{});
for(size_t i{0u};i < aers.size();++i)
{
std::array vals{xyzs[i*3], xyzs[i*3 + 1], xyzs[i*3 + 2]};
mysofa_c2s(vals.data());
@@ -213,14 +214,14 @@ std::vector<SofaField> GetCompatibleLayout(const size_t m, const float *xyzs)
if(step <= 0.0)
{
if(elevs.empty())
fprintf(stdout, "No usable elevations on field distance %f.\n", dist);
fmt::println("No usable elevations on field distance {:f}.", dist);
else
{
fprintf(stdout, "Non-uniform elevations on field distance %.3f.\nGot: %+.2f", dist,
fmt::print("Non-uniform elevations on field distance {:.3f}.\nGot: {:+.2f}", dist,
elevs[0]);
for(size_t ei{1u};ei < elevs.size();++ei)
fprintf(stdout, ", %+.2f", elevs[ei]);
fputc('\n', stdout);
fmt::print(", {:+.2f}", elevs[ei]);
fmt::println("");
}
continue;
}
@@ -230,7 +231,7 @@ std::vector<SofaField> GetCompatibleLayout(const size_t m, const float *xyzs)
{
if(!(elevs[ei] < 0.0))
{
fprintf(stdout, "Too many missing elevations on field distance %f.\n", dist);
fmt::println("Too many missing elevations on field distance {:f}.", dist);
return fds;
}
@@ -247,7 +248,7 @@ std::vector<SofaField> GetCompatibleLayout(const size_t m, const float *xyzs)
const auto evCount = static_cast<uint>(std::round(180.0 / step)) + 1;
if(evCount < 5)
{
fprintf(stdout, "Too few uniform elevations on field distance %f.\n", dist);
fmt::println("Too few uniform elevations on field distance {:f}.", dist);
continue;
}
@@ -267,7 +268,7 @@ std::vector<SofaField> GetCompatibleLayout(const size_t m, const float *xyzs)
{
if(azims.size() != 1)
{
fprintf(stdout, "Non-singular poles on field distance %f.\n", dist);
fmt::println("Non-singular poles on field distance {:f}.", dist);
return fds;
}
azCounts[ei] = 1;
@@ -277,7 +278,7 @@ std::vector<SofaField> GetCompatibleLayout(const size_t m, const float *xyzs)
step = GetUniformAzimStep(0.1, azims);
if(step <= 0.0)
{
fprintf(stdout, "Non-uniform azimuths on elevation %f, field distance %f.\n",
fmt::println("Non-uniform azimuths on elevation {:f}, field distance {:f}.",
ev, dist);
return fds;
}
+3 -1
View File
@@ -5,6 +5,8 @@
#include <memory>
#include <vector>
#include "alspan.h"
#include "mysofa.h"
@@ -25,6 +27,6 @@ struct SofaField {
const char *SofaErrorStr(int err);
std::vector<SofaField> GetCompatibleLayout(const size_t m, const float *xyzs);
auto GetCompatibleLayout(al::span<const float> xyzs) -> std::vector<SofaField>;
#endif /* UTILS_SOFA_SUPPORT_H */
+64 -56
View File
@@ -24,21 +24,26 @@
#include "config.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cerrno>
#include <complex>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <memory>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
#include <vector>
#include "albit.h"
#include "alcomplex.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alspan.h"
#include "fmt/core.h"
#include "vector.h"
#include "opthelpers.h"
#include "phase_shifter.h"
@@ -48,6 +53,8 @@
#include "win_main_utf8.h"
namespace {
struct FileDeleter {
void operator()(gsl::owner<FILE*> file) { fclose(file); }
};
@@ -85,23 +92,14 @@ void fwrite32le(uint val, FILE *f)
fwrite(data.data(), 1, data.size(), f);
}
template<al::endian = al::endian::native>
byte4 f32AsLEBytes(const float &value) = delete;
template<>
byte4 f32AsLEBytes<al::endian::little>(const float &value)
byte4 f32AsLEBytes(const float value)
{
byte4 ret{};
std::memcpy(ret.data(), &value, 4);
return ret;
}
template<>
byte4 f32AsLEBytes<al::endian::big>(const float &value)
{
byte4 ret{};
std::memcpy(ret.data(), &value, 4);
std::swap(ret[0], ret[3]);
std::swap(ret[1], ret[2]);
auto ret = al::bit_cast<byte4>(value);
if constexpr(al::endian::native == al::endian::big)
{
std::swap(ret[0], ret[3]);
std::swap(ret[1], ret[2]);
}
return ret;
}
@@ -126,9 +124,9 @@ struct UhjDecoder {
alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mTemp{};
void decode(const float *RESTRICT InSamples, const std::size_t InChannels,
void decode(const al::span<const float> InSamples, const std::size_t InChannels,
const al::span<FloatBufferLine> OutSamples, const std::size_t SamplesToDo);
void decode2(const float *RESTRICT InSamples, const al::span<FloatBufferLine> OutSamples,
void decode2(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutSamples,
const std::size_t SamplesToDo);
};
@@ -208,14 +206,14 @@ const PhaseShifterT<UhjDecoder::sFilterDelay*2> PShift{};
*
* Not halving produces a result matching the original input.
*/
void UhjDecoder::decode(const float *RESTRICT InSamples, const std::size_t InChannels,
void UhjDecoder::decode(const al::span<const float> InSamples, const std::size_t InChannels,
const al::span<FloatBufferLine> OutSamples, const std::size_t SamplesToDo)
{
ASSUME(SamplesToDo > 0);
float *woutput{OutSamples[0].data()};
float *xoutput{OutSamples[1].data()};
float *youtput{OutSamples[2].data()};
auto woutput = al::span{OutSamples[0]};
auto xoutput = al::span{OutSamples[1]};
auto youtput = al::span{OutSamples[2]};
/* Add a delay to the input channels, to align it with the all-passed
* signal.
@@ -247,7 +245,7 @@ void UhjDecoder::decode(const float *RESTRICT InSamples, const std::size_t InCha
std::transform(mD.cbegin(), mD.cbegin()+SamplesToDo+sFilterDelay, mT.cbegin(), tmpiter,
[](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; });
std::copy_n(mTemp.cbegin()+SamplesToDo, mDTHistory.size(), mDTHistory.begin());
PShift.process({xoutput, SamplesToDo}, mTemp.data());
PShift.process(xoutput.first(SamplesToDo), mTemp);
for(std::size_t i{0};i < SamplesToDo;++i)
{
@@ -261,7 +259,7 @@ void UhjDecoder::decode(const float *RESTRICT InSamples, const std::size_t InCha
tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin());
std::copy_n(mS.cbegin(), SamplesToDo+sFilterDelay, tmpiter);
std::copy_n(mTemp.cbegin()+SamplesToDo, mSHistory.size(), mSHistory.begin());
PShift.process({youtput, SamplesToDo}, mTemp.data());
PShift.process(youtput.first(SamplesToDo), mTemp);
for(std::size_t i{0};i < SamplesToDo;++i)
{
@@ -271,7 +269,7 @@ void UhjDecoder::decode(const float *RESTRICT InSamples, const std::size_t InCha
if(OutSamples.size() > 3)
{
float *zoutput{OutSamples[3].data()};
auto zoutput = al::span{OutSamples[3]};
/* Z = 1.023332*Q */
for(std::size_t i{0};i < SamplesToDo;++i)
zoutput[i] = 1.023332f*mQ[i];
@@ -302,14 +300,14 @@ void UhjDecoder::decode(const float *RESTRICT InSamples, const std::size_t InCha
* NOTE: As above, S and D should not be halved. The only consequence of
* halving here is merely a -6dB reduction in output, but it's still incorrect.
*/
void UhjDecoder::decode2(const float *RESTRICT InSamples,
void UhjDecoder::decode2(const al::span<const float> InSamples,
const al::span<FloatBufferLine> OutSamples, const std::size_t SamplesToDo)
{
ASSUME(SamplesToDo > 0);
float *woutput{OutSamples[0].data()};
float *xoutput{OutSamples[1].data()};
float *youtput{OutSamples[2].data()};
auto woutput = al::span{OutSamples[0]};
auto xoutput = al::span{OutSamples[1]};
auto youtput = al::span{OutSamples[2]};
/* S = Left + Right */
for(std::size_t i{0};i < SamplesToDo;++i)
@@ -323,7 +321,7 @@ void UhjDecoder::decode2(const float *RESTRICT InSamples,
auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin());
std::copy_n(mD.cbegin(), SamplesToDo+sFilterDelay, tmpiter);
std::copy_n(mTemp.cbegin()+SamplesToDo, mDTHistory.size(), mDTHistory.begin());
PShift.process({xoutput, SamplesToDo}, mTemp.data());
PShift.process(xoutput.first(SamplesToDo), mTemp);
for(std::size_t i{0};i < SamplesToDo;++i)
{
@@ -337,7 +335,7 @@ void UhjDecoder::decode2(const float *RESTRICT InSamples,
tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin());
std::copy_n(mS.cbegin(), SamplesToDo+sFilterDelay, tmpiter);
std::copy_n(mTemp.cbegin()+SamplesToDo, mSHistory.size(), mSHistory.begin());
PShift.process({youtput, SamplesToDo}, mTemp.data());
PShift.process(youtput.first(SamplesToDo), mTemp);
for(std::size_t i{0};i < SamplesToDo;++i)
{
@@ -350,11 +348,11 @@ void UhjDecoder::decode2(const float *RESTRICT InSamples,
}
int main(int argc, char **argv)
int main(al::span<std::string_view> args)
{
if(argc < 2 || std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0)
if(args.size() < 2 || args[1] == "-h" || args[1] == "--help")
{
printf("Usage: %s <[options] filename.wav...>\n\n"
fmt::println("Usage: {} <[options] filename.wav...>\n\n"
" Options:\n"
" --general Use the general equations for 2-channel UHJ (default).\n"
" --alternative Use the alternative equations for 2-channel UHJ.\n"
@@ -362,35 +360,35 @@ int main(int argc, char **argv)
"Note: When decoding 2-channel UHJ to an .amb file, the result should not use\n"
"the normal B-Format shelf filters! Only 3- and 4-channel UHJ can accurately\n"
"reconstruct the original B-Format signal.",
argv[0]);
args[0]);
return 1;
}
std::size_t num_files{0}, num_decoded{0};
bool use_general{true};
for(int fidx{1};fidx < argc;++fidx)
for(size_t fidx{1};fidx < args.size();++fidx)
{
if(std::strcmp(argv[fidx], "--general") == 0)
if(args[fidx] == "--general")
{
use_general = true;
continue;
}
if(std::strcmp(argv[fidx], "--alternative") == 0)
if(args[fidx] == "--alternative")
{
use_general = false;
continue;
}
++num_files;
SF_INFO ininfo{};
SndFilePtr infile{sf_open(argv[fidx], SFM_READ, &ininfo)};
SndFilePtr infile{sf_open(std::string{args[fidx]}.c_str(), SFM_READ, &ininfo)};
if(!infile)
{
fprintf(stderr, "Failed to open %s\n", argv[fidx]);
fmt::println(stderr, "Failed to open {}", args[fidx]);
continue;
}
if(sf_command(infile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT)
{
fprintf(stderr, "%s is already B-Format\n", argv[fidx]);
fmt::println(stderr, "{} is already B-Format", args[fidx]);
continue;
}
uint outchans{};
@@ -400,13 +398,13 @@ int main(int argc, char **argv)
outchans = static_cast<uint>(ininfo.channels);
else
{
fprintf(stderr, "%s is not a 2-, 3-, or 4-channel file\n", argv[fidx]);
fmt::println(stderr, "{} is not a 2-, 3-, or 4-channel file", args[fidx]);
continue;
}
printf("Converting %s from %d-channel UHJ%s...\n", argv[fidx], ininfo.channels,
fmt::println("Converting {} from {}-channel UHJ%s...\n", args[fidx], ininfo.channels,
(ininfo.channels == 2) ? use_general ? " (general)" : " (alternative)" : "");
std::string outname{argv[fidx]};
std::string outname{args[fidx]};
auto lastslash = outname.find_last_of('/');
if(lastslash != std::string::npos)
outname.erase(0, lastslash+1);
@@ -418,7 +416,7 @@ int main(int argc, char **argv)
FilePtr outfile{fopen(outname.c_str(), "wb")};
if(!outfile)
{
fprintf(stderr, "Failed to create %s\n", outname.c_str());
fmt::println(stderr, "Failed to create {}", outname);
continue;
}
@@ -455,8 +453,8 @@ int main(int argc, char **argv)
fwrite32le(0xFFFFFFFF, outfile.get()); // 'data' header len; filled in at close
if(ferror(outfile.get()))
{
fprintf(stderr, "Error writing wave file header: %s (%d)\n",
std::generic_category().message(errno).c_str(), errno);
fmt::println(stderr, "Error writing wave file header: {} ({})",
std::generic_category().message(errno), errno);
continue;
}
@@ -481,16 +479,16 @@ int main(int argc, char **argv)
if(sgot < BufferLineSize)
{
const sf_count_t remaining{std::min(BufferLineSize - sgot, LeadOut)};
std::fill_n(inmem.data() + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f);
std::fill_n(inmem.begin() + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f);
sgot += remaining;
LeadOut -= remaining;
}
auto got = static_cast<std::size_t>(sgot);
if(ininfo.channels > 2 || use_general)
decoder->decode(inmem.data(), static_cast<uint>(ininfo.channels), decmem, got);
decoder->decode(inmem, static_cast<uint>(ininfo.channels), decmem, got);
else
decoder->decode2(inmem.data(), decmem, got);
decoder->decode2(inmem, decmem, got);
if(LeadIn >= got)
{
LeadIn -= got;
@@ -510,8 +508,8 @@ int main(int argc, char **argv)
std::size_t wrote{fwrite(outmem.data(), sizeof(byte4)*outchans, got, outfile.get())};
if(wrote < got)
{
fprintf(stderr, "Error writing wave data: %s (%d)\n",
std::generic_category().message(errno).c_str(), errno);
fmt::println(stderr, "Error writing wave data: {} ({})",
std::generic_category().message(errno), errno);
break;
}
}
@@ -529,10 +527,20 @@ int main(int argc, char **argv)
++num_decoded;
}
if(num_decoded == 0)
fprintf(stderr, "Failed to decode any input files\n");
fmt::println(stderr, "Failed to decode any input files");
else if(num_decoded < num_files)
fprintf(stderr, "Decoded %zu of %zu files\n", num_decoded, num_files);
fmt::println(stderr, "Decoded {} of {} files", num_decoded, num_files);
else
printf("Decoded %zu file%s\n", num_decoded, (num_decoded==1)?"":"s");
fmt::println("Decoded {} file{}", num_decoded, (num_decoded==1)?"":"s");
return 0;
}
} /* namespace */
int main(int argc, char **argv)
{
assert(argc >= 0);
auto args = std::vector<std::string_view>(static_cast<unsigned int>(argc));
std::copy_n(argv, args.size(), args.begin());
return main(al::span{args});
}
+145 -81
View File
@@ -24,18 +24,20 @@
#include "config.h"
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstring>
#include <cstdio>
#include <memory>
#include <string>
#include <utility>
#include <string_view>
#include <vector>
#include "alnumbers.h"
#include "alspan.h"
#include "opthelpers.h"
#include "fmt/core.h"
#include "phase_shifter.h"
#include "vector.h"
@@ -46,6 +48,8 @@
namespace {
using namespace std::string_view_literals;
struct SndFileDeleter {
void operator()(SNDFILE *sndfile) { sf_close(sndfile); }
};
@@ -80,7 +84,7 @@ struct UhjEncoder {
alignas(16) std::array<float,BufferLineSize + sFilterDelay*2> mTemp{};
void encode(const al::span<FloatBufferLine> OutSamples,
const al::span<FloatBufferLine,4> InSamples, const size_t SamplesToDo);
const al::span<const FloatBufferLine,4> InSamples, const size_t SamplesToDo);
};
const PhaseShifterT<UhjEncoder::sFilterDelay*2> PShift{};
@@ -100,18 +104,18 @@ const PhaseShifterT<UhjEncoder::sFilterDelay*2> PShift{};
* output, and Q is excluded from 2- and 3-channel output.
*/
void UhjEncoder::encode(const al::span<FloatBufferLine> OutSamples,
const al::span<FloatBufferLine,4> InSamples, const size_t SamplesToDo)
const al::span<const FloatBufferLine,4> InSamples, const size_t SamplesToDo)
{
const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0].data())};
const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1].data())};
const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2].data())};
const float *RESTRICT zinput{al::assume_aligned<16>(InSamples[3].data())};
const auto winput = al::span{InSamples[0]}.first(SamplesToDo);
const auto xinput = al::span{InSamples[1]}.first(SamplesToDo);
const auto yinput = al::span{InSamples[2]}.first(SamplesToDo);
const auto zinput = al::span{InSamples[3]}.first(SamplesToDo);
/* 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);
std::copy(winput.begin(), winput.end(), mW.begin()+sFilterDelay);
std::copy(xinput.begin(), xinput.end(), mX.begin()+sFilterDelay);
std::copy(yinput.begin(), yinput.end(), mY.begin()+sFilterDelay);
std::copy(zinput.begin(), zinput.end(), mZ.begin()+sFilterDelay);
/* S = 0.9396926*W + 0.1855740*X */
for(size_t i{0};i < SamplesToDo;++i)
@@ -119,22 +123,22 @@ void UhjEncoder::encode(const al::span<FloatBufferLine> OutSamples,
/* 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,
std::transform(winput.begin(), winput.end(), xinput.begin(), 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.process({mD.data(), SamplesToDo}, mTemp.data());
PShift.process(al::span{mD}.first(SamplesToDo), mTemp);
/* 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())};
auto left = al::span{OutSamples[0]};
for(size_t i{0};i < SamplesToDo;i++)
left[i] = (mS[i] + mD[i]) * 0.5f;
/* Right = (S - D)/2.0 */
float *RESTRICT right{al::assume_aligned<16>(OutSamples[1].data())};
auto right = al::span{OutSamples[1]};
for(size_t i{0};i < SamplesToDo;i++)
right[i] = (mS[i] - mD[i]) * 0.5f;
@@ -142,21 +146,21 @@ void UhjEncoder::encode(const al::span<FloatBufferLine> OutSamples,
{
/* 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,
std::transform(winput.begin(), winput.end(), xinput.begin(), 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.process({mT.data(), SamplesToDo}, mTemp.data());
PShift.process(al::span{mT}.first(SamplesToDo), mTemp);
/* T = j(-0.1432*W + 0.6512*X) - 0.7071068*Y */
float *RESTRICT t{al::assume_aligned<16>(OutSamples[2].data())};
auto t = al::span{OutSamples[2]};
for(size_t i{0};i < SamplesToDo;i++)
t[i] = mT[i] - 0.7071068f*mY[i];
}
if(OutSamples.size() > 3)
{
/* Q = 0.9772*Z */
float *RESTRICT q{al::assume_aligned<16>(OutSamples[3].data())};
auto q = al::span{OutSamples[3]};
for(size_t i{0};i < SamplesToDo;i++)
q[i] = 0.9772f*mZ[i];
}
@@ -176,6 +180,9 @@ struct SpeakerPos {
};
/* Azimuth is counter-clockwise. */
constexpr std::array MonoMap{
SpeakerPos{SF_CHANNEL_MAP_CENTER, 0.0f, 0.0f},
};
constexpr std::array StereoMap{
SpeakerPos{SF_CHANNEL_MAP_LEFT, 30.0f, 0.0f},
SpeakerPos{SF_CHANNEL_MAP_RIGHT, -30.0f, 0.0f},
@@ -238,55 +245,68 @@ constexpr auto GenCoeffs(double x /*+front*/, double y /*+left*/, double z /*+up
}};
}
} // namespace
int main(int argc, char **argv)
int main(al::span<std::string_view> args)
{
if(argc < 2 || std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0)
if(args.size() < 2 || args[1] == "-h" || args[1] == "--help")
{
printf("Usage: %s <infile...>\n\n", argv[0]);
fmt::println("Usage: {} <[options] infile...>\n\n"
" Options:\n"
" -bhj Encode 2-channel UHJ, aka \"BJH\" (default).\n"
" -thj Encode 3-channel UHJ, aka \"TJH\".\n"
" -phj Encode 4-channel UHJ, aka \"PJH\".\n"
"\n"
"3-channel UHJ supplements 2-channel UHJ with an extra channel that allows full\n"
"reconstruction of first-order 2D ambisonics. 4-channel UHJ supplements 3-channel\n"
"UHJ with an extra channel carrying height information, providing for full\n"
"reconstruction of first-order 3D ambisonics.\n"
"\n"
"Note: The third and fourth channels should be ignored if they're not being\n"
"decoded. Unlike the first two channels, they are not designed for undecoded\n"
"playback, so the resulting files will not play correctly if this isn't handled.",
args[0]);
return 1;
}
args = args.subspan(1);
uint uhjchans{2};
size_t num_files{0}, num_encoded{0};
for(int fidx{1};fidx < argc;++fidx)
auto process_arg = [&uhjchans,&num_files,&num_encoded](std::string_view arg) -> void
{
if(strcmp(argv[fidx], "-bhj") == 0)
if(arg == "-bhj"sv)
{
uhjchans = 2;
continue;
return;
}
if(strcmp(argv[fidx], "-thj") == 0)
if(arg == "-thj"sv)
{
uhjchans = 3;
continue;
return;
}
if(strcmp(argv[fidx], "-phj") == 0)
if(arg == "-phj"sv)
{
uhjchans = 4;
continue;
return;
}
++num_files;
std::string outname{argv[fidx]};
size_t lastslash{outname.find_last_of('/')};
auto outname = std::string{arg};
const auto lastslash = outname.rfind('/');
if(lastslash != std::string::npos)
outname.erase(0, lastslash+1);
size_t extpos{outname.find_last_of('.')};
const auto extpos = outname.rfind('.');
if(extpos != std::string::npos)
outname.resize(extpos);
outname += ".uhj.flac";
SF_INFO ininfo{};
SndFilePtr infile{sf_open(argv[fidx], SFM_READ, &ininfo)};
SndFilePtr infile{sf_open(std::string{arg}.c_str(), SFM_READ, &ininfo)};
if(!infile)
{
fprintf(stderr, "Failed to open %s\n", argv[fidx]);
continue;
fmt::println(stderr, "Failed to open {}", arg);
return;
}
printf("Converting %s to %s...\n", argv[fidx], outname.c_str());
fmt::println("Converting {} to {}...", arg, outname);
/* Work out the channel map, preferably using the actual channel map
* from the file/format, but falling back to assuming WFX order.
@@ -296,6 +316,7 @@ int main(int argc, char **argv)
if(sf_command(infile.get(), SFC_GET_CHANNEL_MAP_INFO, chanmap.data(),
ininfo.channels*int{sizeof(int)}) == SF_TRUE)
{
static const std::array<int,1> monomap{{SF_CHANNEL_MAP_CENTER}};
static const std::array<int,2> stereomap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT}};
static const std::array<int,4> quadmap{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT,
SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}};
@@ -329,7 +350,9 @@ int main(int argc, char **argv)
{ return std::find(b.begin(), b.end(), id) != b.end(); };
return std::all_of(a.cbegin(), a.cend(), find_channel);
};
if(match_chanmap(chanmap, stereomap))
if(match_chanmap(chanmap, monomap))
spkrs = MonoMap;
else if(match_chanmap(chanmap, stereomap))
spkrs = StereoMap;
else if(match_chanmap(chanmap, quadmap))
spkrs = QuadMap;
@@ -357,36 +380,66 @@ int main(int argc, char **argv)
mapstr += std::to_string(idx);
}
}
fprintf(stderr, " ... %zu channels not supported (map: %s)\n", chanmap.size(),
mapstr.c_str());
continue;
fmt::println(stderr, " ... {} channels not supported (map: {})", chanmap.size(),
mapstr);
return;
}
}
else if(sf_command(infile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr,
0) == SF_AMBISONIC_B_FORMAT)
{
if(ininfo.channels == 4)
{
fmt::println(stderr, " ... detected FuMa 3D B-Format");
chanmap[0] = SF_CHANNEL_MAP_AMBISONIC_B_W;
chanmap[1] = SF_CHANNEL_MAP_AMBISONIC_B_X;
chanmap[2] = SF_CHANNEL_MAP_AMBISONIC_B_Y;
chanmap[3] = SF_CHANNEL_MAP_AMBISONIC_B_Z;
}
else if(ininfo.channels == 3)
{
fmt::println(stderr, " ... detected FuMa 2D B-Format");
chanmap[0] = SF_CHANNEL_MAP_AMBISONIC_B_W;
chanmap[1] = SF_CHANNEL_MAP_AMBISONIC_B_X;
chanmap[2] = SF_CHANNEL_MAP_AMBISONIC_B_Y;
}
else
{
fmt::println(stderr, " ... unhandled {}-channel B-Format", ininfo.channels);
return;
}
}
else if(ininfo.channels == 1)
{
fmt::println(stderr, " ... assuming front-center");
spkrs = MonoMap;
chanmap[0] = SF_CHANNEL_MAP_CENTER;
}
else if(ininfo.channels == 2)
{
fprintf(stderr, " ... assuming WFX order stereo\n");
fmt::println(stderr, " ... assuming WFX order stereo");
spkrs = StereoMap;
chanmap[0] = SF_CHANNEL_MAP_FRONT_LEFT;
chanmap[1] = SF_CHANNEL_MAP_FRONT_RIGHT;
chanmap[0] = SF_CHANNEL_MAP_LEFT;
chanmap[1] = SF_CHANNEL_MAP_RIGHT;
}
else if(ininfo.channels == 6)
{
fprintf(stderr, " ... assuming WFX order 5.1\n");
fmt::println(stderr, " ... assuming WFX order 5.1");
spkrs = X51Map;
chanmap[0] = SF_CHANNEL_MAP_FRONT_LEFT;
chanmap[1] = SF_CHANNEL_MAP_FRONT_RIGHT;
chanmap[2] = SF_CHANNEL_MAP_FRONT_CENTER;
chanmap[0] = SF_CHANNEL_MAP_LEFT;
chanmap[1] = SF_CHANNEL_MAP_RIGHT;
chanmap[2] = SF_CHANNEL_MAP_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");
fmt::println(stderr, " ... assuming WFX order 7.1");
spkrs = X71Map;
chanmap[0] = SF_CHANNEL_MAP_FRONT_LEFT;
chanmap[1] = SF_CHANNEL_MAP_FRONT_RIGHT;
chanmap[2] = SF_CHANNEL_MAP_FRONT_CENTER;
chanmap[0] = SF_CHANNEL_MAP_LEFT;
chanmap[1] = SF_CHANNEL_MAP_RIGHT;
chanmap[2] = SF_CHANNEL_MAP_CENTER;
chanmap[3] = SF_CHANNEL_MAP_LFE;
chanmap[4] = SF_CHANNEL_MAP_REAR_LEFT;
chanmap[5] = SF_CHANNEL_MAP_REAR_RIGHT;
@@ -395,8 +448,8 @@ int main(int argc, char **argv)
}
else
{
fprintf(stderr, " ... unmapped %d-channel audio not supported\n", ininfo.channels);
continue;
fmt::println(stderr, " ... unmapped {}-channel audio not supported", ininfo.channels);
return;
}
SF_INFO outinfo{};
@@ -407,16 +460,20 @@ int main(int argc, char **argv)
SndFilePtr outfile{sf_open(outname.c_str(), SFM_WRITE, &outinfo)};
if(!outfile)
{
fprintf(stderr, " ... failed to create %s\n", outname.c_str());
continue;
fmt::println(stderr, " ... failed to create {}", outname);
return;
}
auto encoder = std::make_unique<UhjEncoder>();
auto splbuf = al::vector<FloatBufferLine, 16>(static_cast<uint>(ininfo.channels)+9+size_t{uhjchans});
auto splbuf = al::vector<FloatBufferLine, 16>(9);
auto ambmem = al::span{splbuf}.subspan<0,4>();
auto encmem = al::span{splbuf}.subspan<4,4>();
auto srcmem = al::span{splbuf[8]};
auto outmem = al::span<float>{splbuf[9].data(), size_t{BufferLineSize}*uhjchans};
auto membuf = al::vector<float,16>((static_cast<uint>(ininfo.channels)+size_t{uhjchans})
* BufferLineSize);
auto outmem = al::span{membuf}.first(size_t{BufferLineSize}*uhjchans);
auto inmem = al::span{membuf}.last(size_t{BufferLineSize}
* static_cast<uint>(ininfo.channels));
/* A number of initial samples need to be skipped to cut the lead-in
* from the all-pass filter delay. The same number of samples need to
@@ -428,14 +485,13 @@ int main(int argc, char **argv)
sf_count_t LeadOut{UhjEncoder::sFilterDelay};
while(LeadIn > 0 || LeadOut > 0)
{
auto inmem = outmem.data() + outmem.size();
auto sgot = sf_readf_float(infile.get(), inmem, BufferLineSize);
auto sgot = sf_readf_float(infile.get(), inmem.data(), BufferLineSize);
sgot = std::max<sf_count_t>(sgot, 0);
if(sgot < BufferLineSize)
{
const sf_count_t remaining{std::min(BufferLineSize - sgot, LeadOut)};
std::fill_n(inmem + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f);
std::fill_n(inmem.begin() + sgot*ininfo.channels, remaining*ininfo.channels, 0.0f);
sgot += remaining;
LeadOut -= remaining;
}
@@ -454,30 +510,26 @@ int main(int argc, char **argv)
for(size_t c{0};c < chans;++c)
{
for(size_t i{0};i < got;++i)
ambmem[c][i] = inmem[i*static_cast<uint>(ininfo.channels)] * scale;
++inmem;
ambmem[c][i] = inmem[i*static_cast<uint>(ininfo.channels) + c] * scale;
}
}
else for(const int chanid : chanmap)
else for(size_t idx{0};idx < chanmap.size();++idx)
{
const int chanid{chanmap[idx]};
/* Skip LFE. Or mix directly into W? Or W+X? */
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;});
[chanid](const SpeakerPos pos){return pos.mChannelID == chanid;});
if(spkr == spkrs.cend())
{
fprintf(stderr, " ... failed to find channel ID %d\n", chanid);
fmt::println(stderr, " ... failed to find channel ID {}", chanid);
continue;
}
for(size_t i{0};i < got;++i)
srcmem[i] = inmem[i * static_cast<uint>(ininfo.channels)];
++inmem;
srcmem[i] = inmem[i*static_cast<uint>(ininfo.channels) + idx];
static constexpr auto Deg2Rad = al::numbers::pi / 180.0;
const auto coeffs = GenCoeffs(
@@ -510,19 +562,31 @@ int main(int argc, char **argv)
sf_count_t wrote{sf_writef_float(outfile.get(), outmem.data(),
static_cast<sf_count_t>(got))};
if(wrote < 0)
fprintf(stderr, " ... failed to write samples: %d\n", sf_error(outfile.get()));
fmt::println(stderr, " ... failed to write samples: {}", sf_error(outfile.get()));
else
total_wrote += static_cast<size_t>(wrote);
}
printf(" ... wrote %zu samples (%" PRId64 ").\n", total_wrote, int64_t{ininfo.frames});
fmt::println(" ... wrote {} samples ({}).", total_wrote, ininfo.frames);
++num_encoded;
}
};
std::for_each(args.begin(), args.end(), process_arg);
if(num_encoded == 0)
fprintf(stderr, "Failed to encode any input files\n");
fmt::println(stderr, "Failed to encode any input files");
else if(num_encoded < num_files)
fprintf(stderr, "Encoded %zu of %zu files\n", num_encoded, num_files);
fmt::println(stderr, "Encoded {} of {} files", num_encoded, num_files);
else
printf("Encoded %s%zu file%s\n", (num_encoded > 1) ? "all " : "", num_encoded,
fmt::println("Encoded {}{} file{}", (num_encoded > 1) ? "all " : "", num_encoded,
(num_encoded == 1) ? "" : "s");
return 0;
}
} /* namespace */
int main(int argc, char **argv)
{
assert(argc >= 0);
auto args = std::vector<std::string_view>(static_cast<unsigned int>(argc));
std::copy_n(argv, args.size(), args.begin());
return main(al::span{args});
}