dspx 1.4.2 → 1.4.8

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 CHANGED
@@ -11,6 +11,8 @@
11
11
 
12
12
  A modern DSP library built for Node.js backends processing real-time biosignals, audio streams, and sensor data. Features native C++ filters with full state serialization (to Redis, S3, or any storage backend), enabling seamless processing across service restarts and distributed workers.
13
13
 
14
+ [View the benchmarks](https://github.com/A-KGeorge/dspx-benchmark/)
15
+
14
16
  ---
15
17
 
16
18
  ## ✨ Features
@@ -475,7 +477,7 @@ const clean = notch.process(noisySignal);
475
477
  import { createDspPipeline, Convolution } from "dspx";
476
478
  const pipeline = createDspPipeline();
477
479
  pipeline.addStage(
478
- new Convolution({ kernel: OPTIMAL_LOWPASS_COEFFS.cutoff_0_2 })
480
+ new Convolution({ kernel: OPTIMAL_LOWPASS_COEFFS.cutoff_0_2 }),
479
481
  );
480
482
 
481
483
  // ✅ Zero Python dependency - coefficients ship with the library!
@@ -1661,7 +1663,7 @@ const saveBreaker = new CircuitBreaker(
1661
1663
  timeout: 2000, // Fail if >2s
1662
1664
  errorThresholdPercentage: 50, // Trip after 50% failures
1663
1665
  resetTimeout: 30000, // Try recovery after 30s
1664
- }
1666
+ },
1665
1667
  );
1666
1668
 
1667
1669
  saveBreaker.fallback(() => {
package/binding.gyp CHANGED
@@ -93,7 +93,7 @@
93
93
  'OTHER_CPLUSPLUSFLAGS+': [ '-msse3', '-mavx', '-mavx2' ]
94
94
  }
95
95
  }],
96
- # Condition for arm64 architecture (Android, iOS, M1/M2 Macs, Tensor G4, etc.)
96
+ # Condition for arm64 architecture (Android, iOS, M1/M2 Macs, etc.)
97
97
  ['target_arch=="arm64"', {
98
98
  # ARMv8-a baseline: NEON + FP support (compatible with all ARMv8 CPUs)
99
99
  "cflags+": [ "-march=armv8-a+fp+simd" ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dspx",
3
- "version": "1.4.2",
3
+ "version": "1.4.8",
4
4
  "description": "High-performance DSP library with native C++ acceleration and Redis state persistence",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "build:ts": "tsc",
22
22
  "build:native": "node-gyp rebuild",
23
23
  "build": "npm run build:native && npm run build:ts",
24
- "prebuildify": "prebuildify --napi --strip --target 18.0.0 --target 20.0.0 --target 22.0.0",
24
+ "prebuildify": "prebuildify --napi --strip --target 18.0.0 --target 20.0.0 --target 22.0.0 --target 24.0.0",
25
25
  "changeset": "changeset",
26
26
  "version": "changeset version",
27
27
  "publish-packages": "changeset publish"
@@ -33,7 +33,11 @@
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",
36
- "url": "https://github.com/A-KGeorge/dspx"
36
+ "url": "git+https://github.com/A-KGeorge/dspx.git"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "provenance": true
37
41
  },
38
42
  "keywords": [
39
43
  "dsp",
@@ -47,6 +51,10 @@
47
51
  "author": "Alan Kochukalam George",
48
52
  "license": "Apache-2.0",
49
53
  "type": "module",
54
+ "engines": {
55
+ "node": ">=18.0.0",
56
+ "npm": ">=11.5.1"
57
+ },
50
58
  "dependencies": {
51
59
  "cross-env": "^7.0.3",
52
60
  "node-addon-api": "^8.5.0",
Binary file
@@ -318,6 +318,19 @@ namespace dsp
318
318
  template <typename T>
319
319
  std::pair<std::vector<T>, size_t> FirFilter<T>::getState() const
320
320
  {
321
+ #if defined(__ARM_NEON) || defined(__aarch64__)
322
+ if (m_useNeon && m_neonFilter)
323
+ {
324
+ // Use NEON filter's linearization on ARM
325
+ auto [linearState, stateIndex] = m_neonFilter->exportLinearState();
326
+ std::vector<T> state(linearState.size());
327
+ for (size_t i = 0; i < linearState.size(); ++i)
328
+ {
329
+ state[i] = static_cast<T>(linearState[i]);
330
+ }
331
+ return {state, stateIndex};
332
+ }
333
+ #endif
321
334
  return {m_state, m_stateIndex};
322
335
  }
323
336
 
@@ -339,6 +352,19 @@ namespace dsp
339
352
  throw std::invalid_argument("stateIndex out of range");
340
353
  }
341
354
 
355
+ #if defined(__ARM_NEON) || defined(__aarch64__)
356
+ if (m_useNeon && m_neonFilter)
357
+ {
358
+ // Convert to float vector for NEON filter's linearization
359
+ std::vector<float> floatState(state.size());
360
+ for (size_t i = 0; i < state.size(); ++i)
361
+ {
362
+ floatState[i] = static_cast<float>(state[i]);
363
+ }
364
+ m_neonFilter->importLinearState(floatState, stateIndex);
365
+ return;
366
+ }
367
+ #endif
342
368
  m_state = state;
343
369
  m_stateIndex = stateIndex;
344
370
  }
@@ -227,6 +227,66 @@ namespace dsp::core
227
227
  m_samplesProcessed = 0;
228
228
  }
229
229
 
230
+ /**
231
+ * @brief Export state in linear format (oldest->newest) for serialization
232
+ * @return Pair of linear state vector and state index
233
+ */
234
+ std::pair<std::vector<float>, size_t> exportLinearState() const
235
+ {
236
+ #if defined(__ARM_NEON) || defined(__aarch64__)
237
+ const float *state = getState();
238
+ std::vector<float> linearState(m_bufferSize, 0.0f);
239
+
240
+ // Calculate oldest sample position in circular buffer
241
+ size_t oldestPos = (m_head >= m_numTaps - 1)
242
+ ? m_head - (m_numTaps - 1)
243
+ : m_head + m_bufferSize - (m_numTaps - 1);
244
+
245
+ // Un-rotate: copy from oldest->newest into linear array
246
+ for (size_t i = 0; i < m_bufferSize; ++i)
247
+ {
248
+ linearState[i] = state[(oldestPos + i) & m_headMask];
249
+ }
250
+
251
+ return {linearState, m_numTaps - 1};
252
+ #else
253
+ // Scalar path: state is already linear
254
+ const float *state = getState();
255
+ std::vector<float> linearState(state, state + m_bufferSize);
256
+ return {linearState, m_head};
257
+ #endif
258
+ }
259
+
260
+ /**
261
+ * @brief Import state from linear format (oldest->newest) after deserialization
262
+ * @param linearState Linear state vector (oldest->newest)
263
+ * @param stateIndex State index (number of valid samples - 1)
264
+ */
265
+ void importLinearState(const std::vector<float> &linearState, size_t stateIndex)
266
+ {
267
+ #if defined(__ARM_NEON) || defined(__aarch64__)
268
+ float *state = getState();
269
+
270
+ // Copy linear state into circular buffer
271
+ for (size_t i = 0; i < m_bufferSize && i < linearState.size(); ++i)
272
+ {
273
+ state[i] = linearState[i];
274
+ state[i + m_bufferSize] = linearState[i]; // Guard zone
275
+ }
276
+
277
+ // Set head to point where newest sample will be written
278
+ m_head = stateIndex;
279
+ #else
280
+ // Scalar path: direct copy
281
+ float *state = getState();
282
+ for (size_t i = 0; i < m_bufferSize && i < linearState.size(); ++i)
283
+ {
284
+ state[i] = linearState[i];
285
+ }
286
+ m_head = stateIndex;
287
+ #endif
288
+ }
289
+
230
290
  size_t getNumTaps() const { return m_numTaps; }
231
291
  size_t getBufferSize() const { return m_bufferSize; }
232
292