-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathExpSmootherCascade.hpp
131 lines (109 loc) · 4.82 KB
/
ExpSmootherCascade.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*******************************************************************************
*
* Mono-input, mono-output exponential smoother via cascaded one-pole filters
* with 2π*tau time constant.
*
* Copyright (c) 2022 Dario Sanfilippo - [email protected]
*
* ****************************************************************************/
#pragma once
#include <cmath>
#include <algorithm>
#include <limits>
template<size_t stages, typename real>
class ExpSmootherCascade {
static_assert(stages > 0, "The ExpSmootherCascade class expects one or more stages.");
private:
/* Coefficient correction factor to maintain consistent attack and
* decay rates when cascading multiple one-pole sections. */
const real coeffCorrection =
1.0 / std::sqrt(std::pow(2.0, 1.0 / real(stages)) - 1.0);
const real epsilon = std::numeric_limits<real>::epsilon();
real SR = 48000.0; // Samplerate as a float variable for later calculations.
real T = 1.0 / SR; // Sampling period.
const real twoPiC = 2.0 * M_PI * coeffCorrection;
real twoPiCT = twoPiC * T;
real attTime = .001; // Attack time in seconds.
real relTime = .01; // Release time in seconds.
real attCoeff = std::exp(-twoPiCT / attTime);
real relCoeff = std::exp(-twoPiCT / relTime);
/* We store the coefficients in an array for efficient Boolean
* fetching without branching. */
real coeff[2] = {
relCoeff,
attCoeff
};
real output[stages] = { .0 };
public:
void SetSR(real _SR);
void SetAttTime(real _attTime);
void SetRelTime(real _relTime);
void Reset() { memset(output, 0, stages); };
void Process(real* xVec, real* yVec, size_t vecLen);
ExpSmootherCascade() { };
ExpSmootherCascade(real _SR, real _attTime, real _relTime);
};
template<size_t stages, typename real>
void ExpSmootherCascade<stages, real>::SetSR(real _SR) {
SR = std::max<real>(1.0, _SR);
T = 1.0 / SR;
twoPiCT = twoPiC * T;
}
/* We store attack and relelease phases coefficients in an array for look-up
* table selection using a Boolean index, which is faster than two
* multiplications by bools. */
template<size_t stages, typename real>
void ExpSmootherCascade<stages, real>::SetAttTime(real _attTime) {
attTime = std::max<real>(epsilon, _attTime);
attCoeff = std::exp(-twoPiCT / attTime);
coeff[1] = attCoeff;
}
template<size_t stages, typename real>
void ExpSmootherCascade<stages, real>::SetRelTime(real _relTime) {
relTime = std::max<real>(epsilon, _relTime);
relCoeff = std::exp(-twoPiCT / relTime);
coeff[0] = relCoeff;
}
/* Given input and output vectors, the function processes a block of vecLen
* samples of the input signal and stores it in the output vector. */
template<size_t stages, typename real>
void ExpSmootherCascade<stages, real>::Process(real* xVec, real* yVec, size_t vecLen) {
for (size_t n = 0; n < vecLen; n++) { // Level-0 for-loop.
/* Outside of the inner for-loop, we assign the input vector sample
* to an auxiliary variable, which will be the input to the first
* section. */
real input = xVec[n];
/* Compute M series exponential smoothers in an inner for-loop. */
for (size_t stage = 0; stage < stages; stage++) { // Level-1 for-loop.
/* Determine whether the system is in the attack or release
* phase, namely checking if the input is greater than the
* output. */
bool isAttackPhase = input > output[stage];
/* Compute the output of the one-pole section "stage" using the
* corresponding attack or release coefficient. */
output[stage] =
input + coeff[isAttackPhase] * (output[stage] - input);
/* We can now update the input to the next section with the
* output of the current one. */
input = output[stage];
} // End of level-1 for-loop.
/* Finally, we assign the output of the last section to the output
* vector sample. */
yVec[n] = output[stages - 1];
} // End of level-0 for-loop.
}
/* We store attack and relelease phases coefficients in an array for look-up
* table selection using a Boolean index, which is faster than two
* multiplications by bools. */
template<size_t stages, typename real>
ExpSmootherCascade<stages, real>::ExpSmootherCascade(real _SR, real _attTime, real _relTime) {
SR = std::max<real>(1.0, _SR);
T = 1.0 / SR;
twoPiCT = twoPiC * T;
attTime = std::max<real>(epsilon, _attTime);
relTime = std::max<real>(epsilon, _relTime);
attCoeff = std::exp(-twoPiCT / attTime);
relCoeff = std::exp(-twoPiCT / relTime);
coeff[0] = relCoeff;
coeff[1] = attCoeff;
}