Tests and cleanup for the envelopes
This commit is contained in:
parent
49bfa53a6c
commit
2bdbdf2d98
10 changed files with 254 additions and 75 deletions
|
|
@ -72,7 +72,9 @@ set(COMMON_SOURCES
|
||||||
sources/Synth.cpp
|
sources/Synth.cpp
|
||||||
sources/FilePool.cpp
|
sources/FilePool.cpp
|
||||||
sources/Region.cpp
|
sources/Region.cpp
|
||||||
sources/ADSREnvelope.cpp
|
sources/FloatEnvelopes.cpp
|
||||||
|
# sources/LinearEnvelope.cpp
|
||||||
|
# sources/ADSREnvelope.cpp
|
||||||
sources/Parser.cpp
|
sources/Parser.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -109,6 +111,7 @@ set(TEST_SOURCES
|
||||||
tests/OnePoleFilterT.cpp
|
tests/OnePoleFilterT.cpp
|
||||||
tests/RegionActivationT.cpp
|
tests/RegionActivationT.cpp
|
||||||
tests/ADSREnvelopeT.cpp
|
tests/ADSREnvelopeT.cpp
|
||||||
|
tests/LinearEnvelopeT.cpp
|
||||||
tests/MainT.cpp
|
tests/MainT.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,4 @@ constexpr bool ADSREnvelope<Type>::isSmoothing() noexcept
|
||||||
return currentState != State::Done;
|
return currentState != State::Done;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit instantiation
|
|
||||||
template class ADSREnvelope<float>;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#pragma once
|
||||||
#include <absl/types/span.h>
|
#include <absl/types/span.h>
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#pragma once
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
|
|
|
||||||
13
sources/FloatEnvelopes.cpp
Normal file
13
sources/FloatEnvelopes.cpp
Normal file
|
|
@ -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<float>;
|
||||||
|
template class ADSREnvelope<float>;
|
||||||
|
}
|
||||||
83
sources/LinearEnvelope.cpp
Normal file
83
sources/LinearEnvelope.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#include "LinearEnvelope.h"
|
||||||
|
#include "Helpers.h"
|
||||||
|
#include "SIMDHelpers.h"
|
||||||
|
#include <absl/algorithm/container.h>
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
LinearEnvelope<Type>::LinearEnvelope()
|
||||||
|
{
|
||||||
|
setMaxCapacity(maxCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
LinearEnvelope<Type>::LinearEnvelope(int maxCapacity, std::function<Type(Type)> function)
|
||||||
|
{
|
||||||
|
setMaxCapacity(maxCapacity);
|
||||||
|
setFunction(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
void LinearEnvelope<Type>::setMaxCapacity(int maxCapacity)
|
||||||
|
{
|
||||||
|
events.reserve(maxCapacity);
|
||||||
|
this->maxCapacity = maxCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
void LinearEnvelope<Type>::setFunction(std::function<Type(Type)> function)
|
||||||
|
{
|
||||||
|
this->function = function;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
void LinearEnvelope<Type>::registerEvent(int timestamp, Type inputValue)
|
||||||
|
{
|
||||||
|
if (static_cast<int>(events.size()) < maxCapacity)
|
||||||
|
events.emplace_back(timestamp, function(inputValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
void LinearEnvelope<Type>::clear()
|
||||||
|
{
|
||||||
|
events.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
void LinearEnvelope<Type>::reset(Type value)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
currentValue = function(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
void LinearEnvelope<Type>::getBlock(absl::Span<Type> 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<int>(output.size())) - index;
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
currentValue = event.second;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto step = (event.second - currentValue) / length;
|
||||||
|
currentValue = ::linearRamp<Type>(output.subspan(index, length), currentValue, step);
|
||||||
|
index += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < static_cast<int>(output.size()))
|
||||||
|
::fill<Type>(output.subspan(index), currentValue);
|
||||||
|
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
|
#pragma once
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "Helpers.h"
|
|
||||||
#include "SIMDHelpers.h"
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <absl/algorithm/container.h>
|
#include <absl/types/span.h>
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
|
|
@ -12,78 +11,20 @@ template<class Type>
|
||||||
class LinearEnvelope
|
class LinearEnvelope
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LinearEnvelope()
|
LinearEnvelope();
|
||||||
{
|
LinearEnvelope(int maxCapacity, std::function<Type(Type)> function);
|
||||||
setMaxCapacity(maxCapacity);
|
void setMaxCapacity(int maxCapacity);
|
||||||
}
|
void setFunction(std::function<Type(Type)> function);
|
||||||
LinearEnvelope(int maxCapacity, std::function<Type(Type)> function)
|
void registerEvent(int timestamp, Type inputValue);
|
||||||
{
|
void clear();
|
||||||
setMaxCapacity(maxCapacity);
|
void reset(Type value=0.0);
|
||||||
setFunction(function);
|
void getBlock(absl::Span<Type> output);
|
||||||
}
|
|
||||||
|
|
||||||
void setMaxCapacity(int maxCapacity)
|
|
||||||
{
|
|
||||||
events.reserve(maxCapacity);
|
|
||||||
this->maxCapacity = maxCapacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setFunction(std::function<Type(Type)> 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<Type> 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<Type>(output.subspan(index, length), currentValue, step);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < output.size())
|
|
||||||
::fill<Type>(output.subspan(index), currentValue);
|
|
||||||
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Event
|
|
||||||
{
|
|
||||||
int timestamp;
|
|
||||||
Type value;
|
|
||||||
};
|
|
||||||
std::function<Type(Type)> function { [](Type input) { return input; } };
|
std::function<Type(Type)> function { [](Type input) { return input; } };
|
||||||
static_assert(std::is_numeric<Type>::value);
|
static_assert(std::is_arithmetic<Type>::value);
|
||||||
std::vector<Event> events;
|
std::vector<std::pair<int, Type>> events;
|
||||||
int maxCapacity { config::defaultSamplesPerBlock };
|
int maxCapacity { config::defaultSamplesPerBlock };
|
||||||
Type currentValue;
|
Type currentValue { 0.0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#pragma once
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <absl/types/span.h>
|
#include <absl/types/span.h>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#pragma once
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include <absl/types/span.h>
|
#include <absl/types/span.h>
|
||||||
#include <absl/algorithm/container.h>
|
#include <absl/algorithm/container.h>
|
||||||
|
|
|
||||||
137
tests/LinearEnvelopeT.cpp
Normal file
137
tests/LinearEnvelopeT.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
#include "../sources/LinearEnvelope.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("[LinearEnvelope] Basic state")
|
||||||
|
{
|
||||||
|
sfz::LinearEnvelope<float> envelope;
|
||||||
|
std::array<float, 5> output;
|
||||||
|
std::array<float, 5> 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<float> envelope;
|
||||||
|
envelope.registerEvent(4, 1.0);
|
||||||
|
std::array<float, 8> output;
|
||||||
|
std::array<float, 8> 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<float> envelope;
|
||||||
|
envelope.registerEvent(4, 1.0);
|
||||||
|
envelope.registerEvent(5, 2.0);
|
||||||
|
std::array<float, 8> output;
|
||||||
|
std::array<float, 8> 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<float> envelope;
|
||||||
|
envelope.registerEvent(2, 1.0);
|
||||||
|
envelope.registerEvent(6, 2.0);
|
||||||
|
std::array<float, 8> output;
|
||||||
|
std::array<float, 8> 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<float> envelope;
|
||||||
|
envelope.registerEvent(6, 2.0);
|
||||||
|
envelope.registerEvent(2, 1.0);
|
||||||
|
std::array<float, 8> output;
|
||||||
|
std::array<float, 8> 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<float> envelope;
|
||||||
|
envelope.registerEvent(2, 1.0);
|
||||||
|
envelope.registerEvent(6, 2.0);
|
||||||
|
envelope.registerEvent(6, 3.0);
|
||||||
|
std::array<float, 8> output;
|
||||||
|
std::array<float, 8> 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<float> envelope;
|
||||||
|
envelope.registerEvent(2, 1.0);
|
||||||
|
envelope.registerEvent(6, 2.0);
|
||||||
|
envelope.registerEvent(10, 3.0);
|
||||||
|
std::array<float, 8> output;
|
||||||
|
std::array<float, 8> 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<float> envelope;
|
||||||
|
envelope.registerEvent(2, 1.0);
|
||||||
|
envelope.registerEvent(6, 2.0);
|
||||||
|
envelope.registerEvent(10, 3.0);
|
||||||
|
std::array<float, 8> output;
|
||||||
|
std::array<float, 8> 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<float> envelope;
|
||||||
|
envelope.registerEvent(2, 1.0);
|
||||||
|
envelope.registerEvent(6, 2.0);
|
||||||
|
std::array<float, 8> output;
|
||||||
|
std::array<float, 8> 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<float> envelope;
|
||||||
|
envelope.setFunction([](auto x) { return 2*x; });
|
||||||
|
envelope.registerEvent(2, 1.0);
|
||||||
|
envelope.registerEvent(6, 2.0);
|
||||||
|
std::array<float, 8> output;
|
||||||
|
std::array<float, 8> expected { 1, 2, 2.5, 3, 3.5, 4.0, 4.0, 4.0 };
|
||||||
|
envelope.getBlock(absl::MakeSpan(output));
|
||||||
|
REQUIRE( output == expected );
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue