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
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "../IDspStage.h"
|
|
4
|
+
#include "../core/ExponentialMovingAverageFilter.h"
|
|
5
|
+
#include "../utils/SimdOps.h"
|
|
6
|
+
#include <vector>
|
|
7
|
+
#include <stdexcept>
|
|
8
|
+
#include <cmath>
|
|
9
|
+
#include <string>
|
|
10
|
+
|
|
11
|
+
namespace dsp::adapters
|
|
12
|
+
{
|
|
13
|
+
enum class EmaMode
|
|
14
|
+
{
|
|
15
|
+
Batch,
|
|
16
|
+
Moving
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
class ExponentialMovingAverageStage : public IDspStage
|
|
20
|
+
{
|
|
21
|
+
public:
|
|
22
|
+
/**
|
|
23
|
+
* @brief Constructs a new Exponential Moving Average Stage.
|
|
24
|
+
* @param mode The averaging mode (Batch or Moving).
|
|
25
|
+
* @param alpha The smoothing factor (0 < α ≤ 1).
|
|
26
|
+
*/
|
|
27
|
+
explicit ExponentialMovingAverageStage(EmaMode mode, float alpha)
|
|
28
|
+
: m_mode(mode),
|
|
29
|
+
m_alpha(alpha)
|
|
30
|
+
{
|
|
31
|
+
if (alpha <= 0.0f || alpha > 1.0f)
|
|
32
|
+
{
|
|
33
|
+
throw std::invalid_argument("ExponentialMovingAverage: alpha must be in range (0, 1]");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return the type identifier for this stage
|
|
38
|
+
const char *getType() const override
|
|
39
|
+
{
|
|
40
|
+
return "exponentialMovingAverage";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// This is the implementation of the interface method
|
|
44
|
+
void process(float *buffer, size_t numSamples, int numChannels, const float *timestamps = nullptr) override
|
|
45
|
+
{
|
|
46
|
+
if (m_mode == EmaMode::Batch)
|
|
47
|
+
{
|
|
48
|
+
processBatch(buffer, numSamples, numChannels);
|
|
49
|
+
}
|
|
50
|
+
else // EmaMode::Moving
|
|
51
|
+
{
|
|
52
|
+
processMoving(buffer, numSamples, numChannels);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Serialize the stage's state to a Napi::Object
|
|
57
|
+
Napi::Object serializeState(Napi::Env env) const override
|
|
58
|
+
{
|
|
59
|
+
Napi::Object state = Napi::Object::New(env);
|
|
60
|
+
std::string modeStr = (m_mode == EmaMode::Moving) ? "moving" : "batch";
|
|
61
|
+
state.Set("mode", modeStr);
|
|
62
|
+
state.Set("alpha", Napi::Number::New(env, m_alpha));
|
|
63
|
+
|
|
64
|
+
if (m_mode == EmaMode::Moving)
|
|
65
|
+
{
|
|
66
|
+
state.Set("numChannels", static_cast<uint32_t>(m_filters.size()));
|
|
67
|
+
|
|
68
|
+
// Serialize each channel's filter state
|
|
69
|
+
Napi::Array channelsArray = Napi::Array::New(env, m_filters.size());
|
|
70
|
+
for (size_t i = 0; i < m_filters.size(); ++i)
|
|
71
|
+
{
|
|
72
|
+
Napi::Object channelState = Napi::Object::New(env);
|
|
73
|
+
|
|
74
|
+
// Get the filter's internal state
|
|
75
|
+
auto [ema, initialized] = m_filters[i].getState();
|
|
76
|
+
|
|
77
|
+
channelState.Set("ema", Napi::Number::New(env, ema));
|
|
78
|
+
channelState.Set("initialized", Napi::Boolean::New(env, initialized));
|
|
79
|
+
|
|
80
|
+
channelsArray.Set(static_cast<uint32_t>(i), channelState);
|
|
81
|
+
}
|
|
82
|
+
state.Set("channels", channelsArray);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return state;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Deserialize and restore the stage's state
|
|
89
|
+
void deserializeState(const Napi::Object &state) override
|
|
90
|
+
{
|
|
91
|
+
std::string modeStr = state.Get("mode").As<Napi::String>().Utf8Value();
|
|
92
|
+
EmaMode newMode = (modeStr == "moving") ? EmaMode::Moving : EmaMode::Batch;
|
|
93
|
+
|
|
94
|
+
if (newMode != m_mode)
|
|
95
|
+
{
|
|
96
|
+
throw std::runtime_error("ExponentialMovingAverage mode mismatch during deserialization");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
float alpha = state.Get("alpha").As<Napi::Number>().FloatValue();
|
|
100
|
+
if (std::abs(alpha - m_alpha) > 1e-6f)
|
|
101
|
+
{
|
|
102
|
+
throw std::runtime_error("ExponentialMovingAverage alpha mismatch during deserialization");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (m_mode == EmaMode::Moving)
|
|
106
|
+
{
|
|
107
|
+
// Get number of channels
|
|
108
|
+
uint32_t numChannels = state.Get("channels").As<Napi::Array>().Length();
|
|
109
|
+
|
|
110
|
+
// Recreate filters
|
|
111
|
+
m_filters.clear();
|
|
112
|
+
for (uint32_t i = 0; i < numChannels; ++i)
|
|
113
|
+
{
|
|
114
|
+
m_filters.emplace_back(m_alpha);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Restore each channel's state
|
|
118
|
+
Napi::Array channelsArray = state.Get("channels").As<Napi::Array>();
|
|
119
|
+
for (uint32_t i = 0; i < numChannels; ++i)
|
|
120
|
+
{
|
|
121
|
+
Napi::Object channelState = channelsArray.Get(i).As<Napi::Object>();
|
|
122
|
+
|
|
123
|
+
float ema = channelState.Get("ema").As<Napi::Number>().FloatValue();
|
|
124
|
+
bool initialized = channelState.Get("initialized").As<Napi::Boolean>().Value();
|
|
125
|
+
|
|
126
|
+
// Restore the filter's state
|
|
127
|
+
m_filters[i].setState(ema, initialized);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Reset all filters to initial state
|
|
133
|
+
void reset() override
|
|
134
|
+
{
|
|
135
|
+
for (auto &filter : m_filters)
|
|
136
|
+
{
|
|
137
|
+
filter.clear();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// TOON Binary Serialization
|
|
142
|
+
void serializeToon(dsp::toon::Serializer &serializer) const override
|
|
143
|
+
{
|
|
144
|
+
serializer.startObject();
|
|
145
|
+
|
|
146
|
+
// 1. Mode
|
|
147
|
+
serializer.writeString("mode");
|
|
148
|
+
serializer.writeString((m_mode == EmaMode::Moving) ? "moving" : "batch");
|
|
149
|
+
|
|
150
|
+
// 2. Alpha
|
|
151
|
+
serializer.writeString("alpha");
|
|
152
|
+
serializer.writeFloat(m_alpha);
|
|
153
|
+
|
|
154
|
+
if (m_mode == EmaMode::Moving)
|
|
155
|
+
{
|
|
156
|
+
// 3. Channels
|
|
157
|
+
serializer.writeString("channels");
|
|
158
|
+
serializer.startArray();
|
|
159
|
+
|
|
160
|
+
for (const auto &filter : m_filters)
|
|
161
|
+
{
|
|
162
|
+
auto [ema, initialized] = filter.getState();
|
|
163
|
+
|
|
164
|
+
serializer.startObject();
|
|
165
|
+
|
|
166
|
+
serializer.writeString("ema");
|
|
167
|
+
serializer.writeFloat(ema);
|
|
168
|
+
|
|
169
|
+
serializer.writeString("initialized");
|
|
170
|
+
serializer.writeBool(initialized);
|
|
171
|
+
|
|
172
|
+
serializer.endObject();
|
|
173
|
+
}
|
|
174
|
+
serializer.endArray();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
serializer.endObject();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// TOON Binary Deserialization
|
|
181
|
+
void deserializeToon(dsp::toon::Deserializer &deserializer) override
|
|
182
|
+
{
|
|
183
|
+
deserializer.consumeToken(dsp::toon::T_OBJECT_START);
|
|
184
|
+
|
|
185
|
+
// 1. Mode
|
|
186
|
+
std::string key = deserializer.readString(); // "mode"
|
|
187
|
+
std::string modeStr = deserializer.readString();
|
|
188
|
+
|
|
189
|
+
EmaMode newMode = (modeStr == "moving") ? EmaMode::Moving : EmaMode::Batch;
|
|
190
|
+
if (newMode != m_mode)
|
|
191
|
+
{
|
|
192
|
+
throw std::runtime_error("ExponentialMovingAverage mode mismatch during TOON deserialization");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 2. Alpha
|
|
196
|
+
key = deserializer.readString(); // "alpha"
|
|
197
|
+
float alpha = deserializer.readFloat();
|
|
198
|
+
if (std::abs(alpha - m_alpha) > 1e-6f)
|
|
199
|
+
{
|
|
200
|
+
throw std::runtime_error("ExponentialMovingAverage alpha mismatch during TOON deserialization");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (modeStr == "moving")
|
|
204
|
+
{
|
|
205
|
+
// 3. Channels
|
|
206
|
+
key = deserializer.readString(); // "channels"
|
|
207
|
+
deserializer.consumeToken(dsp::toon::T_ARRAY_START);
|
|
208
|
+
|
|
209
|
+
m_filters.clear();
|
|
210
|
+
|
|
211
|
+
while (deserializer.peekToken() != dsp::toon::T_ARRAY_END)
|
|
212
|
+
{
|
|
213
|
+
deserializer.consumeToken(dsp::toon::T_OBJECT_START);
|
|
214
|
+
|
|
215
|
+
// EMA value
|
|
216
|
+
deserializer.readString(); // "ema"
|
|
217
|
+
float ema = deserializer.readFloat();
|
|
218
|
+
|
|
219
|
+
// Initialized flag
|
|
220
|
+
deserializer.readString(); // "initialized"
|
|
221
|
+
bool initialized = deserializer.readBool();
|
|
222
|
+
|
|
223
|
+
// Reconstruct filter
|
|
224
|
+
m_filters.emplace_back(m_alpha);
|
|
225
|
+
m_filters.back().setState(ema, initialized);
|
|
226
|
+
|
|
227
|
+
deserializer.consumeToken(dsp::toon::T_OBJECT_END);
|
|
228
|
+
}
|
|
229
|
+
deserializer.consumeToken(dsp::toon::T_ARRAY_END);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
deserializer.consumeToken(dsp::toon::T_OBJECT_END);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private:
|
|
236
|
+
/**
|
|
237
|
+
* @brief Batch mode: Compute exponential moving average over entire buffer per channel.
|
|
238
|
+
* Each sample in the channel is replaced by the progressive EMA.
|
|
239
|
+
*/
|
|
240
|
+
void processBatch(float *buffer, size_t numSamples, int numChannels)
|
|
241
|
+
{
|
|
242
|
+
// Process each channel independently
|
|
243
|
+
for (int c = 0; c < numChannels; ++c)
|
|
244
|
+
{
|
|
245
|
+
// Initialize EMA with first sample of this channel
|
|
246
|
+
float ema = (c < numSamples) ? buffer[c] : 0.0f;
|
|
247
|
+
|
|
248
|
+
// Process all samples for this channel
|
|
249
|
+
for (size_t i = c; i < numSamples; i += numChannels)
|
|
250
|
+
{
|
|
251
|
+
// EMA formula: EMA(t) = α * value(t) + (1 - α) * EMA(t-1)
|
|
252
|
+
ema = m_alpha * buffer[i] + (1.0f - m_alpha) * ema;
|
|
253
|
+
buffer[i] = ema;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @brief Moving mode: Statefully process samples using EMA filters with continuity.
|
|
260
|
+
* @param buffer The interleaved audio buffer.
|
|
261
|
+
* @param numSamples The total number of samples.
|
|
262
|
+
* @param numChannels The number of channels.
|
|
263
|
+
*/
|
|
264
|
+
void processMoving(float *buffer, size_t numSamples, int numChannels)
|
|
265
|
+
{
|
|
266
|
+
// Lazily initialize our filters, one for each channel
|
|
267
|
+
if (m_filters.size() != static_cast<size_t>(numChannels))
|
|
268
|
+
{
|
|
269
|
+
m_filters.clear();
|
|
270
|
+
for (int i = 0; i < numChannels; ++i)
|
|
271
|
+
{
|
|
272
|
+
m_filters.emplace_back(m_alpha);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Process the buffer sample by sample, de-interleaving
|
|
277
|
+
for (size_t i = 0; i < numSamples; ++i)
|
|
278
|
+
{
|
|
279
|
+
int channel = i % numChannels;
|
|
280
|
+
buffer[i] = m_filters[channel].addSample(buffer[i]);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
EmaMode m_mode;
|
|
285
|
+
float m_alpha;
|
|
286
|
+
// Separate filter instance for each channel's state
|
|
287
|
+
std::vector<dsp::core::ExponentialMovingAverageFilter<float>> m_filters;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
} // namespace dsp::adapters
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#include "FilterBankStage.h"
|
|
2
|
+
#include <iostream>
|
|
3
|
+
|
|
4
|
+
namespace dsp::adapters
|
|
5
|
+
{
|
|
6
|
+
|
|
7
|
+
FilterBankStage::FilterBankStage(const std::vector<FilterDefinition> &definitions, int numInputChannels)
|
|
8
|
+
: m_definitions(definitions), m_numInputChannels(numInputChannels)
|
|
9
|
+
{
|
|
10
|
+
if (definitions.empty())
|
|
11
|
+
{
|
|
12
|
+
throw std::runtime_error("FilterBank: definitions cannot be empty");
|
|
13
|
+
}
|
|
14
|
+
if (numInputChannels <= 0)
|
|
15
|
+
{
|
|
16
|
+
throw std::runtime_error("FilterBank: Invalid channel count");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
initializeFilters();
|
|
20
|
+
}
|
|
21
|
+
FilterBankStage::~FilterBankStage()
|
|
22
|
+
{
|
|
23
|
+
try
|
|
24
|
+
{
|
|
25
|
+
// Explicitly clear filters first to ensure IirFilter destructors run
|
|
26
|
+
// before scratch buffers are destroyed
|
|
27
|
+
for (size_t ch = 0; ch < m_filters.size(); ++ch)
|
|
28
|
+
{
|
|
29
|
+
m_filters[ch].clear(); // Destroys all unique_ptrs in this channel
|
|
30
|
+
}
|
|
31
|
+
m_filters.clear(); // Clear outer vector
|
|
32
|
+
|
|
33
|
+
// Clear scratch buffers
|
|
34
|
+
m_planarInput.clear();
|
|
35
|
+
m_planarOutput.clear();
|
|
36
|
+
}
|
|
37
|
+
catch (const std::exception &e)
|
|
38
|
+
{
|
|
39
|
+
std::cerr << "[FilterBankStage] ERROR in destructor: " << e.what() << std::endl;
|
|
40
|
+
}
|
|
41
|
+
catch (...)
|
|
42
|
+
{
|
|
43
|
+
std::cerr << "[FilterBankStage] UNKNOWN ERROR in destructor" << std::endl;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void FilterBankStage::processResizing(const float *inputBuffer, size_t inputSize,
|
|
48
|
+
float *outputBuffer, size_t &outputSize,
|
|
49
|
+
int numChannels, const float *timestamps)
|
|
50
|
+
{
|
|
51
|
+
if (numChannels != m_numInputChannels)
|
|
52
|
+
{
|
|
53
|
+
throw std::runtime_error("FilterBank: Input channel mismatch");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
size_t samplesPerChannel = inputSize / numChannels;
|
|
57
|
+
size_t numBands = m_definitions.size();
|
|
58
|
+
size_t totalOutputChannels = numChannels * numBands;
|
|
59
|
+
|
|
60
|
+
outputSize = samplesPerChannel * totalOutputChannels;
|
|
61
|
+
|
|
62
|
+
ensureScratchSize(samplesPerChannel);
|
|
63
|
+
|
|
64
|
+
std::vector<float *> inputPtrs(numChannels);
|
|
65
|
+
for (int i = 0; i < numChannels; ++i)
|
|
66
|
+
{
|
|
67
|
+
inputPtrs[i] = m_planarInput[i].data();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (numChannels == 2)
|
|
71
|
+
{
|
|
72
|
+
dsp::simd::deinterleave2Ch(inputBuffer, inputPtrs[0], inputPtrs[1], samplesPerChannel);
|
|
73
|
+
}
|
|
74
|
+
else
|
|
75
|
+
{
|
|
76
|
+
dsp::simd::deinterleaveNCh(inputBuffer, inputPtrs.data(), numChannels, samplesPerChannel);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (int ch = 0; ch < numChannels; ++ch)
|
|
80
|
+
{
|
|
81
|
+
for (size_t band = 0; band < numBands; ++band)
|
|
82
|
+
{
|
|
83
|
+
int outChIndex = (ch * static_cast<int>(numBands)) + static_cast<int>(band);
|
|
84
|
+
|
|
85
|
+
m_filters[ch][band]->process(
|
|
86
|
+
m_planarInput[ch].data(),
|
|
87
|
+
m_planarOutput[outChIndex].data(),
|
|
88
|
+
samplesPerChannel);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
std::vector<const float *> outputPtrs(totalOutputChannels);
|
|
93
|
+
for (size_t i = 0; i < totalOutputChannels; ++i)
|
|
94
|
+
{
|
|
95
|
+
outputPtrs[i] = m_planarOutput[i].data();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
dsp::simd::interleaveNCh(outputPtrs.data(), outputBuffer, static_cast<int>(totalOutputChannels), samplesPerChannel);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
void FilterBankStage::initializeFilters()
|
|
102
|
+
{
|
|
103
|
+
m_filters.resize(m_numInputChannels);
|
|
104
|
+
for (int ch = 0; ch < m_numInputChannels; ++ch)
|
|
105
|
+
{
|
|
106
|
+
m_filters[ch].clear();
|
|
107
|
+
for (const auto &def : m_definitions)
|
|
108
|
+
{
|
|
109
|
+
// Convert double coefficients to float for IirFilter
|
|
110
|
+
std::vector<float> b_float(def.b.begin(), def.b.end());
|
|
111
|
+
std::vector<float> a_float(def.a.begin(), def.a.end());
|
|
112
|
+
|
|
113
|
+
// Create stateful IIR filter for each band
|
|
114
|
+
m_filters[ch].push_back(
|
|
115
|
+
std::make_unique<dsp::core::IirFilter<float>>(b_float, a_float, true));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void FilterBankStage::ensureScratchSize(size_t samplesPerChannel)
|
|
121
|
+
{
|
|
122
|
+
if (m_planarInput.size() != static_cast<size_t>(m_numInputChannels))
|
|
123
|
+
{
|
|
124
|
+
m_planarInput.resize(m_numInputChannels);
|
|
125
|
+
}
|
|
126
|
+
for (size_t i = 0; i < m_planarInput.size(); ++i)
|
|
127
|
+
{
|
|
128
|
+
if (m_planarInput[i].size() < samplesPerChannel)
|
|
129
|
+
{
|
|
130
|
+
m_planarInput[i].resize(samplesPerChannel, 0.0f);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
size_t numOut = m_numInputChannels * m_definitions.size();
|
|
135
|
+
|
|
136
|
+
if (m_planarOutput.size() != numOut)
|
|
137
|
+
{
|
|
138
|
+
m_planarOutput.resize(numOut);
|
|
139
|
+
}
|
|
140
|
+
for (size_t i = 0; i < m_planarOutput.size(); ++i)
|
|
141
|
+
{
|
|
142
|
+
if (m_planarOutput[i].size() < samplesPerChannel)
|
|
143
|
+
{
|
|
144
|
+
m_planarOutput[i].resize(samplesPerChannel, 0.0f);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
void FilterBankStage::reset()
|
|
150
|
+
{
|
|
151
|
+
for (auto &ch : m_filters)
|
|
152
|
+
{
|
|
153
|
+
for (auto &filter : ch)
|
|
154
|
+
{
|
|
155
|
+
filter->reset();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
Napi::Object FilterBankStage::serializeState(Napi::Env env) const
|
|
161
|
+
{
|
|
162
|
+
Napi::Object state = Napi::Object::New(env);
|
|
163
|
+
state.Set("type", getType());
|
|
164
|
+
state.Set("numInputChannels", m_numInputChannels);
|
|
165
|
+
state.Set("numBands", m_definitions.size());
|
|
166
|
+
|
|
167
|
+
// Serialize filter states as nested arrays: channels[bands[state]]
|
|
168
|
+
Napi::Array channels = Napi::Array::New(env, m_filters.size());
|
|
169
|
+
for (size_t ch = 0; ch < m_filters.size(); ++ch)
|
|
170
|
+
{
|
|
171
|
+
Napi::Array bands = Napi::Array::New(env, m_filters[ch].size());
|
|
172
|
+
for (size_t band = 0; band < m_filters[ch].size(); ++band)
|
|
173
|
+
{
|
|
174
|
+
const auto &filter = m_filters[ch][band];
|
|
175
|
+
|
|
176
|
+
Napi::Object filterState = Napi::Object::New(env);
|
|
177
|
+
|
|
178
|
+
// Get filter state (input and output history)
|
|
179
|
+
auto [inputHistory, outputHistory] = filter->getState();
|
|
180
|
+
|
|
181
|
+
// Serialize input history
|
|
182
|
+
Napi::Array inputArray = Napi::Array::New(env, inputHistory.size());
|
|
183
|
+
for (size_t i = 0; i < inputHistory.size(); ++i)
|
|
184
|
+
{
|
|
185
|
+
inputArray.Set(i, inputHistory[i]);
|
|
186
|
+
}
|
|
187
|
+
filterState.Set("inputHistory", inputArray);
|
|
188
|
+
|
|
189
|
+
// Serialize output history
|
|
190
|
+
Napi::Array outputArray = Napi::Array::New(env, outputHistory.size());
|
|
191
|
+
for (size_t i = 0; i < outputHistory.size(); ++i)
|
|
192
|
+
{
|
|
193
|
+
outputArray.Set(i, outputHistory[i]);
|
|
194
|
+
}
|
|
195
|
+
filterState.Set("outputHistory", outputArray);
|
|
196
|
+
|
|
197
|
+
bands.Set(band, filterState);
|
|
198
|
+
}
|
|
199
|
+
channels.Set(ch, bands);
|
|
200
|
+
}
|
|
201
|
+
state.Set("filterStates", channels);
|
|
202
|
+
|
|
203
|
+
return state;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
void FilterBankStage::deserializeState(const Napi::Object &state)
|
|
207
|
+
{
|
|
208
|
+
if (!state.Has("filterStates"))
|
|
209
|
+
{
|
|
210
|
+
throw std::runtime_error("FilterBank: Missing filterStates in serialized data");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
Napi::Array channels = state.Get("filterStates").As<Napi::Array>();
|
|
214
|
+
if (channels.Length() != m_filters.size())
|
|
215
|
+
{
|
|
216
|
+
throw std::runtime_error("FilterBank: Channel count mismatch in deserialization");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (size_t ch = 0; ch < channels.Length(); ++ch)
|
|
220
|
+
{
|
|
221
|
+
Napi::Array bands = channels.Get(ch).As<Napi::Array>();
|
|
222
|
+
if (bands.Length() != m_filters[ch].size())
|
|
223
|
+
{
|
|
224
|
+
throw std::runtime_error("FilterBank: Band count mismatch in deserialization");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (size_t band = 0; band < bands.Length(); ++band)
|
|
228
|
+
{
|
|
229
|
+
Napi::Object filterState = bands.Get(band).As<Napi::Object>();
|
|
230
|
+
|
|
231
|
+
// Deserialize input history
|
|
232
|
+
std::vector<float> inputHistory;
|
|
233
|
+
if (filterState.Has("inputHistory"))
|
|
234
|
+
{
|
|
235
|
+
Napi::Array inputArray = filterState.Get("inputHistory").As<Napi::Array>();
|
|
236
|
+
inputHistory.reserve(inputArray.Length());
|
|
237
|
+
for (size_t i = 0; i < inputArray.Length(); ++i)
|
|
238
|
+
{
|
|
239
|
+
inputHistory.push_back(inputArray.Get(i).As<Napi::Number>().FloatValue());
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Deserialize output history
|
|
244
|
+
std::vector<float> outputHistory;
|
|
245
|
+
if (filterState.Has("outputHistory"))
|
|
246
|
+
{
|
|
247
|
+
Napi::Array outputArray = filterState.Get("outputHistory").As<Napi::Array>();
|
|
248
|
+
outputHistory.reserve(outputArray.Length());
|
|
249
|
+
for (size_t i = 0; i < outputArray.Length(); ++i)
|
|
250
|
+
{
|
|
251
|
+
outputHistory.push_back(outputArray.Get(i).As<Napi::Number>().FloatValue());
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Restore filter state
|
|
256
|
+
m_filters[ch][band]->setState(inputHistory, outputHistory);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
void FilterBankStage::serializeToon(toon::Serializer &serializer) const
|
|
262
|
+
{
|
|
263
|
+
// Serialize configuration
|
|
264
|
+
serializer.writeInt32(m_numInputChannels);
|
|
265
|
+
serializer.writeInt32(static_cast<int32_t>(m_definitions.size()));
|
|
266
|
+
|
|
267
|
+
// Serialize each filter's state
|
|
268
|
+
for (const auto &ch : m_filters)
|
|
269
|
+
{
|
|
270
|
+
for (const auto &filter : ch)
|
|
271
|
+
{
|
|
272
|
+
// Get filter state (input and output history)
|
|
273
|
+
auto [inputHistory, outputHistory] = filter->getState();
|
|
274
|
+
|
|
275
|
+
// Write input history
|
|
276
|
+
serializer.writeInt32(static_cast<int32_t>(inputHistory.size()));
|
|
277
|
+
for (float val : inputHistory)
|
|
278
|
+
{
|
|
279
|
+
serializer.writeFloat(val);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Write output history
|
|
283
|
+
serializer.writeInt32(static_cast<int32_t>(outputHistory.size()));
|
|
284
|
+
for (float val : outputHistory)
|
|
285
|
+
{
|
|
286
|
+
serializer.writeFloat(val);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
void FilterBankStage::deserializeToon(toon::Deserializer &deserializer)
|
|
293
|
+
{
|
|
294
|
+
// Read and validate configuration
|
|
295
|
+
int numChannels = deserializer.readInt32();
|
|
296
|
+
int32_t numBands = deserializer.readInt32();
|
|
297
|
+
|
|
298
|
+
if (numChannels != m_numInputChannels)
|
|
299
|
+
{
|
|
300
|
+
throw std::runtime_error("FilterBank TOON: Channel count mismatch");
|
|
301
|
+
}
|
|
302
|
+
if (static_cast<size_t>(numBands) != m_definitions.size())
|
|
303
|
+
{
|
|
304
|
+
throw std::runtime_error("FilterBank TOON: Band count mismatch");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Restore each filter's state
|
|
308
|
+
for (auto &ch : m_filters)
|
|
309
|
+
{
|
|
310
|
+
for (auto &filter : ch)
|
|
311
|
+
{
|
|
312
|
+
// Read input history
|
|
313
|
+
int32_t inputSize = deserializer.readInt32();
|
|
314
|
+
std::vector<float> inputHistory;
|
|
315
|
+
inputHistory.reserve(inputSize);
|
|
316
|
+
for (int32_t i = 0; i < inputSize; ++i)
|
|
317
|
+
{
|
|
318
|
+
inputHistory.push_back(deserializer.readFloat());
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Read output history
|
|
322
|
+
int32_t outputSize = deserializer.readInt32();
|
|
323
|
+
std::vector<float> outputHistory;
|
|
324
|
+
outputHistory.reserve(outputSize);
|
|
325
|
+
for (int32_t i = 0; i < outputSize; ++i)
|
|
326
|
+
{
|
|
327
|
+
outputHistory.push_back(deserializer.readFloat());
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Restore filter state
|
|
331
|
+
filter->setState(inputHistory, outputHistory);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
} // namespace dsp::adapters
|