Wrapping up the ADSR envelope and tests

This commit is contained in:
paul 2019-08-22 17:18:09 +02:00
parent 989acdaac6
commit b909e468bf
8 changed files with 491 additions and 36 deletions

View file

@ -75,6 +75,7 @@ set(COMMON_SOURCES
sources/Synth.cpp
sources/FilePool.cpp
sources/Region.cpp
sources/ADSREnvelope.cpp
sources/Parser.cpp
)
@ -110,6 +111,7 @@ set(TEST_SOURCES
tests/FilesT.cpp
tests/OnePoleFilterT.cpp
tests/RegionActivationT.cpp
tests/ADSREnvelopeT.cpp
tests/MainT.cpp
)
@ -168,4 +170,7 @@ add_executable(bm_looping benchmarks/BM_looping.cpp sources/SIMDSSE.cpp)
target_link_libraries(bm_looping benchmark absl::span absl::algorithm)
add_executable(bm_ramp benchmarks/BM_ramp.cpp sources/SIMDSSE.cpp)
target_link_libraries(bm_ramp benchmark absl::span absl::algorithm)
target_link_libraries(bm_ramp benchmark absl::span absl::algorithm)
add_executable(bm_ADSR benchmarks/BM_ADSR.cpp sources/SIMDSSE.cpp)
target_link_libraries(bm_ADSR benchmark absl::span absl::algorithm)

45
benchmarks/BM_ADSR.cpp Normal file
View file

@ -0,0 +1,45 @@
#include <benchmark/benchmark.h>
#include "../sources/ADSREnvelope.h"
#include <algorithm>
#include <random>
#include <numeric>
constexpr int fixedAmount { 12 };
constexpr int envelopeSize { 2 << 16 };
constexpr int attack = static_cast<int>(envelopeSize / 4) - fixedAmount;
constexpr int decay = attack;
constexpr int release = attack;
constexpr int releaseTime = envelopeSize - static_cast<int>(envelopeSize / 4);
static void Point(benchmark::State& state) {
std::vector<float> output(state.range(0));
sfz::ADSREnvelope<float> envelope;
for (auto _ : state) {
envelope.reset(attack, release, 1.0, fixedAmount, decay, fixedAmount);
envelope.startRelease(releaseTime);
for(int offset = 0; offset < envelopeSize; offset += state.range(0))
for (auto& out: output)
out = envelope.getNextValue();
benchmark::DoNotOptimize(output);
}
state.counters["Per block"] = benchmark::Counter(envelopeSize / state.range(0), benchmark::Counter::kIsIterationInvariantRate | benchmark::Counter::kInvert);
}
static void Block(benchmark::State& state) {
std::vector<float> output(state.range(0));
sfz::ADSREnvelope<float> envelope;
for (auto _ : state) {
envelope.reset(attack, release, 1.0, fixedAmount, decay, fixedAmount);
envelope.startRelease(releaseTime);
for(int offset = 0; offset < envelopeSize; offset += state.range(0))
envelope.getBlock(absl::MakeSpan(output));
benchmark::DoNotOptimize(output);
}
state.counters["Per block"] = benchmark::Counter(envelopeSize / state.range(0), benchmark::Counter::kIsIterationInvariantRate | benchmark::Counter::kInvert);
}
BENCHMARK(Point)->RangeMultiplier(2)->Range((2<<6), (2<<11));
BENCHMARK(Block)->RangeMultiplier(2)->Range((2<<6), (2<<11));
BENCHMARK_MAIN();

217
sources/ADSREnvelope.cpp Normal file
View file

@ -0,0 +1,217 @@
#include "Globals.h"
#include "SIMDHelpers.h"
#include "Helpers.h"
#include "ADSREnvelope.h"
#include <algorithm>
namespace sfz
{
template<class Type>
void ADSREnvelope<Type>::reset(int attack, int release, Type sustain, int delay, int decay, int hold, Type start, Type depth) noexcept
{
ASSERT(start <= 1.0f);
ASSERT(sustain <= 1.0f);
sustain = std::clamp<Type>(sustain, 0.0, 1.0);
start = std::clamp<Type>(start, 0.0, 1.0);
currentState = State::Done;
this->delay = delay;
this->attack = attack;
this->decay = decay;
this->release = release;
this->hold = hold;
this->start = depth * start;
this->sustain = depth * sustain;
this->peak = depth;
releaseDelay = 0;
shouldRelease = false;
step = 0.0;
currentValue = this->start;
currentState = State::Delay;
}
template<class Type>
Type ADSREnvelope<Type>::getNextValue() noexcept
{
if (shouldRelease && releaseDelay-- == 0)
{
currentState = State::Release;
step = std::exp((std::log(config::virtuallyZero) - std::log(currentValue)) / (release > 0 ? release : 1));
}
switch(currentState)
{
case State::Delay:
if (delay-- > 0)
return start;
currentState = State::Attack;
step = (1.0 - currentValue) / (attack > 0 ? attack : 1);
[[fallthrough]];
case State::Attack:
if (attack-- > 0)
{
currentValue += step;
return currentValue;
}
currentState = State::Hold;
currentValue = 1.0;
[[fallthrough]];
case State::Hold:
if (hold-- > 0)
return currentValue;
step = std::exp(std::log(sustain) / (decay > 0 ? decay : 1));
currentState = State::Decay;
[[fallthrough]];
case State::Decay:
if (decay-- > 0)
{
currentValue *= step;
return currentValue;
}
currentState = State::Sustain;
currentValue = sustain;
[[fallthrough]];
case State::Sustain:
return currentValue;
case State::Release:
if (release-- > 0)
{
currentValue *= step;
return currentValue;
}
currentState = State::Done;
currentValue = 0.0;
[[fallthrough]];
default:
return 0.0;
}
}
template<class Type>
void ADSREnvelope<Type>::getBlock(absl::Span<Type> output) noexcept
{
auto originalSpan = output;
auto remainingSamples = static_cast<int>(output.size());
int length;
switch(currentState)
{
case State::Delay:
length = min(remainingSamples, delay);
::fill<Type>(output, currentValue);
output.remove_prefix(length);
remainingSamples -= length;
delay -= length;
if (remainingSamples == 0)
break;
currentState = State::Attack;
step = (peak - start) / (attack > 0 ? attack : 1);
[[fallthrough]];
case State::Attack:
length = min(remainingSamples, attack);
currentValue = ::linearRamp<Type>(output, currentValue, step);
output.remove_prefix(length);
remainingSamples -= length;
attack -= length;
if (remainingSamples == 0)
break;
currentValue = peak;
currentState = State::Hold;
[[fallthrough]];
case State::Hold:
length = min(remainingSamples, hold);
::fill<Type>(output, currentValue);
output.remove_prefix(length);
remainingSamples -= length;
hold -= length;
if (remainingSamples == 0)
break;
step = std::exp(std::log(sustain) / (decay > 0 ? decay : 1));
currentState = State::Decay;
[[fallthrough]];
case State::Decay:
length = min(remainingSamples, decay);
currentValue = ::multiplicativeRamp<Type>(output, currentValue, step);
output.remove_prefix(length);
remainingSamples -= length;
decay -= length;
if (remainingSamples == 0)
break;
currentValue = sustain;
currentState = State::Sustain;
[[fallthrough]];
case State::Sustain:
break;
case State::Release:
length = min(remainingSamples, release);
currentValue = ::multiplicativeRamp<Type>(output, currentValue, step);
output.remove_prefix(length);
remainingSamples -= length;
release -= length;
if (remainingSamples == 0)
break;
currentValue = 0.0;
currentState = State::Done;
[[fallthrough]];
case State::Done:
[[fallthrough]];
default:
break;
}
::fill<Type>(output, currentValue);
if (shouldRelease)
{
remainingSamples = static_cast<int>(originalSpan.size());
if (releaseDelay > remainingSamples)
{
releaseDelay -= remainingSamples;
return;
}
originalSpan.remove_prefix(releaseDelay);
currentValue = originalSpan.front();
step = std::exp((std::log(config::virtuallyZero) - std::log(currentValue)) / (release > 0 ? release : 1));
remainingSamples -= releaseDelay;
length = min(remainingSamples, release);
currentState = State::Release;
currentValue = ::multiplicativeRamp<Type>(originalSpan, currentValue, step);
originalSpan.remove_prefix(length);
release -= length;
if (release == 0)
{
currentValue = 0.0;
currentState = State::Done;
::fill<Type>(originalSpan, 0.0);
}
}
}
template<class Type>
void ADSREnvelope<Type>::startRelease(int releaseDelay) noexcept
{
shouldRelease = true;
this->releaseDelay = releaseDelay;
}
template<class Type>
constexpr bool ADSREnvelope<Type>::isSmoothing() noexcept
{
return currentState != State::Done;
}
template class ADSREnvelope<float>;
}

View file

@ -1,5 +1,4 @@
#include "Globals.h"
#include <absl/types/span.h>
namespace sfz
{
@ -7,34 +6,30 @@ template<class Type>
class ADSREnvelope
{
public:
struct Description
{
Description()
: depth(1) {}
Descrition(Type depth)
: depth(depth) {}
int delay { 0 };
int attack { 0 };
int decay { 0 };
int release { 0 };
int hold { 0 };
float start { 0 };
float sustain { 0 };
Type depth;
};
ADSREnvelope() = default;
void reset(Description desc)
{
}
void reset(int attack, int release, Type sustain=1.0, int delay = 0, int decay = 0, int hold = 0, Type start=0.0, Type depth=1) noexcept;
Type getNextValue() noexcept;
void getBlock(absl::Span<Type> output) noexcept;
void startRelease(int releaseDelay) noexcept;
constexpr bool isSmoothing() noexcept;
private:
enum class State
{
Delay, Attack, Hold, Sustain, Release, Done
Delay, Attack, Hold, Decay, Sustain, Release, Done
};
State currentState { Done };
State currentState { State::Done };
Type currentValue { 0.0 };
Type step { 0.0 };
int delay { 0 };
int attack { 0 };
int decay { 0 };
int release { 0 };
int hold { 0 };
Type start { 0 };
Type peak { 0 };
Type sustain { 0 };
int releaseDelay { 0 };
bool shouldRelease { false };
};
}

View file

@ -68,13 +68,13 @@ void loopingSFZIndex<float, true>(absl::Span<const float> jumps, absl::Span<floa
}
template<>
void linearRamp<float, true>(absl::Span<float> output, float start, float step) noexcept
float linearRamp<float, true>(absl::Span<float> output, float start, float step) noexcept
{
linearRamp<float, false>(output, start, step);
return linearRamp<float, false>(output, start, step);
}
template<>
void multiplicativeRamp<float, true>(absl::Span<float> output, float start, float step) noexcept
float multiplicativeRamp<float, true>(absl::Span<float> output, float start, float step) noexcept
{
multiplicativeRamp<float, false>(output, start, step);
return multiplicativeRamp<float, false>(output, start, step);
}

View file

@ -206,11 +206,12 @@ inline void snippetRampLinear(T*& output, T& value, T step)
}
template<class T, bool SIMD=SIMDConfig::linearRamp>
void linearRamp(absl::Span<T> output, T start, T step) noexcept
T linearRamp(absl::Span<T> output, T start, T step) noexcept
{
auto* out = output.begin();
while(out < output.end())
snippetRampLinear<T>(out, start, step);
return start;
}
template<class T>
@ -221,16 +222,17 @@ inline void snippetRampMultiplicative(T*& output, T& value, T step)
}
template<class T, bool SIMD=SIMDConfig::multiplicativeRamp>
void multiplicativeRamp(absl::Span<T> output, T start, T step) noexcept
T multiplicativeRamp(absl::Span<T> output, T start, T step) noexcept
{
auto* out = output.begin();
while(out < output.end())
snippetRampMultiplicative<T>(out, start, step);
return start;
}
template<>
void linearRamp<float, true>(absl::Span<float> output, float start, float step) noexcept;
float linearRamp<float, true>(absl::Span<float> output, float start, float step) noexcept;
template<>
void multiplicativeRamp<float, true>(absl::Span<float> output, float start, float step) noexcept;
float multiplicativeRamp<float, true>(absl::Span<float> output, float start, float step) noexcept;

View file

@ -306,7 +306,7 @@ void loopingSFZIndex<float, true>(absl::Span<const float> jumps, absl::Span<floa
}
template<>
void linearRamp<float, true>(absl::Span<float> output, float value, float step) noexcept
float linearRamp<float, true>(absl::Span<float> output, float value, float step) noexcept
{
auto* out = output.begin();
const auto* lastAligned = prevAligned(output.end());
@ -328,10 +328,11 @@ void linearRamp<float, true>(absl::Span<float> output, float value, float step)
value = _mm_cvtss_f32(mmValue);
while(out < output.end())
snippetRampLinear<float>(out, value, step);
return value;
}
template<>
void multiplicativeRamp<float, true>(absl::Span<float> output, float value, float step) noexcept
float multiplicativeRamp<float, true>(absl::Span<float> output, float value, float step) noexcept
{
auto* out = output.begin();
const auto* lastAligned = prevAligned(output.end());
@ -353,4 +354,5 @@ void multiplicativeRamp<float, true>(absl::Span<float> output, float value, floa
value = _mm_cvtss_f32(mmValue);
while(out < output.end())
snippetRampMultiplicative<float>(out, value, step);
return value;
}

189
tests/ADSREnvelopeT.cpp Normal file
View file

@ -0,0 +1,189 @@
#include "catch2/catch.hpp"
#include "../sources/ADSREnvelope.h"
#include <array>
#include <algorithm>
#include <iostream>
#include <absl/types/span.h>
#include <absl/algorithm/container.h>
using namespace Catch::literals;
template<class Type>
inline bool approxEqual(absl::Span<const Type> lhs, absl::Span<const Type> 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("[ADSREnvelope] Basic state")
{
sfz::ADSREnvelope<float> envelope;
std::array<float, 5> output;
std::array<float, 5> expected { 0.0, 0.0, 0.0, 0.0, 0.0 };
for (auto& out: output)
out = envelope.getNextValue();
REQUIRE( approxEqual<float>(output, expected) );
absl::c_fill(output, -1.0);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE( approxEqual<float>(output, expected) );
}
TEST_CASE("[ADSREnvelope] Attack")
{
sfz::ADSREnvelope<float> envelope;
envelope.reset(2, 0);
std::array<float, 5> output;
std::array<float, 5> expected { 0.5, 1.0, 1.0, 1.0, 1.0 };
for (auto& out: output)
out = envelope.getNextValue();
REQUIRE( approxEqual<float>(output, expected) );
envelope.reset(2, 0);
absl::c_fill(output, -1.0);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE( approxEqual<float>(output, expected) );
}
TEST_CASE("[ADSREnvelope] Attack again")
{
sfz::ADSREnvelope<float> envelope;
envelope.reset(3, 0);
std::array<float, 5> output;
std::array<float, 5> expected { 0.33333, 0.66667, 1.0, 1.0, 1.0 };
for (auto& out: output)
out = envelope.getNextValue();
REQUIRE( approxEqual<float>(output, expected) );
envelope.reset(3, 0);
absl::c_fill(output, -1.0);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE( approxEqual<float>(output, expected) );
}
TEST_CASE("[ADSREnvelope] Release")
{
sfz::ADSREnvelope<float> envelope;
envelope.reset(2, 4);
envelope.startRelease(2);
std::array<float, 8> output;
std::array<float, 8> expected { 0.5, 1.0, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f };
for (auto& out: output)
out = envelope.getNextValue();
REQUIRE( approxEqual<float>(output, expected) );
envelope.reset(2, 4);
envelope.startRelease(2);
absl::c_fill(output, -1.0);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE( approxEqual<float>(output, expected) );
}
TEST_CASE("[ADSREnvelope] Delay")
{
sfz::ADSREnvelope<float> envelope;
envelope.reset(2, 4, 1.0f, 2);
std::array<float, 10> output;
envelope.startRelease(4);
std::array<float, 10> expected { 0.0, 0.0, 0.5, 1.0, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f };
for (auto& out: output)
out = envelope.getNextValue();
REQUIRE( approxEqual<float>(output, expected) );
envelope.reset(2, 4, 1.0f, 2);
envelope.startRelease(4);
absl::c_fill(output, -1.0);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE( approxEqual<float>(output, expected) );
}
TEST_CASE("[ADSREnvelope] Lower sustain")
{
sfz::ADSREnvelope<float> envelope;
envelope.reset(2, 4, 0.5, 2);
std::array<float, 10> output;
std::array<float, 10> expected { 0.0, 0.0, 0.5, 1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5 };
for (auto& out: output)
out = envelope.getNextValue();
REQUIRE( approxEqual<float>(output, expected) );
envelope.reset(2, 4, 0.5, 2);
absl::c_fill(output, -1.0);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE( approxEqual<float>(output, expected) );
}
TEST_CASE("[ADSREnvelope] Decay")
{
sfz::ADSREnvelope<float> envelope;
envelope.reset(2, 4, 0.5, 2, 2);
std::array<float, 10> output;
std::array<float, 10> expected { 0.0, 0.0, 0.5, 1.0, 0.707107, 0.5, 0.5, 0.5, 0.5, 0.5 };
for (auto& out: output)
out = envelope.getNextValue();
REQUIRE( approxEqual<float>(output, expected) );
envelope.reset(2, 4, 0.5, 2, 2);
absl::c_fill(output, -1.0);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE( approxEqual<float>(output, expected) );
}
TEST_CASE("[ADSREnvelope] Hold")
{
sfz::ADSREnvelope<float> envelope;
envelope.reset(2, 4, 0.5, 2, 2, 2);
std::array<float, 12> output;
std::array<float, 12> expected { 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 0.707107, 0.5, 0.5, 0.5, 0.5, 0.5 };
for (auto& out: output)
out = envelope.getNextValue();
REQUIRE( approxEqual<float>(output, expected) );
envelope.reset(2, 4, 0.5, 2, 2, 2);
absl::c_fill(output, -1.0);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE( approxEqual<float>(output, expected) );
}
TEST_CASE("[ADSREnvelope] Hold with release")
{
sfz::ADSREnvelope<float> envelope;
envelope.reset(2, 4, 0.5, 2, 2, 2);
envelope.startRelease(8);
std::array<float, 14> output;
std::array<float, 14> expected { 0.0, 0.0, 0.5, 1.0, 1.0, 1.0, 0.707107, 0.5, 0.05, 0.005, 0.0005, 0.00005, 0.0, 0.0 };
for (auto& out: output)
out = envelope.getNextValue();
REQUIRE( approxEqual<float>(output, expected) );
envelope.reset(2, 4, 0.5, 2, 2, 2);
envelope.startRelease(8);
absl::c_fill(output, -1.0);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE( approxEqual<float>(output, expected) );
}
TEST_CASE("[ADSREnvelope] Hold with release 2")
{
sfz::ADSREnvelope<float> envelope;
envelope.reset(2, 4, 0.5, 2, 2, 2);
envelope.startRelease(4);
std::array<float, 14> output;
std::array<float, 14> expected { 0.0, 0.0, 0.5, 1.0, 0.08409, 0.00707, 0.000594604, 0.00005, 0.0, 0.0, 0.0, 0.0 };
for (auto& out: output)
out = envelope.getNextValue();
REQUIRE( approxEqual<float>(output, expected) );
envelope.reset(2, 4, 0.5, 2, 2, 2);
envelope.startRelease(4);
absl::c_fill(output, -1.0);
envelope.getBlock(absl::MakeSpan(output));
REQUIRE( approxEqual<float>(output, expected) );
}