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,170 @@
1
+ #pragma once
2
+ #include "../IDspStage.h"
3
+ #include "../core/IirFilter.h"
4
+ #include "../utils/SimdOps.h"
5
+ #include <vector>
6
+ #include <memory>
7
+ #include <stdexcept>
8
+
9
+ namespace dsp::adapters
10
+ {
11
+ /**
12
+ * @brief Lightweight struct to pass filter coefficients from JavaScript
13
+ */
14
+ struct FilterDefinition
15
+ {
16
+ std::vector<double> b; // Feedforward coefficients
17
+ std::vector<double> a; // Feedback coefficients
18
+ };
19
+
20
+ /**
21
+ * @brief Filter Bank Stage - Splits N input channels into N × M sub-bands
22
+ *
23
+ * Architecture:
24
+ * - Input: N channels (interleaved)
25
+ * - Output: N × M channels (interleaved, channel-major layout)
26
+ * - Layout: [ (Ch1_Band1, Ch1_Band2, ...), (Ch2_Band1, Ch2_Band2, ...) ]
27
+ *
28
+ * Optimization Strategy (Planar-Core):
29
+ * 1. De-interleave input to planar scratch buffers (cache-efficient)
30
+ * 2. Process filters on contiguous planar data (SIMD-friendly)
31
+ * 3. Interleave output back to standard format
32
+ *
33
+ * Use Cases:
34
+ * - Speech recognition: Mel-scale filter banks (20-40 bands)
35
+ * - Audio compression: Bark-scale for psychoacoustic analysis
36
+ * - Musical analysis: Octave bands (log scale)
37
+ * - Research: Linear-scale frequency analysis
38
+ *
39
+ * @example
40
+ * // 2 input channels → 20 output channels (10 bands per channel)
41
+ * // Input: [L, R, L, R, ...]
42
+ * // Output: [L_B1, L_B2, ..., L_B10, R_B1, R_B2, ..., R_B10, ...]
43
+ */
44
+ class FilterBankStage : public dsp::IDspStage
45
+ {
46
+ public:
47
+ /**
48
+ * @brief Construct a filter bank stage
49
+ * @param bandDefinitions Array of filter coefficient definitions (one per band)
50
+ * @param numInputChannels Number of input channels
51
+ */
52
+ FilterBankStage(const std::vector<FilterDefinition> &bandDefinitions, int numInputChannels);
53
+
54
+ /**
55
+ * @brief Destructor - ensures proper cleanup of filters and scratch buffers
56
+ */
57
+ virtual ~FilterBankStage();
58
+
59
+ const char *getType() const override { return "filterBank"; }
60
+
61
+ /**
62
+ * @brief This stage expands the buffer size (N channels → N×M channels)
63
+ * @return Always true
64
+ */
65
+ bool isResizing() const override { return true; }
66
+
67
+ /**
68
+ * @brief Returns output channel count = inputChannels × numBands
69
+ * @return Output channel count
70
+ */
71
+ int getOutputChannels() const override
72
+ {
73
+ return m_numInputChannels * static_cast<int>(m_definitions.size());
74
+ }
75
+
76
+ /**
77
+ * @brief Calculate output buffer size for resizing
78
+ * @param inputSize Input buffer size in samples
79
+ * @return Output buffer size in samples
80
+ */
81
+ size_t calculateOutputSize(size_t inputSize) const override
82
+ {
83
+ return (inputSize / m_numInputChannels) * getOutputChannels();
84
+ }
85
+
86
+ /**
87
+ * @brief Standard process() throws because this stage requires resizing
88
+ */
89
+ void process(float *buffer, size_t numSamples, int numChannels, const float *timestamps) override
90
+ {
91
+ throw std::runtime_error("FilterBank requires processResizing");
92
+ }
93
+
94
+ /**
95
+ * @brief Process audio data with buffer resizing
96
+ *
97
+ * Algorithm:
98
+ * 1. De-interleave input to planar scratch buffers
99
+ * 2. Apply each band's filter to each input channel (planar processing)
100
+ * 3. Interleave results to output in channel-major order
101
+ *
102
+ * @param inputBuffer Source interleaved buffer
103
+ * @param inputSize Input size in samples
104
+ * @param outputBuffer Destination buffer (caller must allocate)
105
+ * @param outputSize Reference to store actual output size
106
+ * @param numChannels Number of input channels
107
+ * @param timestamps Optional timestamp array
108
+ */
109
+ void processResizing(const float *inputBuffer, size_t inputSize,
110
+ float *outputBuffer, size_t &outputSize,
111
+ int numChannels, const float *timestamps) override;
112
+
113
+ /**
114
+ * @brief Reset all filter states to initial conditions
115
+ */
116
+ void reset() override;
117
+
118
+ /**
119
+ * @brief Serialize filter bank state to JavaScript object
120
+ * @param env N-API environment
121
+ * @return Napi::Object containing all filter states
122
+ */
123
+ Napi::Object serializeState(Napi::Env env) const override;
124
+
125
+ /**
126
+ * @brief Restore filter bank state from JavaScript object
127
+ * @param state Napi::Object containing serialized filter states
128
+ */
129
+ void deserializeState(const Napi::Object &state) override;
130
+
131
+ /**
132
+ * @brief Serialize state to TOON binary format
133
+ * @param serializer TOON serializer instance
134
+ */
135
+ void serializeToon(toon::Serializer &serializer) const override;
136
+
137
+ /**
138
+ * @brief Deserialize state from TOON binary format
139
+ * @param deserializer TOON deserializer instance
140
+ */
141
+ void deserializeToon(toon::Deserializer &deserializer) override;
142
+
143
+ private:
144
+ /**
145
+ * @brief Initialize the 2D matrix of IIR filters [channel][band]
146
+ */
147
+ void initializeFilters();
148
+
149
+ /**
150
+ * @brief Ensure scratch buffers are allocated and sized correctly
151
+ * @param samplesPerChannel Number of samples per channel to process
152
+ */
153
+ void ensureScratchSize(size_t samplesPerChannel);
154
+
155
+ // Configuration
156
+ int m_numInputChannels;
157
+ std::vector<FilterDefinition> m_definitions;
158
+
159
+ // 2D Matrix of filters: m_filters[channelIndex][bandIndex]
160
+ std::vector<std::vector<std::unique_ptr<dsp::core::IirFilter<float>>>> m_filters;
161
+
162
+ // Optimization: Persistent scratch buffers in planar layout
163
+ // m_planarInput[channel][sample] - de-interleaved input
164
+ std::vector<std::vector<float>> m_planarInput;
165
+
166
+ // m_planarOutput[flatOutputChannelIndex][sample] - filtered planar data
167
+ std::vector<std::vector<float>> m_planarOutput;
168
+ };
169
+
170
+ } // namespace dsp::adapters
@@ -1,5 +1,6 @@
1
1
  #include "FilterStage.h"
2
2
  #include <stdexcept>
3
+ #include <iostream> // For optional debug logging
3
4
 
4
5
  namespace dsp::adapters
5
6
  {
@@ -303,4 +304,125 @@ namespace dsp::adapters
303
304
  }
304
305
  }
305
306
 
307
+ void FilterStage::serializeToon(dsp::toon::Serializer &s) const
308
+ {
309
+ // Serialize filter type
310
+ s.writeString(m_isFir ? "fir" : "iir");
311
+
312
+ // Serialize coefficients
313
+ s.writeInt32(static_cast<int32_t>(m_bCoeffs.size()));
314
+ for (double coeff : m_bCoeffs)
315
+ {
316
+ s.writeDouble(coeff);
317
+ }
318
+
319
+ s.writeInt32(static_cast<int32_t>(m_aCoeffs.size()));
320
+ for (double coeff : m_aCoeffs)
321
+ {
322
+ s.writeDouble(coeff);
323
+ }
324
+
325
+ // Serialize filter states
326
+ if (m_isFir)
327
+ {
328
+ s.writeInt32(static_cast<int32_t>(m_firFilters.size()));
329
+ for (const auto &filter : m_firFilters)
330
+ {
331
+ if (filter)
332
+ {
333
+ auto [stateBuffer, stateIndex] = filter->getState();
334
+ s.writeFloatArray(stateBuffer);
335
+ s.writeInt32(static_cast<int32_t>(stateIndex));
336
+ }
337
+ else
338
+ {
339
+ s.writeInt32(0); // Empty state buffer size
340
+ s.writeInt32(0); // stateIndex
341
+ }
342
+ }
343
+ }
344
+ else // IIR
345
+ {
346
+ s.writeInt32(static_cast<int32_t>(m_iirFilters.size()));
347
+ for (const auto &filter : m_iirFilters)
348
+ {
349
+ if (filter)
350
+ {
351
+ auto [xState, yState] = filter->getState();
352
+ s.writeFloatArray(xState);
353
+ s.writeFloatArray(yState);
354
+ }
355
+ else
356
+ {
357
+ s.writeInt32(0); // Empty xState size
358
+ s.writeInt32(0); // Empty yState size
359
+ }
360
+ }
361
+ }
362
+ }
363
+
364
+ void FilterStage::deserializeToon(dsp::toon::Deserializer &d)
365
+ {
366
+ // Deserialize filter type
367
+ std::string filterType = d.readString();
368
+ bool isFir = (filterType == "fir");
369
+
370
+ if (isFir != m_isFir)
371
+ {
372
+ throw std::runtime_error("FilterStage TOON load: filter type mismatch");
373
+ }
374
+
375
+ // Deserialize and validate coefficients
376
+ int32_t bSize = d.readInt32();
377
+ if (bSize != static_cast<int32_t>(m_bCoeffs.size()))
378
+ {
379
+ throw std::runtime_error("FilterStage TOON load: bCoeffs size mismatch");
380
+ }
381
+ for (int32_t i = 0; i < bSize; ++i)
382
+ {
383
+ d.readDouble(); // Skip validation for now
384
+ }
385
+
386
+ int32_t aSize = d.readInt32();
387
+ if (aSize != static_cast<int32_t>(m_aCoeffs.size()))
388
+ {
389
+ throw std::runtime_error("FilterStage TOON load: aCoeffs size mismatch");
390
+ }
391
+ for (int32_t i = 0; i < aSize; ++i)
392
+ {
393
+ d.readDouble(); // Skip validation for now
394
+ }
395
+
396
+ // Deserialize filter states
397
+ int32_t numChannels = d.readInt32();
398
+ initializeFilters(numChannels);
399
+
400
+ if (m_isFir)
401
+ {
402
+ for (int32_t i = 0; i < numChannels && i < static_cast<int32_t>(m_firFilters.size()); ++i)
403
+ {
404
+ std::vector<float> stateBuffer = d.readFloatArray();
405
+ int32_t stateIndex = d.readInt32();
406
+
407
+ if (m_firFilters[i] && !stateBuffer.empty())
408
+ {
409
+ m_firFilters[i]->setState(stateBuffer, static_cast<size_t>(stateIndex));
410
+ }
411
+ }
412
+ }
413
+ else // IIR
414
+ {
415
+ for (int32_t i = 0; i < numChannels && i < static_cast<int32_t>(m_iirFilters.size()); ++i)
416
+ {
417
+ std::vector<float> xState = d.readFloatArray();
418
+ std::vector<float> yState = d.readFloatArray();
419
+
420
+ if (m_iirFilters[i] && !xState.empty())
421
+ {
422
+ m_iirFilters[i]->setState(xState, yState);
423
+ }
424
+ }
425
+ }
426
+ }
427
+
306
428
  } // namespace dsp::adapters
@@ -3,6 +3,7 @@
3
3
  #include "../IDspStage.h"
4
4
  #include "../core/IirFilter.h"
5
5
  #include "../core/FirFilter.h"
6
+ #include "../utils/Toon.h"
6
7
  #include <memory>
7
8
  #include <vector>
8
9
  #include <string>
@@ -23,6 +24,9 @@ namespace dsp::adapters
23
24
  Napi::Object serializeState(Napi::Env env) const override;
24
25
  void deserializeState(const Napi::Object &state) override;
25
26
 
27
+ void serializeToon(dsp::toon::Serializer &serializer) const override;
28
+ void deserializeToon(dsp::toon::Deserializer &deserializer) override;
29
+
26
30
  private:
27
31
  void initializeFilters(int numChannels);
28
32
 
@@ -31,6 +31,7 @@
31
31
  #include "../core/FftEngine.h"
32
32
  #include "../utils/CircularBufferArray.h"
33
33
  #include "../utils/SimdOps.h"
34
+ #include "../utils/Toon.h"
34
35
  #include <vector>
35
36
  #include <complex>
36
37
  #include <memory>
@@ -199,6 +200,60 @@ namespace dsp::adapters
199
200
  }
200
201
  }
201
202
 
203
+ inline void serializeToon(dsp::toon::Serializer &s) const override
204
+ {
205
+ // Write configuration
206
+ s.writeInt32(static_cast<int32_t>(m_window_size));
207
+ s.writeInt32(static_cast<int32_t>(m_hop_size));
208
+ s.writeInt32(static_cast<int32_t>(m_channel_buffers.size()));
209
+
210
+ // Write per-channel state
211
+ for (size_t i = 0; i < m_channel_buffers.size(); ++i)
212
+ {
213
+ std::vector<float> buffer_data = m_channel_buffers[i].toVector();
214
+ s.writeFloatArray(buffer_data);
215
+ s.writeInt32(static_cast<int32_t>(m_samples_since_output[i]));
216
+ }
217
+ }
218
+
219
+ inline void deserializeToon(dsp::toon::Deserializer &d) override
220
+ {
221
+ // Read configuration
222
+ int32_t window_size = d.readInt32();
223
+ int32_t hop_size = d.readInt32();
224
+
225
+ if (window_size <= 0 || hop_size <= 0)
226
+ throw std::runtime_error("Invalid window/hop size in HilbertEnvelopeStage deserialization");
227
+
228
+ if (static_cast<size_t>(window_size) != m_window_size || static_cast<size_t>(hop_size) != m_hop_size)
229
+ throw std::runtime_error("Window/hop size mismatch in HilbertEnvelopeStage deserialization");
230
+
231
+ int32_t num_channels = d.readInt32();
232
+ if (num_channels < 0)
233
+ throw std::runtime_error("Invalid num_channels in HilbertEnvelopeStage deserialization");
234
+
235
+ // Recreate channel buffers
236
+ m_channel_buffers.clear();
237
+ m_samples_since_output.clear();
238
+
239
+ for (int32_t i = 0; i < num_channels; ++i)
240
+ {
241
+ m_channel_buffers.emplace_back(m_window_size);
242
+ m_samples_since_output.push_back(0);
243
+ }
244
+
245
+ // Restore per-channel state
246
+ for (int32_t i = 0; i < num_channels; ++i)
247
+ {
248
+ std::vector<float> buffer_data = d.readFloatArray();
249
+ for (float value : buffer_data)
250
+ {
251
+ m_channel_buffers[i].push(value);
252
+ }
253
+ m_samples_since_output[i] = static_cast<size_t>(d.readInt32());
254
+ }
255
+ }
256
+
202
257
  void reset() override
203
258
  {
204
259
  for (auto &buffer : m_channel_buffers)
@@ -2,6 +2,7 @@
2
2
  #define INTEGRATOR_STAGE_H
3
3
 
4
4
  #include "../IDspStage.h"
5
+ #include "../utils/Toon.h"
5
6
  #include <vector>
6
7
  #include <cmath>
7
8
  #include <stdexcept>
@@ -129,6 +130,20 @@ namespace dsp::adapters
129
130
  std::fill(m_prev_output.begin(), m_prev_output.end(), 0.0f);
130
131
  }
131
132
 
133
+ void serializeToon(dsp::toon::Serializer &s) const override
134
+ {
135
+ s.writeFloat(m_alpha);
136
+ s.writeInt32(m_num_channels);
137
+ s.writeFloatArray(m_prev_output);
138
+ }
139
+
140
+ void deserializeToon(dsp::toon::Deserializer &d) override
141
+ {
142
+ m_alpha = d.readFloat();
143
+ m_num_channels = d.readInt32();
144
+ m_prev_output = d.readFloatArray();
145
+ }
146
+
132
147
  bool isResizing() const override
133
148
  {
134
149
  return false;
@@ -8,6 +8,7 @@
8
8
  #pragma once
9
9
 
10
10
  #include "../IDspStage.h"
11
+ #include "../utils/Toon.h"
11
12
  #include <vector>
12
13
  #include <cmath>
13
14
  #include <stdexcept>
@@ -154,6 +155,56 @@ namespace dsp
154
155
  }
155
156
  }
156
157
 
158
+ void serializeToon(dsp::toon::Serializer &s) const override
159
+ {
160
+ s.writeInt32(interpolationFactor_);
161
+ s.writeInt32(filterOrder_);
162
+ s.writeDouble(sampleRate_);
163
+ s.writeInt32(numChannels_);
164
+
165
+ // Serialize state buffers for each channel
166
+ for (const auto &buf : stateBuffers_)
167
+ {
168
+ s.writeFloatArray(buf);
169
+ }
170
+
171
+ // Serialize state indices
172
+ for (size_t idx : stateIndices_)
173
+ {
174
+ s.writeInt32(static_cast<int32_t>(idx));
175
+ }
176
+ }
177
+
178
+ void deserializeToon(dsp::toon::Deserializer &d) override
179
+ {
180
+ int32_t factor = d.readInt32();
181
+ int32_t order = d.readInt32();
182
+ double sr = d.readDouble();
183
+ int32_t nCh = d.readInt32();
184
+
185
+ if (factor != interpolationFactor_ || order != filterOrder_)
186
+ {
187
+ throw std::runtime_error("Interpolator TOON: parameter mismatch");
188
+ }
189
+
190
+ if (nCh != numChannels_)
191
+ {
192
+ initializeStateBuffers(nCh);
193
+ }
194
+
195
+ // Deserialize state buffers
196
+ for (auto &buf : stateBuffers_)
197
+ {
198
+ buf = d.readFloatArray();
199
+ }
200
+
201
+ // Deserialize state indices
202
+ for (size_t &idx : stateIndices_)
203
+ {
204
+ idx = static_cast<size_t>(d.readInt32());
205
+ }
206
+ }
207
+
157
208
  private:
158
209
  int interpolationFactor_;
159
210
  int filterOrder_;
@@ -3,6 +3,7 @@
3
3
 
4
4
  #include "../IDspStage.h"
5
5
  #include "../utils/SimdOps.h"
6
+ #include "../utils/Toon.h"
6
7
  #include <vector>
7
8
  #include <cmath>
8
9
  #include <stdexcept>
@@ -321,6 +322,45 @@ namespace dsp
321
322
  }
322
323
  }
323
324
 
325
+ inline void serializeToon(dsp::toon::Serializer &s) const override
326
+ {
327
+ // Write configuration
328
+ s.writeInt32(static_cast<int32_t>(m_windowSize));
329
+ s.writeInt32(static_cast<int32_t>(m_numChannels));
330
+
331
+ // Write per-channel state
332
+ for (size_t ch = 0; ch < m_numChannels; ++ch)
333
+ {
334
+ s.writeFloatArray(m_buffers[ch]);
335
+ s.writeInt32(static_cast<int32_t>(m_writeIndices[ch]));
336
+ s.writeInt32(static_cast<int32_t>(m_sampleCounts[ch]));
337
+ }
338
+ }
339
+
340
+ inline void deserializeToon(dsp::toon::Deserializer &d) override
341
+ {
342
+ // Read configuration
343
+ int32_t windowSize = d.readInt32();
344
+ if (windowSize < 2)
345
+ throw std::runtime_error("Invalid windowSize in LinearRegressionStage deserialization");
346
+ if (static_cast<size_t>(windowSize) != m_windowSize)
347
+ throw std::runtime_error("Window size mismatch in LinearRegressionStage deserialization");
348
+
349
+ int32_t numChannels = d.readInt32();
350
+ if (numChannels < 0)
351
+ throw std::runtime_error("Invalid numChannels in LinearRegressionStage deserialization");
352
+
353
+ init(static_cast<size_t>(numChannels));
354
+
355
+ // Read per-channel state
356
+ for (size_t ch = 0; ch < m_numChannels; ++ch)
357
+ {
358
+ m_buffers[ch] = d.readFloatArray();
359
+ m_writeIndices[ch] = static_cast<size_t>(d.readInt32());
360
+ m_sampleCounts[ch] = static_cast<size_t>(d.readInt32());
361
+ }
362
+ }
363
+
324
364
  void reset() override
325
365
  {
326
366
  for (size_t ch = 0; ch < m_numChannels; ++ch)
@@ -176,6 +176,69 @@ namespace dsp
176
176
  }
177
177
  }
178
178
 
179
+ void serializeToon(dsp::toon::Serializer &s) const override
180
+ {
181
+ s.startObject();
182
+
183
+ s.writeString("numTaps");
184
+ s.writeInt32(static_cast<int32_t>(m_numTaps));
185
+
186
+ s.writeString("learningRate");
187
+ s.writeFloat(m_learningRate);
188
+
189
+ s.writeString("normalized");
190
+ s.writeBool(m_normalized);
191
+
192
+ s.writeString("lambda");
193
+ s.writeFloat(m_lambda);
194
+
195
+ s.writeString("initialized");
196
+ s.writeBool(m_initialized);
197
+
198
+ if (m_initialized)
199
+ {
200
+ auto weights = m_filter->getWeights(0);
201
+ s.writeString("weights");
202
+ s.writeFloatArray(weights);
203
+ }
204
+
205
+ s.endObject();
206
+ }
207
+
208
+ void deserializeToon(dsp::toon::Deserializer &d) override
209
+ {
210
+ d.consumeToken(dsp::toon::T_OBJECT_START);
211
+
212
+ std::string key = d.readString(); // "numTaps"
213
+ m_numTaps = d.readInt32();
214
+
215
+ key = d.readString(); // "learningRate"
216
+ m_learningRate = d.readFloat();
217
+
218
+ key = d.readString(); // "normalized"
219
+ m_normalized = d.readBool();
220
+
221
+ key = d.readString(); // "lambda"
222
+ m_lambda = d.readFloat();
223
+
224
+ key = d.readString(); // "initialized"
225
+ m_initialized = d.readBool();
226
+
227
+ // Recreate filter with restored parameters
228
+ m_filter = std::make_unique<dsp::core::DifferentiableFilter<float>>(m_numTaps, m_learningRate, m_lambda, m_normalized);
229
+
230
+ if (m_initialized)
231
+ {
232
+ m_filter->init(1); // Reinitialize filter
233
+
234
+ key = d.readString(); // "weights"
235
+ std::vector<float> weights = d.readFloatArray();
236
+ m_filter->setWeights(0, weights);
237
+ }
238
+
239
+ d.consumeToken(dsp::toon::T_OBJECT_END);
240
+ }
241
+
179
242
  private:
180
243
  void ensureScratchBuffers(size_t samplesPerChannel)
181
244
  {