dspx 1.2.4 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -78
- package/binding.gyp +10 -0
- package/dist/FilterBankDesign.d.ts +233 -0
- package/dist/FilterBankDesign.d.ts.map +1 -0
- package/dist/FilterBankDesign.js +247 -0
- package/dist/FilterBankDesign.js.map +1 -0
- package/dist/advanced-dsp.d.ts +6 -6
- package/dist/advanced-dsp.d.ts.map +1 -1
- package/dist/advanced-dsp.js +35 -12
- package/dist/advanced-dsp.js.map +1 -1
- package/dist/backends.d.ts +0 -103
- package/dist/backends.d.ts.map +1 -1
- package/dist/backends.js +0 -217
- package/dist/backends.js.map +1 -1
- package/dist/bindings.d.ts +270 -17
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +566 -43
- package/dist/bindings.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +67 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +38 -8
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +84 -26
- package/dist/utils.js.map +1 -1
- package/package.json +1 -2
- package/prebuilds/win32-x64/dspx.node +0 -0
- package/scripts/add-dispose-to-tests.js +145 -0
- package/src/native/DspPipeline.cc +699 -126
- package/src/native/DspPipeline.h +13 -0
- package/src/native/FilterBankDesignBindings.cc +241 -0
- package/src/native/IDspStage.h +24 -0
- package/src/native/UtilityBindings.cc +130 -0
- package/src/native/adapters/AmplifyStage.h +148 -0
- package/src/native/adapters/ClipDetectionStage.h +15 -4
- package/src/native/adapters/ConvolutionStage.h +101 -0
- package/src/native/adapters/CumulativeMovingAverageStage.h +264 -0
- package/src/native/adapters/DecimatorStage.h +80 -0
- package/src/native/adapters/DifferentiatorStage.h +13 -0
- package/src/native/adapters/ExponentialMovingAverageStage.h +290 -0
- package/src/native/adapters/FilterBankStage.cc +336 -0
- package/src/native/adapters/FilterBankStage.h +170 -0
- package/src/native/adapters/FilterStage.cc +122 -0
- package/src/native/adapters/FilterStage.h +4 -0
- package/src/native/adapters/HilbertEnvelopeStage.h +55 -0
- package/src/native/adapters/IntegratorStage.h +15 -0
- package/src/native/adapters/InterpolatorStage.h +51 -0
- package/src/native/adapters/LinearRegressionStage.h +40 -0
- package/src/native/adapters/LmsStage.h +63 -0
- package/src/native/adapters/MeanAbsoluteValueStage.h +76 -0
- package/src/native/adapters/MovingAverageStage.h +119 -0
- package/src/native/adapters/PeakDetectionStage.h +53 -0
- package/src/native/adapters/RectifyStage.h +14 -0
- package/src/native/adapters/ResamplerStage.h +67 -0
- package/src/native/adapters/RlsStage.h +76 -0
- package/src/native/adapters/RmsStage.h +72 -0
- package/src/native/adapters/SnrStage.h +45 -0
- package/src/native/adapters/SquareStage.h +78 -0
- package/src/native/adapters/SscStage.h +65 -0
- package/src/native/adapters/StftStage.h +62 -0
- package/src/native/adapters/VarianceStage.h +59 -0
- package/src/native/adapters/WampStage.h +59 -0
- package/src/native/adapters/WaveformLengthStage.h +51 -0
- package/src/native/adapters/ZScoreNormalizeStage.h +64 -0
- package/src/native/core/CumulativeMovingAverageFilter.h +123 -0
- package/src/native/core/ExponentialMovingAverageFilter.h +129 -0
- package/src/native/core/FilterBankDesign.h +266 -0
- package/src/native/core/Policies.h +124 -0
- package/src/native/utils/CircularBufferArray.cc +2 -1
- package/src/native/utils/SimdOps.h +67 -0
- package/src/native/utils/Toon.h +195 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include "../IDspStage.h"
|
|
4
4
|
#include "../core/SscFilter.h"
|
|
5
5
|
#include "../utils/NapiUtils.h"
|
|
6
|
+
#include "../utils/Toon.h"
|
|
6
7
|
#include <vector>
|
|
7
8
|
#include <string>
|
|
8
9
|
#include <stdexcept>
|
|
@@ -112,6 +113,70 @@ namespace dsp::adapters
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
void serializeToon(dsp::toon::Serializer &s) const override
|
|
117
|
+
{
|
|
118
|
+
s.writeInt32(static_cast<int32_t>(m_window_size));
|
|
119
|
+
s.writeFloat(m_threshold);
|
|
120
|
+
s.writeInt32(static_cast<int32_t>(m_filters.size()));
|
|
121
|
+
|
|
122
|
+
for (const auto &filter : m_filters)
|
|
123
|
+
{
|
|
124
|
+
auto [internalState, filterState] = filter.getState();
|
|
125
|
+
auto [bufferData, runningCount] = internalState;
|
|
126
|
+
|
|
127
|
+
// Serialize bool vector as byte array
|
|
128
|
+
s.writeInt32(static_cast<int32_t>(bufferData.size()));
|
|
129
|
+
for (bool b : bufferData)
|
|
130
|
+
{
|
|
131
|
+
s.writeBool(b);
|
|
132
|
+
}
|
|
133
|
+
s.writeInt32(static_cast<int32_t>(runningCount));
|
|
134
|
+
s.writeFloat(filterState.sample_minus_1);
|
|
135
|
+
s.writeFloat(filterState.sample_minus_2);
|
|
136
|
+
s.writeInt32(filterState.init_count);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
void deserializeToon(dsp::toon::Deserializer &d) override
|
|
141
|
+
{
|
|
142
|
+
size_t windowSize = static_cast<size_t>(d.readInt32());
|
|
143
|
+
float threshold = d.readFloat();
|
|
144
|
+
if (windowSize != m_window_size || threshold != m_threshold)
|
|
145
|
+
{
|
|
146
|
+
throw std::runtime_error("SSC TOON: parameter mismatch");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
int32_t numChannels = d.readInt32();
|
|
150
|
+
m_filters.clear();
|
|
151
|
+
for (int32_t i = 0; i < numChannels; ++i)
|
|
152
|
+
{
|
|
153
|
+
m_filters.emplace_back(m_window_size, m_threshold);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (auto &filter : m_filters)
|
|
157
|
+
{
|
|
158
|
+
int32_t bufSize = d.readInt32();
|
|
159
|
+
std::vector<bool> bufferData(bufSize);
|
|
160
|
+
for (int32_t j = 0; j < bufSize; ++j)
|
|
161
|
+
{
|
|
162
|
+
bufferData[j] = d.readBool();
|
|
163
|
+
}
|
|
164
|
+
size_t runningCount = static_cast<size_t>(d.readInt32());
|
|
165
|
+
|
|
166
|
+
dsp::core::SscFilter<float>::SscFilterState filterState;
|
|
167
|
+
filterState.sample_minus_1 = d.readFloat();
|
|
168
|
+
filterState.sample_minus_2 = d.readFloat();
|
|
169
|
+
filterState.init_count = d.readInt32();
|
|
170
|
+
|
|
171
|
+
if (!dsp::core::CounterPolicy::validateState(runningCount, bufferData))
|
|
172
|
+
{
|
|
173
|
+
throw std::runtime_error("SSC TOON: validation failed");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
filter.setState(bufferData, runningCount, filterState);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
115
180
|
private:
|
|
116
181
|
size_t m_window_size;
|
|
117
182
|
float m_threshold;
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
#include "../core/FftEngine.h"
|
|
39
39
|
#include "../utils/CircularBufferArray.h"
|
|
40
40
|
#include "../utils/SimdOps.h"
|
|
41
|
+
#include "../utils/Toon.h"
|
|
41
42
|
#include <vector>
|
|
42
43
|
#include <complex>
|
|
43
44
|
#include <memory>
|
|
@@ -276,6 +277,67 @@ namespace dsp::adapters
|
|
|
276
277
|
std::fill(m_samples_since_output.begin(), m_samples_since_output.end(), 0);
|
|
277
278
|
}
|
|
278
279
|
|
|
280
|
+
void serializeToon(dsp::toon::Serializer &s) const override
|
|
281
|
+
{
|
|
282
|
+
s.writeInt32(static_cast<int32_t>(m_window_size));
|
|
283
|
+
s.writeInt32(static_cast<int32_t>(m_hop_size));
|
|
284
|
+
s.writeString(m_method);
|
|
285
|
+
s.writeString(m_type);
|
|
286
|
+
s.writeBool(m_forward);
|
|
287
|
+
s.writeString(m_output);
|
|
288
|
+
s.writeString(m_window_type);
|
|
289
|
+
s.writeInt32(static_cast<int32_t>(m_channel_buffers.size()));
|
|
290
|
+
|
|
291
|
+
// Serialize each channel's buffer and counter
|
|
292
|
+
for (size_t i = 0; i < m_channel_buffers.size(); ++i)
|
|
293
|
+
{
|
|
294
|
+
std::vector<float> buffer_data = m_channel_buffers[i].toVector();
|
|
295
|
+
s.writeFloatArray(buffer_data);
|
|
296
|
+
s.writeInt32(static_cast<int32_t>(m_samples_since_output[i]));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
void deserializeToon(dsp::toon::Deserializer &d) override
|
|
301
|
+
{
|
|
302
|
+
size_t windowSize = static_cast<size_t>(d.readInt32());
|
|
303
|
+
size_t hopSize = static_cast<size_t>(d.readInt32());
|
|
304
|
+
std::string method = d.readString();
|
|
305
|
+
std::string type = d.readString();
|
|
306
|
+
bool forward = d.readBool();
|
|
307
|
+
std::string output = d.readString();
|
|
308
|
+
std::string windowType = d.readString();
|
|
309
|
+
|
|
310
|
+
if (windowSize != m_window_size || hopSize != m_hop_size)
|
|
311
|
+
{
|
|
312
|
+
throw std::runtime_error("STFT TOON: window/hop size mismatch");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
uint32_t numChannels = d.readInt32();
|
|
316
|
+
|
|
317
|
+
// Recreate channel buffers
|
|
318
|
+
m_channel_buffers.clear();
|
|
319
|
+
m_samples_since_output.clear();
|
|
320
|
+
|
|
321
|
+
for (uint32_t i = 0; i < numChannels; ++i)
|
|
322
|
+
{
|
|
323
|
+
m_channel_buffers.emplace_back(m_window_size);
|
|
324
|
+
m_samples_since_output.push_back(0);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Restore each channel's state
|
|
328
|
+
for (uint32_t i = 0; i < numChannels; ++i)
|
|
329
|
+
{
|
|
330
|
+
std::vector<float> buffer_data = d.readFloatArray();
|
|
331
|
+
|
|
332
|
+
for (float value : buffer_data)
|
|
333
|
+
{
|
|
334
|
+
m_channel_buffers[i].push(value);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
m_samples_since_output[i] = static_cast<size_t>(d.readInt32());
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
279
341
|
private:
|
|
280
342
|
/**
|
|
281
343
|
* Generate window function coefficients
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include "../IDspStage.h"
|
|
4
4
|
#include "../core/MovingVarianceFilter.h"
|
|
5
|
+
#include "../utils/Toon.h"
|
|
5
6
|
#include <vector>
|
|
6
7
|
#include <string>
|
|
7
8
|
#include <stdexcept>
|
|
@@ -190,6 +191,64 @@ namespace dsp::adapters
|
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
193
|
|
|
194
|
+
void serializeToon(dsp::toon::Serializer &s) const override
|
|
195
|
+
{
|
|
196
|
+
s.writeString(m_mode == VarianceMode::Moving ? "moving" : "batch");
|
|
197
|
+
|
|
198
|
+
if (m_mode == VarianceMode::Moving)
|
|
199
|
+
{
|
|
200
|
+
s.writeInt32(static_cast<int32_t>(m_window_size));
|
|
201
|
+
s.writeDouble(m_window_duration_ms);
|
|
202
|
+
s.writeBool(m_is_initialized);
|
|
203
|
+
|
|
204
|
+
s.writeInt32(static_cast<int32_t>(m_filters.size()));
|
|
205
|
+
for (const auto &filter : m_filters)
|
|
206
|
+
{
|
|
207
|
+
auto [bufferData, sums] = filter.getState();
|
|
208
|
+
auto [runningSum, runningSumOfSquares] = sums;
|
|
209
|
+
s.writeFloatArray(bufferData);
|
|
210
|
+
s.writeFloat(runningSum);
|
|
211
|
+
s.writeFloat(runningSumOfSquares);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
void deserializeToon(dsp::toon::Deserializer &d) override
|
|
217
|
+
{
|
|
218
|
+
std::string modeStr = d.readString();
|
|
219
|
+
VarianceMode newMode = (modeStr == "moving") ? VarianceMode::Moving : VarianceMode::Batch;
|
|
220
|
+
|
|
221
|
+
if (newMode != m_mode)
|
|
222
|
+
{
|
|
223
|
+
throw std::runtime_error("Variance TOON load: mode mismatch");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (m_mode == VarianceMode::Moving)
|
|
227
|
+
{
|
|
228
|
+
int32_t windowSize = d.readInt32();
|
|
229
|
+
if (windowSize != static_cast<int32_t>(m_window_size))
|
|
230
|
+
{
|
|
231
|
+
throw std::runtime_error("Variance TOON load: window size mismatch");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
d.readDouble(); // window_duration_ms
|
|
235
|
+
d.readBool(); // is_initialized
|
|
236
|
+
|
|
237
|
+
int32_t numChannels = d.readInt32();
|
|
238
|
+
m_filters.clear();
|
|
239
|
+
for (int32_t i = 0; i < numChannels; ++i)
|
|
240
|
+
{
|
|
241
|
+
m_filters.emplace_back(m_window_size);
|
|
242
|
+
|
|
243
|
+
std::vector<float> bufferData = d.readFloatArray();
|
|
244
|
+
float runningSum = d.readFloat();
|
|
245
|
+
float runningSumOfSquares = d.readFloat();
|
|
246
|
+
|
|
247
|
+
m_filters[i].setState(bufferData, runningSum, runningSumOfSquares);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
193
252
|
private:
|
|
194
253
|
/**
|
|
195
254
|
* @brief Statelessly calculates the variance for each channel
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include "../IDspStage.h"
|
|
4
4
|
#include "../core/WampFilter.h"
|
|
5
5
|
#include "../utils/NapiUtils.h"
|
|
6
|
+
#include "../utils/Toon.h"
|
|
6
7
|
#include <vector>
|
|
7
8
|
#include <string>
|
|
8
9
|
#include <stdexcept>
|
|
@@ -105,6 +106,64 @@ namespace dsp::adapters
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
void serializeToon(dsp::toon::Serializer &s) const override
|
|
110
|
+
{
|
|
111
|
+
s.writeInt32(static_cast<int32_t>(m_window_size));
|
|
112
|
+
s.writeFloat(m_threshold);
|
|
113
|
+
s.writeInt32(static_cast<int32_t>(m_filters.size()));
|
|
114
|
+
|
|
115
|
+
for (const auto &filter : m_filters)
|
|
116
|
+
{
|
|
117
|
+
auto [internalState, prevSample] = filter.getState();
|
|
118
|
+
auto [bufferData, runningCount] = internalState;
|
|
119
|
+
|
|
120
|
+
// Serialize bool vector as byte array
|
|
121
|
+
s.writeInt32(static_cast<int32_t>(bufferData.size()));
|
|
122
|
+
for (bool b : bufferData)
|
|
123
|
+
{
|
|
124
|
+
s.writeBool(b);
|
|
125
|
+
}
|
|
126
|
+
s.writeInt32(static_cast<int32_t>(runningCount));
|
|
127
|
+
s.writeFloat(prevSample);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
void deserializeToon(dsp::toon::Deserializer &d) override
|
|
132
|
+
{
|
|
133
|
+
size_t windowSize = static_cast<size_t>(d.readInt32());
|
|
134
|
+
float threshold = d.readFloat();
|
|
135
|
+
if (windowSize != m_window_size || threshold != m_threshold)
|
|
136
|
+
{
|
|
137
|
+
throw std::runtime_error("WAMP TOON: parameter mismatch");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
int32_t numChannels = d.readInt32();
|
|
141
|
+
m_filters.clear();
|
|
142
|
+
for (int32_t i = 0; i < numChannels; ++i)
|
|
143
|
+
{
|
|
144
|
+
m_filters.emplace_back(m_window_size, m_threshold);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (auto &filter : m_filters)
|
|
148
|
+
{
|
|
149
|
+
int32_t bufSize = d.readInt32();
|
|
150
|
+
std::vector<bool> bufferData(bufSize);
|
|
151
|
+
for (int32_t j = 0; j < bufSize; ++j)
|
|
152
|
+
{
|
|
153
|
+
bufferData[j] = d.readBool();
|
|
154
|
+
}
|
|
155
|
+
size_t runningCount = static_cast<size_t>(d.readInt32());
|
|
156
|
+
float prevSample = d.readFloat();
|
|
157
|
+
|
|
158
|
+
if (!dsp::core::CounterPolicy::validateState(runningCount, bufferData))
|
|
159
|
+
{
|
|
160
|
+
throw std::runtime_error("WAMP TOON: validation failed");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
filter.setState(bufferData, runningCount, prevSample);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
108
167
|
private:
|
|
109
168
|
size_t m_window_size;
|
|
110
169
|
float m_threshold;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include "../IDspStage.h"
|
|
4
4
|
#include "../core/WaveformLengthFilter.h"
|
|
5
5
|
#include "../utils/NapiUtils.h"
|
|
6
|
+
#include "../utils/Toon.h"
|
|
6
7
|
#include <vector>
|
|
7
8
|
#include <string>
|
|
8
9
|
#include <stdexcept>
|
|
@@ -107,6 +108,56 @@ namespace dsp::adapters
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
void serializeToon(dsp::toon::Serializer &s) const override
|
|
112
|
+
{
|
|
113
|
+
s.writeInt32(static_cast<int32_t>(m_window_size));
|
|
114
|
+
s.writeInt32(static_cast<int32_t>(m_filters.size()));
|
|
115
|
+
|
|
116
|
+
for (const auto &filter : m_filters)
|
|
117
|
+
{
|
|
118
|
+
auto state = filter.getState();
|
|
119
|
+
const auto &internalState = state.first;
|
|
120
|
+
float prevSample = state.second;
|
|
121
|
+
|
|
122
|
+
const std::vector<float> &bufferData = internalState.first;
|
|
123
|
+
double runningSum = internalState.second;
|
|
124
|
+
|
|
125
|
+
s.writeFloatArray(bufferData);
|
|
126
|
+
s.writeDouble(runningSum);
|
|
127
|
+
s.writeFloat(prevSample);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
void deserializeToon(dsp::toon::Deserializer &d) override
|
|
132
|
+
{
|
|
133
|
+
size_t windowSize = static_cast<size_t>(d.readInt32());
|
|
134
|
+
if (windowSize != m_window_size)
|
|
135
|
+
{
|
|
136
|
+
throw std::runtime_error("WaveformLength TOON: window size mismatch");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
int32_t numChannels = d.readInt32();
|
|
140
|
+
m_filters.clear();
|
|
141
|
+
for (int32_t i = 0; i < numChannels; ++i)
|
|
142
|
+
{
|
|
143
|
+
m_filters.emplace_back(m_window_size);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (auto &filter : m_filters)
|
|
147
|
+
{
|
|
148
|
+
std::vector<float> bufferData = d.readFloatArray();
|
|
149
|
+
double runningSum = d.readDouble();
|
|
150
|
+
float prevSample = d.readFloat();
|
|
151
|
+
|
|
152
|
+
if (!dsp::core::SumPolicy<float>::validateState(runningSum, bufferData))
|
|
153
|
+
{
|
|
154
|
+
throw std::runtime_error("WaveformLength TOON: validation failed");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
filter.setState(bufferData, runningSum, prevSample);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
110
161
|
private:
|
|
111
162
|
size_t m_window_size;
|
|
112
163
|
std::vector<dsp::core::WaveformLengthFilter<float>> m_filters;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include "../IDspStage.h"
|
|
4
4
|
#include "../core/MovingZScoreFilter.h"
|
|
5
|
+
#include "../utils/Toon.h"
|
|
5
6
|
#include <vector>
|
|
6
7
|
#include <string>
|
|
7
8
|
#include <stdexcept>
|
|
@@ -195,6 +196,69 @@ namespace dsp::adapters
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
|
|
199
|
+
void serializeToon(dsp::toon::Serializer &s) const override
|
|
200
|
+
{
|
|
201
|
+
s.writeString(m_mode == ZScoreNormalizeMode::Moving ? "moving" : "batch");
|
|
202
|
+
|
|
203
|
+
if (m_mode == ZScoreNormalizeMode::Moving)
|
|
204
|
+
{
|
|
205
|
+
s.writeInt32(static_cast<int32_t>(m_window_size));
|
|
206
|
+
s.writeDouble(m_window_duration_ms);
|
|
207
|
+
s.writeBool(m_is_initialized);
|
|
208
|
+
s.writeFloat(m_epsilon);
|
|
209
|
+
|
|
210
|
+
s.writeInt32(static_cast<int32_t>(m_filters.size()));
|
|
211
|
+
for (const auto &filter : m_filters)
|
|
212
|
+
{
|
|
213
|
+
auto [bufferData, sums] = filter.getState();
|
|
214
|
+
auto [runningSum, runningSumOfSquares] = sums;
|
|
215
|
+
s.writeFloatArray(bufferData);
|
|
216
|
+
s.writeFloat(runningSum);
|
|
217
|
+
s.writeFloat(runningSumOfSquares);
|
|
218
|
+
s.writeFloat(m_epsilon); // Store epsilon explicitly
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
void deserializeToon(dsp::toon::Deserializer &d) override
|
|
224
|
+
{
|
|
225
|
+
std::string modeStr = d.readString();
|
|
226
|
+
ZScoreNormalizeMode newMode = (modeStr == "moving") ? ZScoreNormalizeMode::Moving : ZScoreNormalizeMode::Batch;
|
|
227
|
+
|
|
228
|
+
if (newMode != m_mode)
|
|
229
|
+
{
|
|
230
|
+
throw std::runtime_error("ZScore TOON load: mode mismatch");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (m_mode == ZScoreNormalizeMode::Moving)
|
|
234
|
+
{
|
|
235
|
+
int32_t windowSize = d.readInt32();
|
|
236
|
+
if (windowSize != static_cast<int32_t>(m_window_size))
|
|
237
|
+
{
|
|
238
|
+
throw std::runtime_error("ZScore TOON load: window size mismatch");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
m_window_duration_ms = d.readDouble(); // restore duration
|
|
242
|
+
m_is_initialized = d.readBool(); // restore init flag
|
|
243
|
+
m_epsilon = d.readFloat(); // restore epsilon
|
|
244
|
+
|
|
245
|
+
int32_t numChannels = d.readInt32();
|
|
246
|
+
m_filters.clear();
|
|
247
|
+
for (int32_t i = 0; i < numChannels; ++i)
|
|
248
|
+
{
|
|
249
|
+
m_filters.emplace_back(m_window_size, m_epsilon);
|
|
250
|
+
|
|
251
|
+
std::vector<float> bufferData = d.readFloatArray();
|
|
252
|
+
float runningSum = d.readFloat();
|
|
253
|
+
float runningSumOfSquares = d.readFloat();
|
|
254
|
+
d.readFloat(); // epsilon (already restored)
|
|
255
|
+
|
|
256
|
+
// setState takes (buffer, sum, sumOfSquares) - 3 separate params
|
|
257
|
+
m_filters[i].setState(bufferData, runningSum, runningSumOfSquares);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
198
262
|
private:
|
|
199
263
|
/**
|
|
200
264
|
* @brief Statelessly calculates the Z-Score for each sample
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "Policies.h"
|
|
3
|
+
#include <vector>
|
|
4
|
+
#include <stdexcept>
|
|
5
|
+
|
|
6
|
+
namespace dsp::core
|
|
7
|
+
{
|
|
8
|
+
/**
|
|
9
|
+
* @brief Implements a Cumulative Moving Average (CMA) filter.
|
|
10
|
+
*
|
|
11
|
+
* CMA is the average of all samples seen since initialization.
|
|
12
|
+
* Formula: CMA(n) = (CMA(n-1) * (n-1) + value(n)) / n
|
|
13
|
+
* or: CMA(n) = sum(values[1..n]) / n
|
|
14
|
+
*
|
|
15
|
+
* Unlike simple moving average (SMA), CMA considers ALL historical data:
|
|
16
|
+
* - SMA: Uses fixed window of recent N samples
|
|
17
|
+
* - CMA: Uses all samples from start to current
|
|
18
|
+
*
|
|
19
|
+
* Properties:
|
|
20
|
+
* - Memory-efficient: Only stores running sum and count
|
|
21
|
+
* - Stable convergence: Influence of new samples decreases over time
|
|
22
|
+
* - No window size needed: Adapts to any data length
|
|
23
|
+
*
|
|
24
|
+
* Use cases:
|
|
25
|
+
* - Long-term averages where all history matters
|
|
26
|
+
* - Baseline estimation from calibration data
|
|
27
|
+
* - Online mean estimation for statistics
|
|
28
|
+
*
|
|
29
|
+
* @tparam T The numeric type of the samples (e.g., float, double).
|
|
30
|
+
*/
|
|
31
|
+
template <typename T>
|
|
32
|
+
class CumulativeMovingAverageFilter
|
|
33
|
+
{
|
|
34
|
+
public:
|
|
35
|
+
/**
|
|
36
|
+
* @brief Constructs a new CMA Filter.
|
|
37
|
+
*/
|
|
38
|
+
CumulativeMovingAverageFilter()
|
|
39
|
+
: m_policy()
|
|
40
|
+
{
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Delete copy constructor and copy assignment
|
|
44
|
+
CumulativeMovingAverageFilter(const CumulativeMovingAverageFilter &) = delete;
|
|
45
|
+
CumulativeMovingAverageFilter &operator=(const CumulativeMovingAverageFilter &) = delete;
|
|
46
|
+
|
|
47
|
+
// Enable move semantics
|
|
48
|
+
CumulativeMovingAverageFilter(CumulativeMovingAverageFilter &&) noexcept = default;
|
|
49
|
+
CumulativeMovingAverageFilter &operator=(CumulativeMovingAverageFilter &&) noexcept = default;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @brief Adds a new sample and updates the CMA.
|
|
53
|
+
* @param newValue The new sample value.
|
|
54
|
+
* @return T The updated CMA value.
|
|
55
|
+
*/
|
|
56
|
+
T addSample(T newValue)
|
|
57
|
+
{
|
|
58
|
+
m_policy.onAdd(newValue);
|
|
59
|
+
return m_policy.getResult(0); // Window count parameter unused for CMA
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @brief Process array of samples in batch (optimized for throughput).
|
|
64
|
+
*
|
|
65
|
+
* @param input Input array of samples
|
|
66
|
+
* @param output Output array (same size as input)
|
|
67
|
+
* @param length Number of samples to process
|
|
68
|
+
*/
|
|
69
|
+
void processArray(const T *input, T *output, size_t length)
|
|
70
|
+
{
|
|
71
|
+
for (size_t i = 0; i < length; ++i)
|
|
72
|
+
{
|
|
73
|
+
output[i] = addSample(input[i]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @brief Gets the current CMA value.
|
|
79
|
+
* @return T The cumulative moving average.
|
|
80
|
+
*/
|
|
81
|
+
T getCma() const { return m_policy.getResult(0); }
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @brief Gets the number of samples processed.
|
|
85
|
+
* @return size_t The total count of samples.
|
|
86
|
+
*/
|
|
87
|
+
size_t getCount() const { return m_policy.getCount(); }
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @brief Clears the CMA state (resets to zero).
|
|
91
|
+
*/
|
|
92
|
+
void clear() { m_policy.clear(); }
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @brief Checks if any samples have been processed.
|
|
96
|
+
* @return true if count > 0, false otherwise.
|
|
97
|
+
*/
|
|
98
|
+
bool hasData() const { return m_policy.getCount() > 0; }
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @brief Exports the filter's internal state.
|
|
102
|
+
* @return A pair containing the running sum and sample count.
|
|
103
|
+
*/
|
|
104
|
+
std::pair<T, size_t> getState() const
|
|
105
|
+
{
|
|
106
|
+
return m_policy.getState();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @brief Restores the filter's internal state.
|
|
111
|
+
* @param sum The running sum to restore.
|
|
112
|
+
* @param count The sample count to restore.
|
|
113
|
+
*/
|
|
114
|
+
void setState(T sum, size_t count)
|
|
115
|
+
{
|
|
116
|
+
m_policy.setState(sum, count);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private:
|
|
120
|
+
CmaPolicy<T> m_policy;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
} // namespace dsp::core
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "Policies.h"
|
|
3
|
+
#include <vector>
|
|
4
|
+
#include <stdexcept>
|
|
5
|
+
|
|
6
|
+
namespace dsp::core
|
|
7
|
+
{
|
|
8
|
+
/**
|
|
9
|
+
* @brief Implements an Exponential Moving Average (EMA) filter.
|
|
10
|
+
*
|
|
11
|
+
* EMA gives more weight to recent samples and exponentially decaying weight to older samples.
|
|
12
|
+
* Formula: EMA(t) = α * value(t) + (1 - α) * EMA(t-1)
|
|
13
|
+
*
|
|
14
|
+
* where α (alpha) is the smoothing factor:
|
|
15
|
+
* - α close to 1: Fast response to changes (less smoothing)
|
|
16
|
+
* - α close to 0: Slow response to changes (more smoothing)
|
|
17
|
+
*
|
|
18
|
+
* Common conversions:
|
|
19
|
+
* - From N-period SMA: α = 2 / (N + 1)
|
|
20
|
+
* - From time constant: α = 1 - exp(-Δt / τ)
|
|
21
|
+
*
|
|
22
|
+
* Unlike simple moving average, EMA does not require a fixed window size,
|
|
23
|
+
* making it memory-efficient for very long averaging periods.
|
|
24
|
+
*
|
|
25
|
+
* @tparam T The numeric type of the samples (e.g., float, double).
|
|
26
|
+
*/
|
|
27
|
+
template <typename T>
|
|
28
|
+
class ExponentialMovingAverageFilter
|
|
29
|
+
{
|
|
30
|
+
public:
|
|
31
|
+
/**
|
|
32
|
+
* @brief Constructs a new EMA Filter with specified alpha.
|
|
33
|
+
* @param alpha The smoothing factor (0 < α ≤ 1).
|
|
34
|
+
* @throws std::invalid_argument if alpha is outside valid range.
|
|
35
|
+
*/
|
|
36
|
+
explicit ExponentialMovingAverageFilter(T alpha)
|
|
37
|
+
: m_policy(alpha)
|
|
38
|
+
{
|
|
39
|
+
if (alpha <= 0 || alpha > 1)
|
|
40
|
+
{
|
|
41
|
+
throw std::invalid_argument("EMA alpha must be in range (0, 1]");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Delete copy constructor and copy assignment
|
|
46
|
+
ExponentialMovingAverageFilter(const ExponentialMovingAverageFilter &) = delete;
|
|
47
|
+
ExponentialMovingAverageFilter &operator=(const ExponentialMovingAverageFilter &) = delete;
|
|
48
|
+
|
|
49
|
+
// Enable move semantics
|
|
50
|
+
ExponentialMovingAverageFilter(ExponentialMovingAverageFilter &&) noexcept = default;
|
|
51
|
+
ExponentialMovingAverageFilter &operator=(ExponentialMovingAverageFilter &&) noexcept = default;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @brief Adds a new sample and updates the EMA.
|
|
55
|
+
* @param newValue The new sample value.
|
|
56
|
+
* @return T The updated EMA value.
|
|
57
|
+
*/
|
|
58
|
+
T addSample(T newValue)
|
|
59
|
+
{
|
|
60
|
+
m_policy.onAdd(newValue);
|
|
61
|
+
return m_policy.getResult(0); // Count parameter unused for EMA
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @brief Process array of samples in batch (optimized for throughput).
|
|
66
|
+
*
|
|
67
|
+
* @param input Input array of samples
|
|
68
|
+
* @param output Output array (same size as input)
|
|
69
|
+
* @param length Number of samples to process
|
|
70
|
+
*/
|
|
71
|
+
void processArray(const T *input, T *output, size_t length)
|
|
72
|
+
{
|
|
73
|
+
for (size_t i = 0; i < length; ++i)
|
|
74
|
+
{
|
|
75
|
+
output[i] = addSample(input[i]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @brief Gets the current EMA value.
|
|
81
|
+
* @return T The current exponential moving average.
|
|
82
|
+
*/
|
|
83
|
+
T getEma() const { return m_policy.getResult(0); }
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @brief Gets the alpha (smoothing factor).
|
|
87
|
+
* @return T The alpha value.
|
|
88
|
+
*/
|
|
89
|
+
T getAlpha() const { return m_policy.getAlpha(); }
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @brief Clears the EMA state.
|
|
93
|
+
*/
|
|
94
|
+
void clear() { m_policy.clear(); }
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @brief Checks if the filter has been initialized with at least one sample.
|
|
98
|
+
* @return true if initialized, false otherwise.
|
|
99
|
+
*/
|
|
100
|
+
bool isInitialized() const
|
|
101
|
+
{
|
|
102
|
+
auto [ema, initialized] = m_policy.getState();
|
|
103
|
+
return initialized;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @brief Exports the filter's internal state.
|
|
108
|
+
* @return A pair containing the current EMA value and initialization flag.
|
|
109
|
+
*/
|
|
110
|
+
std::pair<T, bool> getState() const
|
|
111
|
+
{
|
|
112
|
+
return m_policy.getState();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @brief Restores the filter's internal state.
|
|
117
|
+
* @param ema The EMA value to restore.
|
|
118
|
+
* @param initialized The initialization flag to restore.
|
|
119
|
+
*/
|
|
120
|
+
void setState(T ema, bool initialized)
|
|
121
|
+
{
|
|
122
|
+
m_policy.setState(ema, initialized);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private:
|
|
126
|
+
EmaPolicy<T> m_policy;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
} // namespace dsp::core
|