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.
Files changed (74) hide show
  1. package/README.md +40 -78
  2. package/binding.gyp +10 -0
  3. package/dist/FilterBankDesign.d.ts +233 -0
  4. package/dist/FilterBankDesign.d.ts.map +1 -0
  5. package/dist/FilterBankDesign.js +247 -0
  6. package/dist/FilterBankDesign.js.map +1 -0
  7. package/dist/advanced-dsp.d.ts +6 -6
  8. package/dist/advanced-dsp.d.ts.map +1 -1
  9. package/dist/advanced-dsp.js +35 -12
  10. package/dist/advanced-dsp.js.map +1 -1
  11. package/dist/backends.d.ts +0 -103
  12. package/dist/backends.d.ts.map +1 -1
  13. package/dist/backends.js +0 -217
  14. package/dist/backends.js.map +1 -1
  15. package/dist/bindings.d.ts +270 -17
  16. package/dist/bindings.d.ts.map +1 -1
  17. package/dist/bindings.js +566 -43
  18. package/dist/bindings.js.map +1 -1
  19. package/dist/index.d.ts +4 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +2 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/types.d.ts +67 -8
  24. package/dist/types.d.ts.map +1 -1
  25. package/dist/utils.d.ts +38 -8
  26. package/dist/utils.d.ts.map +1 -1
  27. package/dist/utils.js +84 -26
  28. package/dist/utils.js.map +1 -1
  29. package/package.json +1 -2
  30. package/prebuilds/win32-x64/dspx.node +0 -0
  31. package/scripts/add-dispose-to-tests.js +145 -0
  32. package/src/native/DspPipeline.cc +699 -126
  33. package/src/native/DspPipeline.h +13 -0
  34. package/src/native/FilterBankDesignBindings.cc +241 -0
  35. package/src/native/IDspStage.h +24 -0
  36. package/src/native/UtilityBindings.cc +130 -0
  37. package/src/native/adapters/AmplifyStage.h +148 -0
  38. package/src/native/adapters/ClipDetectionStage.h +15 -4
  39. package/src/native/adapters/ConvolutionStage.h +101 -0
  40. package/src/native/adapters/CumulativeMovingAverageStage.h +264 -0
  41. package/src/native/adapters/DecimatorStage.h +80 -0
  42. package/src/native/adapters/DifferentiatorStage.h +13 -0
  43. package/src/native/adapters/ExponentialMovingAverageStage.h +290 -0
  44. package/src/native/adapters/FilterBankStage.cc +336 -0
  45. package/src/native/adapters/FilterBankStage.h +170 -0
  46. package/src/native/adapters/FilterStage.cc +122 -0
  47. package/src/native/adapters/FilterStage.h +4 -0
  48. package/src/native/adapters/HilbertEnvelopeStage.h +55 -0
  49. package/src/native/adapters/IntegratorStage.h +15 -0
  50. package/src/native/adapters/InterpolatorStage.h +51 -0
  51. package/src/native/adapters/LinearRegressionStage.h +40 -0
  52. package/src/native/adapters/LmsStage.h +63 -0
  53. package/src/native/adapters/MeanAbsoluteValueStage.h +76 -0
  54. package/src/native/adapters/MovingAverageStage.h +119 -0
  55. package/src/native/adapters/PeakDetectionStage.h +53 -0
  56. package/src/native/adapters/RectifyStage.h +14 -0
  57. package/src/native/adapters/ResamplerStage.h +67 -0
  58. package/src/native/adapters/RlsStage.h +76 -0
  59. package/src/native/adapters/RmsStage.h +72 -0
  60. package/src/native/adapters/SnrStage.h +45 -0
  61. package/src/native/adapters/SquareStage.h +78 -0
  62. package/src/native/adapters/SscStage.h +65 -0
  63. package/src/native/adapters/StftStage.h +62 -0
  64. package/src/native/adapters/VarianceStage.h +59 -0
  65. package/src/native/adapters/WampStage.h +59 -0
  66. package/src/native/adapters/WaveformLengthStage.h +51 -0
  67. package/src/native/adapters/ZScoreNormalizeStage.h +64 -0
  68. package/src/native/core/CumulativeMovingAverageFilter.h +123 -0
  69. package/src/native/core/ExponentialMovingAverageFilter.h +129 -0
  70. package/src/native/core/FilterBankDesign.h +266 -0
  71. package/src/native/core/Policies.h +124 -0
  72. package/src/native/utils/CircularBufferArray.cc +2 -1
  73. package/src/native/utils/SimdOps.h +67 -0
  74. 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