diff --git a/src/sfizz/EventEnvelopes.cpp b/src/sfizz/EventEnvelopes.cpp index 5cc4620f..a0e5babf 100644 --- a/src/sfizz/EventEnvelopes.cpp +++ b/src/sfizz/EventEnvelopes.cpp @@ -144,7 +144,6 @@ void LinearEnvelope::getQuantizedBlock(absl::Span output, Type quant return static_cast((value + halfStep) / quantizationStep) * quantizationStep; }; - currentValue = quantize(currentValue); const auto outputSize = static_cast(output.size()); for (auto& event : events) { const auto newValue = quantize(event.second); @@ -162,11 +161,111 @@ void LinearEnvelope::getQuantizedBlock(absl::Span output, Type quant continue; } - const int numSteps = abs(newValue - currentValue) / quantizationStep; + const auto difference = abs(newValue - currentValue); + if (difference < quantizationStep) { + fill(output.subspan(index, length), currentValue); + currentValue = newValue; + index += length; + continue; + } + + const int numSteps = difference / quantizationStep; const auto stepLength = static_cast(length / numSteps); for (int i = 0; i < numSteps; ++i) { fill(output.subspan(index, stepLength), currentValue); - currentValue += currentValue <= newValue ? quantizationStep : -quantizationStep; + const auto delta = quantizationStep + currentValue - quantize(currentValue); + currentValue += currentValue <= newValue ? delta : -delta; + index += stepLength; + } + } + + if (index < outputSize) + fill(output.subspan(index), currentValue); +} + +template +MultiplicativeEnvelope::MultiplicativeEnvelope() +{ + EventEnvelope::reset(1.0); +} + +template +void MultiplicativeEnvelope::getBlock(absl::Span output) +{ + 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) { + currentValue = event.second; + continue; + } + + const auto step = std::exp((std::log(event.second) - std::log(currentValue)) / length); + currentValue = multiplicativeRamp(output.subspan(index, length), currentValue, step); + index += length; + } + + if (index < static_cast(output.size())) + fill(output.subspan(index), currentValue); +} + +template +void MultiplicativeEnvelope::getQuantizedBlock(absl::Span output, Type quantizationStep) +{ + EventEnvelope::getQuantizedBlock(output, quantizationStep); + auto& events = EventEnvelope::events; + auto& currentValue = EventEnvelope::currentValue; + + ASSERT(quantizationStep != 0.0); + int index { 0 }; + + const auto logStep = std::log(quantizationStep); + // If we assume that a = b.q^r for b in (1, q) then + // log a log b + // ----- = ----- + r + // log q log q + // and log(b)\log(q) is between 0 and 1. + auto quantize = [logStep](Type value) -> Type { + return std::exp(logStep * static_cast(std::log(value)/logStep)); + }; + + const auto outputSize = static_cast(output.size()); + for (auto& event : events) { + const auto newValue = quantize(event.second); + + 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; + } + + const auto difference = newValue > currentValue ? newValue / currentValue : currentValue / newValue; + if (difference < quantizationStep) { + fill(output.subspan(index, length), currentValue); + currentValue = newValue; + index += length; + continue; + } + + const int numSteps = std::log(difference) / logStep; + const auto stepLength = static_cast(length / numSteps); + for (int i = 0; i < numSteps; ++i) { + fill(output.subspan(index, stepLength), currentValue); + const auto delta = newValue > currentValue ? + quantize(currentValue) / currentValue * quantizationStep : + quantize(currentValue) / currentValue / quantizationStep ; + currentValue *= delta; index += stepLength; } } diff --git a/src/sfizz/EventEnvelopes.h b/src/sfizz/EventEnvelopes.h index add2753a..1797adb4 100644 --- a/src/sfizz/EventEnvelopes.h +++ b/src/sfizz/EventEnvelopes.h @@ -130,4 +130,17 @@ public: void getBlock(absl::Span output) final; void getQuantizedBlock(absl::Span output, Type quantizationStep) final; }; + +/** + * @brief Describes a simple linear envelope. + * + * @tparam Type + */ +template +class MultiplicativeEnvelope: public EventEnvelope { +public: + MultiplicativeEnvelope(); + 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 89f9cef3..975b0c67 100644 --- a/src/sfizz/FloatEnvelopes.cpp +++ b/src/sfizz/FloatEnvelopes.cpp @@ -43,6 +43,7 @@ namespace sfz { template class EventEnvelope; + template class MultiplicativeEnvelope; template class LinearEnvelope; template class ADSREnvelope; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dfdd4255..a60e169e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,7 +18,7 @@ set(SFIZZ_TEST_SOURCES RegionActivationT.cpp RegionValueComputationsT.cpp ADSREnvelopeT.cpp - LinearEnvelopeT.cpp + EventEnvelopesT.cpp MainT.cpp SynthT.cpp RegionTriggersT.cpp diff --git a/tests/LinearEnvelopeT.cpp b/tests/EventEnvelopesT.cpp similarity index 58% rename from tests/LinearEnvelopeT.cpp rename to tests/EventEnvelopesT.cpp index 88e7d6a8..9cfe9b37 100644 --- a/tests/LinearEnvelopeT.cpp +++ b/tests/EventEnvelopesT.cpp @@ -169,6 +169,17 @@ TEST_CASE("[LinearEnvelope] Get quantized") REQUIRE(output == expected); } +TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets") +{ + sfz::LinearEnvelope envelope; + envelope.registerEvent(2, 1.1); + envelope.registerEvent(6, 1.9); + std::array output; + 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); +} + TEST_CASE("[LinearEnvelope] Get quantized with 2 steps") { sfz::LinearEnvelope envelope; @@ -180,6 +191,43 @@ TEST_CASE("[LinearEnvelope] Get quantized with 2 steps") REQUIRE(output == expected); } +TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps") +{ + sfz::LinearEnvelope envelope; + envelope.reset(3.0); + envelope.registerEvent(2, 2.0); + envelope.registerEvent(6, 0.0); + std::array output; + std::array expected { 3.0, 2.0, 2.0, 1.0, 1.0, 0.0, 0.0, 0.0 }; + envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); + REQUIRE(output == expected); +} + +TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and starting unquantized") +{ + sfz::LinearEnvelope envelope; + envelope.reset(0.1); + envelope.registerEvent(3, 1.0); + envelope.registerEvent(7, 3.0); + std::array output; + std::array expected { 0.1, 0.1, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0 }; + envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); + REQUIRE(output == expected); +} + + +TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps and starting unquantized") +{ + sfz::LinearEnvelope envelope; + envelope.reset(3.6); + envelope.registerEvent(4, 1.0); + envelope.registerEvent(7, 0.0); + std::array output; + std::array expected { 3.6, 3.0, 2.0, 2.0, 1.0, 1.0, 0.0, 0.0 }; + envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); + REQUIRE(output == expected); +} + TEST_CASE("[LinearEnvelope] Get quantized with unclean events") { sfz::LinearEnvelope envelope; @@ -204,4 +252,105 @@ TEST_CASE("[LinearEnvelope] Get quantized 3 events, one out of block") REQUIRE(output == expected); envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f); REQUIRE(output == expected2); -} \ No newline at end of file +} + +// +TEST_CASE("[MultiplicativeEnvelope] Basic state") +{ + sfz::MultiplicativeEnvelope envelope; + std::array output; + std::array expected { 1.0, 1.0, 1.0, 1.0, 1.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE(output == expected); +} + +TEST_CASE("[MultiplicativeEnvelope] Basic event") +{ + sfz::MultiplicativeEnvelope envelope; + envelope.registerEvent(4, 2.0); + std::array output; + std::array expected { 1.1892, 1.4142, 1.68176, 2.0, 2.0, 2.0, 2.0, 2.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE(approxEqual(output, expected)); +} + +TEST_CASE("[MultiplicativeEnvelope] 2 events") +{ + sfz::MultiplicativeEnvelope envelope; + envelope.registerEvent(4, 2.0); + envelope.registerEvent(5, 4.0); + std::array output; + std::array expected { 1.1892, 1.4142, 1.68176, 2.0, 4.0, 4.0, 4.0, 4.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE(approxEqual(output, expected)); +} + +TEST_CASE("[MultiplicativeEnvelope] 2 events, far") +{ + sfz::MultiplicativeEnvelope envelope; + envelope.registerEvent(2, 2.0); + envelope.registerEvent(6, 4.0); + std::array output; + std::array expected { 1.4142, 2.0, 2.37841, 2.82843, 3.36358, 4.0, 4.0, 4.0 }; + envelope.getBlock(absl::MakeSpan(output)); + REQUIRE(approxEqual(output, expected)); +} + +TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps") +{ + sfz::MultiplicativeEnvelope envelope; + envelope.registerEvent(2, 2.0); + envelope.registerEvent(6, 4.0); + std::array output; + std::array expected { 1.0, 2.0, 2.0, 2.0, 2.0, 4.0, 4.0, 4.0 }; + envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); + REQUIRE(output == expected); +} + +TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps") +{ + sfz::MultiplicativeEnvelope envelope; + envelope.reset(4.0); + envelope.registerEvent(2, 2.0); + envelope.registerEvent(6, 0.5); + std::array output; + std::array expected { 4.0, 2.0, 2.0, 1.0, 1.0, 0.5, 0.5, 0.5 }; + envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); + REQUIRE(output == expected); +} + +TEST_CASE("[MultiplicativeEnvelope] Get quantized with unclean events") +{ + sfz::MultiplicativeEnvelope envelope; + envelope.registerEvent(2, 1.2); + envelope.registerEvent(6, 2.5); + std::array output; + std::array expected { 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0 }; + envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); + REQUIRE(output == expected); +} + +TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps and starting unquantized") +{ + sfz::MultiplicativeEnvelope envelope; + envelope.reset(0.9); + envelope.registerEvent(3, 1.0); + envelope.registerEvent(7, 4.0); + std::array output; + std::array expected { 0.9, 0.9, 1.0, 1.0, 2.0, 2.0, 4.0, 4.0 }; + envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); + REQUIRE(output == expected); +} + + +TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps and starting unquantized") +{ + sfz::MultiplicativeEnvelope envelope; + envelope.reset(4.6); + envelope.registerEvent(4, 1.0); + envelope.registerEvent(7, 0.25); + std::array output; + std::array expected { 4.6, 2.0, 1.0, 1.0, 0.5, 0.5, 0.25, 0.25 }; + envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f); + REQUIRE(output == expected); +}