Merge pull request #384 from paulfd/simpler-envelope-follower
Simpler power/envelope follower
This commit is contained in:
commit
81169b8209
17 changed files with 335 additions and 72 deletions
|
|
@ -34,7 +34,7 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, Scalar)
|
|||
(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state) {
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::meanSquared, false);
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::sumSquares, false);
|
||||
auto result = sfz::meanSquared<float>(input);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, SIMD)
|
|||
(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state) {
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::meanSquared, true);
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::sumSquares, true);
|
||||
auto result = sfz::meanSquared<float>(input);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, Scalar_Unaligned)
|
|||
(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state) {
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::meanSquared, false);
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::sumSquares, false);
|
||||
auto result = sfz::meanSquared<float>(absl::MakeSpan(input).subspan(1));
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, SIMD_Unaligned)
|
|||
(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state) {
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::meanSquared, true);
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::sumSquares, true);
|
||||
auto result = sfz::meanSquared<float>(absl::MakeSpan(input).subspan(1));
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
|
|
|||
125
benchmarks/BM_powerFollower.cpp
Normal file
125
benchmarks/BM_powerFollower.cpp
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#include "PowerFollower.h"
|
||||
#include "AudioBuffer.h"
|
||||
#include "Config.h"
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <cmath>
|
||||
|
||||
class PowerFollowerFixture : public benchmark::Fixture {
|
||||
public:
|
||||
PowerFollowerFixture()
|
||||
{
|
||||
inputSignal_ = sfz::AudioBuffer<float>(2, numFrames);
|
||||
auto leftSignal = inputSignal_.getSpan(0);
|
||||
auto rightSignal = inputSignal_.getSpan(1);
|
||||
float phase = 0;
|
||||
for (size_t i = 0; i < numFrames; ++i) {
|
||||
constexpr float k2pi = 2.0 * M_PI;
|
||||
leftSignal[i] = std::sin(k2pi * phase);
|
||||
rightSignal[i] = std::cos(k2pi * phase);
|
||||
phase += 440.0f / sfz::config::defaultSampleRate;
|
||||
phase -= static_cast<int>(phase);
|
||||
}
|
||||
}
|
||||
|
||||
void SetUp(const ::benchmark::State& state)
|
||||
{
|
||||
auto blockSize = static_cast<size_t>(state.range(0));
|
||||
follower_.setSampleRate(sfz::config::defaultSampleRate);
|
||||
follower_.setSamplesPerBlock(blockSize);
|
||||
follower_.clear();
|
||||
|
||||
//
|
||||
refFollower_.init(sfz::config::defaultSampleRate);
|
||||
refFollower_.clear();
|
||||
}
|
||||
|
||||
void TearDown(const ::benchmark::State& /* state */)
|
||||
{
|
||||
}
|
||||
|
||||
static constexpr size_t numFrames = 65536;
|
||||
sfz::PowerFollower follower_;
|
||||
sfz::AudioBuffer<float> inputSignal_;
|
||||
|
||||
//
|
||||
struct ReferenceFollower {
|
||||
/*
|
||||
import("stdfaust.lib");
|
||||
process = (_, _) : + : an.amp_follower_ud(att, rel) with { att = 5e-3; rel = 200e-3; };
|
||||
*/
|
||||
|
||||
void init(float sampleRate)
|
||||
{
|
||||
fConst0 = std::min<float>(192000.0f, std::max<float>(1.0f, float(sampleRate)));
|
||||
fConst1 = std::exp((0.0f - (200.0f / fConst0)));
|
||||
fConst2 = (1.0f - fConst1);
|
||||
fConst3 = std::exp((0.0f - (5.0f / fConst0)));
|
||||
fConst4 = (1.0f - fConst3);
|
||||
}
|
||||
void clear()
|
||||
{
|
||||
for (int l0 = 0; (l0 < 2); l0 = (l0 + 1)) {
|
||||
fRec1[l0] = 0.0f;
|
||||
}
|
||||
for (int l1 = 0; (l1 < 2); l1 = (l1 + 1)) {
|
||||
fRec0[l1] = 0.0f;
|
||||
}
|
||||
}
|
||||
float process(float input0, float input1)
|
||||
{
|
||||
float fTemp0 = std::fabs((float(input0) + float(input1)));
|
||||
fRec1[0] = std::max<float>(fTemp0, ((fConst3 * fRec1[1]) + (fConst4 * fTemp0)));
|
||||
fRec0[0] = ((fConst1 * fRec0[1]) + (fConst2 * fRec1[0]));
|
||||
float output = fRec0[0];
|
||||
fRec1[1] = fRec1[0];
|
||||
fRec0[1] = fRec0[0];
|
||||
return output;
|
||||
}
|
||||
|
||||
float fConst0;
|
||||
float fConst1;
|
||||
float fConst2;
|
||||
float fConst3;
|
||||
float fConst4;
|
||||
float fRec1[2];
|
||||
float fRec0[2];
|
||||
};
|
||||
ReferenceFollower refFollower_;
|
||||
};
|
||||
|
||||
constexpr size_t PowerFollowerFixture::numFrames;
|
||||
|
||||
BENCHMARK_DEFINE_F(PowerFollowerFixture, ReferenceFollower) (benchmark::State& state)
|
||||
{
|
||||
sfz::AudioSpan<float> inputSignal(inputSignal_);
|
||||
auto input0 = inputSignal.getConstSpan(0);
|
||||
auto input1 = inputSignal.getConstSpan(1);
|
||||
|
||||
for (auto _ : state) {
|
||||
auto& follower = refFollower_;
|
||||
float output = 0;
|
||||
for (size_t i = 0, n = inputSignal.getNumFrames(); i < n; ++i)
|
||||
output = follower.process(input0[i], input1[i]);
|
||||
benchmark::DoNotOptimize(output);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_DEFINE_F(PowerFollowerFixture, Follower) (benchmark::State& state)
|
||||
{
|
||||
sfz::AudioSpan<float> inputSignal(inputSignal_);
|
||||
for (auto _ : state) {
|
||||
auto& follower = follower_;
|
||||
auto blockSize = static_cast<size_t>(state.range(0));
|
||||
for (size_t i = 0; i < numFrames; i += blockSize)
|
||||
follower.process(inputSignal.subspan(i, blockSize));
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(PowerFollowerFixture, ReferenceFollower)->Range(1, 1);
|
||||
BENCHMARK_REGISTER_F(PowerFollowerFixture, Follower)->RangeMultiplier(2)->Range(1 << 5, 1 << 12);
|
||||
|
|
@ -66,6 +66,8 @@ sfizz_add_benchmark(bm_logger BM_logger.cpp)
|
|||
target_link_libraries(bm_logger PRIVATE sfizz::sfizz)
|
||||
sfizz_add_benchmark(bm_smoothers BM_smoothers.cpp)
|
||||
target_link_libraries(bm_smoothers PRIVATE sfizz::sfizz)
|
||||
sfizz_add_benchmark(bm_powerFollower BM_powerFollower.cpp)
|
||||
target_link_libraries(bm_powerFollower PRIVATE sfizz::sfizz)
|
||||
|
||||
if (TARGET sfizz-samplerate)
|
||||
sfizz_add_benchmark(bm_resample BM_resample.cpp ${BENCHMARK_SIMD_SOURCES})
|
||||
|
|
|
|||
1
dpf.mk
1
dpf.mk
|
|
@ -103,6 +103,7 @@ SFIZZ_SOURCES = \
|
|||
src/sfizz/Parser.cpp \
|
||||
src/sfizz/parser/Parser.cpp \
|
||||
src/sfizz/parser/ParserPrivate.cpp \
|
||||
src/sfizz/PowerFollower.cpp \
|
||||
src/sfizz/Region.cpp \
|
||||
src/sfizz/RTSemaphore.cpp \
|
||||
src/sfizz/ScopedFTZ.cpp \
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ set (SFIZZ_HEADERS
|
|||
sfizz/Oversampler.h
|
||||
sfizz/Panning.h
|
||||
sfizz/PolyphonyGroup.h
|
||||
sfizz/PowerFollower.h
|
||||
sfizz/railsback/2-1.h
|
||||
sfizz/railsback/4-1.h
|
||||
sfizz/railsback/4-2.h
|
||||
|
|
@ -138,6 +139,7 @@ set (SFIZZ_SOURCES
|
|||
sfizz/Effects.cpp
|
||||
sfizz/LFO.cpp
|
||||
sfizz/LFODescription.cpp
|
||||
sfizz/PowerFollower.cpp
|
||||
sfizz/modulations/ModId.cpp
|
||||
sfizz/modulations/ModKey.cpp
|
||||
sfizz/modulations/ModKeyHash.cpp
|
||||
|
|
|
|||
|
|
@ -57,7 +57,9 @@ namespace config {
|
|||
constexpr Oversampling defaultOversamplingFactor { Oversampling::x1 };
|
||||
constexpr float A440 { 440.0 };
|
||||
constexpr size_t powerHistoryLength { 16 };
|
||||
constexpr float filteredEnvelopeCutoff { 5 };
|
||||
constexpr size_t powerFollowerStep { 512 };
|
||||
constexpr float powerFollowerAttackTime { 5e-3f };
|
||||
constexpr float powerFollowerReleaseTime { 200e-3f };
|
||||
constexpr uint16_t numCCs { 512 };
|
||||
constexpr int maxCurves { 256 };
|
||||
constexpr int chunkSize { 1024 };
|
||||
|
|
@ -72,10 +74,10 @@ namespace config {
|
|||
*/
|
||||
constexpr float stealingAgeCoeff { 0.5f };
|
||||
/**
|
||||
* @brief The threshold for envelope stealing.
|
||||
* In percentage of the sum of all envelopes.
|
||||
* @brief The threshold for power stealing.
|
||||
* In percentage of the sum of all powers.
|
||||
*/
|
||||
constexpr float stealingEnvelopeCoeff { 0.5f };
|
||||
constexpr float stealingPowerCoeff { 0.5f };
|
||||
constexpr int filtersPerVoice { 2 };
|
||||
constexpr int eqsPerVoice { 3 };
|
||||
constexpr int oscillatorsPerVoice { 9 };
|
||||
|
|
|
|||
98
src/sfizz/PowerFollower.cpp
Normal file
98
src/sfizz/PowerFollower.cpp
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#include "PowerFollower.h"
|
||||
#include "Defaults.h"
|
||||
#include "SIMDHelpers.h"
|
||||
#include <absl/types/span.h>
|
||||
|
||||
namespace sfz {
|
||||
|
||||
PowerFollower::PowerFollower()
|
||||
: sampleRate_(config::defaultSampleRate),
|
||||
samplesPerBlock_(config::defaultSamplesPerBlock),
|
||||
tempBuffer_(new float[config::defaultSamplesPerBlock])
|
||||
{
|
||||
updateTrackingFactor();
|
||||
}
|
||||
|
||||
void PowerFollower::setSampleRate(float sampleRate) noexcept
|
||||
{
|
||||
if (sampleRate_ != sampleRate) {
|
||||
sampleRate_ = sampleRate;
|
||||
updateTrackingFactor();
|
||||
}
|
||||
}
|
||||
|
||||
void PowerFollower::setSamplesPerBlock(unsigned samplesPerBlock)
|
||||
{
|
||||
if (samplesPerBlock_ != samplesPerBlock) {
|
||||
tempBuffer_.reset(new float[samplesPerBlock]);
|
||||
samplesPerBlock_ = samplesPerBlock;
|
||||
}
|
||||
}
|
||||
|
||||
void PowerFollower::process(AudioSpan<float> buffer) noexcept
|
||||
{
|
||||
size_t numFrames = buffer.getNumFrames();
|
||||
if (numFrames == 0)
|
||||
return;
|
||||
|
||||
///
|
||||
constexpr size_t step = config::powerFollowerStep;
|
||||
float currentPower = currentPower_;
|
||||
float currentSum = currentSum_;
|
||||
size_t currentCount = currentCount_;
|
||||
|
||||
const float attackFactor = attackTrackingFactor_;
|
||||
const float releaseFactor = releaseTrackingFactor_;
|
||||
|
||||
///
|
||||
size_t index = 0;
|
||||
while (index < numFrames) {
|
||||
size_t blockSize = std::min(step - currentCount, numFrames - index);
|
||||
absl::Span<float> tempBuffer(tempBuffer_.get(), blockSize);
|
||||
|
||||
copy(buffer.getConstSpan(0).subspan(index, blockSize), tempBuffer);
|
||||
for (unsigned i = 1, n = buffer.getNumChannels(); i < n; ++i)
|
||||
add(buffer.getConstSpan(i).subspan(index, blockSize), tempBuffer);
|
||||
|
||||
currentSum += sumSquares<float>(tempBuffer);
|
||||
currentCount += blockSize;
|
||||
|
||||
if (currentCount == step) {
|
||||
const float meanPower = currentSum / step;
|
||||
currentPower = max(
|
||||
currentPower * attackFactor + meanPower * (1 - attackFactor),
|
||||
currentPower * releaseFactor + meanPower * (1 - releaseFactor));
|
||||
currentSum = 0;
|
||||
currentCount = 0;
|
||||
}
|
||||
|
||||
index += blockSize;
|
||||
}
|
||||
|
||||
///
|
||||
currentPower_ = currentPower;
|
||||
currentSum_ = currentSum;
|
||||
currentCount_ = currentCount;
|
||||
}
|
||||
|
||||
void PowerFollower::clear() noexcept
|
||||
{
|
||||
currentPower_ = 0;
|
||||
currentSum_ = 0;
|
||||
currentCount_ = 0;
|
||||
}
|
||||
|
||||
void PowerFollower::updateTrackingFactor() noexcept
|
||||
{
|
||||
// Protect the envelope follower against blowups
|
||||
attackTrackingFactor_ = std::exp(-1.0f / ((config::powerFollowerAttackTime / config::powerFollowerStep) * sampleRate_));
|
||||
releaseTrackingFactor_ = std::exp(-1.0f / ((config::powerFollowerReleaseTime / config::powerFollowerStep) * sampleRate_));
|
||||
}
|
||||
|
||||
} // namespace sfz
|
||||
39
src/sfizz/PowerFollower.h
Normal file
39
src/sfizz/PowerFollower.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#pragma once
|
||||
#include "AudioSpan.h"
|
||||
#include <memory>
|
||||
|
||||
namespace sfz {
|
||||
|
||||
class PowerFollower {
|
||||
public:
|
||||
PowerFollower();
|
||||
void setSampleRate(float sampleRate) noexcept;
|
||||
void setSamplesPerBlock(unsigned samplesPerBlock);
|
||||
void process(AudioSpan<float> buffer) noexcept;
|
||||
void clear() noexcept;
|
||||
float getAveragePower() const noexcept { return currentPower_; }
|
||||
|
||||
private:
|
||||
void updateTrackingFactor() noexcept;
|
||||
|
||||
private:
|
||||
float sampleRate_ {};
|
||||
unsigned samplesPerBlock_ {};
|
||||
|
||||
std::unique_ptr<float[]> tempBuffer_;
|
||||
|
||||
float attackTrackingFactor_ {};
|
||||
float releaseTrackingFactor_ {};
|
||||
|
||||
float currentPower_ {};
|
||||
float currentSum_ = 0;
|
||||
size_t currentCount_ = 0;
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
|
|
@ -39,7 +39,7 @@ struct SIMDDispatch {
|
|||
decltype(&cumsumScalar<T>) cumsum = &cumsumScalar<T>;
|
||||
decltype(&diffScalar<T>) diff = &diffScalar<T>;
|
||||
decltype(&meanScalar<T>) mean = &meanScalar<T>;
|
||||
decltype(&meanSquaredScalar<T>) meanSquared = &meanSquaredScalar<T>;
|
||||
decltype(&sumSquaresScalar<T>) sumSquares = &sumSquaresScalar<T>;
|
||||
decltype(&clampAllScalar<T>) clampAll = &clampAllScalar<T>;
|
||||
decltype(&allWithinScalar<T>) allWithin = &allWithinScalar<T>;
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ void SIMDDispatch<float>::setStatus(SIMDOps op, bool enable)
|
|||
SIMD_OP(cumsum)
|
||||
SIMD_OP(diff)
|
||||
SIMD_OP(mean)
|
||||
SIMD_OP(meanSquared)
|
||||
SIMD_OP(sumSquares)
|
||||
SIMD_OP(clampAll)
|
||||
SIMD_OP(allWithin)
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ void SIMDDispatch<float>::setStatus(SIMDOps op, bool enable)
|
|||
SIMD_OP(cumsum)
|
||||
SIMD_OP(diff)
|
||||
SIMD_OP(mean)
|
||||
SIMD_OP(meanSquared)
|
||||
SIMD_OP(sumSquares)
|
||||
SIMD_OP(clampAll)
|
||||
SIMD_OP(allWithin)
|
||||
}
|
||||
|
|
@ -163,7 +163,7 @@ void SIMDDispatch<float>::resetStatus()
|
|||
setStatus(SIMDOps::diff, false);
|
||||
setStatus(SIMDOps::sfzInterpolationCast, true);
|
||||
setStatus(SIMDOps::mean, false);
|
||||
setStatus(SIMDOps::meanSquared, false);
|
||||
setStatus(SIMDOps::sumSquares, false);
|
||||
setStatus(SIMDOps::upsampling, true);
|
||||
setStatus(SIMDOps::clampAll, false);
|
||||
setStatus(SIMDOps::allWithin, true);
|
||||
|
|
@ -292,9 +292,9 @@ float mean<float>(const float* vector, unsigned size) noexcept
|
|||
}
|
||||
|
||||
template <>
|
||||
float meanSquared<float>(const float* vector, unsigned size) noexcept
|
||||
float sumSquares<float>(const float* vector, unsigned size) noexcept
|
||||
{
|
||||
return simdDispatch<float>().meanSquared(vector, size);
|
||||
return simdDispatch<float>().sumSquares(vector, size);
|
||||
}
|
||||
|
||||
template <>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ enum class SIMDOps {
|
|||
diff,
|
||||
sfzInterpolationCast,
|
||||
mean,
|
||||
meanSquared,
|
||||
sumSquares,
|
||||
upsampling,
|
||||
clampAll,
|
||||
allWithin,
|
||||
|
|
@ -515,7 +515,7 @@ T mean(absl::Span<const T> vector) noexcept
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Computes the mean squared of a span
|
||||
* @brief Computes the sum of squares of a span
|
||||
*
|
||||
* @tparam T the underlying type
|
||||
* @tparam SIMD use the SIMD version or the scalar version
|
||||
|
|
@ -523,13 +523,33 @@ T mean(absl::Span<const T> vector) noexcept
|
|||
* @return T
|
||||
*/
|
||||
template <class T>
|
||||
T meanSquared(const T* vector, unsigned size) noexcept
|
||||
T sumSquares(const T* vector, unsigned size) noexcept
|
||||
{
|
||||
meanSquaredScalar(vector, size);
|
||||
return sumSquaresScalar(vector, size);
|
||||
}
|
||||
|
||||
template <>
|
||||
float meanSquared<float>(const float* vector, unsigned size) noexcept;
|
||||
float sumSquares<float>(const float* vector, unsigned size) noexcept;
|
||||
|
||||
template <class T>
|
||||
T sumSquares(absl::Span<const T> vector) noexcept
|
||||
{
|
||||
return sumSquares(vector.data(), vector.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Computes the mean squared of a span
|
||||
*
|
||||
* @tparam T the underlying type
|
||||
* @param vector
|
||||
* @return T
|
||||
*/
|
||||
template <class T>
|
||||
T meanSquared(const T* vector, unsigned size) noexcept
|
||||
{
|
||||
T sum = sumSquares(vector, size);
|
||||
return sum / size;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T meanSquared(absl::Span<const T> vector) noexcept
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ sfz::Voice::Voice(int voiceNumber, sfz::Resources& resources)
|
|||
|
||||
gainSmoother.setSmoothing(config::gainSmoothing, sampleRate);
|
||||
xfadeSmoother.setSmoothing(config::xfadeSmoothing, sampleRate);
|
||||
|
||||
for (auto & filter : channelEnvelopeFilters)
|
||||
filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate));
|
||||
}
|
||||
|
||||
sfz::Voice::~Voice()
|
||||
|
|
@ -252,20 +249,19 @@ void sfz::Voice::setSampleRate(float sampleRate) noexcept
|
|||
gainSmoother.setSmoothing(config::gainSmoothing, sampleRate);
|
||||
xfadeSmoother.setSmoothing(config::xfadeSmoothing, sampleRate);
|
||||
|
||||
for (auto & filter : channelEnvelopeFilters)
|
||||
filter.setGain(vaGain(config::filteredEnvelopeCutoff, sampleRate));
|
||||
|
||||
for (WavetableOscillator& osc : waveOscillators)
|
||||
osc.init(sampleRate);
|
||||
|
||||
for (auto& lfo : lfos)
|
||||
lfo->setSampleRate(sampleRate);
|
||||
|
||||
powerFollower.setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||
{
|
||||
this->samplesPerBlock = samplesPerBlock;
|
||||
this->minEnvelopeDelay = samplesPerBlock / 2;
|
||||
powerFollower.setSamplesPerBlock(samplesPerBlock);
|
||||
}
|
||||
|
||||
void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
|
||||
|
|
@ -301,7 +297,7 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
|
|||
if (!egEnvelope.isSmoothing())
|
||||
switchState(State::cleanMeUp);
|
||||
|
||||
updateChannelPowers(buffer);
|
||||
powerFollower.process(buffer);
|
||||
|
||||
age += buffer.getNumFrames();
|
||||
if (triggerDelay) {
|
||||
|
|
@ -739,11 +735,7 @@ void sfz::Voice::reset() noexcept
|
|||
floatPositionOffset = 0.0f;
|
||||
noteIsOff = false;
|
||||
|
||||
for (auto& f : channelEnvelopeFilters)
|
||||
f.reset();
|
||||
|
||||
for (auto& p : smoothedChannelEnvelopes)
|
||||
p = 0.0f;
|
||||
powerFollower.clear();
|
||||
|
||||
filters.clear();
|
||||
equalizers.clear();
|
||||
|
|
@ -773,9 +765,9 @@ void sfz::Voice::removeVoiceFromRing() noexcept
|
|||
nextSisterVoice = this;
|
||||
}
|
||||
|
||||
float sfz::Voice::getAverageEnvelope() const noexcept
|
||||
float sfz::Voice::getAveragePower() const noexcept
|
||||
{
|
||||
return max(smoothedChannelEnvelopes[0], smoothedChannelEnvelopes[1]);
|
||||
return powerFollower.getAveragePower();
|
||||
}
|
||||
|
||||
bool sfz::Voice::releasedOrFree() const noexcept
|
||||
|
|
@ -870,22 +862,6 @@ void sfz::Voice::setupOscillatorUnison()
|
|||
#endif
|
||||
}
|
||||
|
||||
void sfz::Voice::updateChannelPowers(AudioSpan<float> buffer)
|
||||
{
|
||||
assert(smoothedChannelEnvelopes.size() == channelEnvelopeFilters.size());
|
||||
assert(buffer.getNumChannels() <= channelEnvelopeFilters.size());
|
||||
if (buffer.getNumFrames() == 0)
|
||||
return;
|
||||
|
||||
for (unsigned i = 0; i < smoothedChannelEnvelopes.size(); ++i) {
|
||||
const auto input = buffer.getConstSpan(i);
|
||||
for (unsigned s = 0; s < buffer.getNumFrames(); ++s)
|
||||
smoothedChannelEnvelopes[i] =
|
||||
channelEnvelopeFilters[i].tickLowpass(std::abs(input[s]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void sfz::Voice::switchState(State s)
|
||||
{
|
||||
if (s != state) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include "AudioSpan.h"
|
||||
#include "LeakDetector.h"
|
||||
#include "OnePoleFilter.h"
|
||||
#include "PowerFollower.h"
|
||||
#include "NumericId.h"
|
||||
#include "absl/types/span.h"
|
||||
#include <memory>
|
||||
|
|
@ -261,7 +262,7 @@ public:
|
|||
*
|
||||
* @return float
|
||||
*/
|
||||
float getAverageEnvelope() const noexcept;
|
||||
float getAveragePower() const noexcept;
|
||||
/**
|
||||
* @brief Get the position of the voice in the source, in samples
|
||||
*
|
||||
|
|
@ -450,7 +451,6 @@ private:
|
|||
FilePromisePtr currentPromise { nullptr };
|
||||
|
||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||
int minEnvelopeDelay { config::defaultSamplesPerBlock / 2 };
|
||||
float sampleRate { config::defaultSampleRate };
|
||||
|
||||
Resources& resources;
|
||||
|
|
@ -486,10 +486,8 @@ private:
|
|||
Smoother xfadeSmoother;
|
||||
void resetSmoothers() noexcept;
|
||||
|
||||
std::array<OnePoleFilter<float>, 2> channelEnvelopeFilters;
|
||||
std::array<float, 2> smoothedChannelEnvelopes;
|
||||
PowerFollower powerFollower;
|
||||
|
||||
HistoricalBuffer<float> powerHistory { config::powerHistoryLength };
|
||||
LEAK_DETECTOR(Voice);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ sfz::Voice* sfz::VoiceStealing::steal(absl::Span<sfz::Voice*> voices) noexcept
|
|||
// Start of the voice stealing algorithm
|
||||
absl::c_stable_sort(voices, voiceOrdering);
|
||||
|
||||
const auto sumEnvelope = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) {
|
||||
return sum + v->getAverageEnvelope();
|
||||
const auto sumPower = absl::c_accumulate(voices, 0.0f, [](float sum, const Voice* v) {
|
||||
return sum + v->getAveragePower();
|
||||
});
|
||||
// We are checking the envelope to try and kill voices with relative low contribution
|
||||
// We are checking the power to try and kill voices with relative low contribution
|
||||
// to the output compared to the rest.
|
||||
const auto envThreshold = sumEnvelope
|
||||
/ static_cast<float>(voices.size()) * config::stealingEnvelopeCoeff;
|
||||
const auto powerThreshold = sumPower
|
||||
/ static_cast<float>(voices.size()) * config::stealingPowerCoeff;
|
||||
// We are checking the age so that voices have the time to build up attack
|
||||
// This is not perfect because pad-type voices will take a long time to output
|
||||
// their sound, but it's reasonable for sounds with a quick attack and longer
|
||||
|
|
@ -37,12 +37,12 @@ sfz::Voice* sfz::VoiceStealing::steal(absl::Span<sfz::Voice*> voices) noexcept
|
|||
break;
|
||||
}
|
||||
|
||||
float maxEnvelope { 0.0f };
|
||||
float maxPower { 0.0f };
|
||||
SisterVoiceRing::applyToRing(ref, [&](Voice* v) {
|
||||
maxEnvelope = max(maxEnvelope, v->getAverageEnvelope());
|
||||
maxPower = max(maxPower, v->getAveragePower());
|
||||
});
|
||||
|
||||
if (maxEnvelope < envThreshold) {
|
||||
if (maxPower < powerThreshold) {
|
||||
returnedVoice = ref;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ float meanSSE(const float* vector, unsigned size) noexcept
|
|||
return result / static_cast<float>(size);
|
||||
}
|
||||
|
||||
float meanSquaredSSE(const float* vector, unsigned size) noexcept
|
||||
float sumSquaresSSE(const float* vector, unsigned size) noexcept
|
||||
{
|
||||
const auto sentinel = vector + size;
|
||||
|
||||
|
|
@ -404,7 +404,7 @@ float meanSquaredSSE(const float* vector, unsigned size) noexcept
|
|||
vector++;
|
||||
}
|
||||
|
||||
return result / static_cast<float>(size);
|
||||
return result;
|
||||
}
|
||||
|
||||
void cumsumSSE(const float* input, float* output, unsigned size) noexcept
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ void subtractSSE(const float* input, float* output, unsigned size) noexcept;
|
|||
void subtract1SSE(float value, float* output, unsigned size) noexcept;
|
||||
void copySSE(const float* input, float* output, unsigned size) noexcept;
|
||||
float meanSSE(const float* vector, unsigned size) noexcept;
|
||||
float meanSquaredSSE(const float* vector, unsigned size) noexcept;
|
||||
float sumSquaresSSE(const float* vector, unsigned size) noexcept;
|
||||
void cumsumSSE(const float* input, float* output, unsigned size) noexcept;
|
||||
void diffSSE(const float* input, float* output, unsigned size) noexcept;
|
||||
void clampAllSSE(float* input, float low, float high, unsigned size) noexcept;
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ T meanScalar(const T* vector, unsigned size) noexcept
|
|||
}
|
||||
|
||||
template <class T>
|
||||
T meanSquaredScalar(const T* vector, unsigned size) noexcept
|
||||
T sumSquaresScalar(const T* vector, unsigned size) noexcept
|
||||
{
|
||||
T result{ 0.0 };
|
||||
if (size == 0)
|
||||
|
|
@ -154,7 +154,7 @@ T meanSquaredScalar(const T* vector, unsigned size) noexcept
|
|||
vector++;
|
||||
}
|
||||
|
||||
return result / static_cast<T>(size);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
|
|
|||
|
|
@ -690,9 +690,9 @@ TEST_CASE("[Helpers] Mean (SIMD vs scalar)")
|
|||
TEST_CASE("[Helpers] Mean Squared")
|
||||
{
|
||||
std::array<float, 10> input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f };
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::meanSquared, false);
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::sumSquares, false);
|
||||
REQUIRE(sfz::meanSquared<float>(input) == 38.5f);
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::meanSquared, true);
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::sumSquares, true);
|
||||
REQUIRE(sfz::meanSquared<float>(input) == 38.5f);
|
||||
}
|
||||
|
||||
|
|
@ -700,9 +700,9 @@ TEST_CASE("[Helpers] Mean Squared (SIMD vs scalar)")
|
|||
{
|
||||
std::vector<float> input(medBufferSize);
|
||||
absl::c_iota(input, 0.0f);
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::meanSquared, false);
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::sumSquares, false);
|
||||
auto scalarResult = sfz::meanSquared<float>(input);
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::meanSquared, true);
|
||||
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::sumSquares, true);
|
||||
auto simdResult = sfz::meanSquared<float>(input);
|
||||
REQUIRE( scalarResult == Approx(simdResult).margin(1e-3) );
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue