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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/prebuilds/win32-x64/dspx.node +0 -0
- package/src/native/DspPipeline.cc +38 -0
- package/src/native/adapters/FftStage.cc +441 -0
- package/src/native/adapters/FftStage.h +103 -0
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\")",
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/ts/index.ts"],"names":[],"mappings":"
|
|
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
|
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 ¶ms)
|
|
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 ¶ms)
|
|
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
|