From 8c189544b015b725053af56930f2ded4e6ded9b1 Mon Sep 17 00:00:00 2001 From: Alex St-Onge Date: Mon, 4 Dec 2023 08:38:12 -0800 Subject: [PATCH] More perf test --- .vscode/settings.json | 3 +- CMakeLists.txt | 2 +- src/basic_oscillators.cpp | 78 +++++++++++++++++++++++++++---- tests/basic_oscillators_tests.cpp | 51 ++++++++++++++++---- tests/perf/CMakeLists.txt | 5 +- tests/perf/basicosc_perf.cpp | 70 +++++++++++++++++++++------ tests/perf/phaseshaper_perf.cpp | 70 +++++++++++++++++++++++++++ tools/CMakeLists.txt | 4 +- tools/resampler/CMakeLists.txt | 2 +- 9 files changed, 245 insertions(+), 40 deletions(-) create mode 100644 tests/perf/phaseshaper_perf.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index c5e497c..79036c2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -111,7 +111,8 @@ "shared_mutex": "cpp", "regex": "cpp", "coroutine": "cpp", - "span": "cpp" + "span": "cpp", + "__verbose_abort": "cpp" }, "vscode-nmake-tools.workspaceBuildDirectories": [ "." diff --git a/CMakeLists.txt b/CMakeLists.txt index 9208617..3a84832 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ find_program(CLANG_TIDY_EXE NAMES "clang-tidy") set(CLANG_TIDY_COMMAND "${CLANG_TIDY_EXE}") set(CMAKE_OSX_DEPLOYMENT_TARGET "13") -set(CLANG_COMPILER_OPTION ${CLANG_COMPILER_OPTION} -Wall -Wpedantic -Werror) +set(CLANG_COMPILER_OPTION ${CLANG_COMPILER_OPTION} -Wall -Wpedantic -Werror -Fsave-optimization-record) set(MSVC_COMPILER_OPTION /W4 /analyze) set(GCC_COMPILER_OPTION -Wall -Wpedantic) diff --git a/src/basic_oscillators.cpp b/src/basic_oscillators.cpp index c4dd94e..3112dc8 100644 --- a/src/basic_oscillators.cpp +++ b/src/basic_oscillators.cpp @@ -97,12 +97,39 @@ float Tri(float phase) float Tri(float phase, float phase_increment, float* out, size_t size) { - for (size_t i = 0; i < size; ++i) + size_t write_ptr = 0; + size_t block_count = size / 4; + for (size_t i = 0; i < block_count; ++i) { - phase = std::fmod(phase, 1.f); - const float t = -1.0f + (2.0f * phase); - out[i] = 2.0f * (fabsf(t) - 0.5f); + const float phase1 = fmodf(phase, 1.f); + phase += phase_increment; + const float phase2 = fmodf(phase, 1.f); phase += phase_increment; + const float phase3 = fmodf(phase, 1.f); + phase += phase_increment; + const float phase4 = fmodf(phase, 1.f); + phase += phase_increment; + + const float t1 = -1.0f + (2.0f * phase1); + const float t2 = -1.0f + (2.0f * phase2); + const float t3 = -1.0f + (2.0f * phase3); + const float t4 = -1.0f + (2.0f * phase4); + + out[write_ptr] = 2.0f * (fabsf(t1) - 0.5f); + out[write_ptr + 1] = 2.0f * (fabsf(t2) - 0.5f); + out[write_ptr + 2] = 2.0f * (fabsf(t3) - 0.5f); + out[write_ptr + 3] = 2.0f * (fabsf(t4) - 0.5f); + + write_ptr += 4; + } + + // if size is not a multiple of 4, process the remaining samples + block_count = size % 4; + for (size_t i = 0; i < block_count; ++i) + { + out[write_ptr] = Tri(phase); + phase += phase_increment; + write_ptr += 1; } return phase; @@ -113,6 +140,41 @@ float Saw(float phase) return 2.f * phase - 1.f; } +float Saw(float phase, float phase_increment, float* out, size_t size) +{ + size_t write_ptr = 0; + size_t block_count = size / 4; + for (size_t i = 0; i < block_count; ++i) + { + const float phase1 = fmodf(phase, 1.f); + phase += phase_increment; + const float phase2 = fmodf(phase, 1.f); + phase += phase_increment; + const float phase3 = fmodf(phase, 1.f); + phase += phase_increment; + const float phase4 = fmodf(phase, 1.f); + phase += phase_increment; + + out[write_ptr] = 2.f * phase1 - 1.f; + out[write_ptr + 1] = 2.f * phase2 - 1.f; + out[write_ptr + 2] = 2.f * phase3 - 1.f; + out[write_ptr + 3] = 2.f * phase4 - 1.f; + + write_ptr += 4; + } + + // if size is not a multiple of 4, process the remaining samples + block_count = size % 4; + for (size_t i = 0; i < block_count; ++i) + { + out[write_ptr] = 2.f * phase - 1.f; + phase += phase_increment; + write_ptr += 1; + } + + return phase; +} + float Square(float phase) { return (phase < 0.5f) ? -1.f : 1.f; @@ -203,12 +265,8 @@ void BasicOscillator::ProcessBlock(float* out, size_t size) phase_ = std::fmod(phase_, 1.f); break; case OscillatorType::Saw: - for (size_t i = 0; i < size; ++i) - { - out[i] = Saw(phase_); - phase_ += phase_increment_; - phase_ = std::fmod(phase_, 1.f); - } + phase_ = Saw(phase_, phase_increment_, out, size); + phase_ = std::fmod(phase_, 1.f); break; case OscillatorType::Square: for (size_t i = 0; i < size; ++i) diff --git a/tests/basic_oscillators_tests.cpp b/tests/basic_oscillators_tests.cpp index 6270a68..3f365b4 100644 --- a/tests/basic_oscillators_tests.cpp +++ b/tests/basic_oscillators_tests.cpp @@ -125,7 +125,7 @@ TEST(BasicOscillatorsTests, Triangle) auto test_buffer = std::make_unique(kSize); size_t test_buffer_size = kSize; - LoadWavFile("waves/triangle_48k_440hz.wav", test_buffer, test_buffer_size, info); + ASSERT_TRUE(LoadWavFile("waves/triangle_48k_440hz.wav", test_buffer, test_buffer_size, info)); auto out = std::make_unique(test_buffer_size); @@ -143,33 +143,45 @@ TEST(BasicOscillatorsTests, Triangle) } } -TEST(BasicOscillatorsTests, TriangleBlock) +class BasicOscillatorTestParam : public ::testing::TestWithParam { + public: + BasicOscillatorTestParam() = default; +}; + +TEST_P(BasicOscillatorTestParam, ProcessBlock) +{ + const sfdsp::OscillatorType type = GetParam(); constexpr size_t kSamplerate = 48000; - constexpr float kFreq = 440; + constexpr float kFreq = 750; constexpr size_t kSize = kSamplerate; - SF_INFO info; auto test_buffer = std::make_unique(kSize); size_t test_buffer_size = kSize; - LoadWavFile("waves/triangle_48k_440hz.wav", test_buffer, test_buffer_size, info); + sfdsp::BasicOscillator osc; + osc.Init(kSamplerate, kFreq, type); + + for (auto i = 0; i < test_buffer_size; ++i) + { + test_buffer[i] = osc.Tick(); + } auto out = std::make_unique(test_buffer_size); - sfdsp::BasicOscillator osc; - osc.Init(kSamplerate, kFreq, sfdsp::OscillatorType::Tri); + sfdsp::BasicOscillator osc2; + osc2.Init(kSamplerate, kFreq, type); const size_t block_size = 512; const size_t block_count = test_buffer_size / block_size; for (auto i = 0; i < block_count; ++i) { - osc.ProcessBlock(out.get() + i * block_size, block_size); + osc2.ProcessBlock(out.get() + i * block_size, block_size); } // Process reminder const size_t reminder = test_buffer_size % block_size; - osc.ProcessBlock(out.get() + block_count * block_size, reminder); + osc2.ProcessBlock(out.get() + block_count * block_size, reminder); for (auto i = 0; i < test_buffer_size; ++i) { @@ -203,4 +215,23 @@ TEST(BasicOscillatorsTests, Saw) { ASSERT_NEAR(out[i], test_buffer[i], 0.0001f); } -} \ No newline at end of file +} + +INSTANTIATE_TEST_SUITE_P(BasicOscillatorTest, BasicOscillatorTestParam, + ::testing::Values(sfdsp::OscillatorType::Sine, sfdsp::OscillatorType::Tri, + sfdsp::OscillatorType::Saw, sfdsp::OscillatorType::Square), + [](const testing::TestParamInfo& info) { + switch (info.param) + { + case sfdsp::OscillatorType::Sine: + return "Sine"; + case sfdsp::OscillatorType::Tri: + return "Triangle"; + case sfdsp::OscillatorType::Saw: + return "Saw"; + case sfdsp::OscillatorType::Square: + return "Square"; + default: + return "Unknown"; + } + }); \ No newline at end of file diff --git a/tests/perf/CMakeLists.txt b/tests/perf/CMakeLists.txt index d6a1866..2101fea 100644 --- a/tests/perf/CMakeLists.txt +++ b/tests/perf/CMakeLists.txt @@ -12,6 +12,9 @@ FetchContent_Declare( FetchContent_MakeAvailable(nanobench doctest) -add_executable(perf_tests perf_tests.cpp basicosc_perf.cpp) +add_executable(perf_tests + perf_tests.cpp + basicosc_perf.cpp + phaseshaper_perf.cpp) target_include_directories(perf_tests PRIVATE ${doctest_SOURCE_DIR}/doctest) target_link_libraries(perf_tests PRIVATE nanobench dsp doctest) \ No newline at end of file diff --git a/tests/perf/basicosc_perf.cpp b/tests/perf/basicosc_perf.cpp index a6ee650..af43b38 100644 --- a/tests/perf/basicosc_perf.cpp +++ b/tests/perf/basicosc_perf.cpp @@ -8,24 +8,38 @@ using namespace ankerl; using namespace std::chrono_literals; constexpr size_t kSamplerate = 48000; -constexpr size_t kOutputSize = kSamplerate * 5; +constexpr size_t kOutputSize = kSamplerate * 10; constexpr float kFreq = 750; constexpr size_t kBlockSize = 512; -// Render 5 second of audio -TEST_CASE("Sine") +constexpr const char* OscillatorTypeToString(sfdsp::OscillatorType type) +{ + switch (type) + { + case sfdsp::OscillatorType::Sine: + return "Sine"; + case sfdsp::OscillatorType::Tri: + return "Triangle"; + case sfdsp::OscillatorType::Saw: + return "Saw"; + case sfdsp::OscillatorType::Square: + return "Square"; + default: + return "Unknown"; + } +} + +void RenderBlock(sfdsp::OscillatorType type, nanobench::Bench& bench) { sfdsp::BasicOscillator osc; - osc.Init(kSamplerate, kFreq, sfdsp::OscillatorType::Sine); + osc.Init(kSamplerate, kFreq, type); auto out = std::make_unique(kOutputSize); - nanobench::Bench bench; - bench.title("BasicOscillator (Sine)"); - bench.relative(true); - bench.minEpochIterations(5); - bench.timeUnit(1ms, "ms"); + constexpr const char* format_string = "BasicOscillator::ProcessBlock (%s)"; + char buffer[128]; + snprintf(buffer, sizeof(buffer), format_string, OscillatorTypeToString(type)); - bench.run("BasicOscillator::ProcessBlock (Sine)", [&]() { + bench.run(buffer, [&]() { const size_t block_count = kOutputSize / kBlockSize; float* write_ptr = out.get(); for (size_t i = 0; i < block_count; ++i) @@ -38,12 +52,40 @@ TEST_CASE("Sine") const size_t reminder = kOutputSize % kBlockSize; osc.ProcessBlock(write_ptr, reminder); }); +} - sfdsp::BasicOscillator osc2; - osc2.Init(kSamplerate, kFreq, sfdsp::OscillatorType::Sine); +void RenderTick(sfdsp::OscillatorType type, nanobench::Bench& bench) +{ + sfdsp::BasicOscillator osc; + osc.Init(kSamplerate, kFreq, type); + auto out = std::make_unique(kOutputSize); - bench.run("BasicOscillator::Tick (Sine)", [&]() { + constexpr const char* format_string = "BasicOscillator::Tick (%s)"; + char buffer[128]; + snprintf(buffer, sizeof(buffer), format_string, OscillatorTypeToString(type)); + + bench.run(buffer, [&]() { for (auto i = 0; i < kOutputSize; ++i) - out[i] = osc2.Tick(); + out[i] = osc.Tick(); }); } + +// Render 5 second of audio +TEST_CASE("BasicOscillator") +{ + nanobench::Bench bench; + bench.title("BasicOscillator"); + bench.relative(true); + bench.minEpochIterations(5); + bench.timeUnit(1ms, "ms"); + + RenderBlock(sfdsp::OscillatorType::Sine, bench); + RenderBlock(sfdsp::OscillatorType::Tri, bench); + RenderBlock(sfdsp::OscillatorType::Saw, bench); + RenderBlock(sfdsp::OscillatorType::Square, bench); + + RenderTick(sfdsp::OscillatorType::Sine, bench); + RenderTick(sfdsp::OscillatorType::Tri, bench); + RenderTick(sfdsp::OscillatorType::Saw, bench); + RenderTick(sfdsp::OscillatorType::Square, bench); +} diff --git a/tests/perf/phaseshaper_perf.cpp b/tests/perf/phaseshaper_perf.cpp new file mode 100644 index 0000000..5e70b0a --- /dev/null +++ b/tests/perf/phaseshaper_perf.cpp @@ -0,0 +1,70 @@ +#include "doctest.h" +#include "nanobench.h" +#include + +#include "phaseshapers.h" + +using namespace ankerl; +using namespace std::chrono_literals; + +constexpr size_t kSamplerate = 48000; +constexpr size_t kOutputSize = kSamplerate * 10; +constexpr float kFreq = 750; +constexpr size_t kBlockSize = 512; + +constexpr const char* PhaseshaperTypeToString(sfdsp::Phaseshaper::Waveform type) +{ + switch (type) + { + case sfdsp::Phaseshaper::Waveform::VARIABLE_SLOPE: + return "Variable Slope"; + case sfdsp::Phaseshaper::Waveform::WAVESLICE: + return "Waveslice"; + case sfdsp::Phaseshaper::Waveform::SUPERSAW: + return "Supersaw"; + case sfdsp::Phaseshaper::Waveform::RIPPLE: + return "Ripple"; + case sfdsp::Phaseshaper::Waveform::SOFTSYNC: + return "Softsync"; + case sfdsp::Phaseshaper::Waveform::TRIANGLE_MOD: + return "Triangle Mod"; + default: + return "Unknown"; + } +} + +void RenderTick(sfdsp::Phaseshaper::Waveform type, nanobench::Bench& bench) +{ + sfdsp::Phaseshaper ps; + ps.Init(kSamplerate); + ps.SetFreq(kFreq); + ps.SetWaveform(type); + auto out = std::make_unique(kOutputSize); + + constexpr const char* format_string = "Phaseshaper::Tick (%s)"; + char buffer[128]; + snprintf(buffer, sizeof(buffer), format_string, PhaseshaperTypeToString(type)); + + bench.run(buffer, [&]() { + for (auto i = 0; i < kOutputSize; ++i) + out[i] = ps.Process(); + }); +} + +TEST_CASE("Phaseshaper") +{ + nanobench::Bench bench; + bench.title("Phaseshaper"); + bench.relative(true); + bench.minEpochIterations(5); + bench.timeUnit(1ms, "ms"); + + sfdsp::Phaseshaper ps; + ps.Init(kSamplerate); + ps.SetFreq(kFreq); + + for (size_t i = 0; i < static_cast(sfdsp::Phaseshaper::Waveform::NUM_WAVES); ++i) + { + RenderTick(static_cast(i), bench); + } +} \ No newline at end of file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index d18062e..909de90 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -10,11 +10,11 @@ FetchContent_Declare( GIT_TAG 6.0.1 ) +set(RTMIDI_TARGETNAME_UNINSTALL "rtmidi_uninstall" CACHE STRING "" FORCE) +set(RTAUDIO_TARGETNAME_UNINSTALL "rtaudio_uninstall" CACHE STRING "" FORCE) FetchContent_MakeAvailable(rtmidi rtaudio) target_compile_definitions(rtaudio PRIVATE -D_CRT_SECURE_NO_WARNINGS) -set(RTMIDI_TARGETNAME_UNINSTALL "rtmidi_uninstall" CACHE STRING "" FORCE) -set(RTAUDIO_TARGETNAME_UNINSTALL "rtaudio_uninstall" CACHE STRING "" FORCE) add_subdirectory(player) add_subdirectory(violonist) diff --git a/tools/resampler/CMakeLists.txt b/tools/resampler/CMakeLists.txt index cb1f269..d516550 100644 --- a/tools/resampler/CMakeLists.txt +++ b/tools/resampler/CMakeLists.txt @@ -8,7 +8,7 @@ FetchContent_Declare( FetchContent_MakeAvailable(libsamplerate) add_executable(resampler resampler.cpp) -target_include_directories(resampler PRIVATE externals/libsamplerate/include) +target_include_directories(resampler PRIVATE ${libsamplerate_SOURCE_DIR}/include) target_link_libraries(resampler PUBLIC tool_common dsp sndfile samplerate) if(NOT MSVC)