diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d2bd82..92c6543 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ if(NOT CMAKE_CROSSCOMPILING) "lib/grit/audio/dynamic/transient_shaper_test.cpp" "lib/grit/audio/envelope/envelope_follower_test.cpp" "lib/grit/audio/music/note_test.cpp" + "lib/grit/audio/noise/airwindows_vinyl_dither_test.cpp" "lib/grit/audio/noise/dither_test.cpp" "lib/grit/audio/noise/white_noise_test.cpp" "lib/grit/audio/waveshape/full_wave_rectifier_test.cpp" diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 4b5a3fa..83774c9 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -36,6 +36,7 @@ target_sources(grit-eurorack INTERFACE "grit/audio/music/note.hpp" "grit/audio/noise.hpp" + "grit/audio/noise/airwindows_vinyl_dither.hpp" "grit/audio/noise/dither.hpp" "grit/audio/noise/white_noise.hpp" diff --git a/lib/grit/audio/noise.hpp b/lib/grit/audio/noise.hpp index 13ab2a9..7f0ffe2 100644 --- a/lib/grit/audio/noise.hpp +++ b/lib/grit/audio/noise.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include -#include +#include \ No newline at end of file diff --git a/lib/grit/audio/noise/airwindows_vinyl_dither.hpp b/lib/grit/audio/noise/airwindows_vinyl_dither.hpp new file mode 100644 index 0000000..29465bf --- /dev/null +++ b/lib/grit/audio/noise/airwindows_vinyl_dither.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace grit { + +template +struct AirWindowsVinylDither +{ + using value_type = Float; + using seed_type = typename URNG::result_type; + + AirWindowsVinylDither() = default; + explicit AirWindowsVinylDither(seed_type seed); + + auto setDeRez(Float deRez) -> void; + [[nodiscard]] auto getDeRez() const -> Float; + + [[nodiscard]] auto operator()(Float x) -> Float; + auto reset() -> void; + +private: + auto advanceNoise() -> Float; + + Float _deRez{0}; + Float _inScale{0}; + Float _outScale{0}; + + URNG _rng{42}; + etl::uniform_real_distribution _dist{Float(-0.5), Float(0.5)}; + + Float _nsOdd{0}; + Float _prev{0}; + etl::array _ns{}; +}; + +template +AirWindowsVinylDither::AirWindowsVinylDither(seed_type seed) : _rng{seed} +{} + +template +auto AirWindowsVinylDither::setDeRez(Float deRez) -> void +{ + _deRez = deRez; + + auto scaleFactor = Float(32768.0); + if (deRez > Float(0)) { + scaleFactor *= etl::pow(Float(1) - deRez, Float(6)); + } + if (scaleFactor < Float(0.0001)) { + scaleFactor = Float(0.0001); + } + + auto outScale = scaleFactor; + if (outScale < Float(8)) { + outScale = Float(8); + } + + _inScale = scaleFactor; + _outScale = Float(1) / outScale; +} + +template +auto AirWindowsVinylDither::getDeRez() const -> Float +{ + return _deRez; +} + +template +auto AirWindowsVinylDither::operator()(Float x) -> Float +{ + auto const y = x * _inScale; + + auto absSample = advanceNoise(); + absSample += y; + + if (_nsOdd > 0) { + _nsOdd -= Float(0.97); + } + if (_nsOdd < 0) { + _nsOdd += Float(0.97); + } + + _nsOdd -= (_nsOdd * _nsOdd * _nsOdd * Float(0.475)); + _nsOdd += _prev; + + absSample += (_nsOdd * Float(0.475)); + auto const floor = etl::floor(absSample); + + _prev = floor - y; + return floor * _outScale; +} + +template +auto AirWindowsVinylDither::reset() -> void +{ + _nsOdd = Float(0); + _prev = Float(0); + etl::fill(_ns.begin(), _ns.end(), Float(0)); +} + +template +auto AirWindowsVinylDither::advanceNoise() -> Float +{ + auto absSample = _dist(_rng); + _ns[0] += absSample; + _ns[0] *= Float(0.5); + absSample -= _ns[0]; + + for (auto i{1U}; i < _ns.size(); ++i) { + absSample += _dist(_rng); + _ns[i] += absSample; + _ns[i] *= Float(0.5); + absSample -= _ns[i]; + } + + return absSample; +} + +} // namespace grit diff --git a/lib/grit/audio/noise/airwindows_vinyl_dither_test.cpp b/lib/grit/audio/noise/airwindows_vinyl_dither_test.cpp new file mode 100644 index 0000000..9093c6c --- /dev/null +++ b/lib/grit/audio/noise/airwindows_vinyl_dither_test.cpp @@ -0,0 +1,19 @@ +#include "airwindows_vinyl_dither.hpp" + +#include +#include + +TEMPLATE_TEST_CASE("grit/audio/noise: AirWindowsVinylDither", "", float, double) +{ + using Float = TestType; + + auto proc = grit::AirWindowsVinylDither{42}; + proc.reset(); + REQUIRE(proc(Float(0.0)) == Catch::Approx(Float(0.0))); + + proc.setDeRez(Float(0.5)); + REQUIRE(proc.getDeRez() == Catch::Approx(Float(0.5))); + REQUIRE(proc(Float(0.125)) != Float(0.0)); + REQUIRE(proc(Float(0.111)) != Float(0.0)); + REQUIRE(proc(Float(0.113)) != Float(0.0)); +} diff --git a/lib/grit/audio/noise/dither_test.cpp b/lib/grit/audio/noise/dither_test.cpp index 1e1e21a..a17e238 100644 --- a/lib/grit/audio/noise/dither_test.cpp +++ b/lib/grit/audio/noise/dither_test.cpp @@ -6,7 +6,7 @@ #include TEMPLATE_PRODUCT_TEST_CASE( - "grit/audio/dither:", + "grit/audio/noise: Dither", "", (grit::NoDither, grit::RectangleDither, grit::TriangleDither), (etl::xorshift32, etl::xoshiro128plus, std::mt19937)