diff --git a/src/sfizz/LinearEnvelope.cpp b/src/sfizz/EventEnvelopes.cpp similarity index 69% rename from src/sfizz/LinearEnvelope.cpp rename to src/sfizz/EventEnvelopes.cpp index e110e3aa..5cc4620f 100644 --- a/src/sfizz/LinearEnvelope.cpp +++ b/src/sfizz/EventEnvelopes.cpp @@ -21,7 +21,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "LinearEnvelope.h" +#include "EventEnvelopes.h" #include "SIMDHelpers.h" #include "MathHelpers.h" #include @@ -29,59 +29,90 @@ namespace sfz { template -LinearEnvelope::LinearEnvelope() +EventEnvelope::EventEnvelope() { setMaxCapacity(maxCapacity); } template -LinearEnvelope::LinearEnvelope(int maxCapacity, std::function function) +EventEnvelope::EventEnvelope(int maxCapacity, std::function function) { setMaxCapacity(maxCapacity); setFunction(function); } template -void LinearEnvelope::setMaxCapacity(int maxCapacity) +void EventEnvelope::setMaxCapacity(int maxCapacity) { events.reserve(maxCapacity); this->maxCapacity = maxCapacity; } template -void LinearEnvelope::setFunction(std::function function) +void EventEnvelope::setFunction(std::function function) { this->function = function; } template -void LinearEnvelope::registerEvent(int timestamp, Type inputValue) +void EventEnvelope::registerEvent(int timestamp, Type inputValue) { + if (static_cast(events.size()) < maxCapacity) events.emplace_back(timestamp, function(inputValue)); } template -void LinearEnvelope::clear() +void EventEnvelope::prepareEvents() { - events.clear(); + if (resetEvents) { + if (!events.empty()) + currentValue = events.back().second; + + clear(); + } else { + absl::c_sort(events, [](const auto& lhs, const auto& rhs) { + return lhs.first < rhs.first; + }); + resetEvents = true; + } } template -void LinearEnvelope::reset(Type value) +void EventEnvelope::clear() +{ + events.clear(); + resetEvents = false; +} + +template +void EventEnvelope::reset(Type value) { clear(); currentValue = function(value); + resetEvents = false; +} + +template +void EventEnvelope::getBlock(absl::Span) +{ + prepareEvents(); +} + +template +void EventEnvelope::getQuantizedBlock(absl::Span, Type) +{ + prepareEvents(); } 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 }; + EventEnvelope::getBlock(output); + auto& events = EventEnvelope::events; + auto& currentValue = EventEnvelope::currentValue; + int index { 0 }; for (auto& event : events) { const auto length = min(event.first, static_cast(output.size())) - index; if (length == 0) { @@ -96,18 +127,16 @@ void LinearEnvelope::getBlock(absl::Span output) if (index < static_cast(output.size())) fill(output.subspan(index), currentValue); - - clear(); } template void LinearEnvelope::getQuantizedBlock(absl::Span output, Type quantizationStep) { + EventEnvelope::getQuantizedBlock(output, quantizationStep); + auto& events = EventEnvelope::events; + auto& currentValue = EventEnvelope::currentValue; + ASSERT(quantizationStep != 0.0); - - absl::c_sort(events, [](const auto& lhs, const auto& rhs) { - return lhs.first < rhs.first; - }); int index { 0 }; const auto halfStep = quantizationStep / 2; @@ -118,10 +147,17 @@ void LinearEnvelope::getQuantizedBlock(absl::Span output, Type quant currentValue = quantize(currentValue); const auto outputSize = static_cast(output.size()); for (auto& event : events) { - const auto length = min(event.first, outputSize) - index; const auto newValue = quantize(event.second); - if (length == 0) { + if (event.first > outputSize) { + fill(output.subspan(index), currentValue); + currentValue = newValue; + index = outputSize; + break; + } + + const auto length = event.first - index - 1; + if (length <= 0) { currentValue = newValue; continue; } @@ -137,8 +173,6 @@ void LinearEnvelope::getQuantizedBlock(absl::Span output, Type quant if (index < outputSize) fill(output.subspan(index), currentValue); - - clear(); } } diff --git a/src/sfizz/LinearEnvelope.h b/src/sfizz/EventEnvelopes.h similarity index 83% rename from src/sfizz/LinearEnvelope.h rename to src/sfizz/EventEnvelopes.h index 545b6c56..add2753a 100644 --- a/src/sfizz/LinearEnvelope.h +++ b/src/sfizz/EventEnvelopes.h @@ -31,11 +31,10 @@ namespace sfz { /** - * @brief Describes a simple linear envelope that can be polled in a blockwise + * @brief Describes a simple envelope that can be polled in a blockwise * manner. It works by storing "events" in the immediate future and linearly * interpolating between these events. This envelope can also transform its - * incoming target points through a lambda, although the interpolation will - * always be linear (i.e. the lambda function is applied before the interpolation). + * incoming target points through a lambda, although the lambda function is applied before the interpolation. * * The way to use this class is by repeatedly calling `registerEvent` and then * `getBlock` to get a block of interpolated values in between the specified events. @@ -45,14 +44,14 @@ namespace sfz { * @tparam Type */ template -class LinearEnvelope { +class EventEnvelope { public: /** * @brief Construct a new linear envelope with a default memory size for * incoming events. * */ - LinearEnvelope(); + EventEnvelope(); /** * @brief Construct a new linear envelope with a specific memory size for * incoming events as well as a transformation function for incoming events. @@ -60,7 +59,7 @@ public: * @param maxCapacity * @param function */ - LinearEnvelope(int maxCapacity, std::function function); + EventEnvelope(int maxCapacity, std::function function); /** * @brief Set the maximum memory size for incoming events * @@ -98,7 +97,7 @@ public: * * @param output */ - void getBlock(absl::Span output); + virtual void getBlock(absl::Span output); /** * @brief Get a block of interpolated values with a forced quantization. The * values within the block will vary in quantization steps. @@ -106,14 +105,29 @@ public: * @param output * @param quantizationStep */ - void getQuantizedBlock(absl::Span output, Type quantizationStep); -private: - std::function function { [](Type input) { return input; } }; - static_assert(std::is_arithmetic::value, "Type should be arithmetic"); + virtual void getQuantizedBlock(absl::Span output, Type quantizationStep); +protected: std::vector> events; - int maxCapacity { config::defaultSamplesPerBlock }; Type currentValue { 0.0 }; - LEAK_DETECTOR(LinearEnvelope); +private: + static_assert(std::is_arithmetic::value, "Type should be arithmetic"); + std::function function { [](Type input) { return input; } }; + int maxCapacity { config::defaultSamplesPerBlock }; + void prepareEvents(); + bool resetEvents { false }; + LEAK_DETECTOR(EventEnvelope); }; + +/** + * @brief Describes a simple linear envelope. + * + * @tparam Type + */ +template +class LinearEnvelope: public EventEnvelope { +public: + void getBlock(absl::Span output) final; + void getQuantizedBlock(absl::Span output, Type quantizationStep) final; +}; } diff --git a/src/sfizz/FloatEnvelopes.cpp b/src/sfizz/FloatEnvelopes.cpp index c08a0823..89f9cef3 100644 --- a/src/sfizz/FloatEnvelopes.cpp +++ b/src/sfizz/FloatEnvelopes.cpp @@ -32,16 +32,17 @@ * */ -#include "LinearEnvelope.h" +#include "EventEnvelopes.h" #include "ADSREnvelope.h" // Include the generic implementations -#include "LinearEnvelope.cpp" +#include "EventEnvelopes.cpp" #include "ADSREnvelope.cpp" // And explicitely instantiate the float version namespace sfz { + template class EventEnvelope; template class LinearEnvelope; template class ADSREnvelope; } diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index cbfcbec0..1c3eb46b 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -24,7 +24,7 @@ #pragma once #include "ADSREnvelope.h" #include "Config.h" -#include "LinearEnvelope.h" +#include "EventEnvelopes.h" #include "HistoricalBuffer.h" #include "Region.h" #include "AudioBuffer.h" diff --git a/tests/LinearEnvelopeT.cpp b/tests/LinearEnvelopeT.cpp index d876c8db..88e7d6a8 100644 --- a/tests/LinearEnvelopeT.cpp +++ b/tests/LinearEnvelopeT.cpp @@ -21,7 +21,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "LinearEnvelope.h" +#include "EventEnvelopes.h" #include "catch2/catch.hpp" #include #include @@ -164,7 +164,7 @@ TEST_CASE("[LinearEnvelope] Get quantized") envelope.registerEvent(2, 1.0); envelope.registerEvent(6, 2.0); std::array output; - std::array expected { 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0 }; + std::array expected { 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0 }; envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); REQUIRE(output == expected); } @@ -175,7 +175,7 @@ TEST_CASE("[LinearEnvelope] Get quantized with 2 steps") envelope.registerEvent(2, 1.0); envelope.registerEvent(6, 3.0); std::array output; - std::array expected { 0.0, 0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0 }; + std::array expected { 0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 3.0 }; envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); REQUIRE(output == expected); } @@ -186,7 +186,7 @@ TEST_CASE("[LinearEnvelope] Get quantized with unclean events") envelope.registerEvent(2, 1.2); envelope.registerEvent(6, 2.5); std::array output; - std::array expected { 0.0, 0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0 }; + std::array expected { 0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 3.0 }; envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); REQUIRE(output == expected); } @@ -198,7 +198,7 @@ TEST_CASE("[LinearEnvelope] Get quantized 3 events, one out of block") envelope.registerEvent(6, 2.0); envelope.registerEvent(10, 3.0); std::array output; - std::array expected { 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0 }; + std::array expected { 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0 }; std::array expected2 { 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0 }; envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); REQUIRE(output == expected);