diff --git a/devtools/HIIRDesigner.cpp b/devtools/HIIRDesigner.cpp index d021764a..33be312c 100644 --- a/devtools/HIIRDesigner.cpp +++ b/devtools/HIIRDesigner.cpp @@ -152,6 +152,7 @@ static void generate_cpp_prologue(int argc, char *argv[]) printf( "#pragma once\n" "#include \"OversamplerHelpers.h\"\n" + "#include \"MathHelpers\"\n" "\n" "namespace sfz {\n" ); @@ -209,6 +210,14 @@ static void generate_cpp_upsampler(const Stage *stages, int num_stages) printf("\t\t" "}\n"); printf("\t" "}\n"); + printf("\t" "static unsigned conversionFactor(double sourceRate, double targetRate)\n"); + printf("\t" "{\n"); + printf("\t\t" "int factor = static_cast(std::ceil(targetRate / sourceRate));\n"); + printf("\t\t" "factor = (factor > 1) ? factor : 1;\n"); + printf("\t\t" "factor = (factor < 128) ? factor : 128;\n"); + printf("\t\t" "return nextPow2(factor);\n"); + printf("\t" "}\n"); + printf("\t" "static bool canProcess(int factor)\n"); printf("\t" "{\n"); printf("\t\t" "switch (factor) {\n"); @@ -322,6 +331,14 @@ static void generate_cpp_downsampler(const Stage *stages, int num_stages) printf("\t\t" "}\n"); printf("\t" "}\n"); + printf("\t" "static unsigned conversionFactor(double sourceRate, double targetRate)\n"); + printf("\t" "{\n"); + printf("\t\t" "int factor = static_cast(std::ceil(targetRate / sourceRate));\n"); + printf("\t\t" "factor = (factor > 1) ? factor : 1;\n"); + printf("\t\t" "factor = (factor < 128) ? factor : 128;\n"); + printf("\t\t" "return nextPow2(factor);\n"); + printf("\t" "}\n"); + printf("\t" "static bool canProcess(int factor)\n"); printf("\t" "{\n"); printf("\t\t" "switch (factor) {\n"); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9779d698..ee7dbc3b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -263,8 +263,8 @@ add_library(sfizz::internal ALIAS sfizz_internal) target_sources(sfizz_internal PRIVATE ${SFIZZ_HEADERS} ${SFIZZ_SOURCES} ${FAUST_FILES}) target_include_directories(sfizz_internal PUBLIC "." "sfizz") target_link_libraries(sfizz_internal - PUBLIC absl::strings absl::span sfizz::filesystem sfizz::atomic_queue sfizz::spin_mutex sfizz::bit_array sfizz::simde - PRIVATE sfizz::parser sfizz::messaging absl::flat_hash_map Threads::Threads st_audiofile sfizz::pugixml sfizz::spline sfizz::tunings sfizz::hiir sfizz::kissfft sfizz::cephes sfizz::cpuid sfizz::threadpool sfizz::jsl sfizz::atomic) + PUBLIC absl::strings absl::span sfizz::filesystem sfizz::atomic_queue sfizz::spin_mutex sfizz::bit_array sfizz::simde sfizz::hiir + PRIVATE sfizz::parser sfizz::messaging absl::flat_hash_map Threads::Threads st_audiofile sfizz::pugixml sfizz::spline sfizz::tunings sfizz::kissfft sfizz::cephes sfizz::cpuid sfizz::threadpool sfizz::jsl sfizz::atomic) if(SFIZZ_USE_SNDFILE) target_compile_definitions(sfizz_internal PUBLIC "SFIZZ_USE_SNDFILE=1") target_link_libraries(sfizz_internal PUBLIC st_audiofile) diff --git a/src/sfizz/MathHelpers.h b/src/sfizz/MathHelpers.h index 066b8e04..fa86b8a4 100644 --- a/src/sfizz/MathHelpers.h +++ b/src/sfizz/MathHelpers.h @@ -296,6 +296,25 @@ constexpr long int lroundPositive(T value) return static_cast(0.5f + value); // NOLINT } +/** + * @brief Compute the next power of 2 + * + * @param v An integer + * @return The next power of 2, inclusive from @p v + */ +inline uint32_t nextPow2(uint32_t v) +{ + // Bit Twiddling Hacks + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; +} + /** @brief Wrap a normalized phase into the domain [0;1[ */ diff --git a/src/sfizz/OversamplerHelpers.hxx b/src/sfizz/OversamplerHelpers.hxx index 458cdd87..66a5f1f4 100644 --- a/src/sfizz/OversamplerHelpers.hxx +++ b/src/sfizz/OversamplerHelpers.hxx @@ -11,6 +11,7 @@ #pragma once #include "OversamplerHelpers.h" +#include "MathHelpers.h" namespace sfz { @@ -94,6 +95,13 @@ public: return factor * spl; } } + static unsigned conversionFactor(double sourceRate, double targetRate) + { + int factor = static_cast(std::ceil(targetRate / sourceRate)); + factor = (factor > 1) ? factor : 1; + factor = (factor < 128) ? factor : 128; + return nextPow2(factor); + } static bool canProcess(int factor) { switch (factor) { @@ -293,6 +301,13 @@ public: return factor * spl; } } + static unsigned conversionFactor(double sourceRate, double targetRate) + { + int factor = static_cast(std::ceil(targetRate / sourceRate)); + factor = (factor > 1) ? factor : 1; + factor = (factor < 128) ? factor : 128; + return nextPow2(factor); + } static bool canProcess(int factor) { switch (factor) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6e28170f..497ca0e2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,6 +41,7 @@ set(SFIZZ_TEST_SOURCES ModulationsT.cpp LFOT.cpp MessagingT.cpp + OversamplerT.cpp DataHelpers.h DataHelpers.cpp ) diff --git a/tests/OversamplerT.cpp b/tests/OversamplerT.cpp new file mode 100644 index 00000000..84e179e6 --- /dev/null +++ b/tests/OversamplerT.cpp @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "sfizz/OversamplerHelpers.h" +#include "catch2/catch.hpp" + +TEST_CASE("[Oversampler] Conversion factor") +{ + REQUIRE(sfz::Upsampler::conversionFactor(44100.0, 44100.0) == 1); + REQUIRE(sfz::Upsampler::conversionFactor(44100.0, 44101.0) == 2); + REQUIRE(sfz::Upsampler::conversionFactor(44100.0, 88200.0) == 2); + REQUIRE(sfz::Upsampler::conversionFactor(44100.0, 88201.0) == 4); + REQUIRE(sfz::Upsampler::conversionFactor(44100.0, 176400.0) == 4); + REQUIRE(sfz::Upsampler::conversionFactor(44100.0, 176401.0) == 8); + // low and high limits + REQUIRE(sfz::Upsampler::conversionFactor(44100.0, 1.0) == 1); + REQUIRE(sfz::Upsampler::conversionFactor(44100.0, 1e10) == 128); +}