-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathEMA.h
More file actions
executable file
·162 lines (141 loc) · 4.84 KB
/
Copy pathEMA.h
File metadata and controls
executable file
·162 lines (141 loc) · 4.84 KB
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*
* EMA.h
*
* A simple but efficient low pass filter based on the exponential moving average.
* Single-pole IIR filter with minimal CPU overhead.
*
* by Andrew R. Brown 2025
*
* This file is part of the M16 audio library. Relies on M16.h
*
* M16 is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
*/
#ifndef EMA_H_
#define EMA_H_
#include <atomic>
#include <stdint.h>
class EMA {
public:
/** Default constructor */
EMA() = default;
/** Constructor with initial alpha
* @param newAlpha EMA coefficient 0.0-1.0: low values smooth more, 1.0 is bypass
*/
explicit EMA(float newAlpha) {
setCoefficient((int32_t)(clamp01(newAlpha) * 1024.0f));
}
/** Set resonance - no-op for compatibility with other M16 filters */
inline void setRes(float resonance) { (void)resonance; }
/** Reset filter state to zero - useful for consistent attack transients */
inline void reset() {
outPrev = 0;
inPrev = 0;
}
/** Set cutoff frequency in Hz
* @param freqVal Frequency 40-10000 Hz
*/
inline void setFreq(int32_t freq_val) {
int32_t clampedFreq = max((int32_t)40, min((int32_t)10000, freq_val));
if (coefficientSource == SOURCE_FREQ && f == clampedFreq) return;
f = clampedFreq;
float cutVal = f * 0.0001;
alpha_val.store(
(int16_t)max((int32_t)10,
(int32_t)((1.0f - pow((1.0f - cutVal), 0.3f)) * 1024)),
std::memory_order_relaxed);
coefficientSource = SOURCE_FREQ;
}
/** @return Current cutoff frequency in Hz */
inline float getFreq() const {
return f;
}
/** Set cutoff as normalized value
* @param cutoffVal 0.0-1.0 maps to approx 40-10000 Hz
*/
inline void setCutoff(float cutoff_val) {
float cutVal = clamp01(cutoff_val);
if (coefficientSource == SOURCE_CUTOFF && cutVal == normalizedCutoff) return;
normalizedCutoff = cutVal;
f = max(40.0f, cutVal * 10000.0f);
alpha_val.store(
(int16_t)max((int32_t)10,
(int32_t)((1.0f - pow((1.0f - cutVal), 0.2f)) * 1024.0f)),
std::memory_order_relaxed);
coefficientSource = SOURCE_CUTOFF;
}
/** Return cutoff as normalized value */
inline float getCutoff() const {
return normalizedCutoff;
}
/** Convert a normalized cutoff to EMA's integer coefficient.
* Intended for building a lookup table during setup(), not for audio-rate use.
*/
static inline int16_t coefficientForCutoff(float cutoff_val) {
float cutVal = clamp01(cutoff_val);
return (int16_t)max((int32_t)10,
min((int32_t)1024,
(int32_t)((1.0f - pow((1.0f - cutVal), 0.2f)) * 1024.0f)));
}
/** Set a precomputed filter coefficient without pow().
* @param coefficient 10-1024, normally from coefficientForCutoff()
*/
inline void setCoefficient(int32_t coefficient) {
int16_t clampedCoefficient = (int16_t)max((int32_t)10,
min((int32_t)1024, coefficient));
if (coefficientSource == SOURCE_DIRECT &&
alpha_val.load(std::memory_order_relaxed) == clampedCoefficient) return;
alpha_val.store(clampedCoefficient, std::memory_order_relaxed);
coefficientSource = SOURCE_DIRECT;
}
/** @return Active fixed-point coefficient in the range 10-1024. */
inline int16_t getCoefficient() const {
return alpha_val.load(std::memory_order_relaxed);
}
/** Calculate next lowpass filter sample
* @param input Audio sample
* @return Filtered sample
*/
inline int16_t nextLPF(int32_t input) {
// One-pole recurrence. alpha_val == 1024 naturally produces bypass.
int16_t coefficient = alpha_val.load(std::memory_order_relaxed);
outPrev += ((input - outPrev) * coefficient) >> 10;
return outPrev;
}
/** Calculate next filter sample (alias for nextLPF)
* @param input Audio sample
* @return Filtered sample
*/
inline int16_t next(int32_t input) {
return nextLPF(input);
}
/** Calculate next highpass filter sample
* @param input Audio sample
* @return Filtered sample
*/
inline int16_t nextHPF(int32_t input) {
int16_t coefficient = alpha_val.load(std::memory_order_relaxed);
outPrev = (((2048 - coefficient) * (input - inPrev)) >> 11) +
(((1024 - coefficient) * outPrev) >> 10);
inPrev = input;
return clip16(outPrev);
}
private:
static inline float clamp01(float value) {
return max(0.0f, min(1.0f, value));
}
enum : uint8_t {
SOURCE_DEFAULT,
SOURCE_FREQ,
SOURCE_CUTOFF,
SOURCE_DIRECT
};
// Filter history belongs to one audio stream/core. The coefficient is atomic
// so control code on another core may safely call the coefficient setters.
int32_t outPrev = 0;
int32_t inPrev = 0;
float f = 10000.0f;
float normalizedCutoff = 1.0f;
std::atomic<int16_t> alpha_val{1024};
uint8_t coefficientSource = SOURCE_DEFAULT;
};
#endif /* EMA_H_ */