dspx 1.1.2 → 1.1.3

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/binding.gyp CHANGED
@@ -26,7 +26,8 @@
26
26
  "src/native/utils/NapiUtils.cc",
27
27
  "src/native/utils/SlidingWindowFilter.cc",
28
28
  "src/native/utils/TimeSeriesBuffer.cc",
29
- "src/native/adapters/FilterStage.cc"
29
+ "src/native/adapters/FilterStage.cc",
30
+ "src/native/adapters/FftStage.cc"
30
31
  ],
31
32
  "include_dirs": [
32
33
  "<!@(node -p \"require('node-addon-api').include\")",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/ts/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,EACjB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,0BAA0B,EAC1B,mBAAmB,EACnB,MAAM,EACN,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,aAAa,EACb,UAAU,EACV,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,QAAQ,EACR,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,OAAO,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,GACzB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,2BAA2B,EAC3B,aAAa,EACb,uBAAuB,EACvB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAClD,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,SAAS,EACT,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EAGvB,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,YAAY,EAGZ,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,aAAa,EACb,mBAAmB,EACnB,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EAGjB,eAAe,EACf,eAAe,EAGf,WAAW,EACX,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,YAAY,EACZ,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/ts/index.ts"],"names":[],"mappings":"AA8BA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,EACjB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,0BAA0B,EAC1B,mBAAmB,EACnB,MAAM,EACN,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,aAAa,EACb,UAAU,EACV,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,QAAQ,EACR,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,OAAO,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,GACzB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,2BAA2B,EAC3B,aAAa,EACb,uBAAuB,EACvB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAClD,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,YAAY,EACZ,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,SAAS,EACT,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EAGvB,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,YAAY,EAGZ,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,aAAa,EACb,mBAAmB,EACnB,wBAAwB,EACxB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EAGjB,eAAe,EACf,eAAe,EAGf,WAAW,EACX,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,YAAY,EACZ,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,28 @@
1
+ // Global error handlers for unhandled promise rejections
2
+ // This prevents the library from crashing Node.js processes with exit code 1
3
+ if (typeof process !== "undefined" && process.on) {
4
+ // Only add handlers if they haven't been added yet (check for existing listeners)
5
+ const existingHandlers = process.listenerCount("unhandledRejection");
6
+ if (existingHandlers === 0) {
7
+ process.on("unhandledRejection", (reason, promise) => {
8
+ console.error("[dspx] Unhandled Promise Rejection:", reason);
9
+ if (reason instanceof Error) {
10
+ console.error("[dspx] Stack trace:", reason.stack);
11
+ }
12
+ // Don't crash the process - log and continue
13
+ });
14
+ // Handle uncaught exceptions gracefully
15
+ const existingExceptionHandlers = process.listenerCount("uncaughtException");
16
+ if (existingExceptionHandlers === 0) {
17
+ process.on("uncaughtException", (error) => {
18
+ console.error("[dspx] Uncaught Exception:", error.message);
19
+ console.error("[dspx] Stack trace:", error.stack);
20
+ // Exit with error code since this is more serious
21
+ process.exit(1);
22
+ });
23
+ }
24
+ }
25
+ }
1
26
  // Export the main API
2
27
  export { createDspPipeline, DspProcessor } from "./bindings.js";
3
28
  export { TopicRouter, TopicRouterBuilder, createTopicRouter, } from "./TopicRouter.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/ts/index.ts"],"names":[],"mappings":"AAAA,sBAAsB;AACtB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,EACjB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,0BAA0B,EAC1B,mBAAmB,EACnB,MAAM,EACN,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,GASpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,aAAa,EACb,UAAU,EACV,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,QAAQ,GAIT,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,SAAS,EACT,SAAS,EACT,iBAAiB,GASlB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,2BAA2B,EAC3B,aAAa,EACb,uBAAuB,EACvB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/ts/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,6EAA6E;AAC7E,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;IACjD,kFAAkF;IAClF,MAAM,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;IAErE,IAAI,gBAAgB,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAW,EAAE,OAAqB,EAAE,EAAE;YACtE,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,MAAM,CAAC,CAAC;YAC7D,IAAI,MAAM,YAAY,KAAK,EAAE,CAAC;gBAC5B,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACrD,CAAC;YACD,6CAA6C;QAC/C,CAAC,CAAC,CAAC;QAEH,wCAAwC;QACxC,MAAM,yBAAyB,GAC7B,OAAO,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC;QAC7C,IAAI,yBAAyB,KAAK,CAAC,EAAE,CAAC;YACpC,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,KAAY,EAAE,EAAE;gBAC/C,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC3D,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;gBAClD,kDAAkD;gBAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;AACH,CAAC;AAED,sBAAsB;AACtB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,EACjB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,0BAA0B,EAC1B,mBAAmB,EACnB,MAAM,EACN,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,GASpB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,aAAa,EACb,UAAU,EACV,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,QAAQ,GAIT,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,SAAS,EACT,SAAS,EACT,iBAAiB,GASlB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,2BAA2B,EAC3B,aAAa,EACb,uBAAuB,EACvB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dspx",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
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",
Binary file
@@ -19,6 +19,7 @@
19
19
  #include "adapters/WaveletTransformStage.h" // Wavelet Transform stage
20
20
  #include "adapters/HilbertEnvelopeStage.h" // Hilbert Envelope stage
21
21
  #include "adapters/StftStage.h" // STFT (Short-Time Fourier Transform) stage
22
+ #include "adapters/FftStage.h" // FFT (Fast Fourier Transform) stage
22
23
  #include "adapters/MelSpectrogramStage.h" // Mel Spectrogram stage
23
24
  #include "adapters/MfccStage.h" // MFCC (Mel-Frequency Cepstral Coefficients) stage
24
25
  #include "adapters/MatrixTransformStage.h" // Matrix Transform stage (PCA/ICA/Whitening)
@@ -627,6 +628,43 @@ namespace dsp
627
628
  windowSize, hopSize, method, type, forward, output, window);
628
629
  };
629
630
 
631
+ // Factory for FFT (Fast Fourier Transform) stage
632
+ m_stageFactories["fft"] = [](const Napi::Object &params)
633
+ {
634
+ if (!params.Has("size"))
635
+ {
636
+ throw std::invalid_argument("FFT: 'size' is required");
637
+ }
638
+ size_t size = params.Get("size").As<Napi::Number>().Uint32Value();
639
+
640
+ // Parse transform type (fft, dft, rfft, rdft, etc.)
641
+ std::string typeStr = "rfft"; // Default: real FFT
642
+ if (params.Has("type"))
643
+ {
644
+ typeStr = params.Get("type").As<Napi::String>().Utf8Value();
645
+ }
646
+ dsp::adapters::FftStage::TransformType type =
647
+ dsp::adapters::FftStage::parseTransformType(typeStr);
648
+
649
+ // Parse forward/inverse
650
+ bool forward = true; // Default: forward
651
+ if (params.Has("forward"))
652
+ {
653
+ forward = params.Get("forward").As<Napi::Boolean>().Value();
654
+ }
655
+
656
+ // Parse output format
657
+ std::string outputStr = "magnitude"; // Default: magnitude
658
+ if (params.Has("output"))
659
+ {
660
+ outputStr = params.Get("output").As<Napi::String>().Utf8Value();
661
+ }
662
+ dsp::adapters::FftStage::OutputFormat output =
663
+ dsp::adapters::FftStage::parseOutputFormat(outputStr);
664
+
665
+ return std::make_unique<dsp::adapters::FftStage>(size, type, forward, output);
666
+ };
667
+
630
668
  // Factory for Mel Spectrogram stage
631
669
  m_stageFactories["melSpectrogram"] = [](const Napi::Object &params)
632
670
  {
@@ -0,0 +1,441 @@
1
+ /**
2
+ * FFT Stage Implementation
3
+ */
4
+
5
+ #include "FftStage.h"
6
+ #include <cstring>
7
+ #include <stdexcept>
8
+
9
+ namespace dsp
10
+ {
11
+ namespace adapters
12
+ {
13
+
14
+ FftStage::FftStage(size_t size, TransformType type, bool forward, OutputFormat format)
15
+ : m_fftSize(size), m_type(type), m_forward(forward), m_format(format)
16
+ {
17
+ if (m_fftSize == 0)
18
+ {
19
+ throw std::invalid_argument("FFT size must be greater than 0");
20
+ }
21
+
22
+ // Create FFT engine
23
+ m_engine = std::make_unique<core::FftEngine<float>>(m_fftSize);
24
+
25
+ // Check power-of-2 requirement for FFT/RFFT
26
+ if ((m_type == TransformType::FFT || m_type == TransformType::RFFT) && !m_engine->isPowerOfTwo())
27
+ {
28
+ throw std::invalid_argument("FFT/RFFT requires power-of-2 size. Use DFT/RDFT for arbitrary sizes.");
29
+ }
30
+
31
+ // Allocate working buffers
32
+ m_complexBuffer.resize(m_fftSize);
33
+ m_tempComplexBuffer.resize(m_fftSize);
34
+ m_realBuffer.resize(m_fftSize);
35
+ }
36
+
37
+ size_t FftStage::calculateOutputSize(size_t inputSize) const
38
+ {
39
+ // Determine transform direction
40
+ bool isInverseComplex = (m_type == TransformType::IFFT || m_type == TransformType::IDFT) ||
41
+ ((m_type == TransformType::FFT || m_type == TransformType::DFT) && !m_forward);
42
+ bool isInverseReal = (m_type == TransformType::IRFFT || m_type == TransformType::IRDFT) ||
43
+ ((m_type == TransformType::RFFT || m_type == TransformType::RDFT) && !m_forward);
44
+ bool isForwardReal = (m_type == TransformType::RFFT || m_type == TransformType::RDFT) && m_forward;
45
+
46
+ // Calculate input size per frame
47
+ size_t inputSizePerFrame;
48
+ if (isInverseComplex)
49
+ {
50
+ inputSizePerFrame = m_fftSize * 2; // Complex input
51
+ }
52
+ else if (isInverseReal)
53
+ {
54
+ inputSizePerFrame = m_engine->getHalfSize() * 2; // Half-spectrum complex input
55
+ }
56
+ else
57
+ {
58
+ inputSizePerFrame = m_fftSize; // Real input
59
+ }
60
+
61
+ size_t numFrames = inputSize / inputSizePerFrame;
62
+
63
+ // Calculate output size per frame
64
+ size_t outputSizePerFrame;
65
+ if (isForwardReal)
66
+ {
67
+ outputSizePerFrame = m_engine->getHalfSize();
68
+ if (m_format == OutputFormat::COMPLEX)
69
+ {
70
+ outputSizePerFrame *= 2;
71
+ }
72
+ }
73
+ else if (isInverseReal || isInverseComplex)
74
+ {
75
+ // All inverse transforms output real time-domain values
76
+ outputSizePerFrame = m_fftSize;
77
+ }
78
+ else if (m_format == OutputFormat::COMPLEX)
79
+ {
80
+ // Forward complex transforms
81
+ outputSizePerFrame = m_fftSize * 2;
82
+ }
83
+ else
84
+ {
85
+ outputSizePerFrame = m_fftSize;
86
+ }
87
+
88
+ return numFrames * outputSizePerFrame;
89
+ }
90
+
91
+ void FftStage::process(
92
+ float *buffer,
93
+ size_t numSamples,
94
+ int numChannels,
95
+ const float *timestamps)
96
+ {
97
+ // FFT is a resizing stage - this shouldn't be called
98
+ // Pipeline will call processResizing() instead
99
+ throw std::runtime_error("FFT stage requires processResizing(), not process()");
100
+ }
101
+
102
+ void FftStage::processResizing(
103
+ const float *inputBuffer,
104
+ size_t inputSize,
105
+ float *outputBuffer,
106
+ size_t &outputSize,
107
+ int numChannels,
108
+ const float *timestamps)
109
+ {
110
+ // Determine transform direction
111
+ bool isInverseComplex = (m_type == TransformType::IFFT || m_type == TransformType::IDFT) ||
112
+ ((m_type == TransformType::FFT || m_type == TransformType::DFT) && !m_forward);
113
+ bool isInverseReal = (m_type == TransformType::IRFFT || m_type == TransformType::IRDFT) ||
114
+ ((m_type == TransformType::RFFT || m_type == TransformType::RDFT) && !m_forward);
115
+ bool isForwardReal = (m_type == TransformType::RFFT || m_type == TransformType::RDFT) && m_forward;
116
+
117
+ // Calculate input/output sizes per frame
118
+ size_t inputSizePerFrame;
119
+ if (isInverseComplex)
120
+ {
121
+ inputSizePerFrame = m_fftSize * 2; // Complex input
122
+ }
123
+ else if (isInverseReal)
124
+ {
125
+ inputSizePerFrame = m_engine->getHalfSize() * 2; // Half-spectrum complex input
126
+ }
127
+ else
128
+ {
129
+ inputSizePerFrame = m_fftSize; // Real input
130
+ }
131
+
132
+ size_t numFrames = inputSize / (inputSizePerFrame * numChannels);
133
+
134
+ // Calculate output size per frame
135
+ size_t outputSizePerFrame;
136
+ if (isForwardReal)
137
+ {
138
+ outputSizePerFrame = m_engine->getHalfSize();
139
+ if (m_format == OutputFormat::COMPLEX)
140
+ {
141
+ outputSizePerFrame *= 2;
142
+ }
143
+ }
144
+ else if (isInverseReal || isInverseComplex)
145
+ {
146
+ // All inverse transforms output real values only
147
+ outputSizePerFrame = m_fftSize;
148
+ }
149
+ else if (m_format == OutputFormat::COMPLEX)
150
+ {
151
+ // Forward complex transforms only
152
+ outputSizePerFrame = m_fftSize * 2;
153
+ }
154
+ else
155
+ {
156
+ outputSizePerFrame = m_fftSize;
157
+ }
158
+
159
+ outputSize = numFrames * outputSizePerFrame * numChannels;
160
+
161
+ for (size_t frame = 0; frame < numFrames; ++frame)
162
+ {
163
+ for (size_t ch = 0; ch < static_cast<size_t>(numChannels); ++ch)
164
+ {
165
+ const float *frameInput = inputBuffer + (frame * inputSizePerFrame * numChannels) + ch;
166
+ float *frameOutput = outputBuffer + (frame * outputSizePerFrame * numChannels) + ch;
167
+
168
+ // Load input based on transform direction
169
+ if (isInverseComplex)
170
+ {
171
+ // Input is complex interleaved: [real0, imag0, real1, imag1, ...]
172
+ for (size_t i = 0; i < m_fftSize; ++i)
173
+ {
174
+ m_complexBuffer[i] = std::complex<float>(
175
+ frameInput[(i * 2) * numChannels], // real part
176
+ frameInput[(i * 2 + 1) * numChannels]); // imag part
177
+ }
178
+ }
179
+ else if (isInverseReal)
180
+ {
181
+ // Input is half-spectrum complex
182
+ size_t halfSize = m_engine->getHalfSize();
183
+ for (size_t i = 0; i < halfSize; ++i)
184
+ {
185
+ m_complexBuffer[i] = std::complex<float>(
186
+ frameInput[(i * 2) * numChannels],
187
+ frameInput[(i * 2 + 1) * numChannels]);
188
+ }
189
+ }
190
+ else
191
+ {
192
+ // Deinterleave real channel data
193
+ for (size_t i = 0; i < m_fftSize; ++i)
194
+ {
195
+ m_realBuffer[i] = frameInput[i * numChannels];
196
+ }
197
+ }
198
+
199
+ // Perform transform based on type and direction
200
+ switch (m_type)
201
+ {
202
+ case TransformType::FFT:
203
+ {
204
+ if (m_forward)
205
+ {
206
+ // Forward FFT: real to complex
207
+ for (size_t i = 0; i < m_fftSize; ++i)
208
+ {
209
+ m_complexBuffer[i] = std::complex<float>(m_realBuffer[i], 0.0f);
210
+ }
211
+ m_engine->fft(m_complexBuffer.data(), m_complexBuffer.data());
212
+ }
213
+ else
214
+ {
215
+ // Inverse FFT: complex input already loaded
216
+ m_engine->ifft(m_complexBuffer.data(), m_complexBuffer.data());
217
+ }
218
+ break;
219
+ }
220
+ case TransformType::IFFT:
221
+ {
222
+ // Complex input already loaded into m_complexBuffer
223
+ m_engine->ifft(m_complexBuffer.data(), m_complexBuffer.data());
224
+ break;
225
+ }
226
+ case TransformType::DFT:
227
+ {
228
+ if (m_forward)
229
+ {
230
+ // Forward DFT: real to complex
231
+ for (size_t i = 0; i < m_fftSize; ++i)
232
+ {
233
+ m_complexBuffer[i] = std::complex<float>(m_realBuffer[i], 0.0f);
234
+ }
235
+ m_engine->dft(m_complexBuffer.data(), m_tempComplexBuffer.data());
236
+ std::copy(m_tempComplexBuffer.begin(), m_tempComplexBuffer.end(), m_complexBuffer.begin());
237
+ }
238
+ else
239
+ {
240
+ // Inverse DFT: complex input already loaded
241
+ m_engine->idft(m_complexBuffer.data(), m_tempComplexBuffer.data());
242
+ std::copy(m_tempComplexBuffer.begin(), m_tempComplexBuffer.end(), m_complexBuffer.begin());
243
+ }
244
+ break;
245
+ }
246
+ case TransformType::IDFT:
247
+ {
248
+ // Complex input already loaded into m_complexBuffer
249
+ m_engine->idft(m_complexBuffer.data(), m_tempComplexBuffer.data());
250
+ std::copy(m_tempComplexBuffer.begin(), m_tempComplexBuffer.end(), m_complexBuffer.begin());
251
+ break;
252
+ }
253
+ case TransformType::RFFT:
254
+ {
255
+ if (m_forward)
256
+ {
257
+ m_engine->rfft(m_realBuffer.data(), m_complexBuffer.data());
258
+ }
259
+ else
260
+ {
261
+ // Inverse RFFT: half-spectrum complex input already loaded
262
+ m_engine->irfft(m_complexBuffer.data(), m_realBuffer.data());
263
+ }
264
+ break;
265
+ }
266
+ case TransformType::IRFFT:
267
+ {
268
+ // Half-spectrum complex input already loaded into m_complexBuffer
269
+ m_engine->irfft(m_complexBuffer.data(), m_realBuffer.data());
270
+ break;
271
+ }
272
+ case TransformType::RDFT:
273
+ {
274
+ if (m_forward)
275
+ {
276
+ m_engine->rdft(m_realBuffer.data(), m_complexBuffer.data());
277
+ }
278
+ else
279
+ {
280
+ // Inverse RDFT: half-spectrum complex input already loaded
281
+ m_engine->irdft(m_complexBuffer.data(), m_realBuffer.data());
282
+ }
283
+ break;
284
+ }
285
+ case TransformType::IRDFT:
286
+ {
287
+ // Half-spectrum complex input already loaded into m_complexBuffer
288
+ m_engine->irdft(m_complexBuffer.data(), m_realBuffer.data());
289
+ break;
290
+ }
291
+ }
292
+
293
+ // Convert to output format
294
+ if (isInverseReal || isInverseComplex)
295
+ {
296
+ // ALL inverse transforms output real time-domain values
297
+ if (isInverseReal)
298
+ {
299
+ // Real inverse transforms: use m_realBuffer directly
300
+ for (size_t i = 0; i < m_fftSize; ++i)
301
+ {
302
+ frameOutput[i * numChannels] = m_realBuffer[i];
303
+ }
304
+ }
305
+ else
306
+ {
307
+ // IFFT/IDFT: extract real parts from complex result
308
+ for (size_t i = 0; i < m_fftSize; ++i)
309
+ {
310
+ frameOutput[i * numChannels] = m_complexBuffer[i].real();
311
+ }
312
+ }
313
+ }
314
+ else
315
+ {
316
+ // Forward transforms only
317
+ switch (m_format)
318
+ {
319
+ case OutputFormat::COMPLEX:
320
+ {
321
+ // Interleave real/imag
322
+ for (size_t i = 0; i < m_fftSize; ++i)
323
+ {
324
+ frameOutput[(i * 2) * numChannels] = m_complexBuffer[i].real();
325
+ frameOutput[(i * 2 + 1) * numChannels] = m_complexBuffer[i].imag();
326
+ }
327
+ break;
328
+ }
329
+ case OutputFormat::MAGNITUDE:
330
+ {
331
+ size_t numBins = (m_type == TransformType::RFFT || m_type == TransformType::RDFT)
332
+ ? m_engine->getHalfSize()
333
+ : m_fftSize;
334
+ std::vector<float> magnitudes(numBins);
335
+ m_engine->getMagnitude(m_complexBuffer.data(), magnitudes.data(), numBins);
336
+ for (size_t i = 0; i < numBins; ++i)
337
+ {
338
+ frameOutput[i * numChannels] = magnitudes[i];
339
+ }
340
+ break;
341
+ }
342
+ case OutputFormat::POWER:
343
+ {
344
+ size_t numBins = (m_type == TransformType::RFFT || m_type == TransformType::RDFT)
345
+ ? m_engine->getHalfSize()
346
+ : m_fftSize;
347
+ std::vector<float> power(numBins);
348
+ m_engine->getPower(m_complexBuffer.data(), power.data(), numBins);
349
+ for (size_t i = 0; i < numBins; ++i)
350
+ {
351
+ frameOutput[i * numChannels] = power[i];
352
+ }
353
+ break;
354
+ }
355
+ case OutputFormat::PHASE:
356
+ {
357
+ size_t numBins = (m_type == TransformType::RFFT || m_type == TransformType::RDFT)
358
+ ? m_engine->getHalfSize()
359
+ : m_fftSize;
360
+ std::vector<float> phases(numBins);
361
+ m_engine->getPhase(m_complexBuffer.data(), phases.data(), numBins);
362
+ for (size_t i = 0; i < numBins; ++i)
363
+ {
364
+ frameOutput[i * numChannels] = phases[i];
365
+ }
366
+ break;
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+ }
373
+
374
+ void FftStage::reset()
375
+ {
376
+ // FFT is stateless, nothing to reset
377
+ }
378
+
379
+ Napi::Object FftStage::serializeState(Napi::Env env) const
380
+ {
381
+ // FFT is stateless, save configuration only
382
+ Napi::Object state = Napi::Object::New(env);
383
+ state.Set("fftSize", Napi::Number::New(env, m_fftSize));
384
+ state.Set("type", Napi::Number::New(env, static_cast<int>(m_type)));
385
+ state.Set("forward", Napi::Boolean::New(env, m_forward));
386
+ state.Set("format", Napi::Number::New(env, static_cast<int>(m_format)));
387
+ return state;
388
+ }
389
+
390
+ void FftStage::deserializeState(const Napi::Object &state)
391
+ {
392
+ size_t fftSize = state.Get("fftSize").As<Napi::Number>().Uint32Value();
393
+
394
+ if (fftSize != m_fftSize)
395
+ {
396
+ throw std::runtime_error("FFT size mismatch during state deserialization");
397
+ }
398
+
399
+ m_type = static_cast<TransformType>(state.Get("type").As<Napi::Number>().Int32Value());
400
+ m_forward = state.Get("forward").As<Napi::Boolean>().Value();
401
+ m_format = static_cast<OutputFormat>(state.Get("format").As<Napi::Number>().Int32Value());
402
+ }
403
+
404
+ FftStage::TransformType FftStage::parseTransformType(const std::string &typeStr)
405
+ {
406
+ if (typeStr == "fft")
407
+ return TransformType::FFT;
408
+ if (typeStr == "ifft")
409
+ return TransformType::IFFT;
410
+ if (typeStr == "dft")
411
+ return TransformType::DFT;
412
+ if (typeStr == "idft")
413
+ return TransformType::IDFT;
414
+ if (typeStr == "rfft")
415
+ return TransformType::RFFT;
416
+ if (typeStr == "irfft")
417
+ return TransformType::IRFFT;
418
+ if (typeStr == "rdft")
419
+ return TransformType::RDFT;
420
+ if (typeStr == "irdft")
421
+ return TransformType::IRDFT;
422
+
423
+ throw std::invalid_argument("Unknown transform type: " + typeStr);
424
+ }
425
+
426
+ FftStage::OutputFormat FftStage::parseOutputFormat(const std::string &formatStr)
427
+ {
428
+ if (formatStr == "complex")
429
+ return OutputFormat::COMPLEX;
430
+ if (formatStr == "magnitude")
431
+ return OutputFormat::MAGNITUDE;
432
+ if (formatStr == "power")
433
+ return OutputFormat::POWER;
434
+ if (formatStr == "phase")
435
+ return OutputFormat::PHASE;
436
+
437
+ throw std::invalid_argument("Unknown output format: " + formatStr);
438
+ }
439
+
440
+ } // namespace adapters
441
+ } // namespace dsp
@@ -0,0 +1,103 @@
1
+ /**
2
+ * FFT Stage Adapter
3
+ *
4
+ * Wraps FftEngine for use in the DSP pipeline.
5
+ * Supports forward/inverse transforms with multiple output formats.
6
+ */
7
+
8
+ #ifndef DSP_ADAPTERS_FFT_STAGE_H
9
+ #define DSP_ADAPTERS_FFT_STAGE_H
10
+
11
+ #include "../IDspStage.h"
12
+ #include "../core/FftEngine.h"
13
+ #include <memory>
14
+ #include <string>
15
+
16
+ namespace dsp
17
+ {
18
+ namespace adapters
19
+ {
20
+
21
+ class FftStage : public IDspStage
22
+ {
23
+ public:
24
+ enum class TransformType
25
+ {
26
+ FFT, // Complex FFT (power-of-2 only)
27
+ IFFT, // Inverse FFT
28
+ DFT, // Direct FT (any size)
29
+ IDFT, // Inverse DFT
30
+ RFFT, // Real FFT (power-of-2 only)
31
+ IRFFT, // Inverse RFFT
32
+ RDFT, // Real DFT (any size)
33
+ IRDFT // Inverse RDFT
34
+ };
35
+
36
+ enum class OutputFormat
37
+ {
38
+ COMPLEX, // Full complex output (interleaved real/imag)
39
+ MAGNITUDE, // |X[k]|
40
+ POWER, // |X[k]|²
41
+ PHASE // ∠X[k]
42
+ };
43
+
44
+ /**
45
+ * Constructor
46
+ * @param size FFT size
47
+ * @param type Transform type (fft, dft, rfft, rdft, etc.)
48
+ * @param forward True for forward, false for inverse
49
+ * @param format Output format (complex, magnitude, power, phase)
50
+ */
51
+ FftStage(size_t size, TransformType type, bool forward, OutputFormat format);
52
+
53
+ ~FftStage() override = default;
54
+
55
+ const char *getType() const override { return "fft"; }
56
+
57
+ bool isResizing() const override { return true; }
58
+
59
+ size_t calculateOutputSize(size_t inputSize) const override;
60
+
61
+ void process(
62
+ float *buffer,
63
+ size_t numSamples,
64
+ int numChannels,
65
+ const float *timestamps = nullptr) override;
66
+
67
+ void processResizing(
68
+ const float *inputBuffer,
69
+ size_t inputSize,
70
+ float *outputBuffer,
71
+ size_t &outputSize,
72
+ int numChannels,
73
+ const float *timestamps = nullptr) override;
74
+
75
+ void reset() override;
76
+
77
+ // State serialization
78
+ Napi::Object serializeState(Napi::Env env) const override;
79
+ void deserializeState(const Napi::Object &state) override;
80
+
81
+ // Helper to parse transform type from string
82
+ static TransformType parseTransformType(const std::string &typeStr);
83
+ static OutputFormat parseOutputFormat(const std::string &formatStr);
84
+
85
+ private:
86
+ size_t m_fftSize;
87
+ TransformType m_type;
88
+ bool m_forward;
89
+ OutputFormat m_format;
90
+
91
+ // FFT engine instance
92
+ std::unique_ptr<core::FftEngine<float>> m_engine;
93
+
94
+ // Working buffers
95
+ std::vector<std::complex<float>> m_complexBuffer;
96
+ std::vector<std::complex<float>> m_tempComplexBuffer; // For DFT/IDFT in-place issue
97
+ std::vector<float> m_realBuffer;
98
+ };
99
+
100
+ } // namespace adapters
101
+ } // namespace dsp
102
+
103
+ #endif // DSP_ADAPTERS_FFT_STAGE_H