diff --git a/src/sfizz/Wavetables.cpp b/src/sfizz/Wavetables.cpp index c74ee822..4562240c 100644 --- a/src/sfizz/Wavetables.cpp +++ b/src/sfizz/Wavetables.cpp @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "Wavetables.h" +#include "MathHelpers.h" #include #include @@ -112,4 +113,33 @@ const HarmonicProfile& HarmonicProfile::getSquare() return squareProfile; } +//------------------------------------------------------------------------------ +constexpr unsigned WavetableRange::countOctaves; +constexpr float WavetableRange::frequencyScaleFactor; + +unsigned WavetableRange::getOctaveForFrequency(float f) +{ + int oct = fp_exponent(frequencyScaleFactor * f); + return clamp(oct, 0, countOctaves - 1); +} + +WavetableRange WavetableRange::getRangeForOctave(int o) +{ + WavetableRange range; + + Fraction mant = fp_mantissa(0.0f); + float k = 1.0f / frequencyScaleFactor; + + range.minFrequency = k * fp_from_parts(0, o, 0); + range.maxFrequency = k * fp_from_parts(0, o, mant.den - 1); + + return range; +} + +WavetableRange WavetableRange::getRangeForFrequency(float f) +{ + int oct = getOctaveForFrequency(f); + return getRangeForOctave(oct); +} + } // namespace sfz diff --git a/src/sfizz/Wavetables.h b/src/sfizz/Wavetables.h index c150b612..9226ce44 100644 --- a/src/sfizz/Wavetables.h +++ b/src/sfizz/Wavetables.h @@ -38,4 +38,36 @@ public: void generate(absl::Span table, double amplitude, double cutoff) const; }; +/** + A helper to select ranges of a multi-sampled oscillator, according to the + frequency of an oscillator. + + The ranges are identified by octave numbers; not octaves in a musical sense, + but as logarithmic divisions of the frequency range. + */ +class WavetableRange { +public: + float minFrequency = 0; + float maxFrequency = 0; + + static constexpr unsigned countOctaves = 10; + static constexpr float frequencyScaleFactor = 0.05; + + static unsigned getOctaveForFrequency(float f); + static WavetableRange getRangeForOctave(int o); + static WavetableRange getRangeForFrequency(float f); + + // Note: using the frequency factor 0.05, octaves are as follows: + // octave 0: 20 Hz - 40 Hz + // octave 1: 40 Hz - 80 Hz + // octave 2: 80 Hz - 160 Hz + // octave 3: 160 Hz - 320 Hz + // octave 4: 320 Hz - 640 Hz + // octave 5: 640 Hz - 1280 Hz + // octave 6: 1280 Hz - 2560 Hz + // octave 7: 2560 Hz - 5120 Hz + // octave 8: 5120 Hz - 10240 Hz + // octave 9: 10240 Hz - 20480 Hz +}; + } // namespace sfz diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c2d030ee..7218f2a7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -26,6 +26,7 @@ set(SFIZZ_TEST_SOURCES CurveT.cpp RegionTriggersT.cpp FloatHelpersT.cpp + WavetablesT.cpp ) add_executable(sfizz_tests ${SFIZZ_TEST_SOURCES}) diff --git a/tests/WavetablesT.cpp b/tests/WavetablesT.cpp new file mode 100644 index 00000000..51231614 --- /dev/null +++ b/tests/WavetablesT.cpp @@ -0,0 +1,40 @@ +// 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/Wavetables.h" +#include "sfizz/MathHelpers.h" +#include "catch2/catch.hpp" +#include + +TEST_CASE("[Wavetables] Frequency ranges") +{ + int cur_oct = std::numeric_limits::min(); + int min_oct = std::numeric_limits::max(); + int max_oct = std::numeric_limits::min(); + + for (int note = 0; note < 128; ++note) { + double f = midiNoteFrequency(note); + + int oct = sfz::WavetableRange::getOctaveForFrequency(f); + + REQUIRE(oct >= 0); + REQUIRE(oct < sfz::WavetableRange::countOctaves); + + REQUIRE(oct >= cur_oct); + cur_oct = oct; + + min_oct = std::min(min_oct, oct); + max_oct = std::max(max_oct, oct); + + sfz::WavetableRange range = sfz::WavetableRange::getRangeForOctave(oct); + REQUIRE((f >= range.minFrequency || oct == 0)); + REQUIRE((f <= range.maxFrequency || oct == sfz::WavetableRange::countOctaves - 1)); + } + + // check ranges to be decently adjusted to the MIDI frequency range + REQUIRE(min_oct == 0); + REQUIRE(max_oct == sfz::WavetableRange::countOctaves - 1); +}