Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 116 additions & 14 deletions tests/audio_engine_rates_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include <QBuffer>
#include <QCoreApplication>
#include <QElapsedTimer>
#include <QEvent>
#include <QJsonArray>
#include <QJsonObject>
Expand Down Expand Up @@ -846,11 +847,14 @@ bool samePcm(const QByteArray& lhs, const QByteArray& rhs)
return true;
}

void concurrentSourcesAndEffects()
void concurrentSourcesAndEffects(std::optional<Effect> selectedEffect = std::nullopt)
{
for (int mainRate : {24000, 48000}) {
for (Effect effect : {Effect::None, Effect::Eq, Effect::Nr2, Effect::Rn2,
Effect::Nr4, Effect::Dfnr, Effect::Mnr}) {
if (selectedEffect && effect != *selectedEffect) {
continue;
}
const Rendered mainOnly = renderSources(mainRate, effect, true, false);
if (!mainOnly.enabled) {
const bool optional = effect == Effect::Nr4 || effect == Effect::Dfnr
Expand Down Expand Up @@ -1090,27 +1094,125 @@ void fixedProcessingDomains()
"RADE resampler never adopts main producer's 48 kHz rate");
}
}

struct AudioCase {
const char* name;
std::optional<Effect> effect;
};

void printUsage(const char* program)
{
std::fprintf(stderr,
"usage: %s [--case=rateMatrix|bandwidthAndMono|negotiatedSpeakerOutput|"
"negotiationOpenFailure|negotiationLifetimeAndCoalescing|auxiliaryAcrossNegotiation|"
"transitionsAndRejection|queueBudgetsAndDeviceTransitions|concurrentSourcesAndEffects|"
"effects-bypass|effects-EQ|effects-NR2|effects-RN2|effects-NR4|"
"effects-DFNR|effects-MNR|kiwiDeviceRateMatrix|processingStateReset|"
"legacyReplacementAndKiwiReset|fixedProcessingDomains]\n", program);
}

std::optional<AudioCase> parseCase(int argc, char** argv, bool& valid)
{
valid = true;
if (argc == 1) {
return std::nullopt;
}
if (argc != 2 || std::strncmp(argv[1], "--case=", 7) != 0) {
valid = false;
return std::nullopt;
}
const char* name = argv[1] + 7;
for (const AudioCase candidate : {
AudioCase{"rateMatrix", std::nullopt},
AudioCase{"bandwidthAndMono", std::nullopt},
AudioCase{"negotiatedSpeakerOutput", std::nullopt},
AudioCase{"negotiationOpenFailure", std::nullopt},
AudioCase{"negotiationLifetimeAndCoalescing", std::nullopt},
AudioCase{"auxiliaryAcrossNegotiation", std::nullopt},
AudioCase{"transitionsAndRejection", std::nullopt},
AudioCase{"queueBudgetsAndDeviceTransitions", std::nullopt},
AudioCase{"concurrentSourcesAndEffects", std::nullopt},
AudioCase{"effects-bypass", Effect::None},
AudioCase{"effects-EQ", Effect::Eq},
AudioCase{"effects-NR2", Effect::Nr2},
AudioCase{"effects-RN2", Effect::Rn2},
AudioCase{"effects-NR4", Effect::Nr4},
AudioCase{"effects-DFNR", Effect::Dfnr},
AudioCase{"effects-MNR", Effect::Mnr},
AudioCase{"kiwiDeviceRateMatrix", std::nullopt},
AudioCase{"processingStateReset", std::nullopt},
AudioCase{"legacyReplacementAndKiwiReset", std::nullopt},
AudioCase{"fixedProcessingDomains", std::nullopt},
}) {
if (std::strcmp(name, candidate.name) == 0) {
return candidate;
}
}
valid = false;
return std::nullopt;
}

template <class Function>
void runStage(const char* name, Function function)
{
QElapsedTimer elapsed;
elapsed.start();
std::fprintf(stderr, "STAGE start %s\n", name);
std::fflush(stderr);
function();
std::fprintf(stderr, "STAGE finish %s elapsed_ms=%lld\n", name,
static_cast<long long>(elapsed.elapsed()));
std::fflush(stderr);
}
} // namespace

int main(int argc, char** argv)
{
bool validCase = false;
const std::optional<AudioCase> selectedCase = parseCase(argc, argv, validCase);
if (!validCase) {
printUsage(argv[0]);
return 2;
}
TestSettingsProfile settings(QStringLiteral("audio-engine-rates"));
qputenv("AETHER_AUTOMATION", "1");
QCoreApplication application(argc, argv);
check(settings.isValid(), "isolated settings profile");
rateMatrix();
bandwidthAndMono();
negotiatedSpeakerOutput();
negotiationOpenFailure();
negotiationLifetimeAndCoalescing();
auxiliaryAcrossNegotiation();
transitionsAndRejection();
queueBudgetsAndDeviceTransitions();
concurrentSourcesAndEffects();
kiwiDeviceRateMatrix();
processingStateReset();
legacyReplacementAndKiwiReset();
fixedProcessingDomains();
const auto selected = [&selectedCase](const char* name) {
return !selectedCase || std::strcmp(selectedCase->name, name) == 0;
};
if (selected("rateMatrix")) { runStage("rateMatrix", rateMatrix); }
if (selected("bandwidthAndMono")) { runStage("bandwidthAndMono", bandwidthAndMono); }
if (selected("negotiatedSpeakerOutput")) {
runStage("negotiatedSpeakerOutput", negotiatedSpeakerOutput);
}
if (selected("negotiationOpenFailure")) {
runStage("negotiationOpenFailure", negotiationOpenFailure);
}
if (selected("negotiationLifetimeAndCoalescing")) {
runStage("negotiationLifetimeAndCoalescing", negotiationLifetimeAndCoalescing);
}
if (selected("auxiliaryAcrossNegotiation")) {
runStage("auxiliaryAcrossNegotiation", auxiliaryAcrossNegotiation);
}
if (selected("transitionsAndRejection")) {
runStage("transitionsAndRejection", transitionsAndRejection);
}
if (selected("queueBudgetsAndDeviceTransitions")) {
runStage("queueBudgetsAndDeviceTransitions", queueBudgetsAndDeviceTransitions);
}
if (selected("concurrentSourcesAndEffects")
|| (selectedCase && selectedCase->effect)) {
const std::optional<Effect> effect = selectedCase ? selectedCase->effect : std::nullopt;
runStage(selectedCase ? selectedCase->name : "concurrentSourcesAndEffects",
[effect] { concurrentSourcesAndEffects(effect); });
}
if (selected("kiwiDeviceRateMatrix")) { runStage("kiwiDeviceRateMatrix", kiwiDeviceRateMatrix); }
if (selected("processingStateReset")) { runStage("processingStateReset", processingStateReset); }
if (selected("legacyReplacementAndKiwiReset")) {
runStage("legacyReplacementAndKiwiReset", legacyReplacementAndKiwiReset);
}
if (selected("fixedProcessingDomains")) { runStage("fixedProcessingDomains", fixedProcessingDomains); }
Comment thread
jensenpat marked this conversation as resolved.
std::printf("AudioEngine rates: %d checks, %d failures\n", checks, failures);
return failures ? 1 : 0;
}
168 changes: 144 additions & 24 deletions tests/nr_stereo_independence_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@
#include "nr_stereo_independence.h"

#include <QCoreApplication>
#include <QElapsedTimer>

#include <cstdio>
#include <cstring>
#include <optional>

using namespace AetherSDR;
using NrStereoIndependence::MakeProcess;
Expand All @@ -37,11 +40,82 @@ int g_failures = 0;
void check(bool condition, const QString& name)
{
std::printf("%s %s\n", condition ? "PASS" : "FAIL", qPrintable(name));
std::fflush(stdout);
if (!condition) {
++g_failures;
}
}

enum class Method { Nr2, Nr4, Nnr, Dfnr, Bnr };

struct Selection {
std::optional<Method> method;
std::optional<int> rate;
};

void printUsage(const char* program)
{
std::fprintf(stderr, "usage: %s [--method=nr2|nr4|nnr|dfnr|bnr] [--rate=24000|48000]\n",
program);
}

std::optional<Method> parseMethod(const char* value)
{
if (std::strcmp(value, "nr2") == 0) { return Method::Nr2; }
if (std::strcmp(value, "nr4") == 0) { return Method::Nr4; }
if (std::strcmp(value, "nnr") == 0) { return Method::Nnr; }
if (std::strcmp(value, "dfnr") == 0) { return Method::Dfnr; }
if (std::strcmp(value, "bnr") == 0) { return Method::Bnr; }
return std::nullopt;
}

std::optional<Selection> parseSelection(int argc, char** argv)
{
Selection selection;
for (int index = 1; index < argc; ++index) {
const char* argument = argv[index];
if (std::strncmp(argument, "--method=", 9) == 0 && !selection.method) {
selection.method = parseMethod(argument + 9);
if (!selection.method) { return std::nullopt; }
continue;
}
if (std::strncmp(argument, "--rate=", 7) == 0 && !selection.rate) {
if (std::strcmp(argument + 7, "24000") == 0) {
selection.rate = 24000;
} else if (std::strcmp(argument + 7, "48000") == 0) {
selection.rate = 48000;
} else {
return std::nullopt;
}
continue;
}
return std::nullopt;
}
return selection;
}

bool selected(const Selection& selection, Method method)
{
return !selection.method || *selection.method == method;
}

void printStage(const char* state, const char* method, int rate, const QElapsedTimer& elapsed)
{
std::fprintf(stderr, "STAGE %s method=%s rate=%d elapsed_ms=%lld\n", state, method, rate,
static_cast<long long>(elapsed.elapsed()));
std::fflush(stderr);
}

template <class Function>
void runMethodStage(const char* method, int rate, Function function)
{
QElapsedTimer elapsed;
elapsed.start();
printStage("start", method, rate, elapsed);
function();
printStage("finish", method, rate, elapsed);
}

void checkMethod(const char* method, int rate, const MakeProcess& make)
{
const QString label = QStringLiteral("%1 @ %2 Hz: ").arg(QLatin1String(method)).arg(rate);
Expand Down Expand Up @@ -91,48 +165,94 @@ bool bnrRecycledEffectStartsClean(const QString& pack, int rate)

int main(int argc, char** argv)
{
const std::optional<Selection> selection = parseSelection(argc, argv);
if (!selection) {
printUsage(argv[0]);
return 2;
}
QCoreApplication app(argc, argv);

for (const int rate : {24000, 48000}) {
checkMethod("NR2", rate, [rate]() -> Process {
auto nr2 = std::make_shared<SpectralNR>(1024, rate, 4);
return [nr2](const QByteArray& pcm) {
QByteArray out(pcm.size(), Qt::Uninitialized);
nr2->processStereo(reinterpret_cast<const float*>(pcm.constData()),
reinterpret_cast<float*>(out.data()),
pcm.size() / (2 * static_cast<int>(sizeof(float))));
return out;
};
});
if (selection->rate && *selection->rate != rate) {
continue;
}
if (selected(*selection, Method::Nr2)) {
runMethodStage("nr2", rate, [rate] {
checkMethod("NR2", rate, [rate]() -> Process {
auto nr2 = std::make_shared<SpectralNR>(1024, rate, 4);
return [nr2](const QByteArray& pcm) {
QByteArray out(pcm.size(), Qt::Uninitialized);
nr2->processStereo(reinterpret_cast<const float*>(pcm.constData()),
reinterpret_cast<float*>(out.data()),
pcm.size() / (2 * static_cast<int>(sizeof(float))));
return out;
};
});
});
}

#ifdef HAVE_SPECBLEACH
checkMethod("NR4", rate, makeWrapper<SpecbleachFilter>(rate));
if (selected(*selection, Method::Nr4)) {
runMethodStage("nr4", rate, [rate] {
checkMethod("NR4", rate, makeWrapper<SpecbleachFilter>(rate));
});
}
#else
if (selection->method == Method::Nr4) {
std::printf("SKIP NR4 @ %d Hz: not built with Specbleach\n", rate);
return g_failures != 0 ? 1 : 77;
}
#endif

if (NnrFilter(rate).isValid()) {
checkMethod("NNR", rate, makeWrapper<NnrFilter>(rate));
} else {
std::printf("SKIP NNR @ %d Hz: create_nnr() failed\n", rate);
if (selected(*selection, Method::Nnr) && NnrFilter(rate).isValid()) {
runMethodStage("nnr", rate, [rate] {
checkMethod("NNR", rate, makeWrapper<NnrFilter>(rate));
});
} else if (selected(*selection, Method::Nnr)) {
check(false, QStringLiteral("NNR @ %1 Hz: unconditional vendored filter initializes")
.arg(rate));
}

#ifdef HAVE_DFNR
if (DeepFilterFilter(rate).isValid()) {
checkMethod("DFNR", rate, makeWrapper<DeepFilterFilter>(rate));
} else {
if (selected(*selection, Method::Dfnr) && DeepFilterFilter(rate).isValid()) {
runMethodStage("dfnr", rate, [rate] {
checkMethod("DFNR", rate, makeWrapper<DeepFilterFilter>(rate));
});
} else if (selected(*selection, Method::Dfnr)) {
std::printf("SKIP DFNR @ %d Hz: model not found\n", rate);
if (selection->method) {
return g_failures != 0 ? 1 : 77;
}
}
#else
if (selection->method == Method::Dfnr) {
std::printf("SKIP DFNR @ %d Hz: not built with DeepFilterNet\n", rate);
return g_failures != 0 ? 1 : 77;
}
#endif

#ifdef HAVE_NVIDIA_AFX
const QString pack = qEnvironmentVariable("AETHER_NVAFX_DIR");
if (!pack.isEmpty() && NvidiaAfxFilter(pack, rate).isValid()) {
checkMethod("BNR", rate, makeWrapper<NvidiaAfxFilter>(pack, rate));
check(bnrRecycledEffectStartsClean(pack, rate),
QStringLiteral("BNR @ %1 Hz: a filter created while another is alive, "
"after a third was destroyed, matches a fresh one").arg(rate));
} else {
if (selected(*selection, Method::Bnr) && !pack.isEmpty()
&& NvidiaAfxFilter(pack, rate).isValid()) {
runMethodStage("bnr", rate, [&pack, rate] {
checkMethod("BNR", rate, makeWrapper<NvidiaAfxFilter>(pack, rate));
check(bnrRecycledEffectStartsClean(pack, rate),
QStringLiteral("BNR @ %1 Hz: a filter created while another is alive, "
"after a third was destroyed, matches a fresh one").arg(rate));
});
} else if (selected(*selection, Method::Bnr)) {
std::printf("SKIP BNR @ %d Hz: set AETHER_NVAFX_DIR to a working AFX pack\n",
rate);
if (selection->method) {
return g_failures != 0 ? 1 : 77;
}
}
#else
if (selection->method == Method::Bnr) {
std::printf("SKIP BNR @ %d Hz: set AETHER_NVAFX_DIR to a working AFX pack\n",
rate);
return g_failures != 0 ? 1 : 77;
}
#endif
}
Expand Down
Loading
Loading