diff --git a/CMakeLists.txt b/CMakeLists.txt index e934d9ac..1efc8675 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,9 @@ set(COMMON_SOURCES sources/Synth.cpp sources/FilePool.cpp sources/Region.cpp - sources/ADSREnvelope.cpp + sources/FloatEnvelopes.cpp + # sources/LinearEnvelope.cpp + # sources/ADSREnvelope.cpp sources/Parser.cpp ) @@ -109,6 +111,7 @@ set(TEST_SOURCES tests/OnePoleFilterT.cpp tests/RegionActivationT.cpp tests/ADSREnvelopeT.cpp + tests/LinearEnvelopeT.cpp tests/MainT.cpp ) diff --git a/sources/ADSREnvelope.cpp b/sources/ADSREnvelope.cpp index 6c0a99f7..c456b660 100644 --- a/sources/ADSREnvelope.cpp +++ b/sources/ADSREnvelope.cpp @@ -213,6 +213,4 @@ constexpr bool ADSREnvelope::isSmoothing() noexcept return currentState != State::Done; } -// Explicit instantiation -template class ADSREnvelope; } diff --git a/sources/ADSREnvelope.h b/sources/ADSREnvelope.h index dff7913d..c4d965c4 100644 --- a/sources/ADSREnvelope.h +++ b/sources/ADSREnvelope.h @@ -1,3 +1,4 @@ +#pragma once #include namespace sfz { diff --git a/sources/CCMap.h b/sources/CCMap.h index 8ab613aa..211bcb80 100644 --- a/sources/CCMap.h +++ b/sources/CCMap.h @@ -1,3 +1,4 @@ +#pragma once #include namespace sfz diff --git a/sources/FloatEnvelopes.cpp b/sources/FloatEnvelopes.cpp new file mode 100644 index 00000000..32412f16 --- /dev/null +++ b/sources/FloatEnvelopes.cpp @@ -0,0 +1,13 @@ +#include "LinearEnvelope.h" +#include "ADSREnvelope.h" + +// Include the generic implementations +#include "LinearEnvelope.cpp" +#include "ADSREnvelope.cpp" + +// And explicitely instantiate the float version +namespace sfz +{ + template class LinearEnvelope; + template class ADSREnvelope; +} \ No newline at end of file diff --git a/sources/LinearEnvelope.cpp b/sources/LinearEnvelope.cpp new file mode 100644 index 00000000..9ca7c36a --- /dev/null +++ b/sources/LinearEnvelope.cpp @@ -0,0 +1,83 @@ +#include "LinearEnvelope.h" +#include "Helpers.h" +#include "SIMDHelpers.h" +#include + +namespace sfz +{ + +template +LinearEnvelope::LinearEnvelope() +{ + setMaxCapacity(maxCapacity); +} + +template +LinearEnvelope::LinearEnvelope(int maxCapacity, std::function function) +{ + setMaxCapacity(maxCapacity); + setFunction(function); +} + +template +void LinearEnvelope::setMaxCapacity(int maxCapacity) +{ + events.reserve(maxCapacity); + this->maxCapacity = maxCapacity; +} + +template +void LinearEnvelope::setFunction(std::function function) +{ + this->function = function; +} + +template +void LinearEnvelope::registerEvent(int timestamp, Type inputValue) +{ + if (static_cast(events.size()) < maxCapacity) + events.emplace_back(timestamp, function(inputValue)); +} + +template +void LinearEnvelope::clear() +{ + events.clear(); +} + +template +void LinearEnvelope::reset(Type value) +{ + clear(); + currentValue = function(value); +} + +template +void LinearEnvelope::getBlock(absl::Span output) +{ + absl::c_sort(events, [](const auto& lhs, const auto& rhs) { + return lhs.first < rhs.first; + }); + int index { 0 }; + + for (auto& event: events) + { + const auto length = min(event.first, static_cast(output.size())) - index; + if (length == 0) + { + currentValue = event.second; + continue; + } + + const auto step = (event.second - currentValue) / length; + currentValue = ::linearRamp(output.subspan(index, length), currentValue, step); + index += length; + } + + if (index < static_cast(output.size())) + ::fill(output.subspan(index), currentValue); + + clear(); +} + +} \ No newline at end of file diff --git a/sources/LinearEnvelope.h b/sources/LinearEnvelope.h index 3a6848c1..089fadfd 100644 --- a/sources/LinearEnvelope.h +++ b/sources/LinearEnvelope.h @@ -1,9 +1,8 @@ +#pragma once #include "Globals.h" -#include "Helpers.h" -#include "SIMDHelpers.h" #include #include -#include +#include namespace sfz { @@ -12,78 +11,20 @@ template class LinearEnvelope { public: - LinearEnvelope() - { - setMaxCapacity(maxCapacity); - } - LinearEnvelope(int maxCapacity, std::function function) - { - setMaxCapacity(maxCapacity); - setFunction(function); - } - - void setMaxCapacity(int maxCapacity) - { - events.reserve(maxCapacity); - this->maxCapacity = maxCapacity; - } - - void setFunction(std::function function) - { - this->function = function; - } - - void registerEvent(int timestamp, Type inputValue) - { - if (events.size() < maxCapacity) - events.emplace_back(timestamp, function(inputValue)); - } - - void clear() - { - events.clear(); - } - - void reset(Type value) - { - clear(); - currentValue = function(value); - } - - void getBlock(absl::span output) - { - absl::c_sort(events, [](const Event& lhs, const Event& rhs) { - return lhs.timestamp < rhs.timestamp; - }); - int index { 0 }; - - for (auto& event: events) - { - const auto length = min(event.timestamp, output.size()) - index; - if (length == 0) - continue; - - const auto step = (function(event.value) - currentValue) / length; - ::linearRamp(output.subspan(index, length), currentValue, step); - } - - if (index < output.size()) - ::fill(output.subspan(index), currentValue); - - clear(); - } - + LinearEnvelope(); + LinearEnvelope(int maxCapacity, std::function function); + void setMaxCapacity(int maxCapacity); + void setFunction(std::function function); + void registerEvent(int timestamp, Type inputValue); + void clear(); + void reset(Type value=0.0); + void getBlock(absl::Span output); private: - struct Event - { - int timestamp; - Type value; - }; std::function function { [](Type input) { return input; } }; - static_assert(std::is_numeric::value); - std::vector events; + static_assert(std::is_arithmetic::value); + std::vector> events; int maxCapacity { config::defaultSamplesPerBlock }; - Type currentValue; + Type currentValue { 0.0 }; }; } \ No newline at end of file diff --git a/sources/OnePoleFilter.h b/sources/OnePoleFilter.h index ef1a9c06..a35500e4 100644 --- a/sources/OnePoleFilter.h +++ b/sources/OnePoleFilter.h @@ -1,3 +1,4 @@ +#pragma once #include "Globals.h" #include #include diff --git a/sources/SIMDHelpers.h b/sources/SIMDHelpers.h index 8b5add13..b6f1c37c 100644 --- a/sources/SIMDHelpers.h +++ b/sources/SIMDHelpers.h @@ -1,3 +1,4 @@ +#pragma once #include "Globals.h" #include #include diff --git a/tests/LinearEnvelopeT.cpp b/tests/LinearEnvelopeT.cpp new file mode 100644 index 00000000..dc70842b --- /dev/null +++ b/tests/LinearEnvelopeT.cpp @@ -0,0 +1,137 @@ +#include "catch2/catch.hpp" +#include "../sources/LinearEnvelope.h" +#include +#include +#include +#include +#include +using namespace Catch::literals; + +template +inline bool approxEqual(absl::Span lhs, absl::Span rhs, Type eps=1e-3) +{ + if (lhs.size() != rhs.size()) + return false; + + for (size_t i = 0; i < rhs.size(); ++i) + if (rhs[i] != Approx(lhs[i]).epsilon(eps)) + { + std::cerr << lhs[i] << " != " << rhs[i] << " at index " << i << '\n'; + return false; + } + + return true; +} + +TEST_CASE("[LinearEnvelope] Basic state") +{ + sfz::LinearEnvelope envelope; + std::array output; + std::array expected { 0.0, 0.0, 0.0, 0.0, 0.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE( output == expected ); +} + +TEST_CASE("[LinearEnvelope] Basic event") +{ + sfz::LinearEnvelope envelope; + envelope.registerEvent(4, 1.0); + std::array output; + std::array expected { 0.25, 0.5, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE( output == expected ); +} + +TEST_CASE("[LinearEnvelope] 2 events, close") +{ + sfz::LinearEnvelope envelope; + envelope.registerEvent(4, 1.0); + envelope.registerEvent(5, 2.0); + std::array output; + std::array expected { 0.25, 0.5, 0.75, 1.0, 2.0, 2.0, 2.0, 2.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE( output == expected ); +} + +TEST_CASE("[LinearEnvelope] 2 events, far") +{ + sfz::LinearEnvelope envelope; + envelope.registerEvent(2, 1.0); + envelope.registerEvent(6, 2.0); + std::array output; + std::array expected { 0.5, 1, 1.25, 1.5, 1.75, 2.0, 2.0, 2.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE( output == expected ); +} + +TEST_CASE("[LinearEnvelope] 2 events, reversed") +{ + sfz::LinearEnvelope envelope; + envelope.registerEvent(6, 2.0); + envelope.registerEvent(2, 1.0); + std::array output; + std::array expected { 0.5, 1, 1.25, 1.5, 1.75, 2.0, 2.0, 2.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE( output == expected ); +} + +TEST_CASE("[LinearEnvelope] 3 events, overlapping") +{ + sfz::LinearEnvelope envelope; + envelope.registerEvent(2, 1.0); + envelope.registerEvent(6, 2.0); + envelope.registerEvent(6, 3.0); + std::array output; + std::array expected { 0.5, 1, 1.25, 1.5, 1.75, 2.0, 3.0, 3.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE( output == expected ); +} + +TEST_CASE("[LinearEnvelope] 3 events, out of block") +{ + sfz::LinearEnvelope envelope; + envelope.registerEvent(2, 1.0); + envelope.registerEvent(6, 2.0); + envelope.registerEvent(10, 3.0); + std::array output; + std::array expected { 0.5, 1, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE( output == expected ); +} + +TEST_CASE("[LinearEnvelope] 3 events, out of block, with another block call") +{ + sfz::LinearEnvelope envelope; + envelope.registerEvent(2, 1.0); + envelope.registerEvent(6, 2.0); + envelope.registerEvent(10, 3.0); + std::array output; + std::array expected { 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0 }; + envelope.getBlock(absl::MakeSpan(output)); + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE( output == expected ); +} + +TEST_CASE("[LinearEnvelope] 2 events, with another block call") +{ + sfz::LinearEnvelope envelope; + envelope.registerEvent(2, 1.0); + envelope.registerEvent(6, 2.0); + std::array output; + std::array expected { 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0 }; + envelope.getBlock(absl::MakeSpan(output)); + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE( output == expected ); +} + +TEST_CASE("[LinearEnvelope] 2 events, function") +{ + sfz::LinearEnvelope envelope; + envelope.setFunction([](auto x) { return 2*x; }); + envelope.registerEvent(2, 1.0); + envelope.registerEvent(6, 2.0); + std::array output; + std::array expected { 1, 2, 2.5, 3, 3.5, 4.0, 4.0, 4.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE( output == expected ); +} \ No newline at end of file