-
Notifications
You must be signed in to change notification settings - Fork 3
/
PeakHoldCascade.hpp
127 lines (107 loc) · 5.13 KB
/
PeakHoldCascade.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
/*******************************************************************************
*
* Mono-input, mono-output cascaded peak-holder sections.
*
* This function inspects the inputs absolute peaks and it holds them
* for "holdtime" seconds (approximately) if they are smaller than the
* currently held peak, otherwise, the inputs absolute peak is output.
*
* This architecture cascades M peak-hold sections, each of them with a hold
* time that is 1 / M of the full hold period. This allows for secondary peaks
* occurring after holdTime / M to also be detected.
*
* Copyright (c) 2022 Dario Sanfilippo - [email protected]
*
* ****************************************************************************/
#pragma once
#include <cmath>
#include <algorithm>
template<size_t stages, typename real>
class PeakHoldCascade {
static_assert(stages > 0, "The PeakHoldCascade class expects one or more stages.");
private:
real SR = 48000.0; // Samplerate as a float variable for later calculations.
real holdTime = .001; // Hold time in seconds.
const real oneOverStages = 1.0 / real(stages);
/* We approximate the given hold time in seconds by rounding the samples
* conversion to the nearest int. Note that the hold time variations are
* constrained to steps of "stages" samples, which is the number of cascaded
* sections. */
size_t holdTimeSamples = std::rint(holdTime * oneOverStages * SR);
size_t timer[stages] = { 0 };
real output[stages] = { .0 };
public:
void SetSR(real _SR);
void SetHoldTime(real _holdTime);
void Reset() {
memset(timer, 0, stages);
memset(output, 0, stages);
};
void Process(real* xVec, real* yVec, size_t vecLen);
PeakHoldCascade() { };
PeakHoldCascade(real _SR, real _holdTime);
};
template<size_t stages, typename real>
void PeakHoldCascade<stages, real>::SetSR(real _SR) {
SR = std::max<real>(1.0, _SR);
holdTimeSamples = std::rint(holdTime * oneOverStages * SR);
}
template<size_t stages, typename real>
void PeakHoldCascade<stages, real>::SetHoldTime(real _holdTime) {
holdTime = std::max<real>(.0, _holdTime);
holdTimeSamples = std::rint(holdTime * oneOverStages * SR);
}
/* This function computes a peak-holder with a given period P as a combination
* of "stages" series peak-holder sections with an hold period of P / stages.
* 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 PeakHoldCascade<stages, real>::Process(real* xVec, real* yVec, size_t vecLen) {
for (size_t n = 0; n < vecLen; n++) { // Level-0 for-loop.
/* Outside of th einner for-loop, we assign the absolute value
* of the input vector sample to an auxiliary variable, which
* will be the input to the first section. */
real input = std::fabs(xVec[n]);
/* Compute a series of peak-holders in an inner for-loop. */
for (size_t stage = 0; stage < stages; stage++) { // level-1 for-loop
/* Compute the necessary Boolean conditions to determine whether
* the system is in a hold or release phase. A new peak is detected
* when the input absolute value is >= to the output of the
* peak-holder. Each time a new peak is detected, a timer is reset.
* The peak-holder is in a release phase when a new peak is
* detected or the time is out, in which case the input absolute
* value becomes the input of the system. Otherwise, the system
* will hold the out value until a release phase occurs. */
bool isNewPeak = input >= output[stage];
bool isTimeOut = timer[stage] >= holdTimeSamples;
bool release = isNewPeak || isTimeOut;
/* Following a branchless programming paradigm, we compute the paths
* for the variables with bifurcation and assign the results using
* Boolean array-fetching, which is faster than two multiplications
* by bools. */
size_t timerPaths[2] = {
timer[stage] + 1,
0
};
timer[stage] = timerPaths[release];
real outPaths[2] = {
output[stage],
input
};
output[stage] = outPaths[release];
/* We can now update the auxiliary variable with the output of
* the current peak-holder section, which will be the input for
* the next section. */
input = output[stage];
} // end of level-1 for-loop
/* Finally, we can assign the output of the last stage to the output
* vector sample. */
yVec[n] = output[stages - 1];
} // End of level-0 for-loop.
}
template<size_t stages, typename real>
PeakHoldCascade<stages, real>::PeakHoldCascade(real _SR, real _holdTime) {
SR = std::max<real>(1.0, _SR);
holdTime = _holdTime;
holdTimeSamples = std::rint(holdTime * oneOverStages * SR);
}